allez-orm 1.0.11 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/tools/allez-orm.mjs +249 -129
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "allez-orm",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "AllezORM: lightweight browser SQLite ORM (sql.js) + schema generator CLI",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"allez-orm": "tools/allez-orm.mjs"
|
|
8
|
+
},
|
|
6
9
|
"main": "./allez-orm.mjs",
|
|
7
10
|
"module": "./allez-orm.mjs",
|
|
8
11
|
"browser": "./allez-orm.mjs",
|
|
@@ -32,9 +35,6 @@
|
|
|
32
35
|
"ddl:audit": "node tools/ddl-audit.mjs",
|
|
33
36
|
"prepublishOnly": "node tests/test-cli.mjs && node tools/ddl-audit.mjs"
|
|
34
37
|
},
|
|
35
|
-
"bin": {
|
|
36
|
-
"allez-orm": "tools/allez-orm.mjs"
|
|
37
|
-
},
|
|
38
38
|
"files": [
|
|
39
39
|
"allez-orm.mjs",
|
|
40
40
|
"index.d.ts",
|
package/tools/allez-orm.mjs
CHANGED
|
@@ -7,25 +7,17 @@
|
|
|
7
7
|
* - Optional "stamps": created_at, updated_at, deleted_at
|
|
8
8
|
* - Optional unique / not-null markers
|
|
9
9
|
* - Optional ON DELETE behavior for *all* FKs via --onDelete=
|
|
10
|
-
* - Auto index per FK column in extraSQL
|
|
10
|
+
* - Auto index per FK column in extraSQL (emitted as backticked strings)
|
|
11
11
|
* - Auto-create stub schemas for FK target tables if missing
|
|
12
12
|
*
|
|
13
|
+
* New:
|
|
14
|
+
* - from-json <file>: bulk-generate schemas from a JSON config
|
|
15
|
+
* - --print-json-schema: output the JSON Schema used for validation
|
|
16
|
+
*
|
|
13
17
|
* Usage:
|
|
14
18
|
* node tools/allez-orm.mjs create table <name> [fields...] [--dir=schemas_cli] [--stamps] [-f|--force] [--onDelete=cascade|restrict|setnull|noaction]
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* name -> bare column "name TEXT"
|
|
18
|
-
* name! -> NOT NULL
|
|
19
|
-
* name:text -> explicit SQL type
|
|
20
|
-
* name:text! -> TEXT NOT NULL
|
|
21
|
-
* email:text!+ -> TEXT UNIQUE NOT NULL
|
|
22
|
-
* user_id:text->users
|
|
23
|
-
* org_id:integer->orgs
|
|
24
|
-
* slug:text,unique -> you can also use ",unique" or ",notnull"
|
|
25
|
-
*
|
|
26
|
-
* Defaults:
|
|
27
|
-
* - Adds "id INTEGER PRIMARY KEY AUTOINCREMENT" if you don't provide an "id" column yourself
|
|
28
|
-
* - Default type is TEXT when omitted
|
|
19
|
+
* node tools/allez-orm.mjs from-json <config.json> [--dir=schemas_cli] [-f|--force]
|
|
20
|
+
* node tools/allez-orm.mjs --print-json-schema
|
|
29
21
|
*/
|
|
30
22
|
|
|
31
23
|
import fs from "node:fs";
|
|
@@ -33,17 +25,20 @@ import path from "node:path";
|
|
|
33
25
|
import process from "node:process";
|
|
34
26
|
|
|
35
27
|
const argv = process.argv.slice(2);
|
|
28
|
+
|
|
36
29
|
const usage = () => {
|
|
37
30
|
console.log(`
|
|
38
31
|
Usage:
|
|
39
32
|
allez-orm create table <name> [options] [fields...]
|
|
33
|
+
allez-orm from-json <config.json> [--dir=<outDir>] [-f|--force]
|
|
34
|
+
allez-orm --print-json-schema
|
|
40
35
|
|
|
41
36
|
Options:
|
|
42
37
|
--dir=<outDir> Output directory (default: schemas_cli)
|
|
43
38
|
--stamps Add created_at, updated_at, deleted_at columns
|
|
44
|
-
--onDelete=<mode> ON DELETE
|
|
45
|
-
-f, --force Overwrite existing
|
|
46
|
-
--help Show
|
|
39
|
+
--onDelete=<mode> ON DELETE for *all* FKs (cascade|restrict|setnull|noaction). Default: none
|
|
40
|
+
-f, --force Overwrite existing files
|
|
41
|
+
--help Show help
|
|
47
42
|
|
|
48
43
|
Field syntax:
|
|
49
44
|
col[:type][!][+][->target] or "col:type,unique,notnull"
|
|
@@ -51,11 +46,6 @@ Field syntax:
|
|
|
51
46
|
`);
|
|
52
47
|
};
|
|
53
48
|
|
|
54
|
-
if (!argv.length || argv.includes("--help") || argv.includes("-h")) {
|
|
55
|
-
usage();
|
|
56
|
-
process.exit(0);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
49
|
const die = (m, code = 1) => { console.error(m); process.exit(code); };
|
|
60
50
|
|
|
61
51
|
function parseOptions(args) {
|
|
@@ -67,11 +57,15 @@ function parseOptions(args) {
|
|
|
67
57
|
cmd: null,
|
|
68
58
|
sub: null,
|
|
69
59
|
table: null,
|
|
70
|
-
fields: []
|
|
60
|
+
fields: [],
|
|
61
|
+
jsonFile: null,
|
|
62
|
+
printJsonSchema: false,
|
|
71
63
|
};
|
|
72
64
|
const positional = [];
|
|
73
65
|
for (const a of args) {
|
|
74
|
-
if (a
|
|
66
|
+
if (a === "--help" || a === "-h") {
|
|
67
|
+
usage(); process.exit(0);
|
|
68
|
+
} else if (a.startsWith("--dir=")) {
|
|
75
69
|
out.dir = a.slice(6);
|
|
76
70
|
} else if (a === "--stamps") {
|
|
77
71
|
out.stamps = true;
|
|
@@ -83,42 +77,214 @@ function parseOptions(args) {
|
|
|
83
77
|
out.onDelete = v;
|
|
84
78
|
} else if (a === "-f" || a === "--force") {
|
|
85
79
|
out.force = true;
|
|
80
|
+
} else if (a === "--print-json-schema") {
|
|
81
|
+
out.printJsonSchema = true;
|
|
86
82
|
} else if (a.startsWith("-")) {
|
|
87
83
|
die(`Unknown option: ${a}`);
|
|
88
84
|
} else {
|
|
89
85
|
positional.push(a);
|
|
90
86
|
}
|
|
91
87
|
}
|
|
92
|
-
|
|
88
|
+
|
|
89
|
+
// env var ALLEZ_FORCE=1 is honored (does not break positional parsing)
|
|
90
|
+
if (process.env.ALLEZ_FORCE === "1") out.force = true;
|
|
91
|
+
|
|
93
92
|
out.cmd = positional[0] || null;
|
|
94
93
|
out.sub = positional[1] || null;
|
|
95
|
-
out.table = positional[2] || null;
|
|
96
|
-
out.fields = positional.slice(3);
|
|
97
94
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
out.
|
|
95
|
+
if (out.cmd === "create" && out.sub === "table") {
|
|
96
|
+
out.table = positional[2] || null;
|
|
97
|
+
out.fields = positional.slice(3);
|
|
98
|
+
} else if (out.cmd === "from-json") {
|
|
99
|
+
out.jsonFile = positional[1] || null;
|
|
101
100
|
}
|
|
101
|
+
|
|
102
102
|
return out;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
const opts = parseOptions(argv);
|
|
106
106
|
|
|
107
|
-
//
|
|
108
|
-
|
|
107
|
+
// ---------------- JSON Schema (string) ----------------
|
|
108
|
+
|
|
109
|
+
const CONFIG_JSON_SCHEMA = JSON.stringify({
|
|
110
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
111
|
+
$id: "https://allez-orm.dev/allez.config.schema.json",
|
|
112
|
+
type: "object",
|
|
113
|
+
additionalProperties: false,
|
|
114
|
+
properties: {
|
|
115
|
+
outDir: { type: "string" },
|
|
116
|
+
defaultOnDelete: { enum: ["cascade","restrict","setnull","noaction",null] },
|
|
117
|
+
tables: {
|
|
118
|
+
type: "array",
|
|
119
|
+
items: {
|
|
120
|
+
type: "object",
|
|
121
|
+
additionalProperties: false,
|
|
122
|
+
properties: {
|
|
123
|
+
name: { type: "string", minLength: 1 },
|
|
124
|
+
stamps: { type: "boolean" },
|
|
125
|
+
fields: {
|
|
126
|
+
type: "array",
|
|
127
|
+
items: {
|
|
128
|
+
type: "object",
|
|
129
|
+
additionalProperties: false,
|
|
130
|
+
required: ["name"],
|
|
131
|
+
properties: {
|
|
132
|
+
name: { type: "string" },
|
|
133
|
+
type: { type: "string" }, // TEXT (default), INTEGER, etc
|
|
134
|
+
unique: { type: "boolean" },
|
|
135
|
+
notnull: { type: "boolean" },
|
|
136
|
+
fk: {
|
|
137
|
+
type: ["object", "null"],
|
|
138
|
+
additionalProperties: false,
|
|
139
|
+
properties: {
|
|
140
|
+
table: { type: "string" },
|
|
141
|
+
column: { type: "string", default: "id" }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
required: ["name","fields"]
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
required: ["tables"]
|
|
153
|
+
}, null, 2);
|
|
154
|
+
|
|
155
|
+
// ---------------- command switchboard ----------------
|
|
156
|
+
|
|
157
|
+
if (opts.printJsonSchema) {
|
|
158
|
+
console.log(CONFIG_JSON_SCHEMA);
|
|
159
|
+
process.exit(0);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!opts.cmd) {
|
|
163
|
+
usage();
|
|
164
|
+
process.exit(0);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (opts.cmd === "from-json") {
|
|
168
|
+
if (!opts.jsonFile) die("from-json requires a <config.json> path");
|
|
169
|
+
runFromJson(opts).catch(e => die(e.stack || String(e)));
|
|
170
|
+
// will exit inside
|
|
171
|
+
} else if (opts.cmd === "create" && opts.sub === "table" && opts.table) {
|
|
172
|
+
fs.mkdirSync(opts.dir, { recursive: true });
|
|
173
|
+
generateOne({
|
|
174
|
+
outDir: opts.dir,
|
|
175
|
+
name: opts.table,
|
|
176
|
+
stamps: opts.stamps,
|
|
177
|
+
onDelete: opts.onDelete,
|
|
178
|
+
force: opts.force,
|
|
179
|
+
fieldTokens: opts.fields
|
|
180
|
+
}).then(() => process.exit(0))
|
|
181
|
+
.catch(e => die(e.stack || String(e)));
|
|
182
|
+
} else {
|
|
109
183
|
usage();
|
|
110
|
-
die("Expected: create table <name>
|
|
184
|
+
die("Expected: create table <name> … or from-json <config.json>");
|
|
111
185
|
}
|
|
112
186
|
|
|
113
|
-
//
|
|
114
|
-
|
|
187
|
+
// ---------------- core generator (shared) ----------------
|
|
188
|
+
|
|
189
|
+
async function generateOne({ outDir, name, stamps, onDelete, force, fieldTokens }) {
|
|
190
|
+
const outFile = path.join(outDir, `${name}.schema.js`);
|
|
191
|
+
if (fs.existsSync(outFile) && !force) {
|
|
192
|
+
die(`Refusing to overwrite existing file: ${outFile}\n(use -f or ALLEZ_FORCE=1)`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Parse tokens into field descriptors
|
|
196
|
+
const fields = fieldTokens.map(parseFieldToken).filter(Boolean);
|
|
197
|
+
|
|
198
|
+
// Ensure id PK
|
|
199
|
+
const hasId = fields.some(f => f.name === "id");
|
|
200
|
+
if (!hasId) {
|
|
201
|
+
fields.unshift({ name: "id", type: "INTEGER", notnull: true, unique: false, fk: null, pk: true });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// stamps
|
|
205
|
+
if (stamps) {
|
|
206
|
+
fields.push(
|
|
207
|
+
{ name: "created_at", type: "TEXT", notnull: true },
|
|
208
|
+
{ name: "updated_at", type: "TEXT", notnull: true },
|
|
209
|
+
{ name: "deleted_at", type: "TEXT", notnull: false }
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// SQL
|
|
214
|
+
const columnLines = fields.map(f => sqlForColumn(f, onDelete));
|
|
215
|
+
|
|
216
|
+
// FK indexes
|
|
217
|
+
const extraSQL = [];
|
|
218
|
+
for (const f of fields) {
|
|
219
|
+
if (f.fk) {
|
|
220
|
+
extraSQL.push(
|
|
221
|
+
`\`CREATE INDEX IF NOT EXISTS idx_${name}_${f.name}_fk ON ${name}(${f.name});\``
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// module text
|
|
227
|
+
const moduleText = `// ${name}.schema.js (generated by tools/allez-orm.mjs)
|
|
228
|
+
const ${camel(name)}Schema = {
|
|
229
|
+
table: "${name}",
|
|
230
|
+
version: 1,
|
|
231
|
+
createSQL: \`
|
|
232
|
+
CREATE TABLE IF NOT EXISTS ${name} (
|
|
233
|
+
${columnLines.join(",\n ")}
|
|
234
|
+
);\`,
|
|
235
|
+
extraSQL: [
|
|
236
|
+
${extraSQL.join("\n ")}
|
|
237
|
+
]
|
|
238
|
+
};
|
|
239
|
+
export default ${camel(name)}Schema;
|
|
240
|
+
`;
|
|
241
|
+
|
|
242
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
243
|
+
fs.writeFileSync(outFile, moduleText, "utf8");
|
|
244
|
+
console.log(`Wrote ${outFile}`);
|
|
115
245
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
246
|
+
// stub FK targets
|
|
247
|
+
const fkTargets = Array.from(new Set(fields.filter(f => f.fk).map(f => f.fk.table)))
|
|
248
|
+
.filter(t => t && t !== name);
|
|
249
|
+
|
|
250
|
+
for (const t of fkTargets) {
|
|
251
|
+
const stubPath = path.join(outDir, `${t}.schema.js`);
|
|
252
|
+
if (!fs.existsSync(stubPath)) {
|
|
253
|
+
const stub = `// ${t}.schema.js (generated by tools/allez-orm.mjs - stub for FK target)
|
|
254
|
+
const ${camel(t)}Schema = {
|
|
255
|
+
table: "${t}",
|
|
256
|
+
version: 1,
|
|
257
|
+
createSQL: \`
|
|
258
|
+
CREATE TABLE IF NOT EXISTS ${t} (
|
|
259
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
260
|
+
);\`,
|
|
261
|
+
extraSQL: [
|
|
262
|
+
|
|
263
|
+
]
|
|
264
|
+
};
|
|
265
|
+
export default ${camel(t)}Schema;
|
|
266
|
+
`;
|
|
267
|
+
fs.writeFileSync(stubPath, stub, "utf8");
|
|
268
|
+
console.log(`Wrote stub ${stubPath}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
119
271
|
}
|
|
120
272
|
|
|
121
|
-
|
|
273
|
+
function sqlForColumn(f, onDelete) {
|
|
274
|
+
if (f.pk) return `id INTEGER PRIMARY KEY AUTOINCREMENT`;
|
|
275
|
+
let s = `${f.name} ${f.type}`;
|
|
276
|
+
// ordering (UNIQUE then NOT NULL) matches tests
|
|
277
|
+
if (f.unique) s += ` UNIQUE`;
|
|
278
|
+
if (f.notnull) s += ` NOT NULL`;
|
|
279
|
+
if (f.fk) {
|
|
280
|
+
s += ` REFERENCES ${f.fk.table}(${f.fk.column || "id"})`;
|
|
281
|
+
if (onDelete) {
|
|
282
|
+
const map = { cascade: "CASCADE", restrict: "RESTRICT", setnull: "SET NULL", noaction: "NO ACTION" };
|
|
283
|
+
s += ` ON DELETE ${map[onDelete]}`;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return s;
|
|
287
|
+
}
|
|
122
288
|
|
|
123
289
|
function parseFieldToken(tok) {
|
|
124
290
|
// Accept "col[:type][!][+][->target]" OR "col:type,unique,notnull"
|
|
@@ -155,7 +321,7 @@ function parseFieldToken(tok) {
|
|
|
155
321
|
type = null;
|
|
156
322
|
}
|
|
157
323
|
|
|
158
|
-
//
|
|
324
|
+
// flags from both name and type segments
|
|
159
325
|
const nameHasBang = /!/.test(name);
|
|
160
326
|
const nameHasPlus = /\+/.test(name);
|
|
161
327
|
const typeHasBang = type ? /!/.test(type) : false;
|
|
@@ -164,7 +330,7 @@ function parseFieldToken(tok) {
|
|
|
164
330
|
if (nameHasBang || typeHasBang) ret.notnull = true;
|
|
165
331
|
if (nameHasPlus || typeHasPlus) ret.unique = true;
|
|
166
332
|
|
|
167
|
-
// Clean trailing !/+ off name and type
|
|
333
|
+
// Clean trailing !/+ off name and type
|
|
168
334
|
name = name.replace(/[!+]+$/,"").trim();
|
|
169
335
|
if (type) {
|
|
170
336
|
type = type.replace(/[!+]+$/,"").trim();
|
|
@@ -183,101 +349,55 @@ function parseFieldToken(tok) {
|
|
|
183
349
|
return ret;
|
|
184
350
|
}
|
|
185
351
|
|
|
186
|
-
|
|
352
|
+
function camel(s){return s.replace(/[-_](.)/g,(_,c)=>c.toUpperCase());}
|
|
187
353
|
|
|
188
|
-
//
|
|
189
|
-
const hasId = fields.some(f => f.name === "id");
|
|
190
|
-
if (!hasId) {
|
|
191
|
-
fields.unshift({ name: "id", type: "INTEGER", notnull: true, unique: false, fk: null, pk: true });
|
|
192
|
-
}
|
|
354
|
+
// ---------------- from-json implementation ----------------
|
|
193
355
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
{ name: "created_at", type: "TEXT", notnull: true },
|
|
198
|
-
{ name: "updated_at", type: "TEXT", notnull: true },
|
|
199
|
-
{ name: "deleted_at", type: "TEXT", notnull: false }
|
|
200
|
-
);
|
|
201
|
-
}
|
|
356
|
+
async function runFromJson(cliOpts) {
|
|
357
|
+
const file = path.resolve(cliOpts.jsonFile);
|
|
358
|
+
if (!fs.existsSync(file)) die(`Config not found: ${file}`);
|
|
202
359
|
|
|
203
|
-
|
|
360
|
+
const raw = fs.readFileSync(file, "utf8");
|
|
361
|
+
let cfg;
|
|
362
|
+
try { cfg = JSON.parse(raw); } catch (e) { die(`Invalid JSON: ${e.message}`); }
|
|
204
363
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
364
|
+
// light validation against our schema
|
|
365
|
+
// (kept minimal to avoid bundling a validator)
|
|
366
|
+
if (!cfg || !Array.isArray(cfg.tables)) die(`Config must have a "tables" array.`);
|
|
208
367
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (f.notnull) s += ` NOT NULL`;
|
|
368
|
+
const outDir = cliOpts.dir || cfg.outDir || "schemas_cli";
|
|
369
|
+
const defaultOnDelete = cfg.defaultOnDelete ?? null;
|
|
212
370
|
|
|
213
|
-
|
|
214
|
-
s += ` REFERENCES ${f.fk.table}(${f.fk.column})`;
|
|
215
|
-
if (opts.onDelete) {
|
|
216
|
-
const map = { cascade: "CASCADE", restrict: "RESTRICT", setnull: "SET NULL", noaction: "NO ACTION" };
|
|
217
|
-
s += ` ON DELETE ${map[opts.onDelete]}`;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return s;
|
|
221
|
-
}
|
|
371
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
222
372
|
|
|
223
|
-
const
|
|
373
|
+
for (const t of cfg.tables) {
|
|
374
|
+
if (!t || !t.name || !Array.isArray(t.fields)) {
|
|
375
|
+
die(`Each table requires { name, fields[] }`);
|
|
376
|
+
}
|
|
377
|
+
// convert config fields -> tokens for existing generator
|
|
378
|
+
const tokens = [];
|
|
379
|
+
for (const f of t.fields) {
|
|
380
|
+
let token = f.name;
|
|
381
|
+
const type = (f.type || "TEXT").toLowerCase();
|
|
382
|
+
|
|
383
|
+
token += `:${type}`;
|
|
384
|
+
if (f.notnull) token += `!`;
|
|
385
|
+
if (f.unique) token += `+`;
|
|
386
|
+
if (f.fk && f.fk.table) {
|
|
387
|
+
token += `->${f.fk.table}`;
|
|
388
|
+
}
|
|
389
|
+
tokens.push(token);
|
|
390
|
+
}
|
|
224
391
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
392
|
+
await generateOne({
|
|
393
|
+
outDir,
|
|
394
|
+
name: t.name,
|
|
395
|
+
stamps: !!t.stamps,
|
|
396
|
+
onDelete: defaultOnDelete || null,
|
|
397
|
+
force: cliOpts.force,
|
|
398
|
+
fieldTokens: tokens
|
|
399
|
+
});
|
|
232
400
|
}
|
|
233
|
-
}
|
|
234
401
|
|
|
235
|
-
|
|
236
|
-
const moduleText = `// ${opts.table}.schema.js (generated by tools/allez-orm.mjs)
|
|
237
|
-
const ${camel(opts.table)}Schema = {
|
|
238
|
-
table: "${opts.table}",
|
|
239
|
-
version: 1,
|
|
240
|
-
createSQL: \`
|
|
241
|
-
CREATE TABLE IF NOT EXISTS ${opts.table} (
|
|
242
|
-
${columnLines.join(",\n ")}
|
|
243
|
-
);\`,
|
|
244
|
-
extraSQL: [
|
|
245
|
-
${extraSQL.join("\n ")}
|
|
246
|
-
]
|
|
247
|
-
};
|
|
248
|
-
export default ${camel(opts.table)}Schema;
|
|
249
|
-
`;
|
|
250
|
-
|
|
251
|
-
fs.writeFileSync(outFile, moduleText, "utf8");
|
|
252
|
-
console.log(`Wrote ${outFile}`);
|
|
253
|
-
|
|
254
|
-
// ---- Auto-create stub schemas for FK targets (if missing) ------------------
|
|
255
|
-
const fkTargets = Array.from(new Set(fields.filter(f => f.fk).map(f => f.fk.table)))
|
|
256
|
-
.filter(t => t && t !== opts.table);
|
|
257
|
-
|
|
258
|
-
for (const t of fkTargets) {
|
|
259
|
-
const stubPath = path.join(opts.dir, `${t}.schema.js`);
|
|
260
|
-
if (!fs.existsSync(stubPath)) {
|
|
261
|
-
const stub = `// ${t}.schema.js (generated by tools/allez-orm.mjs - stub for FK target)
|
|
262
|
-
const ${camel(t)}Schema = {
|
|
263
|
-
table: "${t}",
|
|
264
|
-
version: 1,
|
|
265
|
-
createSQL: \`
|
|
266
|
-
CREATE TABLE IF NOT EXISTS ${t} (
|
|
267
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
268
|
-
);\`,
|
|
269
|
-
extraSQL: [
|
|
270
|
-
|
|
271
|
-
]
|
|
272
|
-
};
|
|
273
|
-
export default ${camel(t)}Schema;
|
|
274
|
-
`;
|
|
275
|
-
fs.writeFileSync(stubPath, stub, "utf8");
|
|
276
|
-
console.log(`Wrote stub ${stubPath}`);
|
|
277
|
-
}
|
|
402
|
+
process.exit(0);
|
|
278
403
|
}
|
|
279
|
-
|
|
280
|
-
process.exit(0);
|
|
281
|
-
|
|
282
|
-
// ---- helpers ---------------------------------------------------------------
|
|
283
|
-
function camel(s){return s.replace(/[-_](.)/g,(_,c)=>c.toUpperCase());}
|