ormbridge 0.8.0 → 0.9.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.
Files changed (71) hide show
  1. package/README.md +1 -0
  2. package/dist/cliRunner.js +3 -3
  3. package/dist/cliRunner.js.map +1 -1
  4. package/dist/config.d.ts +3 -2
  5. package/dist/config.js.map +1 -1
  6. package/dist/emitters/efcore.d.ts +40 -0
  7. package/dist/emitters/efcore.js +686 -0
  8. package/dist/emitters/efcore.js.map +1 -0
  9. package/dist/emitters/efcoreColumns.d.ts +75 -0
  10. package/dist/emitters/efcoreColumns.js +492 -0
  11. package/dist/emitters/efcoreColumns.js.map +1 -0
  12. package/dist/emitters/efcoreNames.d.ts +30 -0
  13. package/dist/emitters/efcoreNames.js +268 -0
  14. package/dist/emitters/efcoreNames.js.map +1 -0
  15. package/dist/emitters/efcoreRender.d.ts +2 -0
  16. package/dist/emitters/efcoreRender.js +570 -0
  17. package/dist/emitters/efcoreRender.js.map +1 -0
  18. package/dist/emitters/efcoreTypes.d.ts +114 -0
  19. package/dist/emitters/efcoreTypes.js +2 -0
  20. package/dist/emitters/efcoreTypes.js.map +1 -0
  21. package/dist/formats.d.ts +7 -4
  22. package/dist/formats.js +34 -0
  23. package/dist/formats.js.map +1 -1
  24. package/dist/index.d.ts +4 -0
  25. package/dist/index.js +2 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/io.js +25 -1
  28. package/dist/io.js.map +1 -1
  29. package/dist/parsers/csharpChains.d.ts +36 -0
  30. package/dist/parsers/csharpChains.js +157 -0
  31. package/dist/parsers/csharpChains.js.map +1 -0
  32. package/dist/parsers/csharpDeclarations.d.ts +3 -0
  33. package/dist/parsers/csharpDeclarations.js +293 -0
  34. package/dist/parsers/csharpDeclarations.js.map +1 -0
  35. package/dist/parsers/csharpSyntax.d.ts +130 -0
  36. package/dist/parsers/csharpSyntax.js +339 -0
  37. package/dist/parsers/csharpSyntax.js.map +1 -0
  38. package/dist/parsers/efcore.d.ts +17 -0
  39. package/dist/parsers/efcore.js +265 -0
  40. package/dist/parsers/efcore.js.map +1 -0
  41. package/dist/parsers/efcoreAnnotations.d.ts +19 -0
  42. package/dist/parsers/efcoreAnnotations.js +293 -0
  43. package/dist/parsers/efcoreAnnotations.js.map +1 -0
  44. package/dist/parsers/efcoreBuild.d.ts +13 -0
  45. package/dist/parsers/efcoreBuild.js +523 -0
  46. package/dist/parsers/efcoreBuild.js.map +1 -0
  47. package/dist/parsers/efcoreEntities.d.ts +13 -0
  48. package/dist/parsers/efcoreEntities.js +468 -0
  49. package/dist/parsers/efcoreEntities.js.map +1 -0
  50. package/dist/parsers/efcoreFields.d.ts +22 -0
  51. package/dist/parsers/efcoreFields.js +285 -0
  52. package/dist/parsers/efcoreFields.js.map +1 -0
  53. package/dist/parsers/efcoreFluent.d.ts +47 -0
  54. package/dist/parsers/efcoreFluent.js +375 -0
  55. package/dist/parsers/efcoreFluent.js.map +1 -0
  56. package/dist/parsers/efcoreModel.d.ts +209 -0
  57. package/dist/parsers/efcoreModel.js +2 -0
  58. package/dist/parsers/efcoreModel.js.map +1 -0
  59. package/dist/parsers/efcoreRelations.d.ts +46 -0
  60. package/dist/parsers/efcoreRelations.js +317 -0
  61. package/dist/parsers/efcoreRelations.js.map +1 -0
  62. package/dist/parsers/efcoreSteps.d.ts +89 -0
  63. package/dist/parsers/efcoreSteps.js +793 -0
  64. package/dist/parsers/efcoreSteps.js.map +1 -0
  65. package/dist/parsers/efcoreTypes.d.ts +35 -0
  66. package/dist/parsers/efcoreTypes.js +404 -0
  67. package/dist/parsers/efcoreTypes.js.map +1 -0
  68. package/dist/styles.d.ts +3 -2
  69. package/dist/styles.js +4 -1
  70. package/dist/styles.js.map +1 -1
  71. package/package.json +3 -1
