@deterministic-code/datasources-cli 0.1.11 → 0.1.13

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.
@@ -7,8 +7,29 @@ import {
7
7
  pluralPascal,
8
8
  repositoryFile
9
9
  } from "./RepositoryModel.js";
10
- import { writeList } from "./writeLists.js";
11
- 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
+ };
12
33
  const enrichment = (item, host = "ctor") => {
13
34
  const via = childField("csharp", item.via, item.viaType);
14
35
  const head = `"${quote(item.destination)}", "${quote(item.source)}", "${quote(item.relatedKey)}"`;
@@ -46,16 +67,17 @@ const occMethods = (model, type) => {
46
67
  }
47
68
  `;
48
69
  };
70
+ const optionsCall = (model) => dialectsOf(model).length === 0 ? "Options()" : "Options(connection)";
49
71
  const createReadonly = (model, type) => {
50
72
  const children = childModels(model);
51
73
  if (children.length === 0 && model.enrichments.length === 0)
52
74
  return ` public static IReadonlyRepository<${type}> CreateReadonly(IConnection connection) =>
53
- new ReadonlyRepository<${type}>(connection, Options(connection));`;
75
+ new ReadonlyRepository<${type}>(connection, ${optionsCall(model)});`;
54
76
  const locals = children.map((child) => ` var ${childField("csharp", child.specName, child.typeName)} = new ${childClass(child.typeName)}(connection);`);
55
77
  const enrich = model.enrichments.map((item) => ` ${enrichment(item, "readonly")}`);
56
78
  return ` public static IReadonlyRepository<${type}> CreateReadonly(IConnection connection)
57
79
  {
58
- var repo = new ReadonlyRepository<${type}>(connection, Options(connection));
80
+ var repo = new ReadonlyRepository<${type}>(connection, ${optionsCall(model)});
59
81
  ${locals.join("\n")}
60
82
  ${enrich.join("\n")}
61
83
  return repo;
@@ -70,19 +92,10 @@ const csharpRepositories = (models) => {
70
92
  ...children.map((child) => ` ${childField("csharp", child.specName, child.typeName)} = new ${childClass(child.typeName)}(connection);`),
71
93
  ...model.enrichments.map((item) => ` ${enrichment(item)}`)
72
94
  ];
73
- const mapping = ` PropertyMappings = new DictionaryMapper<string>(new Dictionary<string, string>
74
- {
75
- ${model.propertyMappings.map((pair) => ` ["${quote(pair.from)}"] = "${quote(pair.to)}",`).join("\n")}
76
- }),
77
- `;
78
- const constructor = lines.length === 0 ? ` public ${model.typeName}Repository(IConnection connection) : base(connection, Options(connection)) { }` : ` public ${model.typeName}Repository(IConnection connection) : base(connection, Options(connection))
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)})
79
96
  {
80
97
  ${lines.join("\n")}
81
98
  }`;
82
- const ids = model.ids.map((name) => `"${quote(name)}"`).join(", ");
83
- const read = model.readProperties.map((name) => `"${quote(name)}"`).join(", ");
84
- const create = writeList("csharp", model, "create");
85
- const update = writeList("csharp", model, "update");
86
99
  const implementsOcc = model.optimisticConcurrency === void 0 ? "" : `, ICRUDRepository<${type}>`;
87
100
  const usings = model.optimisticConcurrency === void 0 ? "using Datasources;\n" : "using Datasources;\nusing static Datasources.Cmp;\n";
88
101
  return content(
@@ -96,15 +109,7 @@ ${fields.length === 0 ? "" : `${fields.join("\n")}
96
109
 
97
110
  ${createReadonly(model, type)}
98
111
  ${occMethods(model, type)}
99
- static CRUDRepositoryOptions<${type}> Options(IConnection connection) => new()
100
- {
101
- Entity = "${quote(model.entity)}",
102
- EntityMapper = new DictionaryMapper<string>(new Dictionary<string, string> { ["${quote(model.entity)}"] = "${quote(model.table)}" }),
103
- ${mapping} Ids = [${ids}],
104
- ReadProperties = [${read}],
105
- CreateProperties = ${create},
106
- UpdateProperties = ${update},
107
- };
112
+ ${optionsMethod(model, type)}
108
113
  }
