@deterministic-code/datasources-cli 0.1.10 → 0.1.12

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 (30) hide show
  1. package/dist/generate/CSharpRepositories.js +28 -18
  2. package/dist/generate/DeterministicProvider.js +35 -1
  3. package/dist/generate/RepositoryPlan.js +7 -0
  4. package/dist/generate/RustRepositories.js +34 -17
  5. package/dist/generate/TypeScriptRepositories.js +24 -11
  6. package/dist/generate/applyGenerate.js +6 -1
  7. package/dist/generate/applyMigrations.js +5 -1
  8. package/dist/generate/datasourceYaml.js +22 -0
  9. package/dist/generate/loadSpec.js +6 -1
  10. package/dist/generate/storedProcedures.js +5 -0
  11. package/dist/generate/writeLists.js +19 -0
  12. package/dist/parse.js +8 -2
  13. package/dist/run.js +6 -10
  14. package/dist/setup.js +4 -2
  15. package/node_modules/@deterministic-code/datasources-code-generators/dist/nestedSelectTypes.d.ts +5 -0
  16. package/node_modules/@deterministic-code/datasources-code-generators/dist/nestedSelectTypes.js +64 -0
  17. package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/generate-procedures.js +22 -10
  18. package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/generate-sql.d.ts +0 -1
  19. package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/generate-sql.js +18 -4
  20. package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/procedures/helpers.d.ts +5 -8
  21. package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/procedures/helpers.js +2 -29
  22. package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/write-inclusion.d.ts +9 -0
  23. package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/write-inclusion.js +9 -0
  24. package/node_modules/@deterministic-code/datasources-code-generators/package.json +9 -8
  25. package/node_modules/@deterministic-code/deterministic-generators-common/typescript/parser/dist/specification-parser.js +67 -2
  26. package/node_modules/@deterministic-code/deterministic-generators-common/typescript/parser/parser/specification-parser.ts +82 -2
  27. package/node_modules/@deterministic-code/deterministic-generators-common/typescript/parser/specification.ts +2 -0
  28. package/node_modules/@deterministic-code/deterministic-specifications-typescript/typescript/parser/parser/specification-parser.ts +82 -2
  29. package/node_modules/@deterministic-code/deterministic-specifications-typescript/typescript/parser/specification.ts +2 -0
  30. package/package.json +8 -8
@@ -7,7 +7,29 @@ import {
7
7
  pluralPascal,
8
8
  repositoryFile
9
9
  } from "./RepositoryModel.js";
