@deterministic-code/datasources-cli 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generate/CSharpRepositories.js +9 -4
- package/dist/generate/DeterministicProvider.js +33 -1
- package/dist/generate/RepositoryPlan.js +7 -0
- package/dist/generate/RustRepositories.js +4 -1
- package/dist/generate/TypeScriptRepositories.js +3 -1
- package/dist/generate/applyMigrations.js +7 -5
- package/dist/generate/datasourceYaml.js +22 -0
- package/dist/generate/loadSpec.js +6 -1
- package/dist/generate/migrateUp.js +13 -6
- package/dist/generate/writeLists.js +25 -0
- package/dist/migrateInit.js +7 -3
- package/dist/parse.js +2 -2
- package/dist/run.js +11 -16
- package/dist/setup.js +4 -2
- package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/generate-procedures.js +22 -10
- package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/generate-sql.d.ts +0 -1
- package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/generate-sql.js +18 -4
- package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/procedures/helpers.d.ts +5 -8
- package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/procedures/helpers.js +2 -29
- package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/seed-generator.js +6 -3
- package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/sql-dialect.d.ts +1 -0
- package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/sql-dialect.js +22 -10
- package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/sql-schema.js +11 -9
- package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/write-inclusion.d.ts +9 -0
- package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/write-inclusion.js +9 -0
- package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/statements.js +2 -7
- package/node_modules/@deterministic-code/datasources-code-generators/package.json +10 -8
- package/node_modules/@deterministic-code/deterministic-generators-common/typescript/parser/dist/specification-parser.js +67 -2
- package/node_modules/@deterministic-code/deterministic-generators-common/typescript/parser/parser/specification-parser.ts +82 -2
- package/node_modules/@deterministic-code/deterministic-generators-common/typescript/parser/specification.ts +2 -0
- package/node_modules/@deterministic-code/deterministic-specifications-typescript/typescript/parser/parser/specification-parser.ts +82 -2
- package/node_modules/@deterministic-code/deterministic-specifications-typescript/typescript/parser/specification.ts +2 -0
- package/package.json +8 -8
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
pluralPascal,
|
|
8
8
|
repositoryFile
|
|
9
9
|
} from "./RepositoryModel.js";
|
|
10
|
+
import { writeList } from "./writeLists.js";
|
|
10
11
|
const quote = (value) => value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
|
|
11
12
|
const enrichment = (item, host = "ctor") => {
|
|
12
13
|
const via = childField("csharp", item.via, item.viaType);
|
|
@@ -49,12 +50,12 @@ const createReadonly = (model, type) => {
|
|
|
49
50
|
const children = childModels(model);
|
|
50
51
|
if (children.length === 0 && model.enrichments.length === 0)
|
|
51
52
|
return ` public static IReadonlyRepository<${type}> CreateReadonly(IConnection connection) =>
|
|
52
|
-
new ReadonlyRepository<${type}>(connection, Options());`;
|
|
53
|
+
new ReadonlyRepository<${type}>(connection, Options(connection));`;
|
|
53
54
|
const locals = children.map((child) => ` var ${childField("csharp", child.specName, child.typeName)} = new ${childClass(child.typeName)}(connection);`);
|
|
54
55
|
const enrich = model.enrichments.map((item) => ` ${enrichment(item, "readonly")}`);
|
|
55
56
|
return ` public static IReadonlyRepository<${type}> CreateReadonly(IConnection connection)
|
|
56
57
|
{
|
|
57
|
-
var repo = new ReadonlyRepository<${type}>(connection, Options());
|
|
58
|
+
var repo = new ReadonlyRepository<${type}>(connection, Options(connection));
|
|
58
59
|
${locals.join("\n")}
|
|
59
60
|
${enrich.join("\n")}
|
|
60
61
|
return repo;
|
|
@@ -74,12 +75,14 @@ const csharpRepositories = (models) => {
|
|
|
74
75
|
${model.propertyMappings.map((pair) => ` ["${quote(pair.from)}"] = "${quote(pair.to)}",`).join("\n")}
|
|
75
76
|
}),
|
|
76
77
|
`;
|
|
77
|
-
const constructor = lines.length === 0 ? ` public ${model.typeName}Repository(IConnection connection) : base(connection, Options()) { }` : ` public ${model.typeName}Repository(IConnection connection) : base(connection, Options())
|
|
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))
|
|
78
79
|
{
|
|
79
80
|
${lines.join("\n")}
|
|
80
81
|
}`;
|
|
81
82
|
const ids = model.ids.map((name) => `"${quote(name)}"`).join(", ");
|
|
82
83
|
const read = model.readProperties.map((name) => `"${quote(name)}"`).join(", ");
|
|
84
|
+
const create = writeList("csharp", model, "create");
|
|
85
|
+
const update = writeList("csharp", model, "update");
|
|
83
86
|
const implementsOcc = model.optimisticConcurrency === void 0 ? "" : `, ICRUDRepository<${type}>`;
|
|
84
87
|
const usings = model.optimisticConcurrency === void 0 ? "using Datasources;\n" : "using Datasources;\nusing static Datasources.Cmp;\n";
|
|
85
88
|
return content(
|
|
@@ -93,12 +96,14 @@ ${fields.length === 0 ? "" : `${fields.join("\n")}
|
|
|
93
96
|
|
|
94
97
|
${createReadonly(model, type)}
|
|
95
98
|
${occMethods(model, type)}
|
|
96
|
-
static CRUDRepositoryOptions<${type}> Options() => new()
|
|
99
|
+
static CRUDRepositoryOptions<${type}> Options(IConnection connection) => new()
|
|
97
100
|
{
|
|
98
101
|
Entity = "${quote(model.entity)}",
|
|
99
102
|
EntityMapper = new DictionaryMapper<string>(new Dictionary<string, string> { ["${quote(model.entity)}"] = "${quote(model.table)}" }),
|
|
100
103
|
${mapping} Ids = [${ids}],
|
|
101
104
|
ReadProperties = [${read}],
|
|
105
|
+
CreateProperties = ${create},
|
|
106
|
+
UpdateProperties = ${update},
|
|
102
107
|
};
|
|
103
108
|
}
|
|
104
109
|
`
|
|
@@ -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,33 @@ const repositoryPacks = {
|
|
|
22
23
|
rust: rustRepositories
|
|
23
24
|
};
|
|
24
25
|
const context = (request) => ({ reader: request.reader, settings: request.settings });
|
|
26
|
+
const withProviderWrites = async (request, models) => {
|
|
27
|
+
const byProvider = {};
|
|
28
|
+
for (const engine of await datasourceEnginesOf(request.reader, request.settings)) {
|
|
29
|
+
const yaml = await engineDatasourceOf(request.reader, engine);
|
|
30
|
+
if (yaml === void 0) continue;
|
|
31
|
+
byProvider[engine] = planRepositories(
|
|
32
|
+
await DeterministicParser(overlayDatasource(request.reader, yaml)).parse(request.settings),
|
|
33
|
+
request.dialect,
|
|
34
|
+
request.settings
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return Object.keys(byProvider).length === 0 ? models : models.map((model) => ({
|
|
38
|
+
...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
|
+
})
|
|
50
|
+
)
|
|
51
|
+
}));
|
|
52
|
+
};
|
|
25
53
|
const dialectsOf = (settings) => {
|
|
26
54
|
const out = [];
|
|
27
55
|
for (const name of (settings["backend.datasources"] ?? "").split(",")) {
|
|
@@ -44,7 +72,11 @@ const deterministic = {
|
|
|
44
72
|
},
|
|
45
73
|
generateRepositories: async (request) => {
|
|
46
74
|
const spec = await DeterministicParser(request.reader).parse(request.settings);
|
|
47
|
-
|
|
75
|
+
const models = planRepositories(spec, request.dialect, request.settings);
|
|
76
|
+
return place(
|
|
77
|
+
`${request.dialect}/repositories`,
|
|
78
|
+
repositoryPacks[request.dialect](await withProviderWrites(request, models), request.settings)
|
|
79
|
+
);
|
|
48
80
|
},
|
|
49
81
|
generateDatasource: async (request) => place("sql", await generateSql(context(request), dialectsOf(request.settings)))
|
|
50
82
|
};
|
|
@@ -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,5 +1,6 @@
|
|
|
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";
|
|
3
4
|
const rustString = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
4
5
|
const uses = `use datasources::{Association, Connection, CRUDRepository, CRUDRepositoryOptions, DictionaryMapper, Enrichment, EnrichmentPropertyType, ICRUDRepository, ReadonlyRepository, ReadonlyRepositoryMappingOptions, Result};
|
|
5
6
|
use std::sync::Arc;`;
|
|
@@ -14,7 +15,9 @@ ${model.propertyMappings.map((pair) => ` (${rustString(pair.from)}.into()
|
|
|
14
15
|
(${rustString(model.entity)}.into(), ${rustString(model.table)}.into()),
|
|
15
16
|
])));
|
|
16
17
|
${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(", ")}]
|
|
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")});`;
|
|
18
21
|
};
|
|
19
22
|
const readonlyOptions = (model) => {
|
|
20
23
|
const mapping = model.propertyMappings.length === 0 ? "" : ` options.property_mappings = Some(Arc::new(DictionaryMapper::new([
|
|
@@ -1,6 +1,7 @@
|
|
|
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";
|
|
4
5
|
const quote = (value) => value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
|
|
5
6
|
const enrichment = (item, host) => {
|
|
6
7
|
const via = host === "this" ? `this.${childField("typescript", item.via, item.viaType)}` : childField("typescript", item.via, item.viaType);
|
|
@@ -58,8 +59,9 @@ const typescriptRepositories = (models, settings) => {
|
|
|
58
59
|
const children = childModels(model);
|
|
59
60
|
const mapping = model.propertyMappings.length === 0 ? "" : `propertyMappings: new DictionaryMapper([${model.propertyMappings.map((pair) => `["${quote(pair.from)}", "${quote(pair.to)}"]`).join(", ")}]), `;
|
|
60
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")}`;
|
|
61
63
|
const read = `readProperties: [${model.readProperties.map((name) => `"${quote(name)}"`).join(", ")}] }`;
|
|
62
|
-
const crudOptions = `${head}ids: [${model.ids.map((id) => `"${quote(id)}"`).join(", ")}], ${read}`;
|
|
64
|
+
const crudOptions = `${head}ids: [${model.ids.map((id) => `"${quote(id)}"`).join(", ")}], ${writes}, ${read}`;
|
|
63
65
|
const readonlyOptions = `${head}${read}`;
|
|
64
66
|
const fields = children.map((child) => ` readonly ${childField("typescript", child.specName, child.typeName)}: ${childClass(child.typeName)};`);
|
|
65
67
|
const wrap = occWrap(model);
|
|
@@ -6,15 +6,17 @@ import { providerByName } from "./providers.js";
|
|
|
6
6
|
import { writeEntries } from "./writeEntries.js";
|
|
7
7
|
const at = (root, path) => isAbsolute(path) ? path : join(root, path);
|
|
8
8
|
const afterMigrations = (filename) => filename.replace(/^[^/]+\/migrations\//, "");
|
|
9
|
-
const dialectsOf = (entries) => [
|
|
10
|
-
...new Set(entries.map((entry) => entry.filename.split("/")[0]))
|
|
11
|
-
];
|
|
12
9
|
const applyMigrations = async (command, flags, deps) => {
|
|
13
10
|
providerByName(command.provider);
|
|
14
11
|
const loaded = await loadSpec(deps.fs, at(flags.cwd, command.input));
|
|
15
|
-
const
|
|
16
|
-
|
|
12
|
+
const listed = [
|
|
13
|
+
...new Set(
|
|
14
|
+
loaded.settings["backend.datasources"].split(",").map((name) => name.trim()).filter((name) => name !== "")
|
|
15
|
+
)
|
|
16
|
+
];
|
|
17
|
+
if (command.migrationsPath !== void 0 && listed.length > 1)
|
|
17
18
|
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 });
|
|
18
20
|
const remapped = command.migrationsPath === void 0 ? entries : entries.map((entry) => ({ ...entry, filename: afterMigrations(entry.filename) }));
|
|
19
21
|
const root = at(flags.cwd, command.migrationsPath ?? command.migrationsRoot);
|
|
20
22
|
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) {
|
|
@@ -60,19 +60,26 @@ const applyMigrateUp = async (command, flags, deps, env = process.env) => {
|
|
|
60
60
|
throw new Error(
|
|
61
61
|
`migrate-up: provider ${provider} is not in backend.datasources (${dialects.join(", ") || "none"})`
|
|
62
62
|
);
|
|
63
|
-
const
|
|
63
|
+
const npx = (verb, extra = []) => [
|
|
64
64
|
"--yes",
|
|
65
65
|
"--package",
|
|
66
|
-
"
|
|
67
|
-
|
|
66
|
+
"@deterministic-code/migraters",
|
|
67
|
+
verb,
|
|
68
68
|
"--provider",
|
|
69
69
|
provider,
|
|
70
70
|
"--connection",
|
|
71
71
|
connection,
|
|
72
|
-
...
|
|
72
|
+
...extra
|
|
73
73
|
];
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
const setup = npx("migrate-setup");
|
|
75
|
+
const up = npx(
|
|
76
|
+
"migrate-up",
|
|
77
|
+
command.migrationsPath ? ["--migrations-path", command.migrationsPath] : ["--migrations-root", command.migrationsRoot]
|
|
78
|
+
);
|
|
79
|
+
deps.log(`$ npx ${setup.join(" ")}`);
|
|
80
|
+
if (!flags.dryRun) await deps.run("npx", setup, flags.cwd);
|
|
81
|
+
deps.log(`$ npx ${up.join(" ")}`);
|
|
82
|
+
if (!flags.dryRun) await deps.run("npx", up, flags.cwd);
|
|
76
83
|
};
|
|
77
84
|
export {
|
|
78
85
|
applyMigrateUp
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const quote = (value) => value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
|
|
2
|
+
const namesOf = (model, kind) => kind === "create" ? model.createProperties : model.updateProperties;
|
|
3
|
+
const same = (left, right) => left.length === right.length && left.every((name, index) => name === right[index]);
|
|
4
|
+
const csharpList = (names) => `[${names.map((name) => `"${quote(name)}"`).join(", ")}]`;
|
|
5
|
+
const tsList = (names) => `[${names.map((name) => `"${quote(name)}"`).join(", ")}]`;
|
|
6
|
+
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
|
+
};
|
|
23
|
+
export {
|
|
24
|
+
writeList
|
|
25
|
+
};
|
package/dist/migrateInit.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
const migrateProviders = ["sqlite", "postgres", "mysql", "sqlserver", "oracle"];
|
|
2
|
+
const migrateLanguage = (ecosystem) => ecosystem === "ts" ? "typescript" : ecosystem;
|
|
2
3
|
const sqlMigrateProviders = (names) => migrateProviders.filter((name) => names.includes(name));
|
|
3
|
-
const migrateInitCommands = (dialects) => dialects.length === 0 ? [] : [
|
|
4
|
-
{ command: "npm", args: ["install", "
|
|
4
|
+
const migrateInitCommands = (dialects, language) => dialects.length === 0 ? [] : [
|
|
5
|
+
{ command: "npm", args: ["install", "@deterministic-code/migraters"] },
|
|
5
6
|
{
|
|
6
7
|
command: "npx",
|
|
7
8
|
args: [
|
|
8
9
|
"--yes",
|
|
9
10
|
"--package",
|
|
10
|
-
"
|
|
11
|
+
"@deterministic-code/migraters",
|
|
11
12
|
"migrate-init",
|
|
13
|
+
"--language",
|
|
14
|
+
language,
|
|
12
15
|
...dialects.flatMap((name) => ["--dialect", name])
|
|
13
16
|
]
|
|
14
17
|
}
|
|
15
18
|
];
|
|
16
19
|
export {
|
|
17
20
|
migrateInitCommands,
|
|
21
|
+
migrateLanguage,
|
|
18
22
|
migrateProviders,
|
|
19
23
|
sqlMigrateProviders
|
|
20
24
|
};
|
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
|
|
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
|
|
@@ -88,7 +88,7 @@ const parseArgs = (argv, cwd = ".") => {
|
|
|
88
88
|
};
|
|
89
89
|
const commandOf = (verb, names, generate) => {
|
|
90
90
|
if (verb === void 0 || verb === "help") return { kind: "help" };
|
|
91
|
-
if (verb === "setup") return { kind: "setup" };
|
|
91
|
+
if (verb === "setup") return { kind: "setup", providers: names };
|
|
92
92
|
if (verb === "add") return { kind: "add", providers: names };
|
|
93
93
|
if (verb === "remove") return { kind: "remove", providers: names };
|
|
94
94
|
if (verb === "generate") return generateCommand(names, generate);
|
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,
|
|
@@ -18,10 +13,10 @@ import {
|
|
|
18
13
|
import { applyGenerate } from "./generate/applyGenerate.js";
|
|
19
14
|
import { applyMigrations } from "./generate/applyMigrations.js";
|
|
20
15
|
import { applyMigrateUp } from "./generate/migrateUp.js";
|
|
21
|
-
import { migrateInitCommands, sqlMigrateProviders } from "./migrateInit.js";
|
|
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);
|
|
@@ -59,7 +54,7 @@ const dispatch = async (command, flags, deps) => {
|
|
|
59
54
|
const needNames = (names, verb) => names.length ? names : (() => {
|
|
60
55
|
throw new Error(`Usage: datasources ${verb} <provider...>`);
|
|
61
56
|
})();
|
|
62
|
-
const applySetup = async (flags, deps) => {
|
|
57
|
+
const applySetup = async (flags, deps, requested) => {
|
|
63
58
|
const entries = await deps.fs.readdir(flags.cwd);
|
|
64
59
|
const found = detect(entries);
|
|
65
60
|
const existing = await readEnv(deps.fs, flags.cwd);
|
|
@@ -67,7 +62,7 @@ const applySetup = async (flags, deps) => {
|
|
|
67
62
|
const typesPath = join(specDir, "types.yaml");
|
|
68
63
|
const datasourcePath = join(specDir, "datasource.yaml");
|
|
69
64
|
const missingSpec = await optional(deps.fs, typesPath) === void 0;
|
|
70
|
-
const answers = flags.yes ? { ...
|
|
65
|
+
const answers = flags.yes ? { ...answersForSetup(existing, requested), ecosystem: flags.ecosystem, scaffold: false } : await promptSetup(deps.prompt, existing, found, flags.ecosystem, missingSpec, requested);
|
|
71
66
|
const ecosystem = resolveEcosystem(found, answers.ecosystem ?? flags.ecosystem);
|
|
72
67
|
const selected = requireProviders(answers.providers);
|
|
73
68
|
const installed = await readInstalled(deps.fs, flags.cwd, ecosystem, entries);
|
|
@@ -82,7 +77,7 @@ const applySetup = async (flags, deps) => {
|
|
|
82
77
|
selected
|
|
83
78
|
);
|
|
84
79
|
await applyMigrateInit(flags, deps, ecosystem, selected);
|
|
85
|
-
const dialect = ecosystem
|
|
80
|
+
const dialect = migrateLanguage(ecosystem);
|
|
86
81
|
await wireRegister(flags.cwd, "generated", dialect, flags.dryRun, deps);
|
|
87
82
|
if (!answers.scaffold) return;
|
|
88
83
|
if (flags.dryRun) {
|
|
@@ -130,8 +125,7 @@ const applyPackages = async (flags, deps, ecosystem, entries, add, remove, remai
|
|
|
130
125
|
}
|
|
131
126
|
};
|
|
132
127
|
const applyMigrateInit = async (flags, deps, ecosystem, remaining) => {
|
|
133
|
-
|
|
134
|
-
for (const proc of migrateInitCommands(sqlMigrateProviders(remaining))) {
|
|
128
|
+
for (const proc of migrateInitCommands(sqlMigrateProviders(remaining), migrateLanguage(ecosystem))) {
|
|
135
129
|
deps.log(`$ ${proc.command} ${proc.args.join(" ")}`);
|
|
136
130
|
if (!flags.dryRun) await deps.run(proc.command, proc.args, flags.cwd);
|
|
137
131
|
}
|
|
@@ -166,9 +160,9 @@ const optional = async (fs, path) => {
|
|
|
166
160
|
return void 0;
|
|
167
161
|
}
|
|
168
162
|
};
|
|
169
|
-
const pluginFileName = (cargoName) => {
|
|
163
|
+
const pluginFileName = (cargoName, platform = process.platform) => {
|
|
170
164
|
const stem = cargoName.replaceAll("-", "_");
|
|
171
|
-
return
|
|
165
|
+
return platform === "win32" ? `${stem}.dll` : platform === "darwin" ? `lib${stem}.dylib` : `lib${stem}.so`;
|
|
172
166
|
};
|
|
173
167
|
const copyPlugin = async (cwd, cargoName) => {
|
|
174
168
|
const file = pluginFileName(cargoName);
|
|
@@ -193,5 +187,6 @@ const firstFile = async (paths) => {
|
|
|
193
187
|
return void 0;
|
|
194
188
|
};
|
|
195
189
|
export {
|
|
190
|
+
pluginFileName,
|
|
196
191
|
run
|
|
197
192
|
};
|
package/dist/setup.js
CHANGED
|
@@ -14,8 +14,9 @@ types:
|
|
|
14
14
|
type: string
|
|
15
15
|
size: 128
|
|
16
16
|
`;
|
|
17
|
-
const
|
|
18
|
-
|
|
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
|
|
@@ -5,9 +5,10 @@ import { createCasing } from "./default-casing.js";
|
|
|
5
5
|
import { byGroupsFromDatasource } from "./datasource-by-fields.js";
|
|
6
6
|
import { SqlMapping } from "./sql-mapping.js";
|
|
7
7
|
import { mapColumnType, q, renderSql } from "./sql-dialect.js";
|
|
8
|
-
import { buildLiveTables, datasourceSettings, hasAuditColumns, overlayOf, sqlTablesFrom, } from "./sql-schema.js";
|
|
8
|
+
import { buildLiveTables, datasourceSettings, hasAuditColumns, overlayOf, sqlTablesFrom, tableIdentity, } from "./sql-schema.js";
|
|
9
9
|
import { procedureSpecs } from "./procedures/driver.js";
|
|
10
|
-
import { createParamFields, pkFieldOf,
|
|
10
|
+
import { createParamFields, pkFieldOf, updateParamFields } from "./procedures/helpers.js";
|
|
11
|
+
import { writeInclusionOf } from "./write-inclusion.js";
|
|
11
12
|
import { createBulkCreateStoredProcedureOf, createBulkDeleteStoredProcedureOf, createBulkUpdateStoredProcedureOf, createCreateStoredProcedureOf, createDeleteStoredProcedureOf, createReadStoredProcedureOf, createUpdateStoredProcedureOf, dropRoutineStatementOf, } from "../statements.js";
|
|
12
13
|
const withNl = (text, casing) => {
|
|
13
14
|
const out = casing.applyKeywords(text);
|
|
@@ -22,26 +23,38 @@ const payloadType = (dialect) => ({
|
|
|
22
23
|
}[dialect] ?? "JSON");
|
|
23
24
|
/** Create / read / update / delete / bulk procedure statements for one live table. */
|
|
24
25
|
export const procedureStatementsForTable = (dialect, t, mapping, casing, groups) => {
|
|
26
|
+
const overlay = overlayOf(t);
|
|
27
|
+
const ids = tableIdentity(t);
|
|
25
28
|
const table = {
|
|
26
29
|
name: t.tableName,
|
|
27
30
|
entityName: t.name,
|
|
28
|
-
fields: t.fields.map((f) =>
|
|
29
|
-
|
|
31
|
+
fields: t.fields.map((f) => {
|
|
32
|
+
const flags = writeInclusionOf(f, ids, overlay.fields.find((item) => item.name === f.name));
|
|
33
|
+
return { name: f.name, type: f.type, ...flags };
|
|
34
|
+
}),
|
|
35
|
+
pkName: pkName(t, overlay),
|
|
30
36
|
};
|
|
31
37
|
const pk = pkFieldOf(table);
|
|
32
38
|
const columnName = (logical) => mapping.columnName(t, logical);
|
|
33
39
|
const columns = table.fields.map((f) => columnName(f.name));
|
|
34
|
-
const writable =
|
|
40
|
+
const writable = updateParamFields(table).map((f) => columnName(f.name));
|
|
35
41
|
const paramType = (field) => mapColumnType(dialect, field);
|
|
36
42
|
const createdParams = createParamFields(table).map((f) => ({
|
|
37
43
|
name: columnName(f.name),
|
|
38
44
|
type: paramType(f),
|
|
39
45
|
}));
|
|
40
46
|
const typedColumns = table.fields.map((f) => ({ name: columnName(f.name), type: paramType(f) }));
|
|
47
|
+
const createColumns = createParamFields(table).map((f) => ({ name: columnName(f.name), type: paramType(f) }));
|
|
48
|
+
const updateColumns = [
|
|
49
|
+
{ name: columnName(pk.name), type: paramType(pk) },
|
|
50
|
+
...updateParamFields(table)
|
|
51
|
+
.filter((f) => f.name !== pk.name)
|
|
52
|
+
.map((f) => ({ name: columnName(f.name), type: paramType(f) })),
|
|
53
|
+
];
|
|
41
54
|
const payload = { name: "payload", type: payloadType(dialect) };
|
|
42
55
|
const key = columnName(pk.name);
|
|
43
56
|
const keyType = paramType(pk);
|
|
44
|
-
const audit = table.fields.some((f) => f.name === "updated") ? columnName("updated") : undefined;
|
|
57
|
+
const audit = table.fields.some((f) => f.name === "updated" && f.includeInUpdate) ? columnName("updated") : undefined;
|
|
45
58
|
const generatedKey = pk.type === "uuid";
|
|
46
59
|
const quoted = q(dialect, t.tableName);
|
|
47
60
|
const statements = [];
|
|
@@ -55,18 +68,17 @@ export const procedureStatementsForTable = (dialect, t, mapping, casing, groups)
|
|
|
55
68
|
});
|
|
56
69
|
const updateParams = [
|
|
57
70
|
...byParams,
|
|
58
|
-
...
|
|
71
|
+
...updateParamFields(table)
|
|
59
72
|
.filter((f) => !proc.byProperties.includes(f.name))
|
|
60
73
|
.map((f) => ({ name: columnName(f.name), type: paramType(f) })),
|
|
61
|
-
...(audit === undefined ? [] : [{ name: columnName("new_updated"), type: paramType(table.fields.find((f) => f.name === "updated")) }]),
|
|
62
74
|
];
|
|
63
75
|
statements.push(({
|
|
64
76
|
create: () => createCreateStoredProcedureOf(proc.name, quoted, columns, createdParams, key, keyType, writable, generatedKey, audit),
|
|
65
77
|
read: () => createReadStoredProcedureOf(proc.name, quoted, columns, byParams, key, keyType, writable, by, generatedKey, audit),
|
|
66
78
|
update: () => createUpdateStoredProcedureOf(proc.name, quoted, columns, updateParams, key, keyType, writable, by, generatedKey, audit),
|
|
67
79
|
delete: () => createDeleteStoredProcedureOf(proc.name, quoted, columns, byParams, key, keyType, writable, by, generatedKey, audit),
|
|
68
|
-
bulkCreate: () => createBulkCreateStoredProcedureOf(proc.name, quoted,
|
|
69
|
-
bulkUpdate: () => createBulkUpdateStoredProcedureOf(proc.name, quoted,
|
|
80
|
+
bulkCreate: () => createBulkCreateStoredProcedureOf(proc.name, quoted, createColumns, payload, key, keyType),
|
|
81
|
+
bulkUpdate: () => createBulkUpdateStoredProcedureOf(proc.name, quoted, updateColumns, payload, key, keyType),
|
|
70
82
|
bulkDelete: () => createBulkDeleteStoredProcedureOf(proc.name, quoted, typedColumns, payload, key, keyType),
|
|
71
83
|
}[proc.op])());
|
|
72
84
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { GenerateContext } from "@deterministic-code/generators-common/generate-context";
|
|
2
2
|
import { type GenerateEntry } from "@deterministic-code/generators-common/generate-entry";
|
|
3
3
|
import { type SqlDialect } from "./sql-dialect.js";
|
|
4
|
-
/** DDL initial migration (+ optional custom migrations and stored procedures) for one SQL dialect. */
|
|
5
4
|
export declare const generateSqlFor: (dialect: SqlDialect, ctx: GenerateContext) => Promise<GenerateEntry[]>;
|
package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/generate-sql.js
CHANGED
|
@@ -76,15 +76,29 @@ const loadSchema = async (ctx) => {
|
|
|
76
76
|
};
|
|
77
77
|
};
|
|
78
78
|
/** DDL initial migration (+ optional custom migrations and stored procedures) for one SQL dialect. */
|
|
79
|
+
const readerFor = async (dialect, ctx) => {
|
|
80
|
+
const named = `datasource.${dialect}.yaml`;
|
|
81
|
+
if (!(await ctx.reader.exists(named)))
|
|
82
|
+
return ctx;
|
|
83
|
+
const yaml = await ctx.reader.read(named);
|
|
84
|
+
return {
|
|
85
|
+
...ctx,
|
|
86
|
+
reader: {
|
|
87
|
+
read: (name) => (name === "datasource.yaml" ? Promise.resolve(yaml) : ctx.reader.read(name)),
|
|
88
|
+
exists: (name) => (name === "datasource.yaml" ? Promise.resolve(true) : ctx.reader.exists(name)),
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
};
|
|
79
92
|
export const generateSqlFor = async (dialect, ctx) => {
|
|
80
93
|
const key = requireDialect(dialect);
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const
|
|
94
|
+
const scoped = await readerFor(key, ctx);
|
|
95
|
+
const casing = createCasing(scoped.settings);
|
|
96
|
+
const dir = scoped.settings["paths.deterministic"];
|
|
97
|
+
const schema = await schemaFiles(key, scoped, casing);
|
|
84
98
|
return [
|
|
85
99
|
...schema.entries,
|
|
86
100
|
...(await customEntries(key, dir)),
|
|
87
|
-
...(await generateProceduresForDialect(dialect,
|
|
101
|
+
...(await generateProceduresForDialect(dialect, scoped, schema.count + 1)),
|
|
88
102
|
];
|
|
89
103
|
};
|
|
90
104
|
const schemaFiles = async (key, ctx, casing) => {
|
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
/** Helpers shared by stored-procedure generation. */
|
|
2
|
-
interface ProcField {
|
|
2
|
+
export interface ProcField {
|
|
3
3
|
name: string;
|
|
4
4
|
type: string;
|
|
5
5
|
size?: number;
|
|
6
|
+
includeInCreate?: boolean;
|
|
7
|
+
includeInUpdate?: boolean;
|
|
6
8
|
}
|
|
7
|
-
interface ProcTable {
|
|
9
|
+
export interface ProcTable {
|
|
8
10
|
fields: ProcField[];
|
|
9
11
|
pkName?: string;
|
|
10
12
|
}
|
|
11
13
|
/** The entity's primary key: `pkName` when set, else a field named `id`. */
|
|
12
14
|
export declare const pkFieldOf: (table: ProcTable) => ProcField;
|
|
13
|
-
/** The writable, non-audit columns: everything but the pk, the system `uuid`, and `created`/`updated`. */
|
|
14
|
-
export declare const writableNonAuditFields: (table: ProcTable) => ProcField[];
|
|
15
|
-
/** CREATE IN-params from expanded columns, in the stored-procedure call order: `uuid` (if present), writable fields, `created`, `updated`. */
|
|
16
15
|
export declare const createParamFields: (table: ProcTable) => ProcField[];
|
|
17
|
-
|
|
18
|
-
export declare const updatedFieldOf: (table: ProcTable) => ProcField;
|
|
16
|
+
export declare const updateParamFields: (table: ProcTable) => ProcField[];
|
|
19
17
|
/** Expanded columns qualified with a table alias (default `t`). */
|
|
20
18
|
export declare const aliasedColumns: (table: ProcTable, alias?: string, columnName?: (field: string) => string) => string;
|
|
21
19
|
export declare const paramAlignWidth: (params: {
|
|
22
20
|
name: string;
|
|
23
21
|
}[]) => number;
|
|
24
22
|
export declare const pad: (s: string, w: number) => string;
|
|
25
|
-
export {};
|
|
@@ -4,35 +4,8 @@ export const pkFieldOf = (table) => (table.pkName
|
|
|
4
4
|
? table.fields.find((f) => f.name === table.pkName)
|
|
5
5
|
: undefined) ??
|
|
6
6
|
table.fields.find((f) => f.name === "id") ?? { name: "id", type: "integer" };
|
|
7
|
-
|
|
8
|
-
export const
|
|
9
|
-
const pk = pkFieldOf(table);
|
|
10
|
-
return table.fields.filter((f) => f.name !== pk.name &&
|
|
11
|
-
f.name !== "uuid" &&
|
|
12
|
-
f.name !== "created" &&
|
|
13
|
-
f.name !== "updated");
|
|
14
|
-
};
|
|
15
|
-
/** CREATE IN-params from expanded columns, in the stored-procedure call order: `uuid` (if present), writable fields, `created`, `updated`. */
|
|
16
|
-
export const createParamFields = (table) => {
|
|
17
|
-
const field = (name) => {
|
|
18
|
-
const found = table.fields.find((f) => f.name === name);
|
|
19
|
-
return found ? [found] : [];
|
|
20
|
-
};
|
|
21
|
-
return [
|
|
22
|
-
...field("uuid"),
|
|
23
|
-
...writableNonAuditFields(table),
|
|
24
|
-
...field("created"),
|
|
25
|
-
...field("updated"),
|
|
26
|
-
];
|
|
27
|
-
};
|
|
28
|
-
/** The expanded `updated` column — present on every table that gets procedures. */
|
|
29
|
-
export const updatedFieldOf = (table) => {
|
|
30
|
-
const field = table.fields.find((f) => f.name === "updated");
|
|
31
|
-
if (!field) {
|
|
32
|
-
throw new Error("procedure table is missing expanded `updated` column");
|
|
33
|
-
}
|
|
34
|
-
return field;
|
|
35
|
-
};
|
|
7
|
+
export const createParamFields = (table) => table.fields.filter((f) => f.includeInCreate !== false);
|
|
8
|
+
export const updateParamFields = (table) => table.fields.filter((f) => f.includeInUpdate === true);
|
|
36
9
|
/** Expanded columns qualified with a table alias (default `t`). */
|
|
37
10
|
export const aliasedColumns = (table, alias = "t", columnName = (field) => field) => table.fields.map((f) => `${alias}.${columnName(f.name)}`).join(", ");
|
|
38
11
|
export const paramAlignWidth = (params) => params.reduce((m, p) => Math.max(m, p.name.length), 0);
|
|
@@ -4,7 +4,8 @@ import { sqlStringLiteral } from "@deterministic-code/datasources";
|
|
|
4
4
|
import { identityInsertStatementOf, seedInsertStatementOf } from "../statements.js";
|
|
5
5
|
import { renderSql, sqlDefault } from "./sql-dialect.js";
|
|
6
6
|
import { dialectConverter } from "./sql-dialect.js";
|
|
7
|
-
import {
|
|
7
|
+
import { converterFieldOf } from "./converter-field.js";
|
|
8
|
+
import { isGeneratedIdentity, overlayOf, tableIdentity, usesGeneratedIdColumn, } from "./sql-schema.js";
|
|
8
9
|
const SEED_UUID_NAMESPACE = "9b3a8e6c-2f1d-4a5b-8c9d-1e2f3a4b5c6d";
|
|
9
10
|
const pkType = (table) => table.fields.find((f) => isPkField(f, table, overlayOf(table)))?.type ??
|
|
10
11
|
"integer";
|
|
@@ -63,8 +64,10 @@ export const seedSections = (dialect, tables, seeds, mapping) => {
|
|
|
63
64
|
if (rows.length === 0)
|
|
64
65
|
continue;
|
|
65
66
|
const generated = isGeneratedIdentity(table) ? tableIdentity(table) : [];
|
|
66
|
-
const
|
|
67
|
-
const identity =
|
|
67
|
+
const pk = table.fields.find((field) => field.name === tableIdentity(table)[0]);
|
|
68
|
+
const identity = pk != null &&
|
|
69
|
+
usesGeneratedIdColumn(table, pk) &&
|
|
70
|
+
sqlDefault(dialect, converterFieldOf(pk)) == null;
|
|
68
71
|
const inserts = rows.map((seed) => {
|
|
69
72
|
const cols = colsForRow(table, seed.row);
|
|
70
73
|
const values = cols.map((c) => {
|
|
@@ -7,6 +7,7 @@ export type SqlGenerateDriver = IDriver & {
|
|
|
7
7
|
defaultValueConverter: DefaultValueConverter;
|
|
8
8
|
statementRenderer: StatementTextRenderer;
|
|
9
9
|
};
|
|
10
|
+
export declare const register: (dialect: string, driver: IDriver) => void;
|
|
10
11
|
export declare const dialectDriver: (dialect: string) => SqlGenerateDriver;
|
|
11
12
|
export declare const renderSql: (dialect: string, statement: IStatement) => string;
|
|
12
13
|
export declare const dialectConverter: (dialect: string) => {
|
package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/sql-dialect.js
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
1
|
import { DriverLoader, ProviderCatalog, } from "@deterministic-code/datasources";
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
if (catalog === undefined)
|
|
2
|
+
const loaded = new Map();
|
|
3
|
+
const named = (dialect) => ProviderCatalog.default[dialect] === undefined
|
|
4
|
+
? (() => {
|
|
7
5
|
throw new Error(`Unknown SQL dialect "${dialect}".`);
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
})()
|
|
7
|
+
: dialect;
|
|
8
|
+
const asGenerate = (dialect, driver) => driver.typeConverter == null ||
|
|
9
|
+
driver.defaultValueConverter == null ||
|
|
10
|
+
driver.statementRenderer == null
|
|
11
|
+
? (() => {
|
|
12
12
|
throw new Error(`"${dialect}" is not a SQL dialect.`);
|
|
13
|
-
}
|
|
13
|
+
})()
|
|
14
|
+
: driver;
|
|
15
|
+
export const register = (dialect, driver) => {
|
|
16
|
+
loaded.set(named(dialect), asGenerate(dialect, driver));
|
|
17
|
+
};
|
|
18
|
+
export const dialectDriver = (dialect) => {
|
|
19
|
+
const key = named(dialect);
|
|
20
|
+
const hit = loaded.get(key);
|
|
21
|
+
if (hit !== undefined)
|
|
22
|
+
return hit;
|
|
23
|
+
const catalog = ProviderCatalog.default[key];
|
|
24
|
+
const driver = asGenerate(key, DriverLoader.load(catalog));
|
|
25
|
+
loaded.set(key, driver);
|
|
14
26
|
return driver;
|
|
15
27
|
};
|
|
16
28
|
export const renderSql = (dialect, statement) => {
|
package/node_modules/@deterministic-code/datasources-code-generators/dist/sql/common/sql-schema.js
CHANGED
|
@@ -11,12 +11,15 @@ export const overlayOf = (table) => ({
|
|
|
11
11
|
...(table.mapping !== undefined ? { mapping: table.mapping } : {}),
|
|
12
12
|
});
|
|
13
13
|
export const fieldOverlay = (table, fieldName) => table.overlays.find((overlay) => overlay.name === fieldName);
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
const requireIdIsId = (spec) => {
|
|
15
|
+
for (const type of spec.types) {
|
|
16
|
+
for (const field of type.fields) {
|
|
17
|
+
if (field.name === "id" && field.isId !== true) {
|
|
18
|
+
throw new Error(`${type.name}.id must have is_id: true`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
20
23
|
export const tableIdentity = (table) => {
|
|
21
24
|
const keys = identityColumns(table, overlayOf(table));
|
|
22
25
|
if (keys.length > 1) {
|
|
@@ -38,10 +41,9 @@ export const isGeneratedIdentity = (table) => {
|
|
|
38
41
|
return false;
|
|
39
42
|
return fieldOverlay(table, keys[0])?.isFixedId !== true;
|
|
40
43
|
};
|
|
41
|
-
export const usesGeneratedIdColumn = (table, field) => isGeneratedIdentity(table)
|
|
42
|
-
field.name === tableIdentity(table)[0] &&
|
|
43
|
-
GENERATED_ID_TYPES.has(field.type);
|
|
44
|
+
export const usesGeneratedIdColumn = (table, field) => field.isId === true && isGeneratedIdentity(table);
|
|
44
45
|
export const sqlTablesFrom = (spec) => {
|
|
46
|
+
requireIdIsId(spec);
|
|
45
47
|
const overlays = tableByName(spec);
|
|
46
48
|
const byName = new Map(spec.expandedTypes.map((t) => [t.name, t]));
|
|
47
49
|
return datasourceTypesOf(spec).map((type) => {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DatasourceFieldOverlay, TypeField } from "@deterministic-code/deterministic-generators-common/parser";
|
|
2
|
+
export declare const resolveWriteInclusion: (create: boolean | undefined, update: boolean | undefined, identity: boolean, fixedId: boolean, singleGeneratedId: boolean) => {
|
|
3
|
+
includeInCreate: boolean;
|
|
4
|
+
includeInUpdate: boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare const writeInclusionOf: (field: TypeField, ids: readonly string[], overlay?: DatasourceFieldOverlay) => {
|
|
7
|
+
includeInCreate: boolean;
|
|
8
|
+
includeInUpdate: boolean;
|
|
9
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const resolveWriteInclusion = (create, update, identity, fixedId, singleGeneratedId) => ({
|
|
2
|
+
includeInCreate: create ?? (!identity || fixedId || !singleGeneratedId),
|
|
3
|
+
includeInUpdate: update ?? (!identity || (!fixedId && !singleGeneratedId)),
|
|
4
|
+
});
|
|
5
|
+
export const writeInclusionOf = (field, ids, overlay) => {
|
|
6
|
+
const identity = ids.includes(field.name);
|
|
7
|
+
const fixedId = overlay?.isFixedId === true;
|
|
8
|
+
return resolveWriteInclusion(overlay?.includeInCreate, overlay?.includeInUpdate, identity, fixedId, identity && ids.length === 1 && !fixedId);
|
|
9
|
+
};
|
|
@@ -7,19 +7,14 @@ export const columnOf = (dialect, mapping, table, field) => {
|
|
|
7
7
|
const overlay = fieldOverlay(table, field.name);
|
|
8
8
|
const converterField = converterFieldOf(field);
|
|
9
9
|
const generated = usesGeneratedIdColumn(table, field);
|
|
10
|
-
const identity = generated && field.type !== "uuid" && field.type !== "string";
|
|
11
10
|
const named = mapping.constraintName(table, mapping.fieldStem(table, field.name), "default_constraint");
|
|
12
|
-
const utcNow = dialectDriver(dialect).defaultValueConverter.convert("datetime", "UtcNow");
|
|
13
|
-
const newId = dialectDriver(dialect).defaultValueConverter.convert("uuid", "NewId");
|
|
14
11
|
const fromSpec = sqlDefault(dialect, converterField);
|
|
15
|
-
const
|
|
16
|
-
(field.name === "created" || field.name === "updated" ? utcNow : null) ??
|
|
17
|
-
(field.name === "uuid" || (generated && field.type === "uuid") ? newId : null);
|
|
12
|
+
const identity = generated && fromSpec == null;
|
|
18
13
|
const keys = tableIdentity(table);
|
|
19
14
|
return {
|
|
20
15
|
name: q(dialect, mapping.columnName(table, field.name)),
|
|
21
16
|
type: mapColumnType(dialect, converterField),
|
|
22
|
-
...(
|
|
17
|
+
...(fromSpec ? { default: new ColumnDefault(fromSpec, q(dialect, named)) } : {}),
|
|
23
18
|
...(!field.isNullable || keys.includes(field.name) ? { notNull: new NotNull() } : {}),
|
|
24
19
|
...(identity ? { identity: true } : {}),
|
|
25
20
|
};
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deterministic-code/datasources-code-generators",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/generateSql.js",
|
|
7
7
|
"./generate-sql": "./dist/generate-sql.js",
|
|
8
|
+
"./sql/generate-sql": "./dist/sql/generate-sql.js",
|
|
8
9
|
"./generate-stored-procedures": "./dist/generate-stored-procedures.js",
|
|
10
|
+
"./sql/generate-stored-procedures": "./dist/sql/generate-stored-procedures.js",
|
|
9
11
|
"./common/sql-dialect": "./dist/sql/common/sql-dialect.js",
|
|
10
|
-
"./sql/
|
|
12
|
+
"./sql/common/write-inclusion": "./dist/sql/common/write-inclusion.js"
|
|
11
13
|
},
|
|
12
14
|
"dependencies": {
|
|
13
|
-
"@deterministic-code/datasources": "^0.1.
|
|
14
|
-
"@deterministic-code/datasources-mysql": "^0.1.
|
|
15
|
-
"@deterministic-code/datasources-oracle": "^0.1.
|
|
16
|
-
"@deterministic-code/datasources-postgres": "^0.1.
|
|
17
|
-
"@deterministic-code/datasources-sqlite": "^0.1.
|
|
18
|
-
"@deterministic-code/datasources-sqlserver": "^0.1.
|
|
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",
|
|
19
21
|
"@deterministic-code/deterministic-generators-common": "0.1.0",
|
|
20
22
|
"@deterministic-code/generators-common": "0.0.1",
|
|
21
23
|
"pluralize": "^8.0.0",
|
|
@@ -557,6 +557,65 @@ var inheritsChain = (name, typeByName) => {
|
|
|
557
557
|
}
|
|
558
558
|
return chain;
|
|
559
559
|
};
|
|
560
|
+
var firstAuthoredInclusion = (walk, overlays, field, key) => {
|
|
561
|
+
for (const typeName of walk) {
|
|
562
|
+
const value = overlays.get(typeName)?.get(field)?.[key];
|
|
563
|
+
if (value !== void 0) return value;
|
|
564
|
+
}
|
|
565
|
+
return void 0;
|
|
566
|
+
};
|
|
567
|
+
var mergeWriteInclusion = (tables, types, authored) => {
|
|
568
|
+
const typeByName = new Map(types.map((type) => [type.name, type]));
|
|
569
|
+
const overlays = new Map(
|
|
570
|
+
authored.map((table) => [
|
|
571
|
+
table.name,
|
|
572
|
+
new Map(table.fields.map((field) => [field.name, field]))
|
|
573
|
+
])
|
|
574
|
+
);
|
|
575
|
+
return tables.map((table) => {
|
|
576
|
+
const walk = [table.name, ...inheritsChain(table.name, typeByName)];
|
|
577
|
+
const names = [];
|
|
578
|
+
const seen = /* @__PURE__ */ new Set();
|
|
579
|
+
const add = (name) => {
|
|
580
|
+
if (seen.has(name)) return;
|
|
581
|
+
seen.add(name);
|
|
582
|
+
names.push(name);
|
|
583
|
+
};
|
|
584
|
+
for (const field of typeByName.get(table.name)?.fields ?? []) add(field.name);
|
|
585
|
+
for (const field of table.fields) add(field.name);
|
|
586
|
+
const fields = table.fields.map((field) => ({ ...field }));
|
|
587
|
+
const byName = new Map(fields.map((field) => [field.name, field]));
|
|
588
|
+
for (const name of names) {
|
|
589
|
+
const includeInCreate = firstAuthoredInclusion(
|
|
590
|
+
walk,
|
|
591
|
+
overlays,
|
|
592
|
+
name,
|
|
593
|
+
"includeInCreate"
|
|
594
|
+
);
|
|
595
|
+
const includeInUpdate = firstAuthoredInclusion(
|
|
596
|
+
walk,
|
|
597
|
+
overlays,
|
|
598
|
+
name,
|
|
599
|
+
"includeInUpdate"
|
|
600
|
+
);
|
|
601
|
+
if (includeInCreate === void 0 && includeInUpdate === void 0) continue;
|
|
602
|
+
const existing = byName.get(name);
|
|
603
|
+
if (existing) {
|
|
604
|
+
if (includeInCreate !== void 0) existing.includeInCreate = includeInCreate;
|
|
605
|
+
if (includeInUpdate !== void 0) existing.includeInUpdate = includeInUpdate;
|
|
606
|
+
} else {
|
|
607
|
+
const created = {
|
|
608
|
+
name,
|
|
609
|
+
...includeInCreate !== void 0 ? { includeInCreate } : {},
|
|
610
|
+
...includeInUpdate !== void 0 ? { includeInUpdate } : {}
|
|
611
|
+
};
|
|
612
|
+
fields.push(created);
|
|
613
|
+
byName.set(name, created);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return { ...table, fields };
|
|
617
|
+
});
|
|
618
|
+
};
|
|
560
619
|
var parseSeedKey = (rowKey) => {
|
|
561
620
|
const m = /^id(\d+)$/.exec(rowKey);
|
|
562
621
|
if (!m) {
|
|
@@ -738,6 +797,8 @@ var Parser = class {
|
|
|
738
797
|
...fnode.has("is_unique") ? { isUnique: fnode.bool("is_unique") } : {},
|
|
739
798
|
...fnode.str("mapping") ? { mapping: fnode.str("mapping") } : {},
|
|
740
799
|
...fnode.has("is_fixed_id") ? { isFixedId: fnode.bool("is_fixed_id") } : {},
|
|
800
|
+
...fnode.has("include_in_create") ? { includeInCreate: fnode.bool("include_in_create") } : {},
|
|
801
|
+
...fnode.has("include_in_update") ? { includeInUpdate: fnode.bool("include_in_update") } : {},
|
|
741
802
|
...fnode.has("is_optimistic_concurrency") ? {
|
|
742
803
|
isOptimisticConcurrency: fnode.bool(
|
|
743
804
|
"is_optimistic_concurrency"
|
|
@@ -759,7 +820,7 @@ var Parser = class {
|
|
|
759
820
|
};
|
|
760
821
|
});
|
|
761
822
|
const filter = this.#typesFilter(root);
|
|
762
|
-
if (!filter) return tables;
|
|
823
|
+
if (!filter) return mergeWriteInclusion(tables, types, tables);
|
|
763
824
|
const selected = new Set(
|
|
764
825
|
types.filter((t) => filter(t)).map((t) => t.name)
|
|
765
826
|
);
|
|
@@ -772,7 +833,11 @@ var Parser = class {
|
|
|
772
833
|
uniqueIndexFields: []
|
|
773
834
|
})
|
|
774
835
|
);
|
|
775
|
-
return
|
|
836
|
+
return mergeWriteInclusion(
|
|
837
|
+
[...tables.filter((t) => selected.has(t.name)), ...extras],
|
|
838
|
+
types,
|
|
839
|
+
tables
|
|
840
|
+
);
|
|
776
841
|
}
|
|
777
842
|
#parseDatasourceSeeds(yaml) {
|
|
778
843
|
const byTable = /* @__PURE__ */ new Map();
|
|
@@ -97,6 +97,76 @@ export const inheritsChain = (
|
|
|
97
97
|
return chain;
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
+
const firstAuthoredInclusion = (
|
|
101
|
+
walk: string[],
|
|
102
|
+
overlays: Map<string, Map<string, DatasourceFieldOverlay>>,
|
|
103
|
+
field: string,
|
|
104
|
+
key: "includeInCreate" | "includeInUpdate",
|
|
105
|
+
): boolean | undefined => {
|
|
106
|
+
for (const typeName of walk) {
|
|
107
|
+
const value = overlays.get(typeName)?.get(field)?.[key];
|
|
108
|
+
if (value !== undefined) return value;
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const mergeWriteInclusion = (
|
|
114
|
+
tables: DatasourceTable[],
|
|
115
|
+
types: Type[],
|
|
116
|
+
authored: DatasourceTable[],
|
|
117
|
+
): DatasourceTable[] => {
|
|
118
|
+
const typeByName = new Map(types.map((type) => [type.name, type]));
|
|
119
|
+
const overlays = new Map(
|
|
120
|
+
authored.map((table) => [
|
|
121
|
+
table.name,
|
|
122
|
+
new Map(table.fields.map((field) => [field.name, field])),
|
|
123
|
+
]),
|
|
124
|
+
);
|
|
125
|
+
return tables.map((table) => {
|
|
126
|
+
const walk = [table.name, ...inheritsChain(table.name, typeByName)];
|
|
127
|
+
const names: string[] = [];
|
|
128
|
+
const seen = new Set<string>();
|
|
129
|
+
const add = (name: string) => {
|
|
130
|
+
if (seen.has(name)) return;
|
|
131
|
+
seen.add(name);
|
|
132
|
+
names.push(name);
|
|
133
|
+
};
|
|
134
|
+
for (const field of typeByName.get(table.name)?.fields ?? []) add(field.name);
|
|
135
|
+
for (const field of table.fields) add(field.name);
|
|
136
|
+
const fields = table.fields.map((field) => ({ ...field }));
|
|
137
|
+
const byName = new Map(fields.map((field) => [field.name, field]));
|
|
138
|
+
for (const name of names) {
|
|
139
|
+
const includeInCreate = firstAuthoredInclusion(
|
|
140
|
+
walk,
|
|
141
|
+
overlays,
|
|
142
|
+
name,
|
|
143
|
+
"includeInCreate",
|
|
144
|
+
);
|
|
145
|
+
const includeInUpdate = firstAuthoredInclusion(
|
|
146
|
+
walk,
|
|
147
|
+
overlays,
|
|
148
|
+
name,
|
|
149
|
+
"includeInUpdate",
|
|
150
|
+
);
|
|
151
|
+
if (includeInCreate === undefined && includeInUpdate === undefined) continue;
|
|
152
|
+
const existing = byName.get(name);
|
|
153
|
+
if (existing) {
|
|
154
|
+
if (includeInCreate !== undefined) existing.includeInCreate = includeInCreate;
|
|
155
|
+
if (includeInUpdate !== undefined) existing.includeInUpdate = includeInUpdate;
|
|
156
|
+
} else {
|
|
157
|
+
const created: DatasourceFieldOverlay = {
|
|
158
|
+
name,
|
|
159
|
+
...(includeInCreate !== undefined ? { includeInCreate } : {}),
|
|
160
|
+
...(includeInUpdate !== undefined ? { includeInUpdate } : {}),
|
|
161
|
+
};
|
|
162
|
+
fields.push(created);
|
|
163
|
+
byName.set(name, created);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return { ...table, fields };
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
|
|
100
170
|
const parseSeedKey = (rowKey: string): number => {
|
|
101
171
|
const m = /^id(\d+)$/.exec(rowKey);
|
|
102
172
|
if (!m) {
|
|
@@ -339,6 +409,12 @@ class Parser {
|
|
|
339
409
|
...(fnode.has("is_fixed_id")
|
|
340
410
|
? { isFixedId: fnode.bool("is_fixed_id") }
|
|
341
411
|
: {}),
|
|
412
|
+
...(fnode.has("include_in_create")
|
|
413
|
+
? { includeInCreate: fnode.bool("include_in_create") }
|
|
414
|
+
: {}),
|
|
415
|
+
...(fnode.has("include_in_update")
|
|
416
|
+
? { includeInUpdate: fnode.bool("include_in_update") }
|
|
417
|
+
: {}),
|
|
342
418
|
...(fnode.has("is_optimistic_concurrency")
|
|
343
419
|
? {
|
|
344
420
|
isOptimisticConcurrency: fnode.bool(
|
|
@@ -367,7 +443,7 @@ class Parser {
|
|
|
367
443
|
});
|
|
368
444
|
|
|
369
445
|
const filter = this.#typesFilter(root);
|
|
370
|
-
if (!filter) return tables;
|
|
446
|
+
if (!filter) return mergeWriteInclusion(tables, types, tables);
|
|
371
447
|
const selected = new Set(
|
|
372
448
|
types.filter((t) => filter(t)).map((t) => t.name),
|
|
373
449
|
);
|
|
@@ -382,7 +458,11 @@ class Parser {
|
|
|
382
458
|
uniqueIndexFields: [],
|
|
383
459
|
}),
|
|
384
460
|
);
|
|
385
|
-
return
|
|
461
|
+
return mergeWriteInclusion(
|
|
462
|
+
[...tables.filter((t) => selected.has(t.name)), ...extras],
|
|
463
|
+
types,
|
|
464
|
+
tables,
|
|
465
|
+
);
|
|
386
466
|
}
|
|
387
467
|
|
|
388
468
|
#parseDatasourceSeeds(yaml: string): Map<string, SeedRow[]> {
|
|
@@ -96,6 +96,76 @@ export const inheritsChain = (
|
|
|
96
96
|
return chain;
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
+
const firstAuthoredInclusion = (
|
|
100
|
+
walk: string[],
|
|
101
|
+
overlays: Map<string, Map<string, DatasourceFieldOverlay>>,
|
|
102
|
+
field: string,
|
|
103
|
+
key: "includeInCreate" | "includeInUpdate",
|
|
104
|
+
): boolean | undefined => {
|
|
105
|
+
for (const typeName of walk) {
|
|
106
|
+
const value = overlays.get(typeName)?.get(field)?.[key];
|
|
107
|
+
if (value !== undefined) return value;
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const mergeWriteInclusion = (
|
|
113
|
+
tables: DatasourceTable[],
|
|
114
|
+
types: Type[],
|
|
115
|
+
authored: DatasourceTable[],
|
|
116
|
+
): DatasourceTable[] => {
|
|
117
|
+
const typeByName = new Map(types.map((type) => [type.name, type]));
|
|
118
|
+
const overlays = new Map(
|
|
119
|
+
authored.map((table) => [
|
|
120
|
+
table.name,
|
|
121
|
+
new Map(table.fields.map((field) => [field.name, field])),
|
|
122
|
+
]),
|
|
123
|
+
);
|
|
124
|
+
return tables.map((table) => {
|
|
125
|
+
const walk = [table.name, ...inheritsChain(table.name, typeByName)];
|
|
126
|
+
const names: string[] = [];
|
|
127
|
+
const seen = new Set<string>();
|
|
128
|
+
const add = (name: string) => {
|
|
129
|
+
if (seen.has(name)) return;
|
|
130
|
+
seen.add(name);
|
|
131
|
+
names.push(name);
|
|
132
|
+
};
|
|
133
|
+
for (const field of typeByName.get(table.name)?.fields ?? []) add(field.name);
|
|
134
|
+
for (const field of table.fields) add(field.name);
|
|
135
|
+
const fields = table.fields.map((field) => ({ ...field }));
|
|
136
|
+
const byName = new Map(fields.map((field) => [field.name, field]));
|
|
137
|
+
for (const name of names) {
|
|
138
|
+
const includeInCreate = firstAuthoredInclusion(
|
|
139
|
+
walk,
|
|
140
|
+
overlays,
|
|
141
|
+
name,
|
|
142
|
+
"includeInCreate",
|
|
143
|
+
);
|
|
144
|
+
const includeInUpdate = firstAuthoredInclusion(
|
|
145
|
+
walk,
|
|
146
|
+
overlays,
|
|
147
|
+
name,
|
|
148
|
+
"includeInUpdate",
|
|
149
|
+
);
|
|
150
|
+
if (includeInCreate === undefined && includeInUpdate === undefined) continue;
|
|
151
|
+
const existing = byName.get(name);
|
|
152
|
+
if (existing) {
|
|
153
|
+
if (includeInCreate !== undefined) existing.includeInCreate = includeInCreate;
|
|
154
|
+
if (includeInUpdate !== undefined) existing.includeInUpdate = includeInUpdate;
|
|
155
|
+
} else {
|
|
156
|
+
const created: DatasourceFieldOverlay = {
|
|
157
|
+
name,
|
|
158
|
+
...(includeInCreate !== undefined ? { includeInCreate } : {}),
|
|
159
|
+
...(includeInUpdate !== undefined ? { includeInUpdate } : {}),
|
|
160
|
+
};
|
|
161
|
+
fields.push(created);
|
|
162
|
+
byName.set(name, created);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { ...table, fields };
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
99
169
|
const parseSeedKey = (rowKey: string): number => {
|
|
100
170
|
const m = /^id(\d+)$/.exec(rowKey);
|
|
101
171
|
if (!m) {
|
|
@@ -338,6 +408,12 @@ class Parser {
|
|
|
338
408
|
...(fnode.has("is_fixed_id")
|
|
339
409
|
? { isFixedId: fnode.bool("is_fixed_id") }
|
|
340
410
|
: {}),
|
|
411
|
+
...(fnode.has("include_in_create")
|
|
412
|
+
? { includeInCreate: fnode.bool("include_in_create") }
|
|
413
|
+
: {}),
|
|
414
|
+
...(fnode.has("include_in_update")
|
|
415
|
+
? { includeInUpdate: fnode.bool("include_in_update") }
|
|
416
|
+
: {}),
|
|
341
417
|
...(fnode.has("is_optimistic_concurrency")
|
|
342
418
|
? {
|
|
343
419
|
isOptimisticConcurrency: fnode.bool(
|
|
@@ -366,7 +442,7 @@ class Parser {
|
|
|
366
442
|
});
|
|
367
443
|
|
|
368
444
|
const filter = this.#typesFilter(root);
|
|
369
|
-
if (!filter) return tables;
|
|
445
|
+
if (!filter) return mergeWriteInclusion(tables, types, tables);
|
|
370
446
|
const selected = new Set(
|
|
371
447
|
types.filter((t) => filter(t)).map((t) => t.name),
|
|
372
448
|
);
|
|
@@ -381,7 +457,11 @@ class Parser {
|
|
|
381
457
|
uniqueIndexFields: [],
|
|
382
458
|
}),
|
|
383
459
|
);
|
|
384
|
-
return
|
|
460
|
+
return mergeWriteInclusion(
|
|
461
|
+
[...tables.filter((t) => selected.has(t.name)), ...extras],
|
|
462
|
+
types,
|
|
463
|
+
tables,
|
|
464
|
+
);
|
|
385
465
|
}
|
|
386
466
|
|
|
387
467
|
#parseDatasourceSeeds(yaml: string): Map<string, SeedRow[]> {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deterministic-code/datasources-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
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.
|
|
16
|
+
"@deterministic-code/datasources-code-generators": "0.1.11",
|
|
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.
|
|
25
|
-
"@deterministic-code/datasources-sqlite": "^0.1.
|
|
26
|
-
"@deterministic-code/datasources-mysql": "^0.1.
|
|
27
|
-
"@deterministic-code/datasources-postgres": "^0.1.
|
|
28
|
-
"@deterministic-code/datasources-sqlserver": "^0.1.
|
|
29
|
-
"@deterministic-code/datasources-oracle": "^0.1.
|
|
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",
|
|
30
30
|
"@inquirer/prompts": "^7.8.0",
|
|
31
31
|
"yaml": "^2.6.0",
|
|
32
32
|
"mustache": "^4.2.0",
|