109
114
  `
110
115
  );
@@ -23,6 +23,14 @@ const repositoryPacks = {
23
23
  rust: rustRepositories
24
24
  };
25
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
+ });
26
34
  const withProviderWrites = async (request, models) => {
27
35
  const byProvider = {};
28
36
  for (const engine of await datasourceEnginesOf(request.reader, request.settings)) {
@@ -36,17 +44,11 @@ const withProviderWrites = async (request, models) => {
36
44
  }
37
45
  return Object.keys(byProvider).length === 0 ? models : models.map((model) => ({
38
46
  ...model,
39
- writesByProvider: Object.fromEntries(
40
- Object.entries(byProvider).map(([engine, planned]) => {
41
- const found = planned.find((item) => item.specName === model.specName);
42
- return [
43
- engine,
44
- {
45
- create: found?.createProperties ?? model.createProperties,
46
- update: found?.updateProperties ?? model.updateProperties
47
- }
48
- ];
49
- })
47
+ optionsByProvider: Object.fromEntries(
48
+ Object.entries(byProvider).map(([engine, planned]) => [
49
+ engine,
50
+ optionsOf(planned.find((item) => item.specName === model.specName) ?? model)
51
+ ])
50
52
  )
51
53
  }));
52
54
  };
@@ -1,24 +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
- import { writeList } from "./writeLists.js";
4
- const rustString = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
5
- 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};
6
7
  use std::sync::Arc;`;
