@assemora/data 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Model instances (SPEC.md §22, §26, §28, §29).
3
+ *
4
+ * An instance is the record itself plus the verbs that act on it, so a caller writes
5
+ * `user.name = 'John'` and `await user.save()` rather than assembling an update.
6
+ */
7
+ import { randomUUID } from 'node:crypto';
8
+ import { NotFoundError } from '@assemora/core';
9
+ import { comparison, emptyQuery } from '@assemora/database';
10
+ import { definePivot } from './pivot.js';
11
+ import { execute } from './runtime.js';
12
+ const ORIGINAL = Symbol('assemora.original');
13
+ const columnNames = (context) => Object.keys(context.columns);
14
+ /** Values a write should carry: everything the model declares as a column. */
15
+ const persistable = (context, row) => {
16
+ const values = {};
17
+ for (const name of columnNames(context)) {
18
+ if (name in row)
19
+ values[name] = row[name];
20
+ }
21
+ return values;
22
+ };
23
+ const touchTimestamps = (context, row, moment) => {
24
+ for (const [name, column] of Object.entries(context.columns)) {
25
+ if (column.timestampRole === 'updated' ||
26
+ (moment === 'create' && column.timestampRole === 'created')) {
27
+ row[name] = new Date();
28
+ }
29
+ }
30
+ };
31
+ const fillDefaults = (context, row) => {
32
+ for (const [name, column] of Object.entries(context.columns)) {
33
+ if (row[name] !== undefined)
34
+ continue;
35
+ if (column.usesRandomDefault)
36
+ row[name] = randomUUID();
37
+ else if (column.hasDefault && column.defaultValue !== undefined)
38
+ row[name] = column.defaultValue;
39
+ }
40
+ };
41
+ export const createInstance = (context, row, origin = { persisted: true }) => {
42
+ const instance = { ...row };
43
+ let persisted = origin.persisted;
44
+ Object.defineProperty(instance, ORIGINAL, {
45
+ enumerable: false,
46
+ writable: true,
47
+ value: { ...row },
48
+ });
49
+ const original = () => instance[ORIGINAL] ?? {};
50
+ const key = context.table.primaryKey;
51
+ const identify = () => {
52
+ const id = instance[key];
53
+ if (id === undefined || id === null) {
54
+ throw new NotFoundError(context.table.name);
55
+ }
56
+ return comparison(key, '=', id);
57
+ };
58
+ const write = async (operation, data) => execute({
59
+ ...emptyQuery(context.table.name, operation),
60
+ where: operation === 'insert' ? [] : [identify()],
61
+ ...(data === undefined ? {} : { data }),
62
+ }, { table: context.table });
63
+ const methods = {
64
+ async save() {
65
+ if (persisted) {
66
+ touchTimestamps(context, instance, 'update');
67
+ const changed = {};
68
+ for (const name of columnNames(context)) {
69
+ if (instance[name] !== original()[name])
70
+ changed[name] = instance[name];
71
+ }
72
+ if (Object.keys(changed).length > 0)
73
+ await write('update', changed);
74
+ }
75
+ else {
76
+ fillDefaults(context, instance);
77
+ touchTimestamps(context, instance, 'create');
78
+ await write('insert', persistable(context, instance));
79
+ }
80
+ instance[ORIGINAL] = { ...persistable(context, instance) };
81
+ persisted = true;
82
+ },
83
+ async update(values) {
84
+ Object.assign(instance, values);
85
+ await methods.save();
86
+ },
87
+ async delete() {
88
+ const softColumn = context.table.softDeleteColumn;
89
+ if (softColumn === undefined) {
90
+ await write('delete');
91
+ return;
92
+ }
93
+ instance[softColumn] = new Date();
94
+ await write('update', { [softColumn]: instance[softColumn] });
95
+ },
96
+ async restore() {
97
+ const softColumn = context.table.softDeleteColumn;
98
+ if (softColumn === undefined)
99
+ return;
100
+ instance[softColumn] = null;
101
+ await write('update', { [softColumn]: null });
102
+ },
103
+ async refresh() {
104
+ const rows = await execute({ ...emptyQuery(context.table.name), where: [identify()], limit: 1 }, { table: context.table });
105
+ const fresh = rows[0];
106
+ if (fresh === undefined)
107
+ throw new NotFoundError(context.table.name, String(instance[key]));
108
+ for (const name of columnNames(context))
109
+ delete instance[name];
110
+ Object.assign(instance, fresh);
111
+ instance[ORIGINAL] = { ...fresh };
112
+ persisted = true;
113
+ },
114
+ isDirty(field) {
115
+ if (field !== undefined)
116
+ return instance[field] !== original()[field];
117
+ return columnNames(context).some((name) => instance[name] !== original()[name]);
118
+ },
119
+ getOriginal(field) {
120
+ return original()[field];
121
+ },
122
+ toJSON() {
123
+ const output = {};
124
+ for (const [name, column] of Object.entries(context.columns)) {
125
+ // A hidden column never reaches serialized output (SPEC.md §28).
126
+ if (column.isHidden)
127
+ continue;
128
+ if (name in instance)
129
+ output[name] = instance[name];
130
+ }
131
+ for (const [name, compute] of Object.entries(context.computed)) {
132
+ output[name] = compute(instance);
133
+ }
134
+ return output;
135
+ },
136
+ };
137
+ Object.assign(instance, methods);
138
+ for (const [name, compute] of Object.entries(context.computed)) {
139
+ Object.defineProperty(instance, name, {
140
+ enumerable: true,
141
+ get: () => compute(instance),
142
+ });
143
+ }
144
+ // `user.roles` exists whether or not anybody loaded roles: the verbs of SPEC.md §24
145
+ // act on the join table, and the row alone is enough to address it.
146
+ for (const relation of context.table.relations) {
147
+ if (relation.kind !== 'belongsToMany')
148
+ continue;
149
+ definePivot(instance, {
150
+ owner: context.table,
151
+ relation,
152
+ row: instance,
153
+ related: context.related,
154
+ });
155
+ }
156
+ return instance;
157
+ };
158
+ //# sourceMappingURL=instance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance.js","sourceRoot":"","sources":["../src/instance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import type { TableDescriptor } from '@assemora/database';
2
+ import { type ComputedValues, type Instance, type NoComputed } from './instance.js';
3
+ import { type Fields, type InferRecord, type Query, type ScopedQuery } from './query.js';
4
+ export type ScopeMap<F extends Fields> = Readonly<Record<string, (query: Query<F>) => Query<F>>>;
5
+ export type NoScopes = Readonly<Record<never, never>>;
6
+ /**
7
+ * Scopes and computed fields are declared through mapped types so that TypeScript
8
+ * types the callback arguments from `fields` alone, and infers the scope names and
9
+ * the computed value types from the object literal.
10
+ */
11
+ export type ModelOptions<F extends Fields, S, C extends ComputedValues> = {
12
+ readonly scopes?: {
13
+ readonly [K in keyof S]: (query: Query<F>) => Query<F>;
14
+ };
15
+ readonly computed?: {
16
+ readonly [K in keyof C]: (record: InferRecord<F>) => C[K];
17
+ };
18
+ /** Marks the model soft-deleting and names the column (SPEC.md §29). */
19
+ readonly softDeletes?: boolean | string;
20
+ };
21
+ export type Model<F extends Fields, SN extends string = never, C extends ComputedValues = NoComputed> = ScopedQuery<F, SN, Instance<F, C>> & {
22
+ readonly table: string;
23
+ readonly primaryKey: string;
24
+ readonly fields: F;
25
+ readonly descriptor: TableDescriptor;
26
+ /** The record type this model produces. A type-level marker (SPEC.md §18). */
27
+ readonly $infer: InferRecord<F>;
28
+ find(id: unknown): Promise<Instance<F, C> | null>;
29
+ findOrFail(id: unknown): Promise<Instance<F, C>>;
30
+ all(): Promise<Instance<F, C>[]>;
31
+ create(values: Partial<InferRecord<F>>): Promise<Instance<F, C>>;
32
+ /**
33
+ * The row of this entry in the language being read (SPEC.md §131).
34
+ *
35
+ * `find()` answers the row whose id was asked for, in whatever language it happens to
36
+ * be written — which is what a policy check and an edit want. This answers the *entry*
37
+ * that row belongs to, read in the language of the operation, with the same fallback
38
+ * as any other read. It is what a relation needs: a translation's foreign keys point
39
+ * at originals, so following one and showing its name means asking for the entry
40
+ * rather than for the row.
41
+ *
42
+ * Identical to `find()` on a model that is not translatable.
43
+ */
44
+ translated(id: unknown): Promise<Instance<F, C> | null>;
45
+ /**
46
+ * One row per language, and the row keeps its own shape (SPEC.md §131).
47
+ *
48
+ * ```ts
49
+ * export const Article = model('articles', {
50
+ * id: uuid().primary(),
51
+ * title: string(),
52
+ * }).translatable()
53
+ * ```
54
+ *
55
+ * The model gains `locale` and `translationOf` and nothing else changes: `title` is
56
+ * still a `string`, `Article.$infer` still has the shape it had, and every scope,
57
+ * relation and query written against it still means what it meant. What changes is
58
+ * that a read is scoped to the language of the operation without a caller asking.
59
+ */
60
+ translatable(): Model<F & TranslationFields, SN, C>;
61
+ };
62
+ export declare const TRANSLATION_FIELDS: {
63
+ /** The language this row is written in. */
64
+ readonly locale: import("./columns.js").ColumnBuilder<string>;
65
+ /** The row this one translates, or null for the original. */
66
+ readonly translationOf: import("./columns.js").ColumnBuilder<string | null>;
67
+ };
68
+ export type TranslationFields = typeof TRANSLATION_FIELDS;
69
+ /** Every declared model, for resolving the other side of a relation. */
70
+ export declare const registeredModels: () => Readonly<Record<string, TableDescriptor>>;
71
+ export declare const clearModelRegistry: () => void;
72
+ export declare const model: <F extends Fields, S extends Readonly<Record<string, unknown>> = NoScopes, C extends ComputedValues = NoComputed>(table: string, fields: F, options?: ModelOptions<F, S, C>) => Model<F, keyof S & string, C>;
73
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
package/dist/model.js ADDED
@@ -0,0 +1,272 @@
1
+ /**
2
+ * `model()` (SPEC.md §9, §17, §21, §25, §29).
3
+ *
4
+ * One declaration produces the record type, the column metadata the database needs,
5
+ * the query entry point and the model's scopes.
6
+ */
7
+ import { ValidationError } from '@assemora/core';
8
+ import { comparison, emptyQuery } from '@assemora/database';
9
+ import { string, uuid } from './columns.js';
10
+ import { createInstance, } from './instance.js';
11
+ import { createQuery, } from './query.js';
12
+ import { isRelation } from './relations.js';
13
+ import { execute } from './runtime.js';
14
+ /**
15
+ * The two columns a translatable model gains (SPEC.md §131).
16
+ *
17
+ * A row per language, and the row keeps its own shape. `title` stays a `string`: the
18
+ * moment a field becomes a map keyed by language, one declaration stops feeding the
19
+ * record type, the column, the form, the OpenAPI schema and the SDK at once, which is
20
+ * §2 and §128 given up for a storage convenience. `Article.where('locale', 'ru')` is an
21
+ * ordinary query and everything already built keeps working.
22
+ *
23
+ * Both are indexed. Every read of a translatable model filters on `locale`, and the
24
+ * fallback groups by `translationOf` — an index each is what the feature is, not a
25
+ * tuning decision somebody makes later.
26
+ */
27
+ /**
28
+ * How `translatable()` tells the `model()` call it re-enters what it is doing.
29
+ *
30
+ * A symbol rather than a field on `ModelOptions`, because it is not something an
31
+ * application says: writing `{ translatable: true }` beside `softDeletes` would be a
32
+ * second way to declare it, and the two would eventually be given different answers.
33
+ */
34
+ const TRANSLATABLE = Symbol.for('assemora.model.translatable');
35
+ export const TRANSLATION_FIELDS = {
36
+ /** The language this row is written in. */
37
+ locale: string().index(),
38
+ /** The row this one translates, or null for the original. */
39
+ translationOf: uuid().nullable().index(),
40
+ };
41
+ const registry = new Map();
42
+ /** Every declared model, for resolving the other side of a relation. */
43
+ export const registeredModels = () => {
44
+ const descriptors = {};
45
+ for (const [table, model] of registry)
46
+ descriptors[table] = model.descriptor;
47
+ return descriptors;
48
+ };
49
+ export const clearModelRegistry = () => {
50
+ registry.clear();
51
+ };
52
+ /** `users` → `userId`. Crude on purpose; pass `foreignKey` when it does not fit. */
53
+ const foreignKeyFor = (table) => `${table.endsWith('s') ? table.slice(0, -1) : table}Id`;
54
+ const describeColumn = (name, column) => ({
55
+ name,
56
+ type: column.type,
57
+ isPrimary: column.isPrimary,
58
+ isNullable: column.isNullable,
59
+ isUnique: column.isUnique,
60
+ isIndexed: column.isIndexed,
61
+ hasDefault: column.hasDefault,
62
+ ...(column.enumValues === undefined ? {} : { enumValues: column.enumValues }),
63
+ });
64
+ const describeRelation = (name, relation, owner) => {
65
+ const target = relation.target();
66
+ return {
67
+ name,
68
+ kind: relation.kind,
69
+ target: target.table,
70
+ foreignKey: relation.foreignKey ??
71
+ (relation.kind === 'belongsTo' ? `${name}Id` : foreignKeyFor(owner.table)),
72
+ ownerKey: relation.ownerKey ?? (relation.kind === 'belongsTo' ? target.primaryKey : owner.primaryKey),
73
+ // Carried rather than resolved: what a join table is called and which of its
74
+ // columns points where is derived once, in `@assemora/database`, so the DDL, the
75
+ // diff and the pivot verbs cannot form three opinions about the same table.
76
+ ...(relation.through === undefined ? {} : { through: relation.through }),
77
+ ...(relation.foreignPivotKey === undefined
78
+ ? {}
79
+ : { foreignPivotKey: relation.foreignPivotKey }),
80
+ ...(relation.relatedPivotKey === undefined
81
+ ? {}
82
+ : { relatedPivotKey: relation.relatedPivotKey }),
83
+ };
84
+ };
85
+ export const model = (table, fields, options = {}) => {
86
+ const columns = {};
87
+ const relations = {};
88
+ for (const [name, field] of Object.entries(fields)) {
89
+ if (isRelation(field))
90
+ relations[name] = field;
91
+ else
92
+ columns[name] = field;
93
+ }
94
+ const primaryKey = Object.entries(columns).find(([, column]) => column.isPrimary)?.[0] ?? 'id';
95
+ const translates = options[TRANSLATABLE] === true;
96
+ const updatedAtColumn = Object.entries(columns).find(([, column]) => column.timestampRole === 'updated')?.[0];
97
+ const softDeleteColumn = options.softDeletes === true
98
+ ? 'deletedAt'
99
+ : typeof options.softDeletes === 'string'
100
+ ? options.softDeletes
101
+ : undefined;
102
+ let cached;
103
+ const descriptor = () => {
104
+ if (cached !== undefined)
105
+ return cached;
106
+ const described = Object.entries(columns).map(([name, column]) => describeColumn(name, column));
107
+ /**
108
+ * On a translatable model, a unique column is unique *within a language*.
109
+ *
110
+ * `slug: string().unique()` on a model with one row per language is a model with no
111
+ * translations: the Russian row would carry the same slug as the Ukrainian one and
112
+ * the constraint would refuse it. Globally unique is never what was meant — two
113
+ * rows sharing a slug *are* the same entry, in two languages, which is exactly what
114
+ * §131 stores.
115
+ *
116
+ * Done here rather than asked of the application, because it is not a choice: an
117
+ * application that had to remember would be one where forgetting is a table that
118
+ * cannot hold a translation, discovered at the first one.
119
+ */
120
+ const perLocale = translates ? described.filter((column) => column.isUnique) : [];
121
+ cached = {
122
+ name: table,
123
+ primaryKey,
124
+ columns: described.map((column) => perLocale.includes(column) ? { ...column, isUnique: false } : column),
125
+ relations: Object.entries(relations).map(([name, relation]) => describeRelation(name, relation, { table, primaryKey })),
126
+ ...(softDeleteColumn === undefined ? {} : { softDeleteColumn }),
127
+ ...(translates ? { translatable: true } : {}),
128
+ ...(updatedAtColumn === undefined ? {} : { updatedAtColumn }),
129
+ ...(perLocale.length === 0
130
+ ? {}
131
+ : { uniqueTogether: perLocale.map((column) => [column.name, 'locale']) }),
132
+ };
133
+ return cached;
134
+ };
135
+ const instanceContext = {
136
+ get table() {
137
+ return descriptor();
138
+ },
139
+ columns,
140
+ computed: (options.computed ?? {}),
141
+ related: registeredModels,
142
+ };
143
+ /** A row that came out of storage already exists there. */
144
+ const hydrate = (row) => createInstance(instanceContext, row, { persisted: true });
145
+ const baseState = () => ({
146
+ table: descriptor,
147
+ where: [],
148
+ order: [],
149
+ load: [],
150
+ limit: undefined,
151
+ offset: undefined,
152
+ trashed: 'without',
153
+ // The language of the operation, and the fallback on: both are what a read means
154
+ // without a caller saying anything (SPEC.md §131).
155
+ locale: 'context',
156
+ fallback: true,
157
+ });
158
+ const query = () => createQuery(baseState(), {
159
+ related: registeredModels,
160
+ hydrate,
161
+ scopes: (options.scopes ?? {}),
162
+ });
163
+ /** Validates a value against the column that will hold it. */
164
+ const validate = (values) => {
165
+ const checked = {};
166
+ const issues = [];
167
+ for (const [name, column] of Object.entries(columns)) {
168
+ const raw = values[name];
169
+ if (raw === undefined) {
170
+ if (!column.hasDefault && !column.isNullable && column.timestampRole === undefined) {
171
+ issues.push({ path: [name], code: 'required', message: 'This field is required' });
172
+ }
173
+ continue;
174
+ }
175
+ const result = column.schema.parse(raw);
176
+ if (result.ok) {
177
+ const transform = column.transform;
178
+ checked[name] = transform === undefined ? result.value : transform(result.value);
179
+ }
180
+ else {
181
+ issues.push(...result.issues.map((issue) => ({ ...issue, path: [name, ...issue.path] })));
182
+ }
183
+ }
184
+ if (issues.length > 0)
185
+ throw new ValidationError(issues);
186
+ return checked;
187
+ };
188
+ const statics = {
189
+ table,
190
+ primaryKey,
191
+ fields,
192
+ // A type-level marker: the value is never read, the type is the point.
193
+ $infer: undefined,
194
+ async find(id) {
195
+ const rows = await execute({
196
+ ...emptyQuery(table),
197
+ where: [
198
+ comparison(primaryKey, '=', id),
199
+ ...(softDeleteColumn === undefined ? [] : [comparison(softDeleteColumn, 'is null')]),
200
+ ],
201
+ limit: 1,
202
+ }, { table: descriptor() });
203
+ const row = rows[0];
204
+ return row === undefined ? null : hydrate(row);
205
+ },
206
+ async findOrFail(id) {
207
+ const found = await statics.find(id);
208
+ if (found === null) {
209
+ const { NotFoundError } = await import('@assemora/core');
210
+ throw new NotFoundError(table, String(id));
211
+ }
212
+ return found;
213
+ },
214
+ all() {
215
+ return query().get();
216
+ },
217
+ async translated(id) {
218
+ if (descriptor().translatable !== true)
219
+ return statics.find(id);
220
+ // The row first, unscoped, because the id may name a translation: asking for the
221
+ // group of `id` without knowing which row it is would miss the original.
222
+ const named = await statics.find(id);
223
+ if (named === null)
224
+ return null;
225
+ const row = named;
226
+ const entry = row.translationOf ?? row[primaryKey];
227
+ /**
228
+ * `translationOf` is a column of a translatable model and not a member of `F`,
229
+ * which is what makes this the one place that has to say so. Everything else —
230
+ * the language filter, the fallback — is the ordinary read.
231
+ */
232
+ const grouped = query();
233
+ const rows = await grouped
234
+ .where((nested) => {
235
+ const scope = nested;
236
+ return scope.where(primaryKey, entry).orWhere('translationOf', entry);
237
+ })
238
+ .get();
239
+ return rows[0] ?? null;
240
+ },
241
+ async create(values) {
242
+ // Not `hydrate`: this row does not exist yet, whether or not it was handed a
243
+ // primary key of its own.
244
+ const instance = createInstance(instanceContext, validate(values), {
245
+ persisted: false,
246
+ });
247
+ await instance.save();
248
+ return instance;
249
+ },
250
+ };
251
+ const translatable = () =>
252
+ // Declared again with the two extra columns rather than patched in place: a model is
253
+ // its fields, and everything derived from them — the record type, the descriptor,
254
+ // the validator, the instance — is built once from that one list. Re-entering here
255
+ // is what keeps a translatable model from being a second kind of model.
256
+ model(table, { ...fields, ...TRANSLATION_FIELDS },
257
+ // The symbol rides along beside the declared options. It is not part of
258
+ // `ModelOptions` and must not become part of it, so it is attached rather than
259
+ // written into the literal.
260
+ Object.assign({ [TRANSLATABLE]: true }, options));
261
+ const built = Object.assign(query(), statics, { translatable });
262
+ // A getter, not a value: Object.assign would resolve it, and resolving a relation
263
+ // while the models are still being declared reaches a target that does not exist yet.
264
+ Object.defineProperty(built, 'descriptor', { enumerable: true, get: descriptor });
265
+ registry.set(table, {
266
+ get descriptor() {
267
+ return descriptor();
268
+ },
269
+ });
270
+ return built;
271
+ };
272
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { TableDescriptor } from '@assemora/database';
2
+ declare module '@assemora/core' {
3
+ interface RegistrySections {
4
+ models: ModelDescriptor;
5
+ }
6
+ interface ModuleBuilder {
7
+ models(...models: {
8
+ readonly table: string;
9
+ readonly descriptor: TableDescriptor;
10
+ }[]): ModuleBuilder;
11
+ }
12
+ }
13
+ export type ModelDescriptor = {
14
+ readonly name: string;
15
+ readonly table: TableDescriptor;
16
+ readonly module?: string;
17
+ };
18
+ export declare const defineModelFacet: () => void;
19
+ //# sourceMappingURL=module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEzD,OAAO,QAAQ,gBAAgB,CAAC;IAC9B,UAAU,gBAAgB;QACxB,MAAM,EAAE,eAAe,CAAA;KACxB;IAED,UAAU,aAAa;QACrB,MAAM,CACJ,GAAG,MAAM,EAAE;YAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAA;SAAE,EAAE,GAC5E,aAAa,CAAA;KACjB;CACF;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAA;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAID,eAAO,MAAM,gBAAgB,QAAO,IAoBnC,CAAA"}
package/dist/module.js ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The `.models()` facet of `module()` (SPEC.md §13, §42, ADR-0009).
3
+ *
4
+ * Registering a model describes its table in the Schema Registry, which is where
5
+ * migrations, introspection and `assemora.describe` read it from.
6
+ */
7
+ import { defineModuleFacet } from '@assemora/core';
8
+ let defined = false;
9
+ export const defineModelFacet = () => {
10
+ if (defined)
11
+ return;
12
+ defineModuleFacet('models', (internals, args) => {
13
+ internals.addRegistration((context) => {
14
+ for (const candidate of args) {
15
+ const declared = candidate;
16
+ if (typeof declared?.table !== 'string')
17
+ continue;
18
+ context.registry.register('models', {
19
+ name: declared.table,
20
+ table: declared.descriptor,
21
+ module: internals.name,
22
+ });
23
+ }
24
+ });
25
+ });
26
+ defined = true;
27
+ };
28
+ defineModelFacet();
29
+ //# sourceMappingURL=module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAqBlD,IAAI,OAAO,GAAG,KAAK,CAAA;AAEnB,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAS,EAAE;IACzC,IAAI,OAAO;QAAE,OAAM;IAEnB,iBAAiB,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE;QAC9C,SAAS,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE,EAAE;YACpC,KAAK,MAAM,SAAS,IAAI,IAAI,EAAE,CAAC;gBAC7B,MAAM,QAAQ,GAAG,SAA2D,CAAA;gBAE5E,IAAI,OAAO,QAAQ,EAAE,KAAK,KAAK,QAAQ;oBAAE,SAAQ;gBAEjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE;oBAClC,IAAI,EAAE,QAAQ,CAAC,KAAK;oBACpB,KAAK,EAAE,QAAQ,CAAC,UAAU;oBAC1B,MAAM,EAAE,SAAS,CAAC,IAAI;iBACvB,CAAC,CAAA;YACJ,CAAC;QACH,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,OAAO,GAAG,IAAI,CAAA;AAChB,CAAC,CAAA;AAED,gBAAgB,EAAE,CAAA"}