@rangka/core 0.1.0 → 0.1.2
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.
- package/package.json +6 -2
- package/.claude/skills/extend-core/SKILL.md +0 -133
- package/.turbo/turbo-build.log +0 -4
- package/CHANGELOG.md +0 -18
- package/CLAUDE.md +0 -180
- package/src/__tests__/coerce.test.ts +0 -154
- package/src/__tests__/context.test.ts +0 -111
- package/src/__tests__/helpers.ts +0 -21
- package/src/__tests__/index.test.ts +0 -7
- package/src/__tests__/widgets.test.ts +0 -197
- package/src/api/__tests__/handlers.test.ts +0 -389
- package/src/api/__tests__/include-resolver.test.ts +0 -393
- package/src/api/__tests__/middleware.test.ts +0 -100
- package/src/api/__tests__/openapi-schema.test.ts +0 -210
- package/src/api/__tests__/query-parser.test.ts +0 -291
- package/src/api/__tests__/route-generator.test.ts +0 -137
- package/src/api/__tests__/server.test.ts +0 -73
- package/src/api/__tests__/swagger.test.ts +0 -166
- package/src/api/handlers.ts +0 -274
- package/src/api/include-resolver.ts +0 -27
- package/src/api/index.ts +0 -4
- package/src/api/meta-handler.ts +0 -254
- package/src/api/openapi-schema.ts +0 -99
- package/src/api/query-parser.ts +0 -315
- package/src/api/route-generator.ts +0 -448
- package/src/api/server.ts +0 -147
- package/src/api/types.ts +0 -16
- package/src/audit/__tests__/audit.test.ts +0 -144
- package/src/audit/index.ts +0 -3
- package/src/audit/record.ts +0 -69
- package/src/audit/tables.ts +0 -48
- package/src/audit/types.ts +0 -26
- package/src/auth/__tests__/core-module.test.ts +0 -54
- package/src/auth/__tests__/debug.test.ts +0 -47
- package/src/auth/__tests__/field-permissions.test.ts +0 -245
- package/src/auth/__tests__/integration.test.ts +0 -208
- package/src/auth/__tests__/meta-boot.test.ts +0 -538
- package/src/auth/__tests__/model-permissions.test.ts +0 -205
- package/src/auth/__tests__/password.test.ts +0 -29
- package/src/auth/__tests__/permission-registry.test.ts +0 -313
- package/src/auth/__tests__/scope-hook.test.ts +0 -509
- package/src/auth/__tests__/scope-registry.test.ts +0 -297
- package/src/auth/__tests__/scopes.test.ts +0 -66
- package/src/auth/__tests__/session.test.ts +0 -214
- package/src/auth/core-models.ts +0 -52
- package/src/auth/core-module.ts +0 -59
- package/src/auth/debug.ts +0 -157
- package/src/auth/field-permissions.ts +0 -116
- package/src/auth/index.ts +0 -37
- package/src/auth/model-permissions.ts +0 -59
- package/src/auth/password.ts +0 -22
- package/src/auth/permission-registry.ts +0 -171
- package/src/auth/scope-filters.ts +0 -11
- package/src/auth/scope-registry.ts +0 -121
- package/src/auth/scopes.ts +0 -146
- package/src/auth/seed.ts +0 -44
- package/src/auth/session.ts +0 -178
- package/src/auth/types.ts +0 -50
- package/src/boot/__tests__/page-scanning.test.ts +0 -170
- package/src/boot/__tests__/page-utils.test.ts +0 -225
- package/src/boot/__tests__/project-scanner.test.ts +0 -88
- package/src/boot/dependency-sort.ts +0 -82
- package/src/boot/discovery.ts +0 -85
- package/src/boot/index.ts +0 -457
- package/src/boot/page-utils.ts +0 -110
- package/src/boot/project-scanner.ts +0 -397
- package/src/boot/schema-loader.ts +0 -26
- package/src/boot/schema-merger.ts +0 -125
- package/src/boot/traits.ts +0 -25
- package/src/boot/types.ts +0 -73
- package/src/context.ts +0 -105
- package/src/db/__tests__/cascade-delete.test.ts +0 -182
- package/src/db/__tests__/desired-state.test.ts +0 -136
- package/src/db/__tests__/diff-engine.test.ts +0 -635
- package/src/db/__tests__/field-mapper.test.ts +0 -355
- package/src/db/__tests__/introspect.test.ts +0 -70
- package/src/db/__tests__/search-filter.test.ts +0 -45
- package/src/db/__tests__/sequence.test.ts +0 -221
- package/src/db/auto-sync.ts +0 -133
- package/src/db/client.ts +0 -147
- package/src/db/desired-state.ts +0 -98
- package/src/db/diff-engine.ts +0 -305
- package/src/db/field-mapper.ts +0 -504
- package/src/db/filter-applier.ts +0 -89
- package/src/db/include-resolver.ts +0 -40
- package/src/db/index.ts +0 -23
- package/src/db/introspect.ts +0 -265
- package/src/db/model-include-resolver.ts +0 -327
- package/src/db/model-ops.ts +0 -281
- package/src/db/scope-enforcer.ts +0 -37
- package/src/db/types.ts +0 -98
- package/src/errors.ts +0 -41
- package/src/events/__tests__/bus.test.ts +0 -105
- package/src/events/bus.ts +0 -89
- package/src/events/index.ts +0 -2
- package/src/events/types.ts +0 -9
- package/src/external-model/__tests__/computed-fields.test.ts +0 -106
- package/src/external-model/__tests__/field-mapper.test.ts +0 -160
- package/src/external-model/__tests__/in-memory-ops.test.ts +0 -247
- package/src/external-model/__tests__/mutation-executor.test.ts +0 -160
- package/src/external-model/__tests__/query-executor.test.ts +0 -284
- package/src/external-model/__tests__/schema-converter.test.ts +0 -174
- package/src/external-model/computed-fields.ts +0 -15
- package/src/external-model/define.ts +0 -5
- package/src/external-model/external-model-ops.ts +0 -108
- package/src/external-model/field-mapper.ts +0 -66
- package/src/external-model/in-memory-ops.ts +0 -107
- package/src/external-model/index.ts +0 -7
- package/src/external-model/mutation-executor.ts +0 -71
- package/src/external-model/query-executor.ts +0 -100
- package/src/external-model/schema-converter.ts +0 -53
- package/src/external-model/types.ts +0 -32
- package/src/fixtures/__tests__/fixtures.test.ts +0 -203
- package/src/fixtures/index.ts +0 -10
- package/src/fixtures/loader.ts +0 -196
- package/src/fixtures/registry.ts +0 -125
- package/src/fixtures/types.ts +0 -33
- package/src/helpers/assert-ownership.ts +0 -19
- package/src/helpers/coerce.ts +0 -28
- package/src/helpers/stamping.ts +0 -28
- package/src/helpers/validation.ts +0 -14
- package/src/hooks/__tests__/context.test.ts +0 -73
- package/src/hooks/__tests__/executor.test.ts +0 -433
- package/src/hooks/__tests__/middleware.test.ts +0 -224
- package/src/hooks/__tests__/registry.test.ts +0 -50
- package/src/hooks/context.ts +0 -89
- package/src/hooks/errors.ts +0 -11
- package/src/hooks/executor.ts +0 -115
- package/src/hooks/index.ts +0 -10
- package/src/hooks/middleware.ts +0 -220
- package/src/hooks/registry.ts +0 -20
- package/src/hooks/types.ts +0 -32
- package/src/index.ts +0 -172
- package/src/jobs/__tests__/enqueue.test.ts +0 -77
- package/src/jobs/__tests__/integration.test.ts +0 -71
- package/src/jobs/__tests__/registry.test.ts +0 -103
- package/src/jobs/__tests__/scheduler.test.ts +0 -92
- package/src/jobs/__tests__/worker-execution.test.ts +0 -202
- package/src/jobs/__tests__/worker.test.ts +0 -119
- package/src/jobs/enqueue.ts +0 -93
- package/src/jobs/index.ts +0 -14
- package/src/jobs/registry.ts +0 -92
- package/src/jobs/scheduler.ts +0 -205
- package/src/jobs/tables.ts +0 -132
- package/src/jobs/types.ts +0 -62
- package/src/jobs/worker.ts +0 -272
- package/src/model-api/__tests__/cross-boundary-includes.test.ts +0 -366
- package/src/model-api/__tests__/extended-api.test.ts +0 -244
- package/src/model-api/__tests__/filter-applier.test.ts +0 -177
- package/src/model-api/__tests__/filter-translator.test.ts +0 -186
- package/src/model-api/__tests__/include-resolver.test.ts +0 -226
- package/src/model-api/__tests__/model-access.test.ts +0 -284
- package/src/model-api/__tests__/query-builder.test.ts +0 -224
- package/src/model-api/__tests__/scope-enforcer.test.ts +0 -268
- package/src/model-api/field-access.ts +0 -28
- package/src/model-api/filter-applier.ts +0 -1
- package/src/model-api/filter-translator.ts +0 -67
- package/src/model-api/include-resolver.ts +0 -2
- package/src/model-api/index.ts +0 -86
- package/src/model-api/query-builder.ts +0 -155
- package/src/model-api/scope-enforcer.ts +0 -3
- package/src/model-api/types.ts +0 -139
- package/src/plugins/__tests__/adapter-registry.test.ts +0 -92
- package/src/plugins/__tests__/lifecycle.test.ts +0 -96
- package/src/plugins/__tests__/loader.test.ts +0 -273
- package/src/plugins/__tests__/validator.test.ts +0 -275
- package/src/plugins/adapter-registry.ts +0 -42
- package/src/plugins/define.ts +0 -5
- package/src/plugins/index.ts +0 -28
- package/src/plugins/lifecycle.ts +0 -27
- package/src/plugins/loader.ts +0 -126
- package/src/plugins/types.ts +0 -76
- package/src/plugins/validator.ts +0 -141
- package/src/schema/__tests__/registry-models-by-module.test.ts +0 -58
- package/src/schema/registry.ts +0 -93
- package/src/schema/relationships.ts +0 -93
- package/src/schema/types.ts +0 -43
- package/src/services/__tests__/integration.test.ts +0 -63
- package/src/services/__tests__/registry.test.ts +0 -175
- package/src/services/index.ts +0 -13
- package/src/services/registry.ts +0 -156
- package/src/services/types.ts +0 -27
- package/src/validation/__tests__/field-validator.test.ts +0 -195
- package/src/validation/field-validator.ts +0 -113
- package/src/validation/index.ts +0 -1
- package/src/widgets/index.ts +0 -3
- package/src/widgets/slot-validator.ts +0 -87
- package/src/widgets/widget-registry.ts +0 -32
- package/tests/boot.test.ts +0 -323
- package/tests/dependency-sort.test.ts +0 -99
- package/tests/discovery.test.ts +0 -126
- package/tests/registry.test.ts +0 -216
- package/tests/schema-loader.test.ts +0 -52
- package/tests/schema-merger.test.ts +0 -180
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -14
package/src/db/field-mapper.ts
DELETED
|
@@ -1,504 +0,0 @@
|
|
|
1
|
-
import type { ResolvedField, ResolvedModel } from '../schema/types.js';
|
|
2
|
-
import type {
|
|
3
|
-
ColumnDefinition,
|
|
4
|
-
ForeignKeyDefinition,
|
|
5
|
-
CheckConstraintDefinition,
|
|
6
|
-
TableDefinition,
|
|
7
|
-
} from './types.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* The complete set of database artifacts produced when mapping a model's fields to columns.
|
|
11
|
-
*/
|
|
12
|
-
export interface FieldMappingResult {
|
|
13
|
-
columns: ColumnDefinition[];
|
|
14
|
-
foreignKeys: ForeignKeyDefinition[];
|
|
15
|
-
checkConstraints: CheckConstraintDefinition[];
|
|
16
|
-
extraTables: TableDefinition[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
// Public API
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Convert a dot-separated qualified model name (e.g. "core.User") to a
|
|
25
|
-
* flat table name using double-underscore as separator (e.g. "core__User").
|
|
26
|
-
*/
|
|
27
|
-
export function modelToTableName(qualifiedName: string): string {
|
|
28
|
-
return qualifiedName.replace('.', '__');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Map all fields of a resolved model into database column definitions,
|
|
33
|
-
* foreign keys, check constraints, and any extra junction/closure tables.
|
|
34
|
-
*/
|
|
35
|
-
export function mapFieldsToColumns(model: ResolvedModel): FieldMappingResult {
|
|
36
|
-
const tableName = modelToTableName(model.qualifiedName);
|
|
37
|
-
const columns: ColumnDefinition[] = [];
|
|
38
|
-
const foreignKeys: ForeignKeyDefinition[] = [];
|
|
39
|
-
const checkConstraints: CheckConstraintDefinition[] = [];
|
|
40
|
-
const extraTables: TableDefinition[] = [];
|
|
41
|
-
|
|
42
|
-
// Every table gets a UUID primary key
|
|
43
|
-
columns.push({
|
|
44
|
-
name: 'id',
|
|
45
|
-
type: 'UUID',
|
|
46
|
-
nullable: false,
|
|
47
|
-
defaultValue: 'gen_random_uuid()',
|
|
48
|
-
primaryKey: true,
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// Add trait-derived columns (timestamped, etc.)
|
|
52
|
-
// but skip any that the model already declares explicitly as a field.
|
|
53
|
-
const traitResult = mapTraitColumns(model.traits, tableName);
|
|
54
|
-
const explicitFieldNames = new Set(model.fields.map((f) => f.name));
|
|
55
|
-
for (const traitColumn of traitResult.columns) {
|
|
56
|
-
if (!explicitFieldNames.has(traitColumn.name)) {
|
|
57
|
-
columns.push(traitColumn);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
foreignKeys.push(...traitResult.foreignKeys);
|
|
61
|
-
|
|
62
|
-
// Map each user-defined field to its database representation
|
|
63
|
-
for (const field of model.fields) {
|
|
64
|
-
const fieldResult = mapField(field, model, tableName);
|
|
65
|
-
columns.push(...fieldResult.columns);
|
|
66
|
-
foreignKeys.push(...fieldResult.foreignKeys);
|
|
67
|
-
checkConstraints.push(...fieldResult.checkConstraints);
|
|
68
|
-
extraTables.push(...fieldResult.extraTables);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return { columns, foreignKeys, checkConstraints, extraTables };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
// Field-type mapping (main dispatcher)
|
|
76
|
-
// ---------------------------------------------------------------------------
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Route a single field to the appropriate column mapping logic based on its type.
|
|
80
|
-
*/
|
|
81
|
-
function mapField(
|
|
82
|
-
field: ResolvedField,
|
|
83
|
-
model: ResolvedModel,
|
|
84
|
-
tableName: string,
|
|
85
|
-
): FieldMappingResult {
|
|
86
|
-
const config = field.config;
|
|
87
|
-
|
|
88
|
-
switch (config.type) {
|
|
89
|
-
case 'string':
|
|
90
|
-
return singleColumn({
|
|
91
|
-
name: field.name,
|
|
92
|
-
type: `VARCHAR(${config.maxLength ?? 255})`,
|
|
93
|
-
nullable: !config.required,
|
|
94
|
-
defaultValue: quoteStringDefault(config.default),
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
case 'text':
|
|
98
|
-
return singleColumn({
|
|
99
|
-
name: field.name,
|
|
100
|
-
type: 'TEXT',
|
|
101
|
-
nullable: !config.required,
|
|
102
|
-
defaultValue: quoteStringDefault(config.default),
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
case 'int':
|
|
106
|
-
return singleColumn({
|
|
107
|
-
name: field.name,
|
|
108
|
-
type: 'INTEGER',
|
|
109
|
-
nullable: !config.required,
|
|
110
|
-
defaultValue: config.default !== undefined ? String(config.default) : undefined,
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
case 'decimal':
|
|
114
|
-
return singleColumn({
|
|
115
|
-
name: field.name,
|
|
116
|
-
type: `DECIMAL(${config.precision ?? 18},${config.scale ?? 6})`,
|
|
117
|
-
nullable: !config.required,
|
|
118
|
-
defaultValue: config.default !== undefined ? String(config.default) : undefined,
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
case 'boolean':
|
|
122
|
-
return singleColumn({
|
|
123
|
-
name: field.name,
|
|
124
|
-
type: 'BOOLEAN',
|
|
125
|
-
nullable: !config.required,
|
|
126
|
-
defaultValue: formatBooleanDefault(config.default),
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
case 'date':
|
|
130
|
-
return singleColumn({
|
|
131
|
-
name: field.name,
|
|
132
|
-
type: 'DATE',
|
|
133
|
-
nullable: !config.required,
|
|
134
|
-
defaultValue: quoteStringDefault(config.default),
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
case 'datetime':
|
|
138
|
-
return singleColumn({
|
|
139
|
-
name: field.name,
|
|
140
|
-
type: 'TIMESTAMPTZ',
|
|
141
|
-
nullable: !config.required,
|
|
142
|
-
defaultValue: quoteStringDefault(config.default),
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
case 'enum':
|
|
146
|
-
return mapEnumField(field, config, tableName);
|
|
147
|
-
|
|
148
|
-
case 'json':
|
|
149
|
-
return singleColumn({
|
|
150
|
-
name: field.name,
|
|
151
|
-
type: 'JSONB',
|
|
152
|
-
nullable: !config.required,
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
case 'code':
|
|
156
|
-
return singleColumn({
|
|
157
|
-
name: field.name,
|
|
158
|
-
type: 'TEXT',
|
|
159
|
-
nullable: !config.required,
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
case 'link':
|
|
163
|
-
return mapLinkField(field, config, model, tableName);
|
|
164
|
-
|
|
165
|
-
case 'dynamicLink':
|
|
166
|
-
return mapDynamicLinkField(field, config);
|
|
167
|
-
|
|
168
|
-
case 'money':
|
|
169
|
-
return mapMoneyField(field, config);
|
|
170
|
-
|
|
171
|
-
case 'tree':
|
|
172
|
-
return mapTreeField(field, model, tableName, config.strategy);
|
|
173
|
-
|
|
174
|
-
case 'sequence':
|
|
175
|
-
return singleColumn({
|
|
176
|
-
name: field.name,
|
|
177
|
-
type: 'VARCHAR(255)',
|
|
178
|
-
nullable: true,
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
// Relation types have no local columns -- they're handled by the related model
|
|
182
|
-
case 'hasMany':
|
|
183
|
-
case 'children':
|
|
184
|
-
return emptyResult();
|
|
185
|
-
|
|
186
|
-
case 'manyToMany':
|
|
187
|
-
return buildManyToManyJunction(field, model);
|
|
188
|
-
|
|
189
|
-
case 'attachment':
|
|
190
|
-
case 'attachments':
|
|
191
|
-
return singleColumn({
|
|
192
|
-
name: field.name,
|
|
193
|
-
type: 'JSONB',
|
|
194
|
-
nullable: !config.required,
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
default:
|
|
198
|
-
return emptyResult();
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// ---------------------------------------------------------------------------
|
|
203
|
-
// Compound field mappers (produce multiple columns or constraints)
|
|
204
|
-
// ---------------------------------------------------------------------------
|
|
205
|
-
|
|
206
|
-
/** Enum fields store a VARCHAR with a CHECK constraint restricting allowed values. */
|
|
207
|
-
function mapEnumField(
|
|
208
|
-
field: ResolvedField,
|
|
209
|
-
config: { required?: boolean; default?: string; options: readonly string[] },
|
|
210
|
-
tableName: string,
|
|
211
|
-
): FieldMappingResult {
|
|
212
|
-
const allowedValues = config.options.map((v) => `'${v}'`).join(', ');
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
columns: [
|
|
216
|
-
{
|
|
217
|
-
name: field.name,
|
|
218
|
-
type: 'VARCHAR(255)',
|
|
219
|
-
nullable: !config.required,
|
|
220
|
-
defaultValue: quoteStringDefault(config.default),
|
|
221
|
-
},
|
|
222
|
-
],
|
|
223
|
-
foreignKeys: [],
|
|
224
|
-
checkConstraints: [
|
|
225
|
-
{
|
|
226
|
-
name: `chk_${tableName}_${field.name}`,
|
|
227
|
-
column: field.name,
|
|
228
|
-
expression: `${field.name} IN (${allowedValues})`,
|
|
229
|
-
},
|
|
230
|
-
],
|
|
231
|
-
extraTables: [],
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/** Link fields store a UUID foreign key pointing to another model's table. */
|
|
236
|
-
function mapLinkField(
|
|
237
|
-
field: ResolvedField,
|
|
238
|
-
config: { required?: boolean; nullable?: boolean; model: string },
|
|
239
|
-
currentModel: ResolvedModel,
|
|
240
|
-
tableName: string,
|
|
241
|
-
): FieldMappingResult {
|
|
242
|
-
const referencedTable =
|
|
243
|
-
config.model === '__self__' ? tableName : resolveLinkedTableName(config.model, currentModel);
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
columns: [
|
|
247
|
-
{
|
|
248
|
-
name: field.name,
|
|
249
|
-
type: 'UUID',
|
|
250
|
-
nullable: config.nullable === true ? true : !config.required,
|
|
251
|
-
},
|
|
252
|
-
],
|
|
253
|
-
foreignKeys: [
|
|
254
|
-
{
|
|
255
|
-
name: `fk_${tableName}_${field.name}`,
|
|
256
|
-
column: field.name,
|
|
257
|
-
referencedTable,
|
|
258
|
-
referencedColumn: 'id',
|
|
259
|
-
},
|
|
260
|
-
],
|
|
261
|
-
checkConstraints: [],
|
|
262
|
-
extraTables: [],
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/** Dynamic links store a model type discriminator column alongside the UUID reference. */
|
|
267
|
-
function mapDynamicLinkField(
|
|
268
|
-
field: ResolvedField,
|
|
269
|
-
config: { required?: boolean; modelField: string },
|
|
270
|
-
): FieldMappingResult {
|
|
271
|
-
return {
|
|
272
|
-
columns: [
|
|
273
|
-
{
|
|
274
|
-
name: config.modelField,
|
|
275
|
-
type: 'VARCHAR(255)',
|
|
276
|
-
nullable: !config.required,
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
name: field.name,
|
|
280
|
-
type: 'UUID',
|
|
281
|
-
nullable: !config.required,
|
|
282
|
-
},
|
|
283
|
-
],
|
|
284
|
-
foreignKeys: [],
|
|
285
|
-
checkConstraints: [],
|
|
286
|
-
extraTables: [],
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/** Money fields store both the raw amount and a base-currency converted amount. */
|
|
291
|
-
function mapMoneyField(field: ResolvedField, config: { required?: boolean }): FieldMappingResult {
|
|
292
|
-
return {
|
|
293
|
-
columns: [
|
|
294
|
-
{
|
|
295
|
-
name: field.name,
|
|
296
|
-
type: 'DECIMAL(18,6)',
|
|
297
|
-
nullable: !config.required,
|
|
298
|
-
},
|
|
299
|
-
{
|
|
300
|
-
name: `${field.name}_base`,
|
|
301
|
-
type: 'DECIMAL(18,6)',
|
|
302
|
-
nullable: !config.required,
|
|
303
|
-
},
|
|
304
|
-
],
|
|
305
|
-
foreignKeys: [],
|
|
306
|
-
checkConstraints: [],
|
|
307
|
-
extraTables: [],
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Tree fields store a self-referencing parent FK plus strategy-specific columns:
|
|
313
|
-
* - materialized_path: path (TEXT) + depth (INTEGER)
|
|
314
|
-
* - nested_set: lft + rgt (INTEGER pair)
|
|
315
|
-
* - closure_table: generates an extra closure junction table
|
|
316
|
-
*/
|
|
317
|
-
function mapTreeField(
|
|
318
|
-
field: ResolvedField,
|
|
319
|
-
_model: ResolvedModel,
|
|
320
|
-
tableName: string,
|
|
321
|
-
strategy: 'materialized_path' | 'nested_set' | 'closure_table',
|
|
322
|
-
): FieldMappingResult {
|
|
323
|
-
const parentColumn = field.name;
|
|
324
|
-
const columns: ColumnDefinition[] = [{ name: parentColumn, type: 'UUID', nullable: true }];
|
|
325
|
-
|
|
326
|
-
const foreignKeys: ForeignKeyDefinition[] = [
|
|
327
|
-
{
|
|
328
|
-
name: `fk_${tableName}_${parentColumn}`,
|
|
329
|
-
column: parentColumn,
|
|
330
|
-
referencedTable: tableName,
|
|
331
|
-
referencedColumn: 'id',
|
|
332
|
-
},
|
|
333
|
-
];
|
|
334
|
-
|
|
335
|
-
const extraTables: TableDefinition[] = [];
|
|
336
|
-
|
|
337
|
-
switch (strategy) {
|
|
338
|
-
case 'materialized_path':
|
|
339
|
-
columns.push(
|
|
340
|
-
{ name: 'path', type: 'TEXT', nullable: true },
|
|
341
|
-
{ name: 'depth', type: 'INTEGER', nullable: true },
|
|
342
|
-
);
|
|
343
|
-
break;
|
|
344
|
-
|
|
345
|
-
case 'nested_set':
|
|
346
|
-
columns.push(
|
|
347
|
-
{ name: 'lft', type: 'INTEGER', nullable: true },
|
|
348
|
-
{ name: 'rgt', type: 'INTEGER', nullable: true },
|
|
349
|
-
);
|
|
350
|
-
break;
|
|
351
|
-
|
|
352
|
-
case 'closure_table':
|
|
353
|
-
extraTables.push(buildClosureTable(tableName));
|
|
354
|
-
break;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return { columns, foreignKeys, checkConstraints: [], extraTables };
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/** Build the closure junction table definition for a tree with closure_table strategy. */
|
|
361
|
-
function buildClosureTable(tableName: string): TableDefinition {
|
|
362
|
-
return {
|
|
363
|
-
name: `${tableName}_closure`,
|
|
364
|
-
columns: [
|
|
365
|
-
{ name: 'ancestor_id', type: 'UUID', nullable: false },
|
|
366
|
-
{ name: 'descendant_id', type: 'UUID', nullable: false },
|
|
367
|
-
{ name: 'depth', type: 'INTEGER', nullable: false },
|
|
368
|
-
],
|
|
369
|
-
foreignKeys: [
|
|
370
|
-
{
|
|
371
|
-
name: `fk_${tableName}_closure_ancestor`,
|
|
372
|
-
column: 'ancestor_id',
|
|
373
|
-
referencedTable: tableName,
|
|
374
|
-
referencedColumn: 'id',
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
name: `fk_${tableName}_closure_descendant`,
|
|
378
|
-
column: 'descendant_id',
|
|
379
|
-
referencedTable: tableName,
|
|
380
|
-
referencedColumn: 'id',
|
|
381
|
-
},
|
|
382
|
-
],
|
|
383
|
-
checkConstraints: [],
|
|
384
|
-
indexes: [],
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/** Build a junction table for a manyToMany field. */
|
|
389
|
-
function buildManyToManyJunction(field: ResolvedField, model: ResolvedModel): FieldMappingResult {
|
|
390
|
-
const config = field.config as { type: 'manyToMany'; model: string; through: string };
|
|
391
|
-
const junctionTable = resolveLinkedTableName(config.through, model);
|
|
392
|
-
const sourceTable = modelToTableName(model.qualifiedName);
|
|
393
|
-
const targetTable = resolveLinkedTableName(config.model, model);
|
|
394
|
-
|
|
395
|
-
const sourceModelName = model.name;
|
|
396
|
-
const targetModelName = config.model.includes('.')
|
|
397
|
-
? config.model.split('.').pop()!
|
|
398
|
-
: config.model;
|
|
399
|
-
|
|
400
|
-
const sourceFk = `${sourceModelName}_id`;
|
|
401
|
-
const targetFk = `${targetModelName}_id`;
|
|
402
|
-
|
|
403
|
-
const junction: TableDefinition = {
|
|
404
|
-
name: junctionTable,
|
|
405
|
-
columns: [
|
|
406
|
-
{ name: sourceFk, type: 'UUID', nullable: false },
|
|
407
|
-
{ name: targetFk, type: 'UUID', nullable: false },
|
|
408
|
-
],
|
|
409
|
-
foreignKeys: [
|
|
410
|
-
{
|
|
411
|
-
name: `fk_${junctionTable}_${sourceFk}`,
|
|
412
|
-
column: sourceFk,
|
|
413
|
-
referencedTable: sourceTable,
|
|
414
|
-
referencedColumn: 'id',
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
name: `fk_${junctionTable}_${targetFk}`,
|
|
418
|
-
column: targetFk,
|
|
419
|
-
referencedTable: targetTable,
|
|
420
|
-
referencedColumn: 'id',
|
|
421
|
-
},
|
|
422
|
-
],
|
|
423
|
-
checkConstraints: [],
|
|
424
|
-
indexes: [
|
|
425
|
-
{
|
|
426
|
-
name: `idx_${junctionTable}_unique`,
|
|
427
|
-
table: junctionTable,
|
|
428
|
-
columns: [sourceFk, targetFk],
|
|
429
|
-
unique: true,
|
|
430
|
-
},
|
|
431
|
-
],
|
|
432
|
-
};
|
|
433
|
-
|
|
434
|
-
return { columns: [], foreignKeys: [], checkConstraints: [], extraTables: [junction] };
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// ---------------------------------------------------------------------------
|
|
438
|
-
// Trait mapping
|
|
439
|
-
// ---------------------------------------------------------------------------
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Map model traits (e.g. "timestamped") into
|
|
443
|
-
* the columns and foreign keys they imply.
|
|
444
|
-
*/
|
|
445
|
-
function mapTraitColumns(
|
|
446
|
-
traits: string[],
|
|
447
|
-
_tableName: string,
|
|
448
|
-
): { columns: ColumnDefinition[]; foreignKeys: ForeignKeyDefinition[] } {
|
|
449
|
-
const columns: ColumnDefinition[] = [];
|
|
450
|
-
const foreignKeys: ForeignKeyDefinition[] = [];
|
|
451
|
-
|
|
452
|
-
for (const trait of traits) {
|
|
453
|
-
switch (trait) {
|
|
454
|
-
case 'timestamped':
|
|
455
|
-
columns.push(
|
|
456
|
-
{ name: 'created_at', type: 'TIMESTAMPTZ', nullable: false },
|
|
457
|
-
{ name: 'updated_at', type: 'TIMESTAMPTZ', nullable: false },
|
|
458
|
-
{ name: 'created_by', type: 'UUID', nullable: true },
|
|
459
|
-
{ name: 'updated_by', type: 'UUID', nullable: true },
|
|
460
|
-
);
|
|
461
|
-
break;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
return { columns, foreignKeys };
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// ---------------------------------------------------------------------------
|
|
469
|
-
// Utility helpers
|
|
470
|
-
// ---------------------------------------------------------------------------
|
|
471
|
-
|
|
472
|
-
/** Wrap a single column definition into a full FieldMappingResult with no extras. */
|
|
473
|
-
function singleColumn(column: ColumnDefinition): FieldMappingResult {
|
|
474
|
-
return { columns: [column], foreignKeys: [], checkConstraints: [], extraTables: [] };
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/** Return an empty result (no columns, no constraints). Used for virtual/relation fields. */
|
|
478
|
-
function emptyResult(): FieldMappingResult {
|
|
479
|
-
return { columns: [], foreignKeys: [], checkConstraints: [], extraTables: [] };
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/** Wrap a string default value in single quotes, or return undefined if not set. */
|
|
483
|
-
function quoteStringDefault(value: string | undefined): string | undefined {
|
|
484
|
-
return value !== undefined ? `'${value}'` : undefined;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/** Format a boolean default as SQL TRUE/FALSE, or return undefined if not set. */
|
|
488
|
-
function formatBooleanDefault(value: boolean | undefined): string | undefined {
|
|
489
|
-
if (value === undefined) return undefined;
|
|
490
|
-
return value ? 'TRUE' : 'FALSE';
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* Resolve a model reference to a table name. If the reference is already
|
|
495
|
-
* fully qualified (contains a dot), convert directly. Otherwise, assume
|
|
496
|
-
* it belongs to the same module as the current model.
|
|
497
|
-
*/
|
|
498
|
-
function resolveLinkedTableName(modelRef: string, currentModel: ResolvedModel): string {
|
|
499
|
-
if (modelRef.includes('.')) {
|
|
500
|
-
return modelToTableName(modelRef);
|
|
501
|
-
}
|
|
502
|
-
const qualifiedName = `${currentModel.module}.${modelRef}`;
|
|
503
|
-
return modelToTableName(qualifiedName);
|
|
504
|
-
}
|
package/src/db/filter-applier.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { sql } from 'kysely';
|
|
2
|
-
import type { TranslatedFilter } from '../model-api/types.js';
|
|
3
|
-
import { toBool } from '../helpers/coerce.js';
|
|
4
|
-
|
|
5
|
-
function escapeLike(value: string): string {
|
|
6
|
-
return value.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_');
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
-
export function applyModelFilters(query: any, filters: TranslatedFilter[]): any {
|
|
11
|
-
let result = query;
|
|
12
|
-
|
|
13
|
-
for (const { field, operator, value } of filters) {
|
|
14
|
-
switch (operator) {
|
|
15
|
-
case 'eq':
|
|
16
|
-
result = result.where(field, '=', value);
|
|
17
|
-
break;
|
|
18
|
-
case 'neq':
|
|
19
|
-
result = result.where(field, '!=', value);
|
|
20
|
-
break;
|
|
21
|
-
case 'gt':
|
|
22
|
-
result = result.where(field, '>', value);
|
|
23
|
-
break;
|
|
24
|
-
case 'gte':
|
|
25
|
-
result = result.where(field, '>=', value);
|
|
26
|
-
break;
|
|
27
|
-
case 'lt':
|
|
28
|
-
result = result.where(field, '<', value);
|
|
29
|
-
break;
|
|
30
|
-
case 'lte':
|
|
31
|
-
result = result.where(field, '<=', value);
|
|
32
|
-
break;
|
|
33
|
-
case 'in': {
|
|
34
|
-
const arr = value as unknown[];
|
|
35
|
-
if (arr.length === 0) {
|
|
36
|
-
result = result.where(sql`1 = 0`);
|
|
37
|
-
} else {
|
|
38
|
-
result = result.where(field, 'in', arr);
|
|
39
|
-
}
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
case 'notIn': {
|
|
43
|
-
const arr = value as unknown[];
|
|
44
|
-
if (arr.length > 0) {
|
|
45
|
-
result = result.where(field, 'not in', arr);
|
|
46
|
-
}
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
case 'contains':
|
|
50
|
-
result = result.where(field, 'ilike', `%${escapeLike(value as string)}%`);
|
|
51
|
-
break;
|
|
52
|
-
case 'startsWith':
|
|
53
|
-
result = result.where(field, 'ilike', `${escapeLike(value as string)}%`);
|
|
54
|
-
break;
|
|
55
|
-
case 'endsWith':
|
|
56
|
-
result = result.where(field, 'ilike', `%${escapeLike(value as string)}`);
|
|
57
|
-
break;
|
|
58
|
-
case 'is':
|
|
59
|
-
if (value === null) {
|
|
60
|
-
result = result.where(field, 'is', null);
|
|
61
|
-
} else {
|
|
62
|
-
result = result.where(field, 'is not', null);
|
|
63
|
-
}
|
|
64
|
-
break;
|
|
65
|
-
case 'isnull':
|
|
66
|
-
if (toBool(value)) {
|
|
67
|
-
result = result.where(field, 'is', null);
|
|
68
|
-
} else {
|
|
69
|
-
result = result.where(field, 'is not', null);
|
|
70
|
-
}
|
|
71
|
-
break;
|
|
72
|
-
case 'like':
|
|
73
|
-
result = result.where(field, 'ilike', `%${value}%`);
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return result;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
82
|
-
export function applySearchFilter(query: any, term: string, fields: string[]): any {
|
|
83
|
-
if (!term || fields.length === 0) return query;
|
|
84
|
-
const escaped = `%${escapeLike(term)}%`;
|
|
85
|
-
return query.where((eb: any) =>
|
|
86
|
-
eb.or(fields.map((field: string) => eb(field, 'ilike', escaped))),
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { Kysely } from 'kysely';
|
|
2
|
-
import type { SchemaRegistry } from '../schema/registry.js';
|
|
3
|
-
import type { IncludeResolver } from '../model-api/types.js';
|
|
4
|
-
import type { AdapterRegistry } from '../plugins/adapter-registry.js';
|
|
5
|
-
import type { ExternalFieldConfig } from '../external-model/types.js';
|
|
6
|
-
import { resolveModelIncludes } from './model-include-resolver.js';
|
|
7
|
-
|
|
8
|
-
export interface CompositeIncludeResolverConfig {
|
|
9
|
-
registry: SchemaRegistry;
|
|
10
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
-
db: Kysely<any> | any;
|
|
12
|
-
adapterRegistry?: AdapterRegistry;
|
|
13
|
-
externalModelFields?: Record<string, Record<string, ExternalFieldConfig>>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class CompositeIncludeResolver implements IncludeResolver {
|
|
17
|
-
private readonly registry: SchemaRegistry;
|
|
18
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
-
private readonly db: Kysely<any>;
|
|
20
|
-
private readonly adapterRegistry?: AdapterRegistry;
|
|
21
|
-
private readonly externalModelFields?: Record<string, Record<string, ExternalFieldConfig>>;
|
|
22
|
-
|
|
23
|
-
constructor(config: CompositeIncludeResolverConfig) {
|
|
24
|
-
this.registry = config.registry;
|
|
25
|
-
this.db = config.db;
|
|
26
|
-
this.adapterRegistry = config.adapterRegistry;
|
|
27
|
-
this.externalModelFields = config.externalModelFields;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async resolve(
|
|
31
|
-
records: Record<string, unknown>[],
|
|
32
|
-
includes: string[],
|
|
33
|
-
sourceModel: string,
|
|
34
|
-
): Promise<void> {
|
|
35
|
-
await resolveModelIncludes(records, includes, this.registry, this.db, sourceModel, {
|
|
36
|
-
adapterRegistry: this.adapterRegistry,
|
|
37
|
-
externalModelFields: this.externalModelFields,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
}
|
package/src/db/index.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export { DatabaseClient } from './client.js';
|
|
2
|
-
export type { DatabaseClientConfig } from './client.js';
|
|
3
|
-
export type {
|
|
4
|
-
ColumnDefinition,
|
|
5
|
-
TableDefinition,
|
|
6
|
-
IndexDefinition,
|
|
7
|
-
ForeignKeyDefinition,
|
|
8
|
-
DesiredState,
|
|
9
|
-
ActualState,
|
|
10
|
-
DdlOperation,
|
|
11
|
-
} from './types.js';
|
|
12
|
-
export { mapFieldsToColumns } from './field-mapper.js';
|
|
13
|
-
export { SchemaToDesired } from './desired-state.js';
|
|
14
|
-
export { DiffEngine } from './diff-engine.js';
|
|
15
|
-
export { introspect } from './introspect.js';
|
|
16
|
-
export { autoSync } from './auto-sync.js';
|
|
17
|
-
export { KyselyModelOps } from './model-ops.js';
|
|
18
|
-
export type { KyselyModelOpsConfig } from './model-ops.js';
|
|
19
|
-
export { applyModelFilters } from './filter-applier.js';
|
|
20
|
-
export { applyScopeEnforcement } from './scope-enforcer.js';
|
|
21
|
-
export type { ScopeEnforcementOptions } from './scope-enforcer.js';
|
|
22
|
-
export { CompositeIncludeResolver } from './include-resolver.js';
|
|
23
|
-
export type { CompositeIncludeResolverConfig } from './include-resolver.js';
|