domain-objects
Version:
A simple, convenient way to represent domain objects, leverage domain knowledge, and add runtime validation in your code base.
85 lines • 5.17 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getContract = void 0;
const helpful_errors_1 = require("helpful-errors");
const validate_1 = require("../instantiation/validate/validate");
const getContractRef_1 = require("./getContractRef");
const getKind_1 = require("./getKind");
/**
* .what = maps a `.nested` declaration to nested dobj names only (string, or string[] for polymorphic choices)
* .why = the contract carries nested identity by name, never the constructor objects (name-only per vision)
* .note = handles both the single-constructor and array-of-constructors forms, like `hydrateNestedDomainObjects`
*/
const toNestedNames = (nested) => {
const entries = Object.entries(nested).map(([key, declaration]) => [
key,
Array.isArray(declaration)
? declaration.map((NestedClass) => NestedClass.name)
: declaration.name,
]);
return Object.fromEntries(entries);
};
/**
* .what = per-class memo of the stamped contract, keyed by the dobj class constructor
* .why = `.meta()` returns a fresh schema on each call; cache per-class so repeated `.contract`
* access is idempotent (same instance back), per the vision's pit-of-success contract
*/
const contractByClass = new WeakMap();
/**
* .what = returns a domain object's `.contract`: its zod `schema` stamped with identity + key metadata
* .why =
* - lets a domain object's identity survive `z.toJSONSchema()` as an `x-domain-object` pragma
* - so a cross-service consumer can name, de-dupe, and reconstruct the dobj from the wire
* - mirrors how `serialize` stamps `_dobj` onto its string output
* .note = `schema` validates; `contract` identifies. the contract is the schema that knows its own name + keys.
* the result is memoized per-class, so repeated access returns the same stamped instance (idempotent).
* .note = the returned contract also carries a `.ref(by)` method — `.contract.ref('primary')` returns
* the schema-level *reference* to this dobj by key (an `x-domain-object-ref` pragma; see getContractRef).
*/
const getContract = (dobj) => {
// return the memoized contract if this class was already stamped (idempotent per-class)
const contractMemoized = contractByClass.get(dobj);
if (contractMemoized)
return contractMemoized;
// fail fast if no schema is declared; a contract has no shape to identify without one
const { schema } = dobj;
if (!schema)
throw new helpful_errors_1.ConstraintError(`${dobj.name}.contract requires a static schema. declare \`static schema\` (zod) on ${dobj.name} to use .contract`, { domainObject: dobj.name });
// fail fast if the schema is not zod; only zod can carry the json-schema identity pragma
if (!(0, validate_1.isZodSchema)(schema))
throw new helpful_errors_1.ConstraintError(`${dobj.name}.contract requires a zod schema; joi/yup cannot carry json-schema identity. keep .schema for validation, but .contract needs zod`, { domainObject: dobj.name });
// assemble the x-domain-object pragma from the declared statics (omit absent fields)
const pragma = {
name: dobj.name,
kind: (0, getKind_1.getKind)(dobj), // the true subclass (entity/literal/event/object), from the class marker
...(dobj.primary ? { primary: dobj.primary } : {}),
...(dobj.unique ? { unique: dobj.unique } : {}),
...(dobj.alias ? { alias: dobj.alias } : {}),
...(dobj.nested ? { nested: toNestedNames(dobj.nested) } : {}),
};
// stamp the pragma via zod's `.meta()` registry (returns a fresh schema, the author's is
// untouched), then augment it with the `.ref(by)` accessor — a function property, absent from
// z.toJSONSchema() output (which walks zod's internal `_zod.def`, not own-enumerable keys).
//
// `.ref` is hung as a NON-enumerable, non-writable, non-configurable own property, the way
// withImmute attaches `.clone` (withImmute.ts). non-enumerable so `Object.keys(X.contract)` /
// `{ ...X.contract }` / a log never leak the fn where a pure zod schema is expected — the raw
// schema stays indistinguishable from an un-augmented one to every enumerable-key consumer.
//
// as-cast boundary (`rule.forbid.as-cast` exception): `.meta()` is typed to return a zod schema,
// which cannot express "this schema now also carries our `.ref` method". the cast asserts the
// shape defineProperty just produced (a zod schema + `.ref`). removal path: when zod exposes a
// typed schema-augmentation api, or the lib returns a wrapper instead of the schema itself.
const stamped = schema.meta({ 'x-domain-object': pragma });
const contract = Object.defineProperty(stamped, 'ref', {
enumerable: false,
configurable: false,
writable: false,
value: (by) => (0, getContractRef_1.getContractRef)(dobj, by),
});
// memoize per-class so the next access returns this same instance (idempotent, vision pit-of-success)
contractByClass.set(dobj, contract);
return contract;
};
exports.getContract = getContract;
//# sourceMappingURL=getContract.js.map