@@ -0,0 +1,793 @@
1
+ import { evaluate, parseTypeText, } from './csharpSyntax.js';
2
+ import { readLambda, selectedMembers, } from './csharpChains.js';
3
+ import { newOwnedConfig, propConfig } from './efcoreAnnotations.js';
4
+ export const NONE = { kind: 'none' };
5
+ /**
6
+ * The interpretation of fluent calls: what each builder method does to the configuration of an entity, a
7
+ * property, an index or a relationship. `ConfigStore` adds the registry of entities and the code that walks
8
+ * statements and finds the calls.
9
+ */
10
+ export class FluentSteps {
11
+ ownedTypes = new Set();
12
+ ignoredEntities = new Set();
13
+ defaultSchema;
14
+ step(target, call, bindings, place, owner) {
15
+ switch (target.kind) {
16
+ case 'model':
17
+ return this.stepModel(call, bindings, place, owner);
18
+ case 'entity':
19
+ return this.stepEntity(target, call, bindings, place, owner);
20
+ case 'property':
21
+ return this.stepProperty(target, call, place);
22
+ case 'index':
23
+ return this.stepIndex(target, call, place);
24
+ case 'key':
25
+ return this.stepKey(target, call, place);
26
+ case 'relation':
27
+ return this.stepRelation(target, call, bindings, place, owner);
28
+ case 'table':
29
+ return this.stepTable(target, call, place);
30
+ case 'discriminator':
31
+ return this.stepDiscriminator(target, call, place);
32
+ case 'join':
33
+ return this.stepJoin(target, call, place);
34
+ case 'joinSide':
35
+ return this.stepJoinSide(target, call, place);
36
+ case 'none':
37
+ return target;
38
+ }
39
+ }
40
+ /** The first argument from `from` on that is a lambda. */
41
+ lambdaArg(call, from = 0) {
42
+ for (const argument of call.args.slice(from)) {
43
+ const lambda = readLambda(argument);
44
+ if (lambda !== undefined) {
45
+ return lambda;
46
+ }
47
+ }
48
+ return undefined;
49
+ }
50
+ stringAt(call, index) {
51
+ const node = call.args[index];
52
+ if (node === undefined) {
53
+ return undefined;
54
+ }
55
+ const value = evaluate(node, this.consts);
56
+ return value.kind === 'string' ? value.value : undefined;
57
+ }
58
+ boolAt(call, index, fallback) {
59
+ const node = call.args[index];
60
+ if (node === undefined) {
61
+ return fallback;
62
+ }
63
+ const value = evaluate(node, this.consts);
64
+ return value.kind === 'bool' ? value.value : fallback;
65
+ }
66
+ numberAt(call, index) {
67
+ const node = call.args[index];
68
+ if (node === undefined) {
69
+ return undefined;
70
+ }
71
+ const value = evaluate(node, this.consts);
72
+ return value.kind === 'number' ? value.value : undefined;
73
+ }
74
+ /** The property names selected by every argument of a call (lambdas, strings, nameof, arrays). */
75
+ members(call, skip = 0) {
76
+ const names = [];
77
+ for (const argument of call.args.slice(skip)) {
78
+ names.push(...(selectedMembers(argument, this.consts) ?? []));
79
+ }
80
+ return names;
81
+ }
82
+ memberAt(call, index) {
83
+ const node = call.args[index];
84
+ return node === undefined
85
+ ? undefined
86
+ : selectedMembers(node, this.consts)?.[0];
87
+ }
88
+ entityNameOf(call) {
89
+ const typeArg = call.typeArgs[0]?.name;
90
+ if (typeArg !== undefined) {
91
+ return typeArg;
92
+ }
93
+ const first = call.args[0];
94
+ if (first !== undefined) {
95
+ const value = evaluate(first, this.consts);
96
+ if (value.kind === 'typeof') {
97
+ return value.type;
98
+ }
99
+ }
100
+ return undefined;
101
+ }
102
+ stepModel(call, bindings, place, owner) {
103
+ switch (call.name) {
104
+ case 'Entity': {
105
+ const name = this.entityNameOf(call);
106
+ const config = name === undefined ? undefined : this.entity(name);
107
+ if (config === undefined) {
108
+ this.warn(`${place}: Entity(${call.node.text.replace(/^.*?\(/s, '').slice(0, 40)}...) names a type that is not a class in the input; its configuration was ignored.`);
109
+ return NONE;
110
+ }
111
+ const entityTarget = this.entityTarget(config);
112
+ const lambda = this.lambdaArg(call);
113
+ if (lambda !== undefined) {
114
+ this.runLambda(lambda, entityTarget, bindings, place, owner);
115
+ return { kind: 'model' };
116
+ }
117
+ return entityTarget;
118
+ }
119
+ case 'ApplyConfiguration': {
120
+ const argument = call.args[0];
121
+ const className = argument?.type === 'object_creation_expression'
122
+ ? parseTypeText(argument.childForFieldName('type')?.text ?? '').name
123
+ : undefined;
124
+ const found = this.project.configurations.find((item) => item.cls.name === className);
125
+ if (found === undefined) {
126
+ this.warn(`${place}: ApplyConfiguration(${argument?.text ?? ''}) does not name a configuration class in the input and was ignored.`);
127
+ }
128
+ else {
129
+ this.runConfiguration(found.cls, found.entity);
130
+ }
131
+ return { kind: 'model' };
132
+ }
133
+ case 'ApplyConfigurationsFromAssembly':
134
+ this.applyAllConfigurations();
135
+ return { kind: 'model' };
136
+ case 'HasDefaultSchema': {
137
+ const schema = this.stringAt(call, 0);
138
+ if (schema !== undefined) {
139
+ this.defaultSchema = schema;
140
+ }
141
+ return { kind: 'model' };
142
+ }
143
+ case 'Owned': {
144
+ const name = call.typeArgs[0]?.name;
145
+ if (name !== undefined) {
146
+ this.ownedTypes.add(name);
147
+ }
148
+ return { kind: 'model' };
149
+ }
150
+ case 'Ignore': {
151
+ const name = this.entityNameOf(call);
152
+ if (name !== undefined) {
153
+ this.ignoredEntities.add(name);
154
+ }
155
+ return { kind: 'model' };
156
+ }
157
+ default:
158
+ this.unknownCall(call.name, place);
159
+ return { kind: 'model' };
160
+ }
161
+ }
162
+ stepEntity(target, call, bindings, place, owner) {
163
+ const { config, scope } = target;
164
+ const isRoot = target.owned === undefined;
165
+ switch (call.name) {
166
+ case 'Property':
167
+ case 'IndexerProperty': {
168
+ const path = this.memberAt(call, 0);
169
+ if (path === undefined) {
170
+ this.unknownCall(call.name, place, 'the property could not be read');
171
+ return NONE;
172
+ }
173
+ const resolved = this.resolveScope(scope, path);
174
+ const settings = propConfig(resolved.scope, resolved.name);
175
+ settings.declared = true;
176
+ // Property<int>("TenantId") declares a shadow property of that type.
177
+ const first = call.args[0];
178
+ if (first !== undefined &&
179
+ call.typeArgs[0] !== undefined &&
180
+ evaluate(first, this.consts).kind === 'string') {
181
+ const shadow = call.typeArgs[0];
182
+ settings.shadowType = `${shadow.name}${shadow.args.length > 0 ? `<${shadow.args.map((argument) => argument.name).join(', ')}>` : ''}${shadow.nullable ? '?' : ''}${'[]'.repeat(shadow.arrayDepth)}`;
183
+ }
184
+ return { kind: 'property', cfg: settings, name: resolved.name };
185
+ }
186
+ case 'HasKey':
187
+ if (isRoot) {
188
+ config.key = this.members(call);
189
+ return { kind: 'key', config };
190
+ }
191
+ return target;
192
+ case 'HasNoKey':
193
+ config.keyless = true;
194
+ return target;
195
+ case 'HasAlternateKey': {
196
+ const index = {
197
+ members: this.members(call),
198
+ unique: true,
199
+ hasFilter: false,
200
+ hasInclude: false,
201
+ descending: [],
202
+ isAlternateKey: true,
203
+ };
204
+ config.indexes.push(this.prefixed(index, target.prefix));
205
+ return { kind: 'index', index };
206
+ }
207
+ case 'HasIndex': {
208
+ const first = call.args[0];
209
+ const lambdaFirst = first !== undefined && readLambda(first) !== undefined;
210
+ // HasIndex(e => e.A, "name") names the index; HasIndex("A", "B") lists properties.
211
+ const name = lambdaFirst
212
+ ? this.stringAt(call, 1)
213
+ : undefined;
214
+ const index = {
215
+ members: lambdaFirst && first !== undefined
216
+ ? (selectedMembers(first, this.consts) ?? [])
217
+ : this.members(call),
218
+ unique: false,
219
+ ...(name === undefined ? {} : { name }),
220
+ hasFilter: false,
221
+ hasInclude: false,
222
+ descending: [],
223
+ };
224
+ config.indexes.push(this.prefixed(index, target.prefix));
225
+ return { kind: 'index', index };
226
+ }
227
+ case 'Ignore': {
228
+ for (const name of this.members(call)) {
229
+ scope.ignored.add(name);
230
+ }
231
+ return target;
232
+ }
233
+ case 'ToTable': {
234
+ const lambda = this.lambdaArg(call);
235
+ const name = this.stringAt(call, 0);
236
+ const schema = this.stringAt(call, 1);
237
+ if (target.owned !== undefined) {
238
+ target.owned.table = name ?? target.owned.nav;
239
+ this.warn(`${place}: the owned type ${target.owned.nav} is stored in its own table "${name ?? target.owned.nav}", which is not represented; its columns were flattened into the owner.`);
240
+ return target;
241
+ }
242
+ if (name !== undefined) {
243
+ config.table = name;
244
+ }
245
+ if (schema !== undefined) {
246
+ config.schema = schema;
247
+ }
248
+ if (lambda !== undefined) {
249
+ this.runLambda(lambda, { kind: 'table', config }, bindings, place, owner);
250
+ }
251
+ return target;
252
+ }
253
+ case 'ToView': {
254
+ const name = this.stringAt(call, 0);
255
+ if (name !== undefined) {
256
+ config.viewName = name;
257
+ }
258
+ const schema = this.stringAt(call, 1);
259
+ if (schema !== undefined) {
260
+ config.schema = schema;
261
+ }
262
+ return target;
263
+ }
264
+ case 'ToSqlQuery':
265
+ config.keyless = true;
266
+ this.warn(`${place}: ${config.name} is mapped to a SQL query (ToSqlQuery), which is not a table; it was read as a table.`);
267
+ return target;
268
+ case 'HasOne':
269
+ case 'HasMany': {
270
+ if (!isRoot) {
271
+ this.warn(`${place}: ${call.name}(...) inside the owned type ${target.owned?.nav ?? ''} is not interpreted.`);
272
+ return NONE;
273
+ }
274
+ const navigation = this.memberAt(call, 0);
275
+ const rel = {
276
+ owner: config.name,
277
+ ...(navigation === undefined ? {} : { ownerNav: navigation }),
278
+ ownerMany: call.name === 'HasMany',
279
+ ...(call.typeArgs[0] === undefined
280
+ ? {}
281
+ : { target: call.typeArgs[0].name }),
282
+ inverseMany: false,
283
+ complete: false,
284
+ };
285
+ config.relations.push(rel);
286
+ return { kind: 'relation', rel };
287
+ }
288
+ case 'OwnsOne':
289
+ case 'OwnsMany':
290
+ case 'ComplexProperty': {
291
+ const nav = this.memberAt(call, 0);
292
+ if (nav === undefined) {
293
+ this.unknownCall(call.name, place, 'the navigation could not be read');
294
+ return target;
295
+ }
296
+ const existing = scope.owned.get(nav);
297
+ const ownedConfig = existing ?? newOwnedConfig(nav, call.name === 'OwnsMany');
298
+ scope.owned.set(nav, ownedConfig);
299
+ const ownedTarget = {
300
+ kind: 'entity',
301
+ entity: target.entity,
302
+ config,
303
+ scope: ownedConfig,
304
+ prefix: `${target.prefix}${nav}.`,
305
+ owned: ownedConfig,
306
+ };
307
+ // The first argument selects the navigation; a second lambda configures the owned type.
308
+ const lambda = this.lambdaArg(call, 1);
309
+ if (lambda !== undefined) {
310
+ this.runLambda(lambda, ownedTarget, bindings, place, owner);
311
+ return target;
312
+ }
313
+ return ownedTarget;
314
+ }
315
+ case 'WithOwner':
316
+ return target;
317
+ case 'ToJson':
318
+ if (target.owned !== undefined) {
319
+ target.owned.toJson = true;
320
+ }
321
+ else {
322
+ this.unknownCall(call.name, place, 'only owned types can be JSON');
323
+ }
324
+ return target;
325
+ case 'HasDiscriminator':
326
+ case 'HasNoDiscriminator': {
327
+ if (call.name === 'HasNoDiscriminator') {
328
+ return target;
329
+ }
330
+ const discriminator = config.discriminator ?? {
331
+ values: new Map(),
332
+ };
333
+ const column = this.memberAt(call, 0);
334
+ if (column !== undefined) {
335
+ discriminator.column = column;
336
+ }
337
+ const type = call.typeArgs[0]?.name;
338
+ if (type !== undefined) {
339
+ discriminator.type = type;
340
+ }
341
+ config.discriminator = discriminator;
342
+ return { kind: 'discriminator', config };
343
+ }
344
+ case 'HasBaseType': {
345
+ const name = this.entityNameOf(call);
346
+ config.baseType = name ?? '';
347
+ return target;
348
+ }
349
+ case 'UseTphMappingStrategy':
350
+ config.mapping = 'tph';
351
+ return target;
352
+ case 'UseTptMappingStrategy':
353
+ config.mapping = 'tpt';
354
+ return target;
355
+ case 'UseTpcMappingStrategy':
356
+ config.mapping = 'tpc';
357
+ return target;
358
+ case 'HasComment': {
359
+ const comment = this.stringAt(call, 0);
360
+ if (comment !== undefined) {
361
+ config.comment = comment;
362
+ }
363
+ return target;
364
+ }
365
+ case 'HasData':
366
+ config.hasData = true;
367
+ return target;
368
+ case 'HasQueryFilter':
369
+ config.hasQueryFilter = true;
370
+ return target;
371
+ case 'Navigation': {
372
+ this.unknownCall('Navigation', place, 'navigation settings do not affect the schema');
373
+ return NONE;
374
+ }
375
+ default:
376
+ this.unknownCall(call.name, place);
377
+ return target;
378
+ }
379
+ }
380
+ /** Finds the scope and property name for a possibly dotted member path. */
381
+ resolveScope(scope, path) {
382
+ const parts = path.split('.');
383
+ let current = scope;
384
+ for (const part of parts.slice(0, -1)) {
385
+ const next = current.owned.get(part) ?? newOwnedConfig(part, false);
386
+ current.owned.set(part, next);
387
+ current = next;
388
+ }
389
+ return { scope: current, name: parts[parts.length - 1] ?? path };
390
+ }
391
+ prefixed(index, prefix) {
392
+ if (prefix !== '') {
393
+ index.members = index.members.map((member) => prefix + member);
394
+ }
395
+ return index;
396
+ }
397
+ stepProperty(target, call, place) {
398
+ const cfg = target.cfg;
399
+ switch (call.name) {
400
+ case 'HasColumnName': {
401
+ const name = this.stringAt(call, 0);
402
+ if (name !== undefined) {
403
+ cfg.column = name;
404
+ }
405
+ return target;
406
+ }
407
+ case 'HasColumnType': {
408
+ const typeName = this.stringAt(call, 0);
409
+ if (typeName !== undefined) {
410
+ cfg.columnType = typeName;
411
+ }
412
+ return target;
413
+ }
414
+ case 'HasMaxLength': {
415
+ const length = this.numberAt(call, 0);
416
+ if (length !== undefined && length > 0) {
417
+ cfg.maxLength = length;
418
+ }
419
+ return target;
420
+ }
421
+ case 'IsRequired':
422
+ cfg.required = this.boolAt(call, 0, true);
423
+ return target;
424
+ case 'HasPrecision': {
425
+ const precision = this.numberAt(call, 0);
426
+ if (precision !== undefined) {
427
+ cfg.precision = precision;
428
+ cfg.scale = this.numberAt(call, 1) ?? 0;
429
+ }
430
+ return target;
431
+ }
432
+ case 'HasDefaultValue': {
433
+ const node = call.args[0];
434
+ if (node !== undefined) {
435
+ cfg.defaultValue = evaluate(node, this.consts);
436
+ }
437
+ return target;
438
+ }
439
+ case 'HasDefaultValueSql': {
440
+ const sql = this.stringAt(call, 0);
441
+ if (sql !== undefined && sql.trim() !== '') {
442
+ cfg.defaultSql = sql;
443
+ }
444
+ return target;
445
+ }
446
+ case 'HasComputedColumnSql': {
447
+ const sql = this.stringAt(call, 0);
448
+ cfg.generated = 'onAddOrUpdate';
449
+ if (sql !== undefined) {
450
+ cfg.computedSql = sql;
451
+ cfg.computedStored = this.boolAt(call, 1, false);
452
+ }
453
+ return target;
454
+ }
455
+ case 'ValueGeneratedOnAdd':
456
+ case 'UseIdentityColumn':
457
+ case 'UseIdentityAlwaysColumn':
458
+ case 'UseSerialColumn':
459
+ case 'UseAutoincrement':
460
+ cfg.generated = 'onAdd';
461
+ return target;
462
+ case 'ValueGeneratedOnAddOrUpdate':
463
+ case 'ValueGeneratedOnUpdate':
464
+ cfg.generated = 'onAddOrUpdate';
465
+ return target;
466
+ case 'ValueGeneratedNever':
467
+ cfg.generated = 'never';
468
+ return target;
469
+ case 'HasValueGenerator':
470
+ // EF Core creates the Guids itself (what ormbridge writes for a UUID default on SQLite).
471
+ if (/GuidValueGenerator/.test(call.typeArgs[0]?.name ?? '')) {
472
+ cfg.defaultSql ??= 'uuid()';
473
+ }
474
+ return target;
475
+ case 'HasConversion': {
476
+ const typeArg = call.typeArgs[0]?.name;
477
+ const argument = call.args[0];
478
+ if (typeArg !== undefined && call.args.length === 0) {
479
+ cfg.conversion = /EnumToString/.test(typeArg)
480
+ ? 'string'
481
+ : /EnumToNumber|EnumToBytes/.test(typeArg)
482
+ ? 'int'
483
+ : typeArg;
484
+ if (/Converter$/.test(typeArg) && !/^Enum/.test(typeArg)) {
485
+ cfg.customConversion = true;
486
+ cfg.converterClass = typeArg;
487
+ delete cfg.conversion;
488
+ }
489
+ }
490
+ else if (argument !== undefined) {
491
+ const value = evaluate(argument, this.consts);
492
+ if (value.kind === 'typeof') {
493
+ cfg.conversion = value.type;
494
+ }
495
+ else {
496
+ cfg.customConversion = true;
497
+ // `new PostStatusConverter()`: remembered so an enum converter written in the input can be read.
498
+ const created = argument.text.match(/^new\s+(?:\w+\.)*([A-Za-z_]\w*)\s*\(\s*\)$/);
499
+ if (created?.[1] !== undefined) {
500
+ cfg.converterClass = created[1];
501
+ }
502
+ }
503
+ }
504
+ return target;
505
+ }
506
+ case 'IsRowVersion':
507
+ cfg.isRowVersion = true;
508
+ cfg.isConcurrencyToken = true;
509
+ return target;
510
+ case 'IsConcurrencyToken':
511
+ cfg.isConcurrencyToken = this.boolAt(call, 0, true);
512
+ return target;
513
+ case 'IsUnicode':
514
+ cfg.isUnicode = this.boolAt(call, 0, true);
515
+ return target;
516
+ case 'HasComment': {
517
+ const comment = this.stringAt(call, 0);
518
+ if (comment !== undefined) {
519
+ cfg.comment = comment;
520
+ }
521
+ return target;
522
+ }
523
+ default:
524
+ this.unknownCall(call.name, place, `on ${target.name}`);
525
+ return target;
526
+ }
527
+ }
528
+ stepKey(target, call, place) {
529
+ if (call.name === 'HasName') {
530
+ const name = this.stringAt(call, 0);
531
+ if (name !== undefined) {
532
+ target.config.keyName = name;
533
+ }
534
+ return target;
535
+ }
536
+ this.unknownCall(call.name, place, 'on a key');
537
+ return target;
538
+ }
539
+ stepIndex(target, call, place) {
540
+ const index = target.index;
541
+ switch (call.name) {
542
+ case 'IsUnique':
543
+ index.unique = this.boolAt(call, 0, true);
544
+ return target;
545
+ case 'HasDatabaseName':
546
+ case 'HasName': {
547
+ const name = this.stringAt(call, 0);
548
+ if (name !== undefined) {
549
+ index.name = name;
550
+ }
551
+ return target;
552
+ }
553
+ case 'HasFilter':
554
+ index.hasFilter = true;
555
+ return target;
556
+ case 'IncludeProperties':
557
+ index.hasInclude = true;
558
+ return target;
559
+ case 'IsDescending': {
560
+ if (call.args.length === 0) {
561
+ index.descending = index.members.map(() => true);
562
+ }
563
+ else {
564
+ index.descending = call.args.map((argument) => {
565
+ const value = evaluate(argument, this.consts);
566
+ return value.kind === 'bool' && value.value;
567
+ });
568
+ }
569
+ return target;
570
+ }
571
+ case 'HasMethod': {
572
+ const method = this.stringAt(call, 0);
573
+ if (method !== undefined) {
574
+ index.method = method;
575
+ }
576
+ return target;
577
+ }
578
+ default:
579
+ this.unknownCall(call.name, place, 'on an index');
580
+ return target;
581
+ }
582
+ }
583
+ stepRelation(target, call, bindings, place, owner) {
584
+ const rel = target.rel;
585
+ switch (call.name) {
586
+ case 'WithOne':
587
+ case 'WithMany': {
588
+ rel.inverseMany = call.name === 'WithMany';
589
+ const navigation = this.memberAt(call, 0);
590
+ if (navigation !== undefined) {
591
+ rel.inverseNav = navigation;
592
+ }
593
+ rel.complete = true;
594
+ return target;
595
+ }
596
+ case 'HasForeignKey': {
597
+ rel.foreignKey = this.members(call);
598
+ const entity = call.typeArgs[0]?.name;
599
+ if (entity !== undefined) {
600
+ rel.foreignKeyEntity = entity;
601
+ }
602
+ return target;
603
+ }
604
+ case 'HasPrincipalKey':
605
+ rel.principalKey = this.members(call);
606
+ return target;
607
+ case 'OnDelete': {
608
+ const value = call.args[0];
609
+ const evaluated = value === undefined ? undefined : evaluate(value, this.consts);
610
+ if (evaluated?.kind === 'member') {
611
+ rel.onDelete = evaluated.path[evaluated.path.length - 1];
612
+ }
613
+ return target;
614
+ }
615
+ case 'IsRequired':
616
+ rel.required = this.boolAt(call, 0, true);
617
+ return target;
618
+ case 'HasConstraintName': {
619
+ const name = this.stringAt(call, 0);
620
+ if (name !== undefined) {
621
+ rel.constraintName = name;
622
+ }
623
+ return target;
624
+ }
625
+ case 'UsingEntity':
626
+ this.readUsingEntity(rel, call, bindings, place, owner);
627
+ return target;
628
+ default:
629
+ this.unknownCall(call.name, place, 'on a relationship');
630
+ return target;
631
+ }
632
+ }
633
+ readUsingEntity(rel, call, bindings, place, owner) {
634
+ const join = rel.join ?? {};
635
+ rel.join = join;
636
+ const typeArg = call.typeArgs[0]?.name;
637
+ const isShared = typeArg === undefined || typeArg === 'Dictionary';
638
+ const stringName = this.stringAt(call, 0);
639
+ if (stringName !== undefined) {
640
+ join.entityName = stringName;
641
+ }
642
+ if (!isShared && typeArg !== undefined) {
643
+ join.entityClass = typeArg;
644
+ }
645
+ const lambdas = call.args
646
+ .map((argument) => readLambda(argument))
647
+ .filter((lambda) => lambda !== undefined);
648
+ const targetName = rel.target ?? '';
649
+ if (join.entityClass !== undefined) {
650
+ // An explicit join class: its relationships are ordinary configuration of that entity.
651
+ const config = this.entity(join.entityClass);
652
+ if (config === undefined) {
653
+ return;
654
+ }
655
+ config.joinEntity = true;
656
+ for (const lambda of lambdas) {
657
+ this.runLambda(lambda, this.entityTarget(config), bindings, place, owner);
658
+ }
659
+ return;
660
+ }
661
+ // Lambdas that return a relationship configure the foreign key columns (right side first, then left);
662
+ // an Action lambda configures the join table.
663
+ const sides = ['right', 'left'];
664
+ let relationLambdas = 0;
665
+ for (const lambda of lambdas) {
666
+ const isRelationLambda = lambda.body.type !== 'block' &&
667
+ /\.With(One|Many)\(/.test(lambda.body.text);
668
+ if (isRelationLambda) {
669
+ const side = sides[relationLambdas] ?? 'left';
670
+ relationLambdas += 1;
671
+ this.runLambda(lambda, {
672
+ kind: 'joinSide',
673
+ join,
674
+ owner: rel.owner,
675
+ target: targetName,
676
+ side,
677
+ }, bindings, place, owner);
678
+ }
679
+ else {
680
+ this.runLambda(lambda, { kind: 'join', join }, bindings, place, owner);
681
+ }
682
+ }
683
+ }
684
+ stepJoin(target, call, place) {
685
+ const join = target.join;
686
+ switch (call.name) {
687
+ case 'ToTable': {
688
+ const name = this.stringAt(call, 0);
689
+ if (name !== undefined) {
690
+ join.table = name;
691
+ }
692
+ const schema = this.stringAt(call, 1);
693
+ if (schema !== undefined) {
694
+ join.schema = schema;
695
+ }
696
+ return target;
697
+ }
698
+ case 'HasKey':
699
+ case 'HasNoKey':
700
+ return target;
701
+ default:
702
+ this.unknownCall(call.name, place, 'on the join entity');
703
+ return target;
704
+ }
705
+ }
706
+ stepJoinSide(target, call, place) {
707
+ const join = target.join;
708
+ switch (call.name) {
709
+ case 'HasOne': {
710
+ const typeName = call.typeArgs[0]?.name;
711
+ // HasOne<Tag>() names the entity on the other end of this foreign key.
712
+ const side = typeName === undefined
713
+ ? target.side
714
+ : typeName === target.target && typeName !== target.owner
715
+ ? 'right'
716
+ : typeName === target.owner && typeName !== target.target
717
+ ? 'left'
718
+ : target.side;
719
+ return { ...target, ...(side === undefined ? {} : { side }) };
720
+ }
721
+ case 'WithMany':
722
+ case 'WithOne':
723
+ return target;
724
+ case 'HasForeignKey': {
725
+ const column = this.members(call)[0];
726
+ if (column !== undefined) {
727
+ if (target.side === 'right') {
728
+ join.rightColumn = column;
729
+ }
730
+ else {
731
+ join.leftColumn = column;
732
+ }
733
+ }
734
+ return target;
735
+ }
736
+ case 'OnDelete': {
737
+ const value = call.args[0];
738
+ const evaluated = value === undefined ? undefined : evaluate(value, this.consts);
739
+ if (evaluated?.kind === 'member') {
740
+ const action = evaluated.path[evaluated.path.length - 1];
741
+ if (target.side === 'right') {
742
+ join.rightOnDelete = action;
743
+ }
744
+ else {
745
+ join.leftOnDelete = action;
746
+ }
747
+ }
748
+ return target;
749
+ }
750
+ default:
751
+ this.unknownCall(call.name, place, 'on the join entity');
752
+ return target;
753
+ }
754
+ }
755
+ stepTable(target, call, place) {
756
+ switch (call.name) {
757
+ case 'HasComment': {
758
+ const comment = this.stringAt(call, 0);
759
+ if (comment !== undefined) {
760
+ target.config.comment = comment;
761
+ }
762
+ return target;
763
+ }
764
+ case 'HasCheckConstraint': {
765
+ target.config.checkConstraints.push(this.stringAt(call, 1) ?? this.stringAt(call, 0) ?? '');
766
+ return target;
767
+ }
768
+ default:
769
+ this.unknownCall(call.name, place, 'on the table');
770
+ return target;
771
+ }
772
+ }
773
+ stepDiscriminator(target, call, place) {
774
+ const discriminator = target.config.discriminator;
775
+ switch (call.name) {
776
+ case 'HasValue': {
777
+ const node = call.args[0];
778
+ if (node !== undefined && discriminator !== undefined) {
779
+ discriminator.values.set(call.typeArgs[0]?.name ?? '', evaluate(node, this.consts));
780
+ }
781
+ return target;
782
+ }
783
+ case 'IsComplete':
784
+ case 'HasMaxLength':
785
+ case 'IsRequired':
786
+ return target;
787
+ default:
788
+ this.unknownCall(call.name, place, 'on the discriminator');
789
+ return target;
790
+ }
791
+ }
792
+ }
793
+ //# sourceMappingURL=efcoreSteps.js.map