npm.io
0.0.3 • Published 2h ago

@devx-retailos/store-config

Licence
MIT
Version
0.0.3
Deps
2
Size
146 kB
Vulns
0
Weekly
0

@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-config

Requires 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:migrate

Usage

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.

  • @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

Keywords