@spinekit/kit
v0.6.0
Published
The spine framework core — the primitives every @spinekit/* domain module is built from. Owns ONLY cross-domain scaffolding: the permission-gate vocabulary, the configurable scope dimension (branch / property / plant) read through arc's validated RequestS
Maintainers
Readme
@spinekit/kit
The spine framework core — the primitives every @spinekit/* domain
module is built from.
spine is the vertical-agnostic ERP layer: commerce and hotel compose the same
domain packages, and the host runs almost no code. This package is the middle
layer that makes that true. It owns only cross-domain scaffolding — no
domain logic, no models, no repositories, no database driver. Its only peer is
@classytic/arc.
@classytic/arc framework — generic backend, zero ERP concepts
@spinekit/kit framework core — THIS PACKAGE
@spinekit/* domains — accounting, inventory, pos, …
be-prod / hotel apps — compose modules, own roles + configWhat it provides
1. One permission vocabulary
PermissionGate was re-declared in 30 packages, 25 of them byte-identical. The
verb set had drifted further: view in 24 packages, manage in 15, then a long
tail of one-offs (operate, run, post, purge, moderate, memberOps…),
so a host composing twenty modules had to learn twenty dialects.
import { type SpinePermissions, assertPermissions } from '@spinekit/kit';
// Two core verbs + this domain's own — the host gets completion for exactly these.
export type PosPermissions = SpinePermissions<'operate' | 'cancel'>;
assertPermissions('pos', deps.permissions, ['view', 'manage', 'operate', 'cancel']);assertPermissions moves a missing gate from "undefined is not a function on
the first request in prod" to a boot error naming the domain and the gap.
2. A configurable scope dimension
An audit found branch hardcoded in 153 files, concentrated in the packages
meant to be the most generic (tenancy, platform, lens, integrations). In hotel
that dimension is a property; in manufacturing a plant. arc already ships
the seam — RequestScope.context, documented for exactly this — and spine used
it in zero files.
import { defineScopeDimension, requireDimension, dimensionFilter } from '@spinekit/kit';
const BRANCH = defineScopeDimension({ key: 'branch' }); // commerce
const PROPERTY = defineScopeDimension({ key: 'property' }); // hotel
const branch = requireDimension(scope, BRANCH); // value, or a 403
const filter = dimensionFilter(scope, BRANCH); // { branch: 'br-1' } | nullEvery accessor reads the verified RequestScope — there is deliberately no
header-reading path. That is not theoretical: spine's guards once resolved the
org from a raw x-organization-id header, and when identity moved to arc's
authenticator they silently resolved to '' — a tenant-isolation failure that
surfaced as 74 unrelated-looking test failures.
dimensionFilter returns null, never {}, for a caller with no value — {}
would silently widen the query to every row.
3. defineSpineModule — owns is derived, not declared
arc's owns supersedes a host's same-named resource. 31 packages restated that
list by hand beside a comment reading "MUST mirror resources()". Two real bugs
came out of one audit — both silent 404s in production, neither a type error:
spine-posclaimedpos-orderunconditionally while mounting it only when the host supplied a checkout pipeline.spine-punch-gatewayclaimedpunch-iclock(conditional) andpunch-device(never provided at all).
import { defineSpineModule, ownedEngine } from '@spinekit/kit';
export function createPosModule(deps: PosModuleDeps) {
const { engine, closeOwned } = ownedEngine(deps.engine, () => createPosEngine(deps));
return defineSpineModule({
name: 'pos',
closeOwned, // set only when WE created the engine
resources: () => [
createShiftResource({ engine, permissions: deps.permissions }),
...(deps.placement ? [createPosOrderResource({ engine, ...deps.placement })] : []),
],
// owns is DERIVED — the conditional resource is owned only when mounted.
});
}There is no ownsAlso escape hatch: if you don't provide it, you don't own
it. ownedEngine encodes the other rule — self-created engines are
self-closed, host-supplied ones are never closed by the module.
4. A module conformance suite
import { assertSpineModuleContract } from '@spinekit/kit/testing';
it('conforms to the spine module contract', () => {
assertSpineModuleContract(createPosModule({ engine, permissions: gates }));
});Synchronous and dependency-free — no Fastify boot, no database — so it belongs in every package's unit tier. It catches claimed-but-not-provided names, provided-but-not-claimed names, unresolved resource factories, and duplicate resource names, reporting all violations at once.
Install
pnpm add @spinekit/kit@classytic/arc >=2.31.0 is a peer.
