@deterministic-code/datasources-cli 0.1.13 → 0.1.14

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 (54) hide show
  1. package/dist/detect.js +21 -7
  2. package/dist/generate/CSharpRepositories.js +18 -14
  3. package/dist/generate/DeterministicProvider.js +23 -7
  4. package/dist/generate/RepositoryModel.js +10 -4
  5. package/dist/generate/RepositoryPlan.js +12 -5
  6. package/dist/generate/RustRepositories.js +42 -31
  7. package/dist/generate/TypeScriptRepositories.js +22 -18
  8. package/dist/generate/applyGenerate.js +9 -2
  9. package/dist/generate/applyMigrations.js +5 -13
  10. package/dist/generate/casing.js +28 -0
  11. package/dist/generate/migrateUp.js +2 -8
  12. package/dist/generate/sqlFlags.js +27 -0
  13. package/dist/migrateInit.js +4 -3
  14. package/dist/parse.js +102 -25
  15. package/dist/run.js +42 -67
  16. package/dist/setup.js +7 -9
  17. package/dist/version/applyVersion.js +11 -14
  18. package/dist/version/delta.js +0 -50
  19. package/dist/version/render.js +1 -11
  20. package/dist/wireRegister.js +29 -11
  21. package/dist/wireRustProviders.js +42 -0
  22. package/node_modules/@deterministic-code/csharp-generators/dist/import-generator.js +16 -12
  23. package/node_modules/@deterministic-code/csharp-generators/src/import-generator.test.ts +22 -11
  24. package/node_modules/@deterministic-code/csharp-generators/src/import-generator.ts +21 -13
  25. package/node_modules/@deterministic-code/datasources-code-generators/dist/nestedSelectTypes.js +2 -6
  26. package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/infer-reference-types.d.ts +2 -0
  27. package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/infer-reference-types.js +37 -0
  28. package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/reference-target.d.ts +2 -0
  29. package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/reference-target.js +6 -0
  30. package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/sql-schema.d.ts +1 -1
  31. package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/sql-schema.js +3 -6
  32. package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/version-migrations.js +25 -14
  33. package/node_modules/@deterministic-code/datasources-code-generators/package.json +4 -2
  34. package/node_modules/@deterministic-code/deterministic-generators-common/README.md +61 -1
  35. package/node_modules/@deterministic-code/deterministic-generators-common/typescript/parser/dist/specification-parser.js +128 -1
  36. package/node_modules/@deterministic-code/deterministic-generators-common/typescript/parser/specification-parser.ts +1 -0
  37. package/node_modules/@deterministic-code/deterministic-generators-common/typescript/parser/version-snapshot.ts +154 -0
  38. package/node_modules/@deterministic-code/deterministic-generators-common/typescript/parser/yaml-node.ts +2 -2
  39. package/node_modules/@deterministic-code/rust-generators/dist/import-generator.js +25 -15
  40. package/node_modules/@deterministic-code/rust-generators/src/import-generator.test.ts +11 -0
  41. package/node_modules/@deterministic-code/rust-generators/src/import-generator.ts +36 -18
  42. package/node_modules/@deterministic-code/typescript-generators/dist/generate-frontend-types.js +6 -1
  43. package/node_modules/@deterministic-code/typescript-generators/dist/generate-frontend-validators.js +6 -1
  44. package/node_modules/@deterministic-code/typescript-generators/dist/generate-routes.js +1 -1
  45. package/node_modules/@deterministic-code/typescript-generators/dist/generate-services.js +1 -1
  46. package/node_modules/@deterministic-code/typescript-generators/dist/import-generator.js +29 -21
  47. package/node_modules/@deterministic-code/typescript-generators/src/emit.ts +2 -5
  48. package/node_modules/@deterministic-code/typescript-generators/src/generate-frontend-types.ts +7 -1
  49. package/node_modules/@deterministic-code/typescript-generators/src/generate-frontend-validators.ts +7 -1
  50. package/node_modules/@deterministic-code/typescript-generators/src/generate-routes.ts +1 -1
  51. package/node_modules/@deterministic-code/typescript-generators/src/generate-services.ts +1 -1
  52. package/node_modules/@deterministic-code/typescript-generators/src/import-generator.test.ts +16 -0
  53. package/node_modules/@deterministic-code/typescript-generators/src/import-generator.ts +37 -22
  54. package/package.json +2 -2
@@ -1,16 +1,27 @@
1
1
  import { dirname, isAbsolute, join, relative, sep } from "node:path";
2
+ import { createCasingStrategy } from "@deterministic-code/generators-common/casing-strategy";
2
3
  import { csharpTarget } from "./detect.js";