7
- const crudOptions = (model) => {
8
- const mapping = model.propertyMappings.length === 0 ? "" : ` options.property_mappings = Some(Arc::new(DictionaryMapper::new([
9
- ${model.propertyMappings.map((pair) => ` (${rustString(pair.from)}.into(), ${rustString(pair.to)}.into()),`).join("\n")}
10
- ])));
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
+ ])));
11
12
  `;
12
- return ` let mut options = CRUDRepositoryOptions::default();
13
- options.entity = ${rustString(model.entity)}.into();
14
- options.entity_mapper = Some(Arc::new(DictionaryMapper::new([
15
- (${rustString(model.entity)}.into(), ${rustString(model.table)}.into()),
16
- ])));
17
- ${mapping} options.ids = vec![${model.ids.map((id) => `${rustString(id)}.into()`).join(", ")}];
18
- options.read_properties = vec![${model.readProperties.map((name) => `${rustString(name)}.into()`).join(", ")}];
19
- options.create_properties = Some(${writeList("rust", model, "create")});
20
- options.update_properties = Some(${writeList("rust", model, "update")});`;
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())?;`;
21
35
  };
36
+ const crudOptions = (model) => pad(rustBlock(model), 4);
22
37
  const readonlyOptions = (model) => {
23
38
  const mapping = model.propertyMappings.length === 0 ? "" : ` options.property_mappings = Some(Arc::new(DictionaryMapper::new([
24
39
  ${model.propertyMappings.map((pair) => ` (${rustString(pair.from)}.into(), ${rustString(pair.to)}.into()),`).join("\n")}
@@ -165,8 +180,7 @@ ${where}
165
180
  }
166
181
  `;
167
182
  };
168
- const pad = (text) => text.split("\n").map((line) => ` ${line}`).join("\n");
169
- const plain = (model, nested) => `${uses}
183
+ const plain = (model, nested) => `${uses(model)}
170
184
 
171
185
  pub fn ${model.specName}_repository(connection: Arc<dyn Connection>) -> Result<CRUDRepository> {
172
186
  ${crudOptions(model)}
@@ -183,7 +197,7 @@ const wrapped = (model, nested) => {
183
197
  ).join("\n");
184
198
  const assign = children.map((child) => child.specName).join(", ");
185
199
  const enrich = model.enrichments.map((item) => ` ${enrichment(item, true, nested)}`).join("\n");
186
- return `${uses}
200
+ return `${uses(model)}
187
201
  use std::ops::Deref;
188
202
 
189
203
  pub struct ${type} {
@@ -193,7 +207,7 @@ ${fields}
193
207
 
194
208
  impl ${type} {
195
209
  pub fn new(connection: Arc<dyn Connection>) -> Result<Self> {
196
- ${pad(crudOptions(model))}
210
+ ${pad(crudOptions(model), 4)}
197
211
  ${childLets(children, nested)}
198
212
  let mut repo = CRUDRepository::new(Arc::clone(&connection), options)?;
199
213
  ${enrich}
@@ -1,8 +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
- import { writeList } from "./writeLists.js";
5
- 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)}`;
6
21
  const enrichment = (item, host) => {
7
22
  const via = host === "this" ? `this.${childField("typescript", item.via, item.viaType)}` : childField("typescript", item.via, item.viaType);
8
23
  const related = item.relatedProperty === void 0 ? "" : `, relatedProperty: "${quote(item.relatedProperty)}"`;
@@ -13,7 +28,8 @@ const enrichment = (item, host) => {
13
28
  }
14
29
  return `${host}.addEnriched({ destinationProperty: "${quote(item.destination)}", sourceProperty: "${quote(item.source)}", relatedKey: "${quote(item.relatedKey)}"${related}, type: "${type}" }, ${via});`;
15
30
  };
16
- const createReadonly = (model, options) => {
31
+ const createReadonly = (model) => {
32
+ const options = dialectsOf(model).length === 0 ? tsReadonly(model) : "readonlyOptions(connection)";
17
33
  const children = childModels(model);
18
34
  if (children.length === 0 && model.enrichments.length === 0)
19
35
  return ` static createReadonly = (connection: IConnection) => new ReadonlyRepository<${model.typeName}>(connection, ${options});`;
@@ -57,12 +73,6 @@ const typescriptRepositories = (models, settings) => {
57
73
  const files = models.map((model) => {
58
74
  const folder = model.typeHome === "view" ? "views" : "datasource";
59
75
  const children = childModels(model);
60
- const mapping = model.propertyMappings.length === 0 ? "" : `propertyMappings: new DictionaryMapper([${model.propertyMappings.map((pair) => `["${quote(pair.from)}", "${quote(pair.to)}"]`).join(", ")}]), `;
61
- const head = `{ entity: "${quote(model.entity)}", entityMapper: new DictionaryMapper([["${quote(model.entity)}", "${quote(model.table)}"]]), ${mapping}`;
62
- const writes = `createProperties: ${writeList("typescript", model, "create")}, updateProperties: ${writeList("typescript", model, "update")}`;
63
- const read = `readProperties: [${model.readProperties.map((name) => `"${quote(name)}"`).join(", ")}] }`;
64
- const crudOptions = `${head}ids: [${model.ids.map((id) => `"${quote(id)}"`).join(", ")}], ${writes}, ${read}`;
65
- const readonlyOptions = `${head}${read}`;
66
76
  const fields = children.map((child) => ` readonly ${childField("typescript", child.specName, child.typeName)}: ${childClass(child.typeName)};`);
67
77
  const wrap = occWrap(model);
68
78
  const lines = [
@@ -70,20 +80,21 @@ const typescriptRepositories = (models, settings) => {
70
80
  ...model.enrichments.map((item) => ` ${enrichment(item, "this")}`)
71
81
  ];
72
82
  const imports = children.map((child) => `import { ${childClass(child.typeName)} } from "${repoImport(child.specName)}";`);
73
- 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";`;
74
85
  return content(
75
86
  repositoryFile("typescript", model),
76
87
  `import type { ${model.typeName} } from "../types/generated/${folder}/${casing.convertFileName(model.specName)}";
77
88
  ${core}
78
89
  ${imports.length === 0 ? "" : `${imports.join("\n")}
79
90
  `}
80
- export class ${model.typeName}Repository extends CRUDRepository<${model.typeName}> {
91
+ ${tsOptionsFn(model)}export class ${model.typeName}Repository extends CRUDRepository<${model.typeName}> {
81
92
  ${fields.length === 0 ? "" : `${fields.join("\n")}
82
93
  `} constructor(connection: IConnection) {
83
- super(connection, ${crudOptions});
94
+ super(connection, ${tsOptionsExpr(model)});
84
95
  ${wrap}${lines.length === 0 ? "" : `${lines.join("\n")}
85
96
  `} }
86
- ${createReadonly(model, readonlyOptions)}
97
+ ${createReadonly(model)}
87
98
  }
88
99
  `
89
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,5 @@
1
+ const key = "datasource.use_stored_procedures";
2
+ const withStoredProcedures = (settings, storedProcedures) => storedProcedures ? { ...settings, [key]: "true" } : settings;
3
+ export {
4
+ withStoredProcedures
5
+ };
@@ -1,25 +1,19 @@
1
- const quote = (value) => value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
2
- const namesOf = (model, kind) => kind === "create" ? model.createProperties : model.updateProperties;
3
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
+ };
4
10
  const csharpList = (names) => `[${names.map((name) => `"${quote(name)}"`).join(", ")}]`;
5
11
  const tsList = (names) => `[${names.map((name) => `"${quote(name)}"`).join(", ")}]`;
6
12
  const rustList = (names) => `vec![${names.map((name) => `"${quote(name)}".into()`).join(", ")}]`;
7
- const fallback = (model, kind) => model.writesByProvider?.sqlite?.[kind] ?? namesOf(model, kind);
8
- const writeList = (dialect, model, kind) => {
9
- const by = model.writesByProvider;
10
- const listed = namesOf(model, kind);
11
- if (by === void 0 || Object.values(by).every((item) => same(item[kind], listed)))
12
- return dialect === "csharp" ? csharpList(listed) : dialect === "typescript" ? tsList(listed) : rustList(listed);
13
- const fallbackList = fallback(model, kind);
14
- return dialect === "csharp" ? `connection.Provider switch
15
- {
16
- ${Object.entries(by).filter(([engine]) => engine !== "sqlite").map(([engine, item]) => ` "${quote(engine)}" => ${csharpList(item[kind])},`).join("\n")}
17
- _ => ${csharpList(fallbackList)},
18
- }` : dialect === "typescript" ? `({ ${Object.entries(by).filter(([engine]) => engine !== "sqlite").map(([engine, item]) => `${engine}: ${tsList(item[kind])}`).join(", ")} }[connection.provider] ?? ${tsList(fallbackList)})` : `match connection.provider() {
19
- ${Object.entries(by).filter(([engine]) => engine !== "sqlite").map(([engine, item]) => `${engine === "mysql" || engine === "postgres" ? `"${engine}"` : `"${quote(engine)}"`} => ${rustList(item[kind])},`).join("\n ")}
20
- _ => ${rustList(fallbackList)},
21
- }`;
22
- };
13
+ const writeList = (dialect, names) => dialect === "csharp" ? csharpList(names) : dialect === "typescript" ? tsList(names) : rustList(names);
23
14
  export {
15
+ dialectsOf,
16
+ pad,
17
+ quote,
24
18
  writeList
25
19
  };
package/dist/parse.js CHANGED
@@ -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");
@@ -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
@@ -43,6 +43,7 @@ const dispatch = async (command, flags, deps) => {
43
43
  kind: "migrations-create",
44
44
  provider: command.provider,
45
45
  input: command.input,
46
+ storedProcedures: command.storedProcedures,
46
47
  migrationsRoot: command.migrationsRoot,
47
48
  ...command.migrationsPath === void 0 ? {} : { migrationsPath: command.migrationsPath }
48
49
  },
@@ -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);
@@ -14,5 +14,6 @@ export declare const applyDocument: (before: {
14
14
  [path: string]: Json;
15
15
  };
16
16
  export declare const isObject: (value: Json | undefined) => value is JsonObject;
17
+ /** Keyed lists compare by key. Version diffs append `added` entries and do not store position. */
17
18
  export declare const snapshotsEqual: (left: Json | undefined, right: Json | undefined) => boolean;
18
19
  export {};
@@ -80,4 +80,41 @@ const isKeyedCollection = (value) => Array.isArray(value) &&
80
80
  typeof item === "object" &&
81
81
  !Array.isArray(item) &&
82
82
  Object.keys(item).length === 1);
83
- export const snapshotsEqual = (left, right) => isDeepStrictEqual(left ?? null, right ?? null);
83
+ /** Keyed lists compare by key. Version diffs append `added` entries and do not store position. */
84
+ export const snapshotsEqual = (left, right) => same(left ?? null, right ?? null);
85
+ const same = (left, right) => {
86
+ if (isKeyedCollection(left) && isKeyedCollection(right))
87
+ return keyedEqual(left, right);
88
+ if (isObject(left) && isObject(right))
89
+ return objectEqual(left, right);
90
+ if (Array.isArray(left) && Array.isArray(right))
91
+ return left.length === right.length && left.every((item, index) => same(item, right[index]));
92
+ return isDeepStrictEqual(left, right);
93
+ };
94
+ const keyedEqual = (left, right) => {
95
+ const leftMap = uniqueKeyed(left);
96
+ const rightMap = uniqueKeyed(right);
97
+ if (leftMap === undefined || rightMap === undefined)
98
+ return isDeepStrictEqual(left, right);
99
+ if (leftMap.size !== rightMap.size)
100
+ return false;
101
+ for (const [key, value] of leftMap)
102
+ if (!rightMap.has(key) || !same(value, rightMap.get(key)))
103
+ return false;
104
+ return true;
105
+ };
106
+ const uniqueKeyed = (items) => {
107
+ const map = new Map();
108
+ for (const entry of items) {
109
+ const key = Object.keys(entry)[0];
110
+ if (map.has(key))
111
+ return undefined;
112
+ map.set(key, entry[key]);
113
+ }
114
+ return map;
115
+ };
116
+ const objectEqual = (left, right) => {
117
+ const keys = Object.keys(left);
118
+ return (keys.length === Object.keys(right).length &&
119
+ keys.every((key) => key in right && same(left[key], right[key])));
120
+ };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deterministic-code/datasources-code-generators",
3
- "version": "0.1.11",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dist/generateSql.js",
@@ -12,12 +12,12 @@
12
12
  "./sql/common/write-inclusion": "./dist/sql/common/write-inclusion.js"
13
13
  },
14
14
  "dependencies": {
15
- "@deterministic-code/datasources": "^0.1.11",
16
- "@deterministic-code/datasources-mysql": "^0.1.11",
17
- "@deterministic-code/datasources-oracle": "^0.1.11",
18
- "@deterministic-code/datasources-postgres": "^0.1.11",
19
- "@deterministic-code/datasources-sqlite": "^0.1.11",
20
- "@deterministic-code/datasources-sqlserver": "^0.1.11",
15
+ "@deterministic-code/datasources": "^0.1.12",
16
+ "@deterministic-code/datasources-mysql": "^0.1.12",
17
+ "@deterministic-code/datasources-oracle": "^0.1.12",
18
+ "@deterministic-code/datasources-postgres": "^0.1.12",
19
+ "@deterministic-code/datasources-sqlite": "^0.1.12",
20
+ "@deterministic-code/datasources-sqlserver": "^0.1.12",
21
21
  "@deterministic-code/deterministic-generators-common": "0.1.0",
22
22
  "@deterministic-code/generators-common": "0.0.1",
23
23
  "pluralize": "^8.0.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deterministic-code/datasources-cli",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "datasources": "./bin/datasources.mjs"
@@ -13,7 +13,7 @@
13
13
  "access": "public"
14
14
  },
15
15
  "dependencies": {
16
- "@deterministic-code/datasources-code-generators": "0.1.11",
16
+ "@deterministic-code/datasources-code-generators": "0.1.2",
17
17
  "@deterministic-code/generators-common": "0.0.1",
18
18
  "@deterministic-code/deterministic-generators-common": "0.1.0",
19
19
  "@deterministic-code/typescript-generators": "0.0.1",
@@ -21,12 +21,12 @@
21
21
  "@deterministic-code/rust-generators": "0.0.1",
22
22
  "@deterministic-code/generators-openapi": "0.0.1",
23
23
  "@deterministic-code/patch-merger": "0.1.0",
24
- "@deterministic-code/datasources": "^0.1.11",
25
- "@deterministic-code/datasources-sqlite": "^0.1.11",
26
- "@deterministic-code/datasources-mysql": "^0.1.11",
27
- "@deterministic-code/datasources-postgres": "^0.1.11",
28
- "@deterministic-code/datasources-sqlserver": "^0.1.11",
29
- "@deterministic-code/datasources-oracle": "^0.1.11",
24
+ "@deterministic-code/datasources": "^0.1.12",
25
+ "@deterministic-code/datasources-sqlite": "^0.1.12",
26
+ "@deterministic-code/datasources-mysql": "^0.1.12",
27
+ "@deterministic-code/datasources-postgres": "^0.1.12",
28
+ "@deterministic-code/datasources-sqlserver": "^0.1.12",
29
+ "@deterministic-code/datasources-oracle": "^0.1.12",
30
30
  "@inquirer/prompts": "^7.8.0",
31
31
  "yaml": "^2.6.0",
32
32
  "mustache": "^4.2.0",