@devx-retailos/store-config
v0.0.2
Published
Typed, org/store-scoped configuration and atomic sequence counters for retailOS POS backends.
Keywords
Readme
@devx-retailos/store-config
Typed, org/store-scoped configuration entries and atomic sequence counters for POS backends. Store-level values override org-level values; secret values are masked at the API boundary; sequences increment in a single SQL statement so concurrent requests never produce duplicate numbers.
Part of retailOS, a Medusa v2 SDK for offline-store POS systems. Each @devx-retailos/* package is an independently installable Medusa plugin that you compose in your brand's Medusa backend.
Installation
npm install @devx-retailos/store-configRequires a Medusa v2 project (@medusajs/framework / @medusajs/medusa ^2.15.0 and React as peers). @devx-retailos/core and @devx-retailos/rbac are installed automatically as dependencies — API routes enforce permissions through the RBAC module, and config entries are linked to RBAC organizations and stores.
Setup
// medusa-config.ts
module.exports = defineConfig({
// ...
plugins: [
{
resolve: "@devx-retailos/rbac",
options: {},
},
{
resolve: "@devx-retailos/store-config",
options: {},
},
],
})Then run migrations to create the retailos_config_entry and retailos_config_sequence tables:
npx medusa db:migrateUsage
import {
STORE_CONFIG_MODULE,
type StoreConfigModuleService,
} from "@devx-retailos/store-config"
const config: StoreConfigModuleService = container.resolve(STORE_CONFIG_MODULE)Typed config entries
Values are stored as strings with a value_type of "string" | "number" | "boolean" | "json" | "secret". Reads resolve the store-level entry first, then fall back to the org-level entry (store_id: null).
const scope = { organization_id: "org_1", store_id: "store_1" }
// Write (validates the value parses as the declared type)
await config.set("invoice.tax_rate", "18", scope, { value_type: "number" })
// Read the raw entry, or the parsed value
const entry = await config.get("invoice.tax_rate", scope)
const rate = await config.getTyped<number>("invoice.tax_rate", scope) // 18
// Optionally register known keys at boot so `set` infers their type
config.registerConfigKey({
key: "invoice.tax_rate",
value_type: "number",
scope: "both",
description: "GST rate applied at billing",
})A failed parse throws StoreConfigTypeError (code RETAILOS_STORE_CONFIG_TYPE_MISMATCH). All error classes extend RetailOSError and are importable from @devx-retailos/store-config/errors.
Sequence counters
Sequences are rows in retailos_config_sequence with a prefix, padding, and current_value. Create one with the generated CRUD, then advance it atomically:
await config.createConfigSequences([
{ organization_id: "org_1", store_id: "store_1", key: "invoice_number", prefix: "INV-", padding: 5 },
])
const next = await config.nextSequence("invoice_number", scope) // "INV-00001"
await config.resetSequence("invoice_number", scope, { value: 0 })nextSequence / resetSequence throw StoreConfigSequenceNotFoundError (code RETAILOS_STORE_CONFIG_SEQUENCE_NOT_FOUND) if no sequence matches the key and scope.
Permissions
Exported as STORE_CONFIG_PERMISSIONS (also from @devx-retailos/store-config/permissions):
| Key | Description |
| --- | --- |
| cms.config.read | List and retrieve configuration entries (secrets excluded) |
| cms.config.update | Create or update configuration entries |
| cms.config.read_secret | Read the plaintext value of secret-typed configuration entries |
| cms.sequence.read | List sequence counters and advance to the next value |
| cms.sequence.reset | Reset a sequence counter to a given value |
API routes
Routes resolve scope from organization_id / store_id query params (or body for POSTs) or the x-retailos-organization-id / x-retailos-store-id headers, and check permissions via RBAC. Entries with value_type: "secret" are returned with value: null unless the caller holds cms.config.read_secret.
| Method | Path | Description | Permission |
| --- | --- | --- | --- |
| GET | /admin/retailos/config | List config entries for a scope | cms.config.read |
| POST | /admin/retailos/config | Create or update a config entry | cms.config.update |
| GET | /admin/retailos/config/:key | Get one entry (store-level wins over org-level) | cms.config.read |
| DELETE | /admin/retailos/config/:key | Delete an entry | cms.config.update |
| GET | /admin/retailos/config/sequences | List sequence counters for a scope | cms.sequence.read |
| POST | /admin/retailos/config/sequences/:key/next | Atomically advance and return the formatted value | cms.sequence.read |
| POST | /admin/retailos/config/sequences/:key/reset | Reset a sequence to a value (default 0) | cms.sequence.reset |
Admin UI
Ships a config-editor widget injected into the store.details.after zone of Medusa Admin, with type-aware inputs (checkbox for booleans, number input, JSON textarea) for editing entries.
Related packages
@devx-retailos/core— shared types,Logger,RetailOSError, permission registry.@devx-retailos/rbac— organizations, stores, roles, permissions; enforces this module's permission keys.@devx-retailos/store-details— extended store metadata (address, contact, tax info).@devx-retailos/footfall— time-series visitor footfall per store.
License
MIT