4
+ import { repositoriesPrefix } from "./generate/casing.js";
5
+ import { rustIdent } from "./generate/RepositoryModel.js";
3
6
  const posix = (path) => path.split(sep).join("/");
4
7
  const specFrom = (from, to) => {
5
8
  const rel = posix(relative(dirname(from), to));
6
9
  return rel.startsWith(".") ? rel : `./${rel}`;
7
10
  };
8
11
  const hookPath = (cwd, dialect, src) => join(cwd, src ? "src" : ".", dialect === "typescript" ? "datasources-register.ts" : "datasources_register.rs");
9
- const hookContents = (dialect, hook, output) => dialect === "typescript" ? `import "${specFrom(hook, join(output, "typescript/repositories/repository-registry.ts"))}";
10
- ` : `#[path = "${specFrom(hook, join(output, "rust/repositories/mod.rs"))}"]
12
+ const registryRel = (dialect, settings) => {
13
+ const casing = createCasingStrategy(dialect, settings);
14
+ const dir = repositoriesPrefix(dialect, casing);
15
+ return dialect === "typescript" ? `${dir}/${casing.convertFileName("repository_registry")}.ts` : `${dir}/mod.rs`;
16
+ };
17
+ const hookContents = (dialect, hook, output, settings = {}) => {
18
+ const casing = createCasingStrategy(dialect, settings);
19
+ return dialect === "typescript" ? `import "${specFrom(hook, join(output, registryRel(dialect, settings)))}";
20
+ ` : `#[path = "${specFrom(hook, join(output, registryRel(dialect, settings)))}"]
11
21
  pub mod repositories;
12
- pub use repositories::repository_registry::Repositories;
22
+ pub use repositories::${rustIdent(casing.convertFileName("repository_registry"))}::Repositories;
13
23
  `;
24
+ };
14
25
  const cargoWithCtor = (cargo) => /^\s*ctor\s*=/m.test(cargo) ? void 0 : cargo.includes("[dependencies]") ? cargo.replace("[dependencies]", `[dependencies]
15
26
  ctor = "0.4"`) : `${cargo.trimEnd()}
16
27
 
@@ -30,7 +41,7 @@ const optional = async (fs, path) => {
30
41
  }
31
42
  };
32
43
  const outputRoot = (cwd, output) => isAbsolute(output) ? output : join(cwd, output);