10
- const quote = (value) => value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
10
+ import { dialectsOf, pad, quote, writeList } from "./writeLists.js";
11
+ const csharpOptionsBody = (model) => `Entity = "${quote(model.entity)}",
12
+ EntityMapper = new DictionaryMapper<string>(new Dictionary<string, string> { ["${quote(model.entity)}"] = "${quote(model.table)}" }),
13
+ PropertyMappings = new DictionaryMapper<string>(new Dictionary<string, string>
14
+ {
15
+ ${model.propertyMappings.map((pair) => ` ["${quote(pair.from)}"] = "${quote(pair.to)}",`).join("\n")}
16
+ }),
17
+ Ids = ${writeList("csharp", model.ids)},
18
+ ReadProperties = ${writeList("csharp", model.readProperties)},
19
+ CreateProperties = ${writeList("csharp", model.createProperties)},
20
+ UpdateProperties = ${writeList("csharp", model.updateProperties)},`;
21
+ const csharpOptions = (model, indent) => `new()
22
+ ${" ".repeat(indent)}{
23
+ ${pad(csharpOptionsBody(model), indent + 4)}
24
+ ${" ".repeat(indent)}}`;
25
+ const optionsMethod = (model, type) => {
26
+ const dialects = dialectsOf(model);
27
+ return dialects.length === 0 ? ` static CRUDRepositoryOptions<${type}> Options() => ${csharpOptions(model, 4)};` : ` static CRUDRepositoryOptions<${type}> Options(IConnection connection) =>
28
+ new CRUDRepositoryBuilder<${type}>()
29
+ .Add(${csharpOptions(model, 12)})${dialects.map(([engine, planned]) => `
30
+ .Add("${quote(engine)}", ${csharpOptions({ entity: model.entity, ...planned }, 12)})`).join("")}
31
+ .For(connection);`;
32
+ };
11
33
  const enrichment = (item, host = "ctor") => {
12
34
  const via = childField("csharp", item.via, item.viaType);
13
35
  const head = `"${quote(item.destination)}", "${quote(item.source)}", "${quote(item.relatedKey)}"`;
@@ -45,16 +67,17 @@ const occMethods = (model, type) => {
45
67
  }
46
68
  `;
47
69
  };
70
+ const optionsCall = (model) => dialectsOf(model).length === 0 ? "Options()" : "Options(connection)";
48
71
  const createReadonly = (model, type) => {
49
72
  const children = childModels(model);
50
73
  if (children.length === 0 && model.enrichments.length === 0)
51
74
  return ` public static IReadonlyRepository<${type}> CreateReadonly(IConnection connection) =>
52
- new ReadonlyRepository<${type}>(connection, Options());`;
75
+ new ReadonlyRepository<${type}>(connection, ${optionsCall(model)});`;
53
76
  const locals = children.map((child) => ` var ${childField("csharp", child.specName, child.typeName)} = new ${childClass(child.typeName)}(connection);`);
54
77
  const enrich = model.enrichments.map((item) => ` ${enrichment(item, "readonly")}`);
55
78
  return ` public static IReadonlyRepository<${type}> CreateReadonly(IConnection connection)
56
79
  {
57
- var repo = new ReadonlyRepository<${type}>(connection, Options());
80
+ var repo = new ReadonlyRepository<${type}>(connection, ${optionsCall(model)});
58
81
  ${locals.join("\n")}
59
82
  ${enrich.join("\n")}
60
83
  return repo;
@@ -69,17 +92,10 @@ const csharpRepositories = (models) => {
69
92
  ...children.map((child) => ` ${childField("csharp", child.specName, child.typeName)} = new ${childClass(child.typeName)}(connection);`),
70
93
  ...model.enrichments.map((item) => ` ${enrichment(item)}`)
71
94
  ];
72
- const mapping = ` PropertyMappings = new DictionaryMapper<string>(new Dictionary<string, string>
73
- {
74
- ${model.propertyMappings.map((pair) => ` ["${quote(pair.from)}"] = "${quote(pair.to)}",`).join("\n")}
75
- }),
76
- `;
77
- const constructor = lines.length === 0 ? ` public ${model.typeName}Repository(IConnection connection) : base(connection, Options()) { }` : ` public ${model.typeName}Repository(IConnection connection) : base(connection, Options())
95
+ const constructor = lines.length === 0 ? ` public ${model.typeName}Repository(IConnection connection) : base(connection, ${optionsCall(model)}) { }` : ` public ${model.typeName}Repository(IConnection connection) : base(connection, ${optionsCall(model)})
78
96
  {
79
97
  ${lines.join("\n")}
80
98
  }`;
81
- const ids = model.ids.map((name) => `"${quote(name)}"`).join(", ");
82
- const read = model.readProperties.map((name) => `"${quote(name)}"`).join(", ");
83
99
  const implementsOcc = model.optimisticConcurrency === void 0 ? "" : `, ICRUDRepository<${type}>`;
84
100
  const usings = model.optimisticConcurrency === void 0 ? "using Datasources;\n" : "using Datasources;\nusing static Datasources.Cmp;\n";
85
101
  return content(
@@ -93,13 +109,7 @@ ${fields.length === 0 ? "" : `${fields.join("\n")}
93
109
 
94
110
  ${createReadonly(model, type)}
95
111
  ${occMethods(model, type)}
96
- static CRUDRepositoryOptions<${type}> Options() => new()
97
- {
98
- Entity = "${quote(model.entity)}",
99
- EntityMapper = new DictionaryMapper<string>(new Dictionary<string, string> { ["${quote(model.entity)}"] = "${quote(model.table)}" }),
100
- ${mapping} Ids = [${ids}],
101
- ReadProperties = [${read}],
102
- };
112
+ ${optionsMethod(model, type)}
103
113
  }
104
114
  `
105
115
  );
@@ -10,6 +10,7 @@ import { csharpRepositories } from "./CSharpRepositories.js";
10
10
  import { rustRepositories } from "./RustRepositories.js";
11
11
  import { typescriptRepositories } from "./TypeScriptRepositories.js";
12
12
  import { planRepositories } from "./RepositoryPlan.js";
13
+ import { datasourceEnginesOf, engineDatasourceOf, overlayDatasource } from "./datasourceYaml.js";
13
14
  const place = (dir, entries) => entries.map((entry) => ({ ...entry, filename: `${dir}/${entry.filename}` }));
14
15
  const typePacks = {
15
16
  csharp: { datasource: csharpDatasourceTypes, views: csharpViewTypes },
@@ -22,6 +23,35 @@ const repositoryPacks = {
22
23
  rust: rustRepositories
23
24
  };
24
25
  const context = (request) => ({ reader: request.reader, settings: request.settings });
26
+ const optionsOf = (model) => ({
27
+ table: model.table,
28
+ ids: model.ids,
29
+ readProperties: model.readProperties,
30
+ createProperties: model.createProperties,
31
+ updateProperties: model.updateProperties,
32
+ propertyMappings: model.propertyMappings
33
+ });
34
+ const withProviderWrites = async (request, models) => {
35
+ const byProvider = {};
36
+ for (const engine of await datasourceEnginesOf(request.reader, request.settings)) {
37
+ const yaml = await engineDatasourceOf(request.reader, engine);
38
+ if (yaml === void 0) continue;
39
+ byProvider[engine] = planRepositories(
40
+ await DeterministicParser(overlayDatasource(request.reader, yaml)).parse(request.settings),
41
+ request.dialect,
42
+ request.settings
43
+ );
44
+ }
45
+ return Object.keys(byProvider).length === 0 ? models : models.map((model) => ({
46
+ ...model,
47
+ optionsByProvider: Object.fromEntries(
48
+ Object.entries(byProvider).map(([engine, planned]) => [
49
+ engine,
50
+ optionsOf(planned.find((item) => item.specName === model.specName) ?? model)
51
+ ])
52
+ )
53
+ }));
54
+ };
25
55
  const dialectsOf = (settings) => {
26
56
  const out = [];
27
57
  for (const name of (settings["backend.datasources"] ?? "").split(",")) {
@@ -44,7 +74,11 @@ const deterministic = {
44
74
  },
45
75
  generateRepositories: async (request) => {
46
76
  const spec = await DeterministicParser(request.reader).parse(request.settings);
47
- return place(`${request.dialect}/repositories`, repositoryPacks[request.dialect](planRepositories(spec, request.dialect, request.settings), request.settings));
77
+ const models = planRepositories(spec, request.dialect, request.settings);
78
+ return place(
79
+ `${request.dialect}/repositories`,
80
+ repositoryPacks[request.dialect](await withProviderWrites(request, models), request.settings)
81
+ );
48
82
  },
49
83
  generateDatasource: async (request) => place("sql", await generateSql(context(request), dialectsOf(request.settings)))
50
84
  };
@@ -1,6 +1,7 @@
1
1
  import { createCasingStrategy } from "@deterministic-code/generators-common/casing-strategy";
2
2
  import { createDatasourceNaming } from "@deterministic-code/generators-common/datasource-naming";
3
3
  import { identityColumns, isCollectionField, persistedColumnFields } from "@deterministic-code/generators-common/spec-types";
4
+ import { writeInclusionOf } from "@deterministic-code/datasources-code-generators/sql/common/write-inclusion";
4
5
  const referenceOf = (references) => {
5
6
  if (references === void 0) return void 0;
6
7
  const [type, field] = [references].flat().join(".").split(".");
@@ -70,6 +71,12 @@ const planRepositories = (spec, dialect, settings) => {
70
71
  }),
71
72
  ids: identityColumns(tableType, table).map(property),
72
73
  readProperties: columns.map((field) => property(field.name)),
74
+ createProperties: columns.filter(
75
+ (field) => writeInclusionOf(field, identityColumns(tableType, table), overlays.get(field.name)).includeInCreate
76
+ ).map((field) => property(field.name)),
77
+ updateProperties: columns.filter(
78
+ (field) => writeInclusionOf(field, identityColumns(tableType, table), overlays.get(field.name)).includeInUpdate
79
+ ).map((field) => property(field.name)),
73
80
  optimisticConcurrency: concurrencyToken(table, tableType, property),
74
81
  enrichments: relations(authored, full, casing, types, expanded, repoName, identity)
75
82
  }
@@ -1,21 +1,39 @@
1
1
  import { content } from "@deterministic-code/generators-common/generate-entry";
2
2
  import { childField, children as childModels, isLinked, pluralSnake, repositoryFile } from "./RepositoryModel.js";
3
- const rustString = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
4
- const uses = `use datasources::{Association, Connection, CRUDRepository, CRUDRepositoryOptions, DictionaryMapper, Enrichment, EnrichmentPropertyType, ICRUDRepository, ReadonlyRepository, ReadonlyRepositoryMappingOptions, Result};
3
+ import { dialectsOf, pad, quote, writeList } from "./writeLists.js";
4
+ const rustString = (value) => `"${quote(value)}"`;
5
+ const uses = (model) => dialectsOf(model).length === 0 ? `use datasources::{Association, Connection, CRUDRepository, CRUDRepositoryOptions, DictionaryMapper, Enrichment, EnrichmentPropertyType, ICRUDRepository, ReadonlyRepository, ReadonlyRepositoryMappingOptions, Result};
6
+ use std::sync::Arc;` : `use datasources::{Association, Connection, CRUDRepository, CRUDRepositoryBuilder, CRUDRepositoryOptions, DictionaryMapper, Enrichment, EnrichmentPropertyType, ICRUDRepository, ReadonlyRepository, ReadonlyRepositoryMappingOptions, Result};
5
7
  use std::sync::Arc;`;
6
- const crudOptions = (model) => {
7
- const mapping = model.propertyMappings.length === 0 ? "" : ` options.property_mappings = Some(Arc::new(DictionaryMapper::new([
8
- ${model.propertyMappings.map((pair) => ` (${rustString(pair.from)}.into(), ${rustString(pair.to)}.into()),`).join("\n")}
9
- ])));
8
+ const rustFill = (model) => {
9
+ const mapping = model.propertyMappings.length === 0 ? "" : `options.property_mappings = Some(Arc::new(DictionaryMapper::new([
10
+ ${model.propertyMappings.map((pair) => ` (${rustString(pair.from)}.into(), ${rustString(pair.to)}.into()),`).join("\n")}
11
+ ])));
10
12
  `;
11
- return ` let mut options = CRUDRepositoryOptions::default();
12
- options.entity = ${rustString(model.entity)}.into();
13
- options.entity_mapper = Some(Arc::new(DictionaryMapper::new([
14
- (${rustString(model.entity)}.into(), ${rustString(model.table)}.into()),
15
- ])));
16
- ${mapping} options.ids = vec![${model.ids.map((id) => `${rustString(id)}.into()`).join(", ")}];
17
- options.read_properties = vec![${model.readProperties.map((name) => `${rustString(name)}.into()`).join(", ")}];`;
13
+ return `let mut options = CRUDRepositoryOptions::default();
14
+ options.entity = ${rustString(model.entity)}.into();
15
+ options.entity_mapper = Some(Arc::new(DictionaryMapper::new([
16
+ (${rustString(model.entity)}.into(), ${rustString(model.table)}.into()),
17
+ ])));
18
+ ${mapping}options.ids = ${writeList("rust", model.ids)};
19
+ options.read_properties = ${writeList("rust", model.readProperties)};
20
+ options.create_properties = Some(${writeList("rust", model.createProperties)});
21
+ options.update_properties = Some(${writeList("rust", model.updateProperties)});`;
22
+ };
23
+ const rustBlock = (model) => {
24
+ const dialects = dialectsOf(model);
25
+ return dialects.length === 0 ? rustFill(model) : `let options = CRUDRepositoryBuilder::new()
26
+ .add({
27
+ ${pad(rustFill(model), 8)}
28
+ options
29
+ })${dialects.map(([engine, planned]) => `
30
+ .add_for(${rustString(engine)}, {
31
+ ${pad(rustFill({ entity: model.entity, ...planned }), 8)}
32
+ options
33
+ })`).join("")}
34
+ .for_connection(connection.as_ref())?;`;
18
35
  };
36
+ const crudOptions = (model) => pad(rustBlock(model), 4);
19
37
  const readonlyOptions = (model) => {
20
38
  const mapping = model.propertyMappings.length === 0 ? "" : ` options.property_mappings = Some(Arc::new(DictionaryMapper::new([
21
39
  ${model.propertyMappings.map((pair) => ` (${rustString(pair.from)}.into(), ${rustString(pair.to)}.into()),`).join("\n")}
@@ -162,8 +180,7 @@ ${where}
162
180
  }
163
181
  `;
164
182
  };
165
- const pad = (text) => text.split("\n").map((line) => ` ${line}`).join("\n");
166
- const plain = (model, nested) => `${uses}
183
+ const plain = (model, nested) => `${uses(model)}
167
184
 
168
185
  pub fn ${model.specName}_repository(connection: Arc<dyn Connection>) -> Result<CRUDRepository> {
169
186
  ${crudOptions(model)}
@@ -180,7 +197,7 @@ const wrapped = (model, nested) => {
180
197
  ).join("\n");
181
198
  const assign = children.map((child) => child.specName).join(", ");
182
199
  const enrich = model.enrichments.map((item) => ` ${enrichment(item, true, nested)}`).join("\n");
183
- return `${uses}
200
+ return `${uses(model)}
184
201
  use std::ops::Deref;
185
202
 
186
203
  pub struct ${type} {
@@ -190,7 +207,7 @@ ${fields}
190
207
 
191
208
  impl ${type} {
192
209
  pub fn new(connection: Arc<dyn Connection>) -> Result<Self> {
193
- ${pad(crudOptions(model))}
210
+ ${pad(crudOptions(model), 4)}
194
211
  ${childLets(children, nested)}
195
212
  let mut repo = CRUDRepository::new(Arc::clone(&connection), options)?;
196
213
  ${enrich}
@@ -1,7 +1,23 @@
1
1
  import { createCasingStrategy } from "@deterministic-code/generators-common/casing-strategy";
2
2
  import { content } from "@deterministic-code/generators-common/generate-entry";
3
3
  import { childClass, childField, children as childModels, isLinked, pluralCamel, repositoryFile } from "./RepositoryModel.js";
4
- const quote = (value) => value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
4
+ import { dialectsOf, quote, writeList } from "./writeLists.js";
5
+ const withEntity = (model, planned) => ({ entity: model.entity, ...planned });
6
+ const entityAndMapping = (model) => {
7
+ const mapping = model.propertyMappings.length === 0 ? "" : `propertyMappings: new DictionaryMapper([${model.propertyMappings.map((pair) => `["${quote(pair.from)}", "${quote(pair.to)}"]`).join(", ")}]), `;
8
+ return `entity: "${quote(model.entity)}", entityMapper: new DictionaryMapper([["${quote(model.entity)}", "${quote(model.table)}"]]), ${mapping}`;
9
+ };
10
+ const tsOptions = (model) => `{ ${entityAndMapping(model)}ids: ${writeList("typescript", model.ids)}, createProperties: ${writeList("typescript", model.createProperties)}, updateProperties: ${writeList("typescript", model.updateProperties)}, readProperties: ${writeList("typescript", model.readProperties)} }`;
11
+ const tsReadonly = (model) => `{ ${entityAndMapping(model)}readProperties: ${writeList("typescript", model.readProperties)} }`;
12
+ const tsOptionsExpr = (model) => dialectsOf(model).length === 0 ? tsOptions(model) : "options(connection)";
13
+ const tsBuilder = (name, model, write) => `const ${name} = (connection: IConnection) =>
14
+ new CRUDRepositoryBuilder<${model.typeName}>()
15
+ .add(${write(model)})${dialectsOf(model).map(([engine, planned]) => `
16
+ .add("${quote(engine)}", ${write(withEntity(model, planned))})`).join("")}
17
+ .for(connection);
18
+
19
+ `;
20
+ const tsOptionsFn = (model) => dialectsOf(model).length === 0 ? "" : `${tsBuilder("options", model, tsOptions)}${tsBuilder("readonlyOptions", model, tsReadonly)}`;
5
21
  const enrichment = (item, host) => {
6
22
  const via = host === "this" ? `this.${childField("typescript", item.via, item.viaType)}` : childField("typescript", item.via, item.viaType);
7
23
  const related = item.relatedProperty === void 0 ? "" : `, relatedProperty: "${quote(item.relatedProperty)}"`;
@@ -12,7 +28,8 @@ const enrichment = (item, host) => {
12
28
  }
13
29
  return `${host}.addEnriched({ destinationProperty: "${quote(item.destination)}", sourceProperty: "${quote(item.source)}", relatedKey: "${quote(item.relatedKey)}"${related}, type: "${type}" }, ${via});`;
14
30
  };
15
- const createReadonly = (model, options) => {
31
+ const createReadonly = (model) => {
32
+ const options = dialectsOf(model).length === 0 ? tsReadonly(model) : "readonlyOptions(connection)";
16
33
  const children = childModels(model);
17
34
  if (children.length === 0 && model.enrichments.length === 0)
18
35
  return ` static createReadonly = (connection: IConnection) => new ReadonlyRepository<${model.typeName}>(connection, ${options});`;
@@ -56,11 +73,6 @@ const typescriptRepositories = (models, settings) => {
56
73
  const files = models.map((model) => {
57
74
  const folder = model.typeHome === "view" ? "views" : "datasource";
58
75
  const children = childModels(model);
59
- const mapping = model.propertyMappings.length === 0 ? "" : `propertyMappings: new DictionaryMapper([${model.propertyMappings.map((pair) => `["${quote(pair.from)}", "${quote(pair.to)}"]`).join(", ")}]), `;
60
- const head = `{ entity: "${quote(model.entity)}", entityMapper: new DictionaryMapper([["${quote(model.entity)}", "${quote(model.table)}"]]), ${mapping}`;
61
- const read = `readProperties: [${model.readProperties.map((name) => `"${quote(name)}"`).join(", ")}] }`;
62
- const crudOptions = `${head}ids: [${model.ids.map((id) => `"${quote(id)}"`).join(", ")}], ${read}`;
63
- const readonlyOptions = `${head}${read}`;
64
76
  const fields = children.map((child) => ` readonly ${childField("typescript", child.specName, child.typeName)}: ${childClass(child.typeName)};`);
65
77
  const wrap = occWrap(model);
66
78
  const lines = [
@@ -68,20 +80,21 @@ const typescriptRepositories = (models, settings) => {
68
80
  ...model.enrichments.map((item) => ` ${enrichment(item, "this")}`)
69
81
  ];
70
82
  const imports = children.map((child) => `import { ${childClass(child.typeName)} } from "${repoImport(child.specName)}";`);
71
- const core = model.optimisticConcurrency === void 0 ? `import { CRUDRepository, DictionaryMapper, ReadonlyRepository, type IConnection } from "@deterministic-code/datasources";` : `import { CRUDRepository, DatasourceError, DictionaryMapper, eq, ReadonlyRepository, Where, type IConnection } from "@deterministic-code/datasources";`;
83
+ const builder = dialectsOf(model).length === 0 ? "" : "CRUDRepositoryBuilder, ";
84
+ const core = model.optimisticConcurrency === void 0 ? `import { CRUDRepository, ${builder}DictionaryMapper, ReadonlyRepository, type IConnection } from "@deterministic-code/datasources";` : `import { CRUDRepository, ${builder}DatasourceError, DictionaryMapper, eq, ReadonlyRepository, Where, type IConnection } from "@deterministic-code/datasources";`;
72
85
  return content(
73
86
  repositoryFile("typescript", model),
74
87
  `import type { ${model.typeName} } from "../types/generated/${folder}/${casing.convertFileName(model.specName)}";
75
88
  ${core}
76
89
  ${imports.length === 0 ? "" : `${imports.join("\n")}
77
90
  `}
78
- export class ${model.typeName}Repository extends CRUDRepository<${model.typeName}> {
91
+ ${tsOptionsFn(model)}export class ${model.typeName}Repository extends CRUDRepository<${model.typeName}> {
79
92
  ${fields.length === 0 ? "" : `${fields.join("\n")}
80
93
  `} constructor(connection: IConnection) {
81
- super(connection, ${crudOptions});
94
+ super(connection, ${tsOptionsExpr(model)});
82
95
  ${wrap}${lines.length === 0 ? "" : `${lines.join("\n")}
83
96
  `} }
84
- ${createReadonly(model, readonlyOptions)}
97
+ ${createReadonly(model)}
85
98
  }
86
99
  `
87
100
  );
@@ -2,13 +2,18 @@ import { isAbsolute, join } from "node:path";
2
2
  import { memoryReader } from "@deterministic-code/generators-common/deterministic-reader";
3
3
  import { loadSpec } from "./loadSpec.js";
4
4
  import { providerByName } from "./providers.js";
5
+ import { withStoredProcedures } from "./storedProcedures.js";
5
6
  import { writeEntries } from "./writeEntries.js";
6
7
  import { wireRegister } from "../wireRegister.js";
7
8
  const at = (root, path) => isAbsolute(path) ? path : join(root, path);
8
9
  const applyGenerate = async (command, flags, deps) => {
9
10
  const provider = providerByName(command.provider);
10
11
  const loaded = await loadSpec(deps.fs, at(flags.cwd, command.input));
11
- const request = { dialect: command.dialect, reader: memoryReader(loaded.files), settings: loaded.settings };
12
+ const request = {
13
+ dialect: command.dialect,
14
+ reader: memoryReader(loaded.files),
15
+ settings: withStoredProcedures(loaded.settings, command.storedProcedures)
16
+ };
12
17
  const entries = await {
13
18
  all: async () => [
14
19
  ...await provider.generateTypes(request),
@@ -3,6 +3,7 @@ import { generate as generateSql } from "@deterministic-code/datasources-code-ge
3
3
  import { memoryReader } from "@deterministic-code/generators-common/deterministic-reader";
4
4
  import { loadSpec } from "./loadSpec.js";
5
5
  import { providerByName } from "./providers.js";
6
+ import { withStoredProcedures } from "./storedProcedures.js";
6
7
  import { writeEntries } from "./writeEntries.js";
7
8
  const at = (root, path) => isAbsolute(path) ? path : join(root, path);
8
9
  const afterMigrations = (filename) => filename.replace(/^[^/]+\/migrations\//, "");
@@ -16,7 +17,10 @@ const applyMigrations = async (command, flags, deps) => {
16
17
  ];
17
18
  if (command.migrationsPath !== void 0 && listed.length > 1)
18
19
  throw new Error("--migrations-path requires a single SQL dialect in backend.datasources");
19
- const entries = await generateSql({ reader: memoryReader(loaded.files), settings: loaded.settings });
20
+ const entries = await generateSql({
21
+ reader: memoryReader(loaded.files),
22
+ settings: withStoredProcedures(loaded.settings, command.storedProcedures)
23
+ });
20
24
  const remapped = command.migrationsPath === void 0 ? entries : entries.map((entry) => ({ ...entry, filename: afterMigrations(entry.filename) }));
21
25
  const root = at(flags.cwd, command.migrationsPath ?? command.migrationsRoot);
22
26
  if (flags.dryRun) {
@@ -0,0 +1,22 @@
1
+ const known = ["sqlite", "mysql", "postgres", "sqlserver", "oracle"];
2
+ const overlayDatasource = (reader, yaml) => ({
3
+ read: (name) => name === "datasource.yaml" ? Promise.resolve(yaml) : reader.read(name),
4
+ exists: (name) => name === "datasource.yaml" ? Promise.resolve(true) : reader.exists(name)
5
+ });
6
+ const engineDatasourceOf = async (reader, engine) => await reader.exists(`datasource.${engine}.yaml`) ? reader.read(`datasource.${engine}.yaml`) : void 0;
7
+ const datasourceEnginesOf = async (reader, settings) => {
8
+ const engines = /* @__PURE__ */ new Set();
9
+ for (const name of (settings["backend.datasources"] ?? "").split(",")) {
10
+ const key = name.trim();
11
+ if (key !== "") engines.add(key);
12
+ }
13
+ for (const name of known) {
14
+ if (await reader.exists(`datasource.${name}.yaml`)) engines.add(name);
15
+ }
16
+ return [...engines];
17
+ };
18
+ export {
19
+ datasourceEnginesOf,
20
+ engineDatasourceOf,
21
+ overlayDatasource
22
+ };
@@ -22,8 +22,13 @@ const loadSpec = async (fs, dir) => {
22
22
  if (text !== void 0) files[name] = text;
23
23
  }
24
24
  if (files["types.yaml"] === void 0) throw new Error(`missing types.yaml in ${dir}`);
25
- files["datasource.yaml"] ??= defaultDatasourceYaml;
26
25
  const names = await fs.readdir(dir).catch(() => []);
26
+ for (const name of names) {
27
+ if (!/^datasource\.[a-z0-9]+\.yaml$/.test(name)) continue;
28
+ const text = await readOptional(fs, join(dir, name));
29
+ if (text !== void 0) files[name] = text;
30
+ }
31
+ files["datasource.yaml"] ??= files["datasource.sqlite.yaml"] ?? defaultDatasourceYaml;
27
32
  const diffs = names.filter((name) => versionDir.test(name)).sort();
28
33
  const versionFiles = [];
29
34
  for (const name of diffs) {
@@ -0,0 +1,5 @@
1
+ const key = "datasource.use_stored_procedures";
2
+ const withStoredProcedures = (settings, storedProcedures) => storedProcedures ? { ...settings, [key]: "true" } : settings;
3
+ export {
4
+ withStoredProcedures
5
+ };
@@ -0,0 +1,19 @@
1
+ const same = (left, right) => left.length === right.length && left.every((name, index) => name === right[index]);
2
+ const samePairs = (left, right) => left.length === right.length && left.every((pair, index) => pair.from === right[index]?.from && pair.to === right[index]?.to);
3
+ const sameOptions = (left, right) => left.table === right.table && same(left.ids, right.ids) && same(left.readProperties, right.readProperties) && same(left.createProperties, right.createProperties) && same(left.updateProperties, right.updateProperties) && samePairs(left.propertyMappings, right.propertyMappings);
4
+ const dialectsOf = (model) => Object.entries(model.optionsByProvider ?? {}).filter(([, planned]) => !sameOptions(model, planned));
5
+ const quote = (value) => value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
6
+ const pad = (text, spaces) => {
7
+ const prefix = " ".repeat(spaces);
8
+ return text.split("\n").map((line) => `${prefix}${line}`).join("\n");
9
+ };
10
+ const csharpList = (names) => `[${names.map((name) => `"${quote(name)}"`).join(", ")}]`;
11
+ const tsList = (names) => `[${names.map((name) => `"${quote(name)}"`).join(", ")}]`;
12
+ const rustList = (names) => `vec![${names.map((name) => `"${quote(name)}".into()`).join(", ")}]`;
13
+ const writeList = (dialect, names) => dialect === "csharp" ? csharpList(names) : dialect === "typescript" ? tsList(names) : rustList(names);
14
+ export {
15
+ dialectsOf,
16
+ pad,
17
+ quote,
18
+ writeList
19
+ };
package/dist/parse.js CHANGED
@@ -3,7 +3,7 @@ import { isDialect } from "./generate/GenerateRequest.js";
3
3
  const generateTargets = ["all", "types", "repositories", "datasource"];
4
4
  const help = `Usage: datasources <command> [options]
5
5
 
6
- setup Interactive .env, provider install, and optional deterministic/
6
+ setup [provider...] Interactive .env, provider install, and optional deterministic/
7
7
  --cwd <dir> Consumer project directory (default: .)
8
8
  --ecosystem ts|rust|csharp Required when more than one project type exists
9
9
  --dry-run Print package-manager commands without running them
@@ -29,12 +29,14 @@ generate <all|types|repositories|datasource>
29
29
  --no-migrate Skip migrate-up (generate all applies them)
30
30
  --migrations-root <dir> Root prefix for generate all (default: sql)
31
31
  --migrations-path <dir> One migrations directory (single SQL dialect)
32
+ --stored-procedures Emit stored-procedure migrations when the dialect can
32
33
 
33
34
  migrations create SQL migrations from the spec (migraters layout)
34
35
  --provider <name> Spec provider (default: deterministic)
35
36
  --input <dir> Deterministic project (default: ./deterministic)
36
37
  --migrations-root <dir> Root prefix (default: sql)
37
38
  --migrations-path <dir> One migrations directory (single SQL dialect)
39
+ --stored-procedures Emit stored-procedure migrations when the dialect can
38
40
 
39
41
  version Snapshot top-level YAML as the next version-*/diff.yaml
40
42
  --cwd <dir> Consumer project directory (default: .)
@@ -54,6 +56,7 @@ const parseArgs = (argv, cwd = ".") => {
54
56
  output: "generated",
55
57
  migrations: true,
56
58
  migrate: true,
59
+ storedProcedures: false,
57
60
  migrationsRoot: "sql",
58
61
  migrationsPath: ""
59
62
  };
@@ -76,6 +79,7 @@ const parseArgs = (argv, cwd = ".") => {
76
79
  else if (arg.startsWith("--output=")) generate.output = arg.slice("--output=".length);
77
80
  else if (arg === "--no-migrations") generate.migrations = false;
78
81
  else if (arg === "--no-migrate") generate.migrate = false;
82
+ else if (arg === "--stored-procedures") generate.storedProcedures = true;
79
83
  else if (arg === "--migrations-root") generate.migrationsRoot = take(argv, ++i, "--migrations-root");
80
84
  else if (arg.startsWith("--migrations-root=")) generate.migrationsRoot = arg.slice("--migrations-root=".length);
81
85
  else if (arg === "--migrations-path") generate.migrationsPath = take(argv, ++i, "--migrations-path");
@@ -88,7 +92,7 @@ const parseArgs = (argv, cwd = ".") => {
88
92
  };
89
93
  const commandOf = (verb, names, generate) => {
90
94
  if (verb === void 0 || verb === "help") return { kind: "help" };
91
- if (verb === "setup") return { kind: "setup" };
95
+ if (verb === "setup") return { kind: "setup", providers: names };
92
96
  if (verb === "add") return { kind: "add", providers: names };
93
97
  if (verb === "remove") return { kind: "remove", providers: names };
94
98
  if (verb === "generate") return generateCommand(names, generate);
@@ -105,6 +109,7 @@ ${help}`);
105
109
  kind: "migrations-create",
106
110
  provider: generate.provider,
107
111
  input: generate.input,
112
+ storedProcedures: generate.storedProcedures,
108
113
  migrationsRoot: generate.migrationsRoot,
109
114
  ...generate.migrationsPath === "" ? {} : { migrationsPath: generate.migrationsPath }
110
115
  };
@@ -130,6 +135,7 @@ ${help}`);
130
135
  output: generate.output,
131
136
  migrations: generate.migrations,
132
137
  migrate: generate.migrate,
138
+ storedProcedures: generate.storedProcedures,
133
139
  migrationsRoot: generate.migrationsRoot,
134
140
  ...generate.migrationsPath === "" ? {} : { migrationsPath: generate.migrationsPath }
135
141
  };
package/dist/run.js CHANGED
@@ -3,12 +3,7 @@ import { join } from "node:path";
3
3
  import { catalog, requireProviders } from "./catalog.js";
4
4
  import { addCommands, removeCommands } from "./commands.js";
5
5
  import { csharpTarget, detect, resolveEcosystem } from "./detect.js";
6
- import {
7
- answersFromEnv,
8
- parseEnv,
9
- serializeEnv,
10
- updateEnvProviders
11
- } from "./env.js";
6
+ import { parseEnv, serializeEnv, updateEnvProviders } from "./env.js";
12
7
  import { defaultDatasourceYaml } from "./generate/loadSpec.js";
13
8
  import {
14
9
  cargoProviders,
@@ -21,7 +16,7 @@ import { applyMigrateUp } from "./generate/migrateUp.js";
21
16
  import { migrateInitCommands, migrateLanguage, sqlMigrateProviders } from "./migrateInit.js";
22
17
  import { help, parseArgs } from "./parse.js";
23
18
  import { applyVersion } from "./version/applyVersion.js";
24
- import { envFromAnswers, promptSetup, starterTypesYaml } from "./setup.js";
19
+ import { answersForSetup, envFromAnswers, promptSetup, starterTypesYaml } from "./setup.js";
25
20
  import { wireRegister } from "./wireRegister.js";
26
21
  const run = async (argv, deps, cwd = ".") => {
27
22
  try {
@@ -35,7 +30,7 @@ const run = async (argv, deps, cwd = ".") => {
35
30
  };
36
31
  const dispatch = async (command, flags, deps) => {
37
32
  if (command.kind === "help") return deps.log(help.trimEnd());
38
- if (command.kind === "setup") return applySetup(flags, deps);
33
+ if (command.kind === "setup") return applySetup(flags, deps, command.providers);
39
34
  if (command.kind === "add") return applyAddRemove(flags, deps, requireProviders(needNames(command.providers, "add")), []);
40
35
  if (command.kind === "remove") return applyAddRemove(flags, deps, [], requireProviders(needNames(command.providers, "remove")));
41
36
  if (command.kind === "version") return applyVersion(command, flags, deps);
@@ -48,6 +43,7 @@ const dispatch = async (command, flags, deps) => {
48
43
  kind: "migrations-create",
49
44
  provider: command.provider,
50
45
  input: command.input,
46
+ storedProcedures: command.storedProcedures,
51
47
  migrationsRoot: command.migrationsRoot,
52
48
  ...command.migrationsPath === void 0 ? {} : { migrationsPath: command.migrationsPath }
53
49
  },
@@ -59,7 +55,7 @@ const dispatch = async (command, flags, deps) => {
59
55
  const needNames = (names, verb) => names.length ? names : (() => {
60
56
  throw new Error(`Usage: datasources ${verb} <provider...>`);
61
57
  })();
62
- const applySetup = async (flags, deps) => {
58
+ const applySetup = async (flags, deps, requested) => {
63
59
  const entries = await deps.fs.readdir(flags.cwd);
64
60
  const found = detect(entries);
65
61
  const existing = await readEnv(deps.fs, flags.cwd);
@@ -67,7 +63,7 @@ const applySetup = async (flags, deps) => {
67
63
  const typesPath = join(specDir, "types.yaml");
68
64
  const datasourcePath = join(specDir, "datasource.yaml");
69
65
  const missingSpec = await optional(deps.fs, typesPath) === void 0;
70
- const answers = flags.yes ? { ...answersFromEnv(existing), ecosystem: flags.ecosystem, scaffold: false } : await promptSetup(deps.prompt, existing, found, flags.ecosystem, missingSpec);
66
+ const answers = flags.yes ? { ...answersForSetup(existing, requested), ecosystem: flags.ecosystem, scaffold: false } : await promptSetup(deps.prompt, existing, found, flags.ecosystem, missingSpec, requested);
71
67
  const ecosystem = resolveEcosystem(found, answers.ecosystem ?? flags.ecosystem);
72
68
  const selected = requireProviders(answers.providers);
73
69
  const installed = await readInstalled(deps.fs, flags.cwd, ecosystem, entries);
package/dist/setup.js CHANGED
@@ -14,8 +14,9 @@ types:
14
14
  type: string
15
15
  size: 128
16
16
  `;
17
- const promptSetup = async (prompt, env, found, explicit, askScaffold = false) => {
18
- const defaults = answersFromEnv(env);
17
+ const answersForSetup = (env, requested = []) => requested.length ? answersFromEnv({ ...env, INSTALLED_PROVIDERS: requested.join(",") }) : answersFromEnv(env);
18
+ const promptSetup = async (prompt, env, found, explicit, askScaffold = false, requested = []) => {
19
+ const defaults = answersForSetup(env, requested);
19
20
  const ecosystem = explicit ?? (found.length === 1 ? found[0] : found.length === 0 ? void 0 : ecosystemOr(await prompt.select({
20
21
  message: "Project type",
21
22
  choices: found.map((value) => ({ name: value, value })),
@@ -48,6 +49,7 @@ const envFromAnswers = (answers) => {
48
49
  };
49
50
  };
50
51
  export {
52
+ answersForSetup,
51
53
  envFromAnswers,
52
54
  promptSetup,
53
55
  starterTypesYaml
@@ -0,0 +1,5 @@
1
+ import { NestedSelectBuilder } from "@deterministic-code/datasources";
2
+ import type { NestedSelectType } from "@deterministic-code/datasources";
3
+ import type { Type } from "@deterministic-code/deterministic-generators-common/parser";
4
+ export declare const nestedSelectFromSpec: (types: readonly Type[], root: string) => NestedSelectType;
5
+ export { NestedSelectBuilder };
@@ -0,0 +1,64 @@
1
+ import { ChangeCasingMapper, NestedSelectBuilder, PluralizerMapper } from "@deterministic-code/datasources";
2
+ const pascal = new ChangeCasingMapper("snakeCase", "pascalCase");
3
+ const plural = new PluralizerMapper();
4
+ export const nestedSelectFromSpec = (types, root) => {
5
+ const tagged = types.some((item) => item.tags?.includes("datasource_type"));
6
+ const catalog = new Map(types
7
+ .filter((item) => !tagged || item.tags?.includes("datasource_type"))
8
+ .map((item) => [item.name, item]));
9
+ const name = catalog.has(root)
10
+ ? root
11
+ : [...catalog.keys()].find((item) => entity(item) === root);
12
+ if (name === undefined)
13
+ throw new Error(`Unknown nested select type "${root}"`);
14
+ return build(catalog.get(name), catalog, new Set());
15
+ };
16
+ export { NestedSelectBuilder };
17
+ const build = (type, catalog, ancestors) => {
18
+ const next = new Set(ancestors);
19
+ next.add(type.name);
20
+ const key = entity(type.fields.find((field) => field.isId)?.name ?? type.ids?.[0] ?? "id");
21
+ const properties = type.fields
22
+ .filter((field) => field.references === undefined)
23
+ .map((field) => entity(field.name));
24
+ const objects = type.fields.flatMap((field) => {
25
+ const [table, column] = target(field);
26
+ if (!table || !catalog.has(table) || next.has(table))
27
+ return [];
28
+ return [{
29
+ name: relationName(field.name, table),
30
+ kind: "object",
31
+ from: entity(field.name),
32
+ to: entity(column),
33
+ target: build(catalog.get(table), catalog, next),
34
+ }];
35
+ });
36
+ const lists = [...catalog.values()].flatMap((child) => {
37
+ if (next.has(child.name))
38
+ return [];
39
+ return child.fields.flatMap((field) => {
40
+ const [table, column] = target(field);
41
+ if (table !== type.name || !column)
42
+ return [];
43
+ return [{
44
+ name: plural.map(entity(child.name)),
45
+ kind: "list",
46
+ from: key,
47
+ to: entity(field.name),
48
+ target: build(child, catalog, next),
49
+ }];
50
+ });
51
+ });
52
+ return { name: entity(type.name), key, properties, relations: [...objects, ...lists] };
53
+ };
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
+ };
60
+ const relationName = (field, table) => {
61
+ const name = entity(field);
62
+ return name.endsWith("Id") && name.length > 2 ? name.slice(0, -2) : entity(table);
63
+ };
64
+ const entity = (name) => pascal.map(name);