33
- const registryPath = (root, dialect) => dialect === "typescript" ? join(root, "typescript/repositories/repository-registry.ts") : join(root, "rust/repositories/mod.rs");
44
+ const registryPath = (root, dialect, settings) => join(root, registryRel(dialect, settings));
34
45
  const firstFile = async (fs, cwd, names) => {
35
46
  for (const name of names) {
36
47
  const path = join(cwd, name);
@@ -39,6 +50,7 @@ const firstFile = async (fs, cwd, names) => {
39
50
  }
40
51
  return void 0;
41
52
  };
53
+ const excludeNodeModules = " <PropertyGroup>\n <DefaultItemExcludes>$(DefaultItemExcludes);node_modules/**</DefaultItemExcludes>\n </PropertyGroup>\n";
42
54
  const wireCsharp = async (cwd, output, dryRun, deps) => {
43
55
  const entries = await deps.fs.readdir(cwd).catch(() => []);
44
56
  const target = csharpTarget(entries);
@@ -46,28 +58,34 @@ const wireCsharp = async (cwd, output, dryRun, deps) => {
46
58
  const csprojPath = join(cwd, target);
47
59
  const generated = join(outputRoot(cwd, output), "csharp");
48
60
  const rel = posix(relative(dirname(csprojPath), generated));
49
- if (!rel.startsWith("..")) return;
50
61
  const include = `${rel}/**/*.cs`;
51
62
  const existing = await optional(deps.fs, csprojPath);
52
- if (existing === void 0 || existing.includes(include)) return;
63
+ if (existing === void 0) return;
64
+ const needExclude = !existing.includes("node_modules/**");
65
+ const needInclude = rel.startsWith("..") && !existing.includes(include);
66
+ if (!needExclude && !needInclude) return;
53
67
  if (dryRun) {
54
68
  deps.log(csprojPath);
55
69
  return;
56
70
  }
57
- await deps.fs.writeFile(csprojPath, existing.replace("</Project>", ` <ItemGroup>
71
+ const patched = [
72
+ needExclude ? excludeNodeModules : "",
73
+ needInclude ? ` <ItemGroup>
58
74
  <Compile Include="${include}" />
59
75
  </ItemGroup>
60
- </Project>`));
76
+ ` : ""
77
+ ].join("");
78
+ await deps.fs.writeFile(csprojPath, existing.replace("</Project>", `${patched}</Project>`));
61
79
  deps.log(csprojPath);
62
80
  };
63
- const wireRegister = async (cwd, output, dialect, dryRun, deps) => {
81
+ const wireRegister = async (cwd, output, dialect, dryRun, deps, settings = {}) => {
64
82
  if (dialect === "csharp") return wireCsharp(cwd, output, dryRun, deps);
65
83
  const root = outputRoot(cwd, output);
66
84
  if (relative(cwd, root).startsWith("..")) return;
67
- if (await optional(deps.fs, registryPath(root, dialect)) === void 0) return;
85
+ if (await optional(deps.fs, registryPath(root, dialect, settings)) === void 0) return;
68
86
  const entries = await deps.fs.readdir(cwd).catch(() => []);
69
87
  const hook = hookPath(cwd, dialect, entries.includes("src"));
70
- const text = hookContents(dialect, hook, root);
88
+ const text = hookContents(dialect, hook, root, settings);
71
89
  if (dryRun) {
72
90
  deps.log(hook);
73
91
  return;
@@ -0,0 +1,42 @@
1
+ import { dirname, join } from "node:path";
2
+ import { catalog } from "./catalog.js";
3
+ const optional = async (fs, path) => {
4
+ try {
5
+ return await fs.readFile(path);
6
+ } catch {
7
+ return void 0;
8
+ }
9
+ };
10
+ const firstFile = async (fs, cwd, names) => {
11
+ for (const name of names) {
12
+ const path = join(cwd, name);
13
+ const text = await optional(fs, path);
14
+ if (text !== void 0) return { path, text };
15
+ }
16
+ return void 0;
17
+ };
18
+ const rustBindContents = (names) => names.map((name) => `use ${catalog[name].cargo.replaceAll("-", "_")} as _;
19
+ `).join("");
20
+ const attachedProviders = (entry) => entry.includes("datasources_providers") ? void 0 : `mod datasources_providers;
21
+ ${entry}`;
22
+ const wireRustProviders = async (cwd, names, dryRun, deps) => {
23
+ const entry = await firstFile(deps.fs, cwd, ["src/lib.rs", "src/main.rs", "lib.rs", "main.rs"]);
24
+ const bind = join(entry ? dirname(entry.path) : cwd, "datasources_providers.rs");
25
+ const text = rustBindContents(names);
26
+ if (dryRun) {
27
+ deps.log(bind);
28
+ return;
29
+ }
30
+ await deps.fs.writeFile(bind, text);
31
+ deps.log(bind);
32
+ if (!entry) return;
33
+ const next = attachedProviders(entry.text);
34
+ if (next === void 0) return;
35
+ await deps.fs.writeFile(entry.path, next);
36
+ deps.log(entry.path);
37
+ };
38
+ export {
39
+ attachedProviders,
40
+ rustBindContents,
41
+ wireRustProviders
42
+ };
@@ -10,10 +10,6 @@ const pluralSnake = (entity) => {
10
10
  parts[parts.length - 1] = pluralize.plural(parts[parts.length - 1]);
11
11
  return parts.join("_");
12
12
  };
13
- const DATASOURCE_DIR = "Types/Datasource/Generated";
14
- const VIEW_DIR = "Types/View/Generated";
15
- const DATASOURCE_VALIDATOR_DIR = `${DATASOURCE_DIR}/Validators`;
16
- const VIEW_VALIDATOR_DIR = `${VIEW_DIR}/Validators`;
17
13
  class CsharpImportGenerator {
18
14
  organizeByFeature;
19
15
  flat;
@@ -26,7 +22,7 @@ class CsharpImportGenerator {
26
22
  this.casing = createCasing(settings);
27
23
  }
28
24
  datasource(entity) {
29
- return this.underBase(this.rel(DATASOURCE_DIR, this.casedFile(entity)));
25
+ return this.underBase(this.rel(this.layered("types/datasource/generated"), this.casedFile(entity)));
30
26
  }
31
27
  datasourceRel(entity) {
32
28
  return this.datasource(entity);
@@ -37,7 +33,7 @@ class CsharpImportGenerator {
37
33
  datasourceValidator(entity) {
38
34
  return this.underBase(
39
35
  this.rel(
40
- DATASOURCE_VALIDATOR_DIR,
36
+ this.layered("types/datasource/generated/validators"),
41
37
  this.csFile(`datasource_${entity}_validator`)
42
38
  )
43
39
  );
@@ -46,7 +42,7 @@ class CsharpImportGenerator {
46
42
  return this.datasourceValidator(entity);
47
43
  }
48
44
  view(entity) {
49
- return this.underBase(this.rel(VIEW_DIR, this.csFile(entity)));
45
+ return this.underBase(this.rel(this.layered("types/view/generated"), this.csFile(entity)));
50
46
  }
51
47
  viewRel(entity) {
52
48
  return this.view(entity);
@@ -56,7 +52,7 @@ class CsharpImportGenerator {
56
52
  }
57
53
  viewValidator(entity) {
58
54
  return this.underBase(
59
- this.rel(VIEW_VALIDATOR_DIR, this.csFile(`${entity}_validator`))
55
+ this.rel(this.layered("types/view/generated/validators"), this.csFile(`${entity}_validator`))
60
56
  );
61
57
  }
62
58
  viewValidatorRel(entity) {
@@ -71,12 +67,12 @@ class CsharpImportGenerator {
71
67
  serviceCustom(name, _module) {
72
68
  const file = this.csFile(name);
73
69
  return this.underBase(
74
- this.organizeByFeature ? `Features/${this.casing.directory(featureEntity(name))}/custom/${file}` : `../custom/${file}`
70
+ this.organizeByFeature ? `${this.featuresDir(featureEntity(name))}/custom/${file}` : `../custom/${file}`
75
71
  );
76
72
  }
77
73
  serviceCustomRel(entity) {
78
74
  const file = this.csFile(entity);
79
- return this.organizeByFeature ? `Features/${this.casing.directory(entity)}/custom/${file}` : `../custom/${file}`;
75
+ return this.organizeByFeature ? `${this.featuresDir(entity)}/custom/${file}` : `../custom/${file}`;
80
76
  }
81
77
  serviceTest(entity) {
82
78
  return this.underBase(this.csFile(`${entity}_service_tests`));
@@ -102,7 +98,7 @@ class CsharpImportGenerator {
102
98
  routeCustom(name, _module) {
103
99
  const file = this.csFile(`${name}_route`);
104
100
  return this.underBase(
105
- this.organizeByFeature ? `Features/${this.casing.directory(featureEntity(name))}/custom/${file}` : `../custom/${file}`
101
+ this.organizeByFeature ? `${this.featuresDir(featureEntity(name))}/custom/${file}` : `../custom/${file}`
106
102
  );
107
103
  }
108
104
  routeTest(entity) {
@@ -149,7 +145,15 @@ class CsharpImportGenerator {
149
145
  }
150
146
  casedFile(entity) {
151
147
  const file = this.csFile(entity);
152
- return this.organizeByFeature ? `Features/${this.casing.directory(entity)}/${file}` : file;
148
+ return this.organizeByFeature ? `${this.featuresDir(entity)}/${file}` : file;
149
+ }
150
+ layered(path) {
151
+ return path.split("/").map(
152
+ (segment) => segment === "custom" ? segment : this.casing.convertDirectories(segment)
153
+ ).join("/");
154
+ }
155
+ featuresDir(entity) {
156
+ return `${this.casing.convertDirectories("features")}/${this.casing.directory(entity)}`;
153
157
  }
154
158
  rel(prefix, file) {
155
159
  if (this.organizeByFeature || this.flat) return file;
@@ -136,6 +136,17 @@ describe("CsharpImportGenerator layered (organize_by_feature unset)", () => {
136
136
  snake.datasource("notification_type"),
137
137
  "Types/Datasource/Generated/notification_type.cs",
138
138
  );
139
+ const kebabDirs = createImportGenerator(".", {
140
+ "languages.csharp.casing.directories": "Kebab",
141
+ });
142
+ assert.equal(
143
+ kebabDirs.datasource("user"),
144
+ "types/datasource/generated/User.cs",
145
+ );
146
+ assert.equal(
147
+ kebabDirs.view("user"),
148
+ "types/view/generated/User.cs",
149
+ );
139
150
  });
140
151
 
141
152
  it("cases type quals from settings independently of files", () => {
@@ -173,11 +184,11 @@ describe("CsharpImportGenerator by-feature", () => {
173
184
  });
174
185
  assert.equal(
175
186
  imports.datasource("notification_type"),
176
- "Features/notification-type/NotificationType.cs",
187
+ "features/notification-type/NotificationType.cs",
177
188
  );
178
189
  assert.equal(
179
190
  imports.datasourceRel("notification_type"),
180
- "Features/notification-type/NotificationType.cs",
191
+ "features/notification-type/NotificationType.cs",
181
192
  );
182
193
  assert.equal(imports.view("user"), "User.cs");
183
194
  assert.equal(imports.viewRel("user"), "User.cs");
@@ -200,31 +211,31 @@ describe("CsharpImportGenerator by-feature", () => {
200
211
  });
201
212
  assert.equal(
202
213
  imports.serviceCustom("user"),
203
- "Features/user/custom/User.cs",
214
+ "features/user/custom/User.cs",
204
215
  );
205
216
  assert.equal(
206
217
  imports.serviceCustom("create_card_payment"),
207
- "Features/card-payment/custom/CreateCardPayment.cs",
218
+ "features/card-payment/custom/CreateCardPayment.cs",
208
219
  );
209
220
  assert.equal(
210
221
  imports.serviceCustom("update_card_payment"),
211
- "Features/card-payment/custom/UpdateCardPayment.cs",
222
+ "features/card-payment/custom/UpdateCardPayment.cs",
212
223
  );
213
224
  assert.equal(
214
225
  imports.serviceCustomRel("notification_type"),
215
- "Features/notification-type/custom/NotificationType.cs",
226
+ "features/notification-type/custom/NotificationType.cs",
216
227
  );
217
228
  assert.equal(
218
229
  imports.routeCustom("get_health"),
219
- "Features/get-health/custom/GetHealthRoute.cs",
230
+ "features/get-health/custom/GetHealthRoute.cs",
220
231
  );
221
232
  assert.equal(
222
233
  imports.routeCustom("create_card_payment"),
223
- "Features/card-payment/custom/CreateCardPaymentRoute.cs",
234
+ "features/card-payment/custom/CreateCardPaymentRoute.cs",
224
235
  );
225
236
  assert.equal(
226
237
  imports.serviceCustom("user", "./ignored/module"),
227
- "Features/user/custom/User.cs",
238
+ "features/user/custom/User.cs",
228
239
  );
229
240
  });
230
241
 
@@ -235,11 +246,11 @@ describe("CsharpImportGenerator by-feature", () => {
235
246
  });
236
247
  assert.equal(
237
248
  imports.datasource("notification_type"),
238
- "Features/notification_type/NotificationType.cs",
249
+ "features/notification_type/NotificationType.cs",
239
250
  );
240
251
  assert.equal(
241
252
  imports.serviceCustomRel("notification_type"),
242
- "Features/notification_type/custom/NotificationType.cs",
253
+ "features/notification_type/custom/NotificationType.cs",
243
254
  );
244
255
  });
245
256
  });
@@ -15,11 +15,6 @@ const pluralSnake = (entity: string): string => {
15
15
  return parts.join("_");
16
16
  };
17
17
 
18
- const DATASOURCE_DIR = "Types/Datasource/Generated";
19
- const VIEW_DIR = "Types/View/Generated";
20
- const DATASOURCE_VALIDATOR_DIR = `${DATASOURCE_DIR}/Validators`;
21
- const VIEW_VALIDATOR_DIR = `${VIEW_DIR}/Validators`;
22
-
23
18
  export class CsharpImportGenerator implements IImportGenerator {
24
19
  private readonly organizeByFeature: boolean;
25
20
  private readonly flat: boolean;
@@ -35,7 +30,7 @@ export class CsharpImportGenerator implements IImportGenerator {
35
30
  }
36
31
 
37
32
  datasource(entity: string): string {
38
- return this.underBase(this.rel(DATASOURCE_DIR, this.casedFile(entity)));
33
+ return this.underBase(this.rel(this.layered("types/datasource/generated"), this.casedFile(entity)));
39
34
  }
40
35
 
41
36
  datasourceRel(entity: string): string {
@@ -49,7 +44,7 @@ export class CsharpImportGenerator implements IImportGenerator {
49
44
  datasourceValidator(entity: string): string {
50
45
  return this.underBase(
51
46
  this.rel(
52
- DATASOURCE_VALIDATOR_DIR,
47
+ this.layered("types/datasource/generated/validators"),
53
48
  this.csFile(`datasource_${entity}_validator`),
54
49
  ),
55
50
  );
@@ -60,7 +55,7 @@ export class CsharpImportGenerator implements IImportGenerator {
60
55
  }
61
56
 
62
57
  view(entity: string): string {
63
- return this.underBase(this.rel(VIEW_DIR, this.csFile(entity)));
58
+ return this.underBase(this.rel(this.layered("types/view/generated"), this.csFile(entity)));
64
59
  }
65
60
 
66
61
  viewRel(entity: string): string {
@@ -73,7 +68,7 @@ export class CsharpImportGenerator implements IImportGenerator {
73
68
 
74
69
  viewValidator(entity: string): string {
75
70
  return this.underBase(
76
- this.rel(VIEW_VALIDATOR_DIR, this.csFile(`${entity}_validator`)),
71
+ this.rel(this.layered("types/view/generated/validators"), this.csFile(`${entity}_validator`)),
77
72
  );
78
73
  }
79
74
 
@@ -93,7 +88,7 @@ export class CsharpImportGenerator implements IImportGenerator {
93
88
  const file = this.csFile(name);
94
89
  return this.underBase(
95
90
  this.organizeByFeature
96
- ? `Features/${this.casing.directory(featureEntity(name))}/custom/${file}`
91
+ ? `${this.featuresDir(featureEntity(name))}/custom/${file}`
97
92
  : `../custom/${file}`,
98
93
  );
99
94
  }
@@ -101,7 +96,7 @@ export class CsharpImportGenerator implements IImportGenerator {
101
96
  serviceCustomRel(entity: string): string {
102
97
  const file = this.csFile(entity);
103
98
  return this.organizeByFeature
104
- ? `Features/${this.casing.directory(entity)}/custom/${file}`
99
+ ? `${this.featuresDir(entity)}/custom/${file}`
105
100
  : `../custom/${file}`;
106
101
  }
107
102
 
@@ -137,7 +132,7 @@ export class CsharpImportGenerator implements IImportGenerator {
137
132
  const file = this.csFile(`${name}_route`);
138
133
  return this.underBase(
139
134
  this.organizeByFeature
140
- ? `Features/${this.casing.directory(featureEntity(name))}/custom/${file}`
135
+ ? `${this.featuresDir(featureEntity(name))}/custom/${file}`
141
136
  : `../custom/${file}`,
142
137
  );
143
138
  }
@@ -205,10 +200,23 @@ export class CsharpImportGenerator implements IImportGenerator {
205
200
  private casedFile(entity: string): string {
206
201
  const file = this.csFile(entity);
207
202
  return this.organizeByFeature
208
- ? `Features/${this.casing.directory(entity)}/${file}`
203
+ ? `${this.featuresDir(entity)}/${file}`
209
204
  : file;
210
205
  }
211
206
 
207
+ private layered(path: string): string {
208
+ return path
209
+ .split("/")
210
+ .map((segment) =>
211
+ segment === "custom" ? segment : this.casing.convertDirectories(segment),
212
+ )
213
+ .join("/");
214
+ }
215
+
216
+ private featuresDir(entity: string): string {
217
+ return `${this.casing.convertDirectories("features")}/${this.casing.directory(entity)}`;
218
+ }
219
+
212
220
  private rel(prefix: string, file: string): string {
213
221
  if (this.organizeByFeature || this.flat) return file;
214
222
  return `${prefix}/${file}`;
@@ -1,3 +1,4 @@
1
+ import { referenceTarget } from "./sql/common/reference-target.js";
1
2
  import { ChangeCasingMapper, NestedSelectBuilder, PluralizerMapper } from "@deterministic-code/datasources";
2
3
  const pascal = new ChangeCasingMapper("snakeCase", "pascalCase");
3
4
  const plural = new PluralizerMapper();
@@ -51,12 +52,7 @@ const build = (type, catalog, ancestors) => {
51
52
  });
52
53
  return { name: entity(type.name), key, properties, relations: [...objects, ...lists] };
53
54
  };
54
- const target = (field) => {
55
- if (Array.isArray(field.references) && field.references.length === 2)
56
- return [field.references[0] ?? "", field.references[1] ?? ""];
57
- const [table, column] = String(field.references ?? "").split(".");
58
- return [table ?? "", column ?? ""];
59
- };
55
+ const target = (field) => field.references === undefined ? ["", ""] : referenceTarget(field.references);
60
56
  const relationName = (field, table) => {
61
57
  const name = entity(field);
62
58
  return name.endsWith("Id") && name.length > 2 ? name.slice(0, -2) : entity(table);
@@ -0,0 +1,2 @@
1
+ import type { Type } from "@deterministic-code/deterministic-generators-common/parser";
2
+ export declare const inferReferenceFieldTypes: (types: Type[]) => Type[];
@@ -0,0 +1,37 @@
1
+ import { referenceTarget } from "./reference-target.js";
2
+ const needsType = (field) => !field.isArray &&
3
+ !field.isMap &&
4
+ field.references !== undefined &&
5
+ field.type === "string" &&
6
+ field.size === undefined;
7
+ const ready = (field) => field.type !== "string" || field.size !== undefined;
8
+ const lookup = (types, field) => {
9
+ const [table, column] = referenceTarget(field.references);
10
+ return types.find((type) => type.name === table)?.fields.find((item) => item.name === column);
11
+ };
12
+ export const inferReferenceFieldTypes = (types) => {
13
+ const working = types.map((type) => ({ ...type, fields: type.fields.map((field) => ({ ...field })) }));
14
+ for (let pass = 0; pass < working.length; pass++) {
15
+ let changed = false;
16
+ for (const type of working) {
17
+ type.fields = type.fields.map((field) => {
18
+ if (!needsType(field))
19
+ return field;
20
+ const parent = lookup(working, field);
21
+ if (parent === undefined || !ready(parent))
22
+ return field;
23
+ changed = true;
24
+ return {
25
+ ...field,
26
+ type: parent.type,
27
+ kind: parent.kind,
28
+ base: parent.base,
29
+ ...(parent.size !== undefined ? { size: parent.size } : {}),
30
+ };
31
+ });
32
+ }
33
+ if (!changed)
34
+ break;
35
+ }
36
+ return working;
37
+ };
@@ -0,0 +1,2 @@
1
+ import type { TypeField } from "@deterministic-code/deterministic-generators-common/parser";
2
+ export declare const referenceTarget: (references: NonNullable<TypeField["references"]>) => [string, string];
@@ -0,0 +1,6 @@
1
+ export const referenceTarget = (references) => {
2
+ if (Array.isArray(references))
3
+ return [references[0], references[1]];
4
+ const [table, col] = references.split(".");
5
+ return [table ?? "", col ?? ""];
6
+ };
@@ -1,4 +1,5 @@
1
1
  import type { DatasourceFieldOverlay, DatasourceIndex, DatasourceTable, IDeterministic, Type, TypeField } from "@deterministic-code/deterministic-generators-common/parser";
2
+ export { referenceTarget } from "./reference-target.js";
2
3
  /** Flattened `datasource.*` flags. Stored procedures are opt-in `"true"`. */
3
4
  export declare const datasourceSettings: (settings: Record<string, string>) => {
4
5
  useStoredProcedures: boolean;
@@ -28,7 +29,6 @@ export type SqlFile = {
28
29
  path: string;
29
30
  content: string;
30
31
  };
31
- export declare const referenceTarget: (references: NonNullable<TypeField["references"]>) => [string, string];
32
32
  /** Attach physical names and order parent tables before children. */
33
33
  export declare const buildLiveTables: (types: SqlTable[], mapping: {
34
34
  tableName: (table: SqlTable) => string;
@@ -1,4 +1,6 @@
1
1
  import { datasourceTypesOf, dictionaryUniqueColumns, identityColumns, persistedColumnFields, tableByName, } from "@deterministic-code/generators-common/spec-types";
2
+ import { inferReferenceFieldTypes } from "./infer-reference-types.js";
3
+ export { referenceTarget } from "./reference-target.js";
2
4
  /** Flattened `datasource.*` flags. Stored procedures are opt-in `"true"`. */
3
5
  export const datasourceSettings = (settings) => ({
4
6
  useStoredProcedures: String(settings["datasource.use_stored_procedures"]) === "true",
@@ -44,6 +46,7 @@ export const isGeneratedIdentity = (table) => {
44
46
  export const usesGeneratedIdColumn = (table, field) => field.isId === true && isGeneratedIdentity(table);
45
47
  export const sqlTablesFrom = (spec) => {
46
48
  requireIdIsId(spec);
49
+ spec = { ...spec, expandedTypes: inferReferenceFieldTypes(spec.expandedTypes) };
47
50
  const overlays = tableByName(spec);
48
51
  const byName = new Map(spec.expandedTypes.map((t) => [t.name, t]));
49
52
  return datasourceTypesOf(spec).map((type) => {
@@ -80,12 +83,6 @@ const referenceParents = (references) => {
80
83
  return [references[0]];
81
84
  return [references.split(".")[0]];
82
85
  };
83
- export const referenceTarget = (references) => {
84
- if (Array.isArray(references))
85
- return [references[0], references[1]];
86
- const [table, col] = references.split(".");
87
- return [table ?? "", col ?? ""];
88
- };
89
86
  const topoSort = (tables) => {
90
87
  const names = new Set(tables.map((t) => t.name));
91
88
  const deps = new Map(tables.map((t) => [
@@ -1,8 +1,7 @@
1
- import { parse, stringify } from "yaml";
1
+ import { parse } from "yaml";
2
2
  import { CreateTableStatement } from "@deterministic-code/datasources";
3
3
  import { memoryReader } from "@deterministic-code/generators-common/deterministic-reader";
4
- import { DeterministicParser } from "@deterministic-code/deterministic-generators-common/parser";
5
- import { applyDocument, isObject, snapshotsEqual } from "./version-apply.js";
4
+ import { DeterministicParser, VersionSnapshot } from "@deterministic-code/deterministic-generators-common/parser";
6
5
  import { createCasing } from "./default-casing.js";
7
6
  import { SqlMapping } from "./sql-mapping.js";
8
7
  import { buildLiveTables, sqlTablesFrom } from "./sql-schema.js";
@@ -23,11 +22,26 @@ export const versionDiffPaths = (settings) => (settings["version.diffs"] ?? "")
23
22
  .split("\n")
24
23
  .map((path) => path.trim())
25
24
  .filter((path) => versionDiff.test(path));
26
- const asDocument = (value, source) => isObject(value) ? value : (() => { throw new Error(`${source}: expected a mapping`); })();
25
+ const own = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
26
+ const parseDiff = (text, source) => {
27
+ const value = parse(text);
28
+ if (value == null || typeof value !== "object" || Array.isArray(value))
29
+ throw new Error(`${source}: expected a mapping`);
30
+ return value;
31
+ };
32
+ const fold = (path, text) => VersionSnapshot.combine([{ [path]: { added: true, content: parse(text) } }]).yaml(path);
33
+ const matches = (snapshot, path, live, whenMissing) => {
34
+ const has = own(snapshot.documents, path);
35
+ const text = live === undefined || live === "" ? whenMissing : live;
36
+ if (text === undefined || text === "")
37
+ return !has;
38
+ const snapshotYaml = has ? snapshot.yaml(path) : whenMissing === undefined ? undefined : fold(path, whenMissing);
39
+ return snapshotYaml !== undefined && snapshotYaml === fold(path, text);
40
+ };
27
41
  const filesOf = (snapshot) => {
28
42
  const files = {};
29
- for (const [path, value] of Object.entries(snapshot))
30
- files[path] = stringify(value);
43
+ for (const path of Object.keys(snapshot.documents))
44
+ files[path] = snapshot.yaml(path);
31
45
  files["datasource.yaml"] ??= defaultDatasourceYaml;
32
46
  return files;
33
47
  };
@@ -37,7 +51,7 @@ const compile = async (snapshot, settings) => {
37
51
  const mapping = new SqlMapping(createCasing(settings), types);
38
52
  return { tables: buildLiveTables(types, mapping), mapping, seeds: spec.datasourceSeeds };
39
53
  };
40
- const emptySnapshot = (settings) => compile({}, settings);
54
+ const emptySnapshot = (settings) => compile(VersionSnapshot.combine([]), settings);
41
55
  const pad = (n) => String(n).padStart(4, "0");
42
56
  const renderAll = (dialect, statements) => statements
43
57
  .map((statement) => renderSql(dialect, statement))
@@ -48,22 +62,19 @@ export const versionMigrationFiles = async (dialect, ctx, casing, live) => {
48
62
  const paths = versionDiffPaths(ctx.settings);
49
63
  if (paths.length === 0)
50
64
  return { files: [], count: 0 };
51
- let before = {};
65
+ const diffs = [];
52
66
  const snapshots = [];
53
67
  for (const path of paths) {
54
68
  const text = await ctx.reader.read(path);
55
69
  if (text === "")
56
70
  continue;
57
- before = applyDocument(before, asDocument(parse(text), path));
58
- snapshots.push(before);
71
+ diffs.push(parseDiff(text, path));
72
+ snapshots.push(VersionSnapshot.combine(diffs));
59
73
  }
60
74
  if (snapshots.length === 0)
61
75
  return { files: [], count: 0 };
62
76
  const last = snapshots[snapshots.length - 1];
63
- const liveTypes = live.typesYaml === undefined ? undefined : parse(live.typesYaml);
64
- const liveDatasource = parse(live.datasourceYaml ?? defaultDatasourceYaml);
65
- const snapshotDatasource = last["datasource.yaml"] ?? parse(defaultDatasourceYaml);
66
- if (!snapshotsEqual(last["types.yaml"], liveTypes) || !snapshotsEqual(snapshotDatasource, liveDatasource)) {
77
+ if (!matches(last, "types.yaml", live.typesYaml, undefined) || !matches(last, "datasource.yaml", live.datasourceYaml, defaultDatasourceYaml)) {
67
78
  throw new Error("types.yaml or datasource.yaml differs from the frozen version diffs; run datasources version first");
68
79
  }
69
80
  const files = [];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deterministic-code/datasources-code-generators",
3
- "version": "0.1.2",
3
+ "version": "0.1.14",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dist/generateSql.js",
@@ -9,7 +9,9 @@
9
9
  "./generate-stored-procedures": "./dist/generate-stored-procedures.js",
10
10
  "./sql/generate-stored-procedures": "./dist/sql/generate-stored-procedures.js",
11
11
  "./common/sql-dialect": "./dist/sql/common/sql-dialect.js",
12
- "./sql/common/write-inclusion": "./dist/sql/common/write-inclusion.js"
12
+ "./sql/common/write-inclusion": "./dist/sql/common/write-inclusion.js",
13
+ "./sql/common/default-casing": "./dist/sql/common/default-casing.js",
14
+ "./sql/common/infer-reference-types": "./dist/sql/common/infer-reference-types.js"
13
15
  },
14
16
  "dependencies": {
15
17
  "@deterministic-code/datasources": "^0.1.12",