tina4-nodejs 3.13.124 → 3.13.130
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/CLAUDE.md +2 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +455 -85
- package/packages/cli/src/bin.ts +6 -0
- package/packages/cli/src/commands/generate.ts +93 -19
- package/packages/cli/src/commands/lint.ts +360 -0
- package/packages/core/dist/index.js +172 -23
- package/packages/core/src/fakeData.ts +37 -2
- package/packages/orm/dist/index.js +172 -23
- package/packages/orm/src/adapters/firebird.ts +7 -0
- package/packages/orm/src/adapters/mssql.ts +10 -0
- package/packages/orm/src/adapters/mysql.ts +6 -0
- package/packages/orm/src/fakeData.ts +27 -2
- package/packages/orm/src/seeder.ts +8 -3
- package/packages/orm/src/sqlTranslator.ts +45 -0
- package/types/cli/src/commands/generate.d.ts +29 -0
- package/types/cli/src/commands/lint.d.ts +5 -0
- package/types/core/src/fakeData.d.ts +12 -0
- package/types/orm/src/fakeData.d.ts +12 -1
- package/types/orm/src/sqlTranslator.d.ts +19 -0
|
@@ -6374,6 +6374,38 @@ var init_sqlTranslator = __esm({
|
|
|
6374
6374
|
return sql;
|
|
6375
6375
|
}
|
|
6376
6376
|
}
|
|
6377
|
+
/**
|
|
6378
|
+
* Translate SQLite-canonical DDL column TYPES + CREATE-TABLE options to the
|
|
6379
|
+
* target engine.
|
|
6380
|
+
*
|
|
6381
|
+
* ONLY acts on `CREATE TABLE` / `ALTER TABLE` statements, so a query or INSERT
|
|
6382
|
+
* that happens to contain the word `TEXT` (a column name, a string literal) is
|
|
6383
|
+
* never rewritten. Complements `autoIncrementSyntax` (which maps the id
|
|
6384
|
+
* keyword) so ONE portable migration — and every `Model.createTable()` DDL,
|
|
6385
|
+
* which is also SQLite-canonical — applies on every engine instead of failing
|
|
6386
|
+
* on Firebird/MSSQL.
|
|
6387
|
+
*
|
|
6388
|
+
* * Firebird has no `TEXT` (-607), no `REAL`, and no `CREATE TABLE IF NOT
|
|
6389
|
+
* EXISTS`.
|
|
6390
|
+
* * MSSQL has no `CREATE TABLE IF NOT EXISTS` and its `TIMESTAMP` is a
|
|
6391
|
+
* rowversion, not a datetime — a `created_at TIMESTAMP` there is wrong.
|
|
6392
|
+
* * MySQL's `TIMESTAMP` carries auto-update / 2038 surprises, so a datetime
|
|
6393
|
+
* column maps to `DATETIME` (matching the adapters' createTableAsync).
|
|
6394
|
+
*/
|
|
6395
|
+
static ddlTypes(sql, engine) {
|
|
6396
|
+
const head = sql.replace(/^(?:\s*--[^\n]*\n)+/, "");
|
|
6397
|
+
if (!/^\s*(?:CREATE\s+TABLE|ALTER\s+TABLE)\b/i.test(head)) return sql;
|
|
6398
|
+
switch ((engine ?? "").toLowerCase()) {
|
|
6399
|
+
case "firebird":
|
|
6400
|
+
return sql.replace(/\bIF\s+NOT\s+EXISTS\b/gi, "").replace(/\bBLOB\s+SUB_TYPE\s+TEXT\b/gi, "\0FBTEXT\0").replace(/\bTEXT\b/gi, "BLOB SUB_TYPE TEXT").replaceAll("\0FBTEXT\0", "BLOB SUB_TYPE TEXT").replace(/\bREAL\b/gi, "DOUBLE PRECISION");
|
|
6401
|
+
case "mssql":
|
|
6402
|
+
return sql.replace(/\bIF\s+NOT\s+EXISTS\b/gi, "").replace(/\bTIMESTAMP\b/gi, "DATETIME2");
|
|
6403
|
+
case "mysql":
|
|
6404
|
+
return sql.replace(/\bTIMESTAMP\b/gi, "DATETIME");
|
|
6405
|
+
default:
|
|
6406
|
+
return sql;
|
|
6407
|
+
}
|
|
6408
|
+
}
|
|
6377
6409
|
/**
|
|
6378
6410
|
* Convert ? placeholders to engine-specific style.
|
|
6379
6411
|
*
|
|
@@ -8415,6 +8447,8 @@ var init_mysql = __esm({
|
|
|
8415
8447
|
translateSql(sql) {
|
|
8416
8448
|
let translated = SQLTranslator.concatPipesToFunc(sql);
|
|
8417
8449
|
translated = SQLTranslator.ilikeToLike(translated);
|
|
8450
|
+
translated = SQLTranslator.autoIncrementSyntax(translated, "mysql");
|
|
8451
|
+
translated = SQLTranslator.ddlTypes(translated, "mysql");
|
|
8418
8452
|
return translated;
|
|
8419
8453
|
}
|
|
8420
8454
|
execute(sql, params) {
|
|
@@ -8836,6 +8870,9 @@ var init_mssql = __esm({
|
|
|
8836
8870
|
let translated = SQLTranslator.limitToTop(sql);
|
|
8837
8871
|
translated = SQLTranslator.concatPipesToFunc(translated);
|
|
8838
8872
|
translated = SQLTranslator.ilikeToLike(translated);
|
|
8873
|
+
translated = SQLTranslator.autoIncrementSyntax(translated, "mssql");
|
|
8874
|
+
translated = SQLTranslator.booleanToInt(translated);
|
|
8875
|
+
translated = SQLTranslator.ddlTypes(translated, "mssql");
|
|
8839
8876
|
return translated;
|
|
8840
8877
|
}
|
|
8841
8878
|
execSqlPromise(sql, params) {
|
|
@@ -9466,6 +9503,8 @@ var init_firebird = __esm({
|
|
|
9466
9503
|
let translated = SQLTranslator.limitToRows(sql);
|
|
9467
9504
|
translated = SQLTranslator.booleanToInt(translated);
|
|
9468
9505
|
translated = SQLTranslator.ilikeToLike(translated);
|
|
9506
|
+
translated = SQLTranslator.autoIncrementSyntax(translated, "firebird");
|
|
9507
|
+
translated = SQLTranslator.ddlTypes(translated, "firebird");
|
|
9469
9508
|
return translated;
|
|
9470
9509
|
}
|
|
9471
9510
|
/**
|
|
@@ -15417,7 +15456,7 @@ function mulberry32(seed) {
|
|
|
15417
15456
|
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
15418
15457
|
};
|
|
15419
15458
|
}
|
|
15420
|
-
var FIRST_NAMES, LAST_NAMES, DOMAINS, WORDS, CITIES, COUNTRIES, STREETS, JOB_TITLES, CURRENCIES, FakeData;
|
|
15459
|
+
var FIRST_NAMES, LAST_NAMES, DOMAINS, WORDS, CITIES, COUNTRIES, STREETS, JOB_TITLES, CURRENCIES, PRODUCT_ADJECTIVES, PRODUCT_NOUNS, FakeData;
|
|
15421
15460
|
var init_fakeData = __esm({
|
|
15422
15461
|
"src/fakeData.ts"() {
|
|
15423
15462
|
"use strict";
|
|
@@ -15679,6 +15718,59 @@ var init_fakeData = __esm({
|
|
|
15679
15718
|
"ZAR",
|
|
15680
15719
|
"INR"
|
|
15681
15720
|
];
|
|
15721
|
+
PRODUCT_ADJECTIVES = [
|
|
15722
|
+
"Wireless",
|
|
15723
|
+
"Organic",
|
|
15724
|
+
"Premium",
|
|
15725
|
+
"Classic",
|
|
15726
|
+
"Eco",
|
|
15727
|
+
"Smart",
|
|
15728
|
+
"Portable",
|
|
15729
|
+
"Deluxe",
|
|
15730
|
+
"Compact",
|
|
15731
|
+
"Rustic",
|
|
15732
|
+
"Handcrafted",
|
|
15733
|
+
"Vintage",
|
|
15734
|
+
"Modern",
|
|
15735
|
+
"Ergonomic",
|
|
15736
|
+
"Stainless",
|
|
15737
|
+
"Bamboo",
|
|
15738
|
+
"Recycled",
|
|
15739
|
+
"Artisan",
|
|
15740
|
+
"Professional",
|
|
15741
|
+
"Ultra",
|
|
15742
|
+
"Insulated",
|
|
15743
|
+
"Lightweight",
|
|
15744
|
+
"Adjustable",
|
|
15745
|
+
"Foldable"
|
|
15746
|
+
];
|
|
15747
|
+
PRODUCT_NOUNS = [
|
|
15748
|
+
"Keyboard",
|
|
15749
|
+
"Coffee Beans",
|
|
15750
|
+
"Backpack",
|
|
15751
|
+
"Water Bottle",
|
|
15752
|
+
"Desk Lamp",
|
|
15753
|
+
"Headphones",
|
|
15754
|
+
"Notebook",
|
|
15755
|
+
"Sneakers",
|
|
15756
|
+
"Sunglasses",
|
|
15757
|
+
"Wallet",
|
|
15758
|
+
"Mug",
|
|
15759
|
+
"Chair",
|
|
15760
|
+
"Blender",
|
|
15761
|
+
"Speaker",
|
|
15762
|
+
"Charger",
|
|
15763
|
+
"Umbrella",
|
|
15764
|
+
"Toothbrush",
|
|
15765
|
+
"Jacket",
|
|
15766
|
+
"Watch",
|
|
15767
|
+
"Kettle",
|
|
15768
|
+
"Picture Frame",
|
|
15769
|
+
"Planter",
|
|
15770
|
+
"Cutlery Set",
|
|
15771
|
+
"Yoga Mat",
|
|
15772
|
+
"Phone Case"
|
|
15773
|
+
];
|
|
15682
15774
|
FakeData = class _FakeData {
|
|
15683
15775
|
rng;
|
|
15684
15776
|
seeded;
|
|
@@ -15750,6 +15842,16 @@ var init_fakeData = __esm({
|
|
|
15750
15842
|
jobTitle() {
|
|
15751
15843
|
return this.pick(JOB_TITLES);
|
|
15752
15844
|
}
|
|
15845
|
+
/**
|
|
15846
|
+
* A plausible product name, e.g. "Wireless Keyboard" or "Organic Coffee
|
|
15847
|
+
* Beans" — an adjective + noun from the product vocabulary. Deterministic
|
|
15848
|
+
* under a seed like every other generator (draws from the same instance
|
|
15849
|
+
* PRNG). Used to seed a generic `name` column on a product-ish table instead
|
|
15850
|
+
* of a person name (see the ORM FakeData `forField` heuristic).
|
|
15851
|
+
*/
|
|
15852
|
+
product() {
|
|
15853
|
+
return `${this.pick(PRODUCT_ADJECTIVES)} ${this.pick(PRODUCT_NOUNS)}`;
|
|
15854
|
+
}
|
|
15753
15855
|
paragraph(sentences = 4) {
|
|
15754
15856
|
const parts = [];
|
|
15755
15857
|
for (let i = 0; i < sentences; i++) {
|
|
@@ -15873,11 +15975,27 @@ var init_fakeData = __esm({
|
|
|
15873
15975
|
});
|
|
15874
15976
|
|
|
15875
15977
|
// ../orm/src/fakeData.ts
|
|
15876
|
-
|
|
15978
|
+
function isProductTable(table2) {
|
|
15979
|
+
const t = (table2 ?? "").toLowerCase();
|
|
15980
|
+
return PRODUCT_TABLE_HINTS.some((hint) => t.includes(hint));
|
|
15981
|
+
}
|
|
15982
|
+
var PRODUCT_TABLE_HINTS, FakeData2;
|
|
15877
15983
|
var init_fakeData2 = __esm({
|
|
15878
15984
|
"../orm/src/fakeData.ts"() {
|
|
15879
15985
|
"use strict";
|
|
15880
15986
|
init_fakeData();
|
|
15987
|
+
PRODUCT_TABLE_HINTS = [
|
|
15988
|
+
"product",
|
|
15989
|
+
"item",
|
|
15990
|
+
"catalog",
|
|
15991
|
+
"inventory",
|
|
15992
|
+
"goods",
|
|
15993
|
+
"merchandise",
|
|
15994
|
+
"sku",
|
|
15995
|
+
"listing",
|
|
15996
|
+
"stock",
|
|
15997
|
+
"ware"
|
|
15998
|
+
];
|
|
15881
15999
|
FakeData2 = class extends FakeData {
|
|
15882
16000
|
constructor(seed) {
|
|
15883
16001
|
super(seed);
|
|
@@ -15899,8 +16017,11 @@ var init_fakeData2 = __esm({
|
|
|
15899
16017
|
*
|
|
15900
16018
|
* @param fieldDef - An ORM FieldDefinition object
|
|
15901
16019
|
* @param columnName - Optional column name for heuristic matching (e.g. "email", "phone")
|
|
16020
|
+
* @param table - Optional table/model name. A generic `name`/`full_name`
|
|
16021
|
+
* column on a product-ish table (see {@link isProductTable}) gets a
|
|
16022
|
+
* product name; with no table context it stays a person name (back-compat).
|
|
15902
16023
|
*/
|
|
15903
|
-
forField(fieldDef, columnName) {
|
|
16024
|
+
forField(fieldDef, columnName, table2) {
|
|
15904
16025
|
if (fieldDef.primaryKey && fieldDef.autoIncrement) {
|
|
15905
16026
|
return void 0;
|
|
15906
16027
|
}
|
|
@@ -15910,7 +16031,9 @@ var init_fakeData2 = __esm({
|
|
|
15910
16031
|
const col = (columnName ?? "").toLowerCase();
|
|
15911
16032
|
if (col.includes("email")) return this.email();
|
|
15912
16033
|
if (col.includes("phone") || col.includes("mobile") || col.includes("tel")) return this.phone();
|
|
15913
|
-
if (col === "name" || col === "full_name" || col === "fullname")
|
|
16034
|
+
if (col === "name" || col === "full_name" || col === "fullname") {
|
|
16035
|
+
return isProductTable(table2) ? this.product() : this.name();
|
|
16036
|
+
}
|
|
15914
16037
|
if (col === "first_name" || col === "firstname") return this.firstName();
|
|
15915
16038
|
if (col === "last_name" || col === "lastname" || col === "surname") return this.lastName();
|
|
15916
16039
|
if (col.includes("address")) return this.address();
|
|
@@ -15997,7 +16120,7 @@ async function autoFieldMap(db, table2, fake = new FakeData2()) {
|
|
|
15997
16120
|
continue;
|
|
15998
16121
|
}
|
|
15999
16122
|
const fieldType = sqlTypeToFieldType(sqlType);
|
|
16000
|
-
fieldMap[name] = () => fake.forField({ type: fieldType }, name);
|
|
16123
|
+
fieldMap[name] = () => fake.forField({ type: fieldType }, name, table2);
|
|
16001
16124
|
}
|
|
16002
16125
|
return fieldMap;
|
|
16003
16126
|
}
|
|
@@ -16143,7 +16266,7 @@ async function seedOrm(ormClass, count = 10, overrides, seed, opts, fkPools) {
|
|
|
16143
16266
|
} else if (pools[name] && pools[name].length > 0) {
|
|
16144
16267
|
attrs[name] = fake.choice(pools[name]);
|
|
16145
16268
|
} else {
|
|
16146
|
-
attrs[name] = fake.forField(def, name);
|
|
16269
|
+
attrs[name] = fake.forField(def, name, modelName);
|
|
16147
16270
|
}
|
|
16148
16271
|
}
|
|
16149
16272
|
validateTypes(fields, attrs, modelName);
|
|
@@ -24370,6 +24493,7 @@ __export(generate_exports, {
|
|
|
24370
24493
|
parseEvery: () => parseEvery,
|
|
24371
24494
|
parseFields: () => parseFields,
|
|
24372
24495
|
pluralizeReserved: () => pluralizeReserved,
|
|
24496
|
+
resolveTable: () => resolveTable,
|
|
24373
24497
|
toPascal: () => toPascal,
|
|
24374
24498
|
toSnake: () => toSnake,
|
|
24375
24499
|
toTableName: () => toTableName
|
|
@@ -24412,12 +24536,35 @@ function toTableName(name) {
|
|
|
24412
24536
|
from: raw,
|
|
24413
24537
|
to: safe,
|
|
24414
24538
|
reason: `SQL reserved word '${raw}' would break CREATE TABLE`,
|
|
24415
|
-
override: `--table
|
|
24539
|
+
override: `--table-name <name> (table names interpolate unquoted; forcing a reserved name is yours to quote in raw SQL)`
|
|
24416
24540
|
});
|
|
24417
24541
|
return safe;
|
|
24418
24542
|
}
|
|
24419
24543
|
return raw;
|
|
24420
24544
|
}
|
|
24545
|
+
function resolveTable(name, flags, opts = {}) {
|
|
24546
|
+
const announce = opts.announce ?? false;
|
|
24547
|
+
const override = (flags ?? {})["table-name"];
|
|
24548
|
+
if (typeof override === "string" && override) {
|
|
24549
|
+
if (announce && SQL_RESERVED_TABLE_NAMES.has(toSnake(override))) {
|
|
24550
|
+
console.error(
|
|
24551
|
+
` ! table_name '${override}' is a SQL reserved word. Tina4 interpolates table names UNQUOTED, so the ORM's generated SQL will fail on it -- quote it yourself in raw SQL and migrations.`
|
|
24552
|
+
);
|
|
24553
|
+
}
|
|
24554
|
+
return override;
|
|
24555
|
+
}
|
|
24556
|
+
const bare = toSnake(name);
|
|
24557
|
+
if (!SQL_RESERVED_TABLE_NAMES.has(bare)) {
|
|
24558
|
+
return bare;
|
|
24559
|
+
}
|
|
24560
|
+
const table2 = toTableName(name);
|
|
24561
|
+
if (announce) {
|
|
24562
|
+
console.error(
|
|
24563
|
+
` \xB7 '${bare}' is a SQL reserved word; using table_name '${table2}' (Tina4 interpolates table names unquoted). Override with --table-name <name>.`
|
|
24564
|
+
);
|
|
24565
|
+
}
|
|
24566
|
+
return table2;
|
|
24567
|
+
}
|
|
24421
24568
|
function resetResolution(target, input, opts) {
|
|
24422
24569
|
__resolution.target = target;
|
|
24423
24570
|
__resolution.input = input;
|
|
@@ -24516,10 +24663,11 @@ function printResolution() {
|
|
|
24516
24663
|
lines.push(` migration ${b.migration_path}`);
|
|
24517
24664
|
}
|
|
24518
24665
|
const reserved = b.transformations.find((t) => t.kind === "reserved_word_pluralize");
|
|
24519
|
-
if (reserved && reserved.from
|
|
24666
|
+
if (reserved && reserved.from) {
|
|
24520
24667
|
lines.push("");
|
|
24521
|
-
lines.push(` To
|
|
24522
|
-
lines.push(` tina4nodejs generate ${__resolution.target} ${__resolution.input.name}
|
|
24668
|
+
lines.push(` To set the table name yourself:`);
|
|
24669
|
+
lines.push(` tina4nodejs generate ${__resolution.target} ${__resolution.input.name} --table-name <name>`);
|
|
24670
|
+
lines.push(` Tina4 interpolates table names unquoted; if you force the reserved '${reserved.from}', you own the quoting in raw SQL.`);
|
|
24523
24671
|
}
|
|
24524
24672
|
if (b.test_paths && b.test_paths.length > 0) {
|
|
24525
24673
|
lines.push("");
|
|
@@ -24655,6 +24803,7 @@ async function generate(what, name, extraArgs = []) {
|
|
|
24655
24803
|
console.error(" Usage: tina4nodejs generate <what> <name> [options]");
|
|
24656
24804
|
console.error(` Generators: ${GENERATOR_LIST}`);
|
|
24657
24805
|
console.error(' Options: --fields "name:string,price:float" --model ModelName');
|
|
24806
|
+
console.error(" --table-name <name> force the model's table name (else derived; reserved words auto-pluralise)");
|
|
24658
24807
|
console.error(" --public open a route's writes (default: secure)");
|
|
24659
24808
|
console.error(' --every 5m | --cron "\u2026" service schedule');
|
|
24660
24809
|
console.error(" --json emit machine-readable resolution envelope on stdout");
|
|
@@ -24705,7 +24854,7 @@ async function generateProgrammatic(what, name, extraArgs = []) {
|
|
|
24705
24854
|
}
|
|
24706
24855
|
function generateModel(name, flags, emitTest = true) {
|
|
24707
24856
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
24708
|
-
const table2 =
|
|
24857
|
+
const table2 = resolveTable(name, flags, { announce: true });
|
|
24709
24858
|
const dir = resolve10("src/models");
|
|
24710
24859
|
ensureDir(dir);
|
|
24711
24860
|
const path8 = join20(dir, `${name}.ts`);
|
|
@@ -24756,7 +24905,7 @@ function generateRoute(name, flags, emitTest = true) {
|
|
|
24756
24905
|
if (__resolution.target === "route") {
|
|
24757
24906
|
setResolutionField("file_path", `src/routes/api/${routePath}/get.ts`);
|
|
24758
24907
|
}
|
|
24759
|
-
const table2 = model ?
|
|
24908
|
+
const table2 = model ? resolveTable(model, flags, { announce: false }) : "";
|
|
24760
24909
|
const modelImportBase = model ? `import ${model} from "../../../models/${model}.js";
|
|
24761
24910
|
` : "";
|
|
24762
24911
|
const modelImportId = model ? `import ${model} from "../../../../models/${model}.js";
|
|
@@ -24977,7 +25126,7 @@ ${aiFill(`delete_${singular}`, {
|
|
|
24977
25126
|
}
|
|
24978
25127
|
}
|
|
24979
25128
|
function generateCrud(name, flags) {
|
|
24980
|
-
const table2 =
|
|
25129
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
24981
25130
|
const routeName = toPlural(table2);
|
|
24982
25131
|
const isPublic = Boolean(flags.public);
|
|
24983
25132
|
if (!__resolution.jsonMode) console.log(`
|
|
@@ -25004,7 +25153,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
|
|
|
25004
25153
|
table2 = tableOverride;
|
|
25005
25154
|
} else {
|
|
25006
25155
|
const raw = name.replace(/^create_/, "").replace(/^add_/, "").replace(/^drop_/, "");
|
|
25007
|
-
table2 =
|
|
25156
|
+
table2 = resolveTable(raw, flags, { announce: false });
|
|
25008
25157
|
}
|
|
25009
25158
|
if (__resolution.target === "migration") {
|
|
25010
25159
|
setResolutionField("table_name", table2);
|
|
@@ -25027,7 +25176,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
|
|
|
25027
25176
|
const defaultClause = info.defaultVal !== "NULL" ? ` DEFAULT ${info.defaultVal}` : "";
|
|
25028
25177
|
colLines.push(` ${fname} ${info.sql}${defaultClause}`);
|
|
25029
25178
|
}
|
|
25030
|
-
colLines.push(" created_at
|
|
25179
|
+
colLines.push(" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP");
|
|
25031
25180
|
upSql = `CREATE TABLE IF NOT EXISTS ${table2} (
|
|
25032
25181
|
-- tina4:edit add columns beyond id + created_at
|
|
25033
25182
|
${colLines.join(",\n")}
|
|
@@ -25230,7 +25379,7 @@ void test${titleName};
|
|
|
25230
25379
|
}
|
|
25231
25380
|
function generateForm(name, flags) {
|
|
25232
25381
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
25233
|
-
const table2 =
|
|
25382
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
25234
25383
|
const routeName = toPlural(table2);
|
|
25235
25384
|
const inputTypes = {
|
|
25236
25385
|
string: "text",
|
|
@@ -25296,7 +25445,7 @@ function generateForm(name, flags) {
|
|
|
25296
25445
|
}
|
|
25297
25446
|
function generateView(name, flags) {
|
|
25298
25447
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
25299
|
-
const table2 =
|
|
25448
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
25300
25449
|
const routeName = toPlural(table2);
|
|
25301
25450
|
const cols = fields.map(([f]) => f);
|
|
25302
25451
|
const dir = resolve10("src/templates/pages");
|
|
@@ -25651,8 +25800,8 @@ ${rules} validator.required("name"); // starter rule (matches the model's def
|
|
|
25651
25800
|
writeFileSafe(path8, content);
|
|
25652
25801
|
emitValidatorTest(name, toSnake(name), toPascal(name));
|
|
25653
25802
|
}
|
|
25654
|
-
function generateSeeder(name,
|
|
25655
|
-
const table2 =
|
|
25803
|
+
function generateSeeder(name, flags) {
|
|
25804
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
25656
25805
|
const dir = resolve10("src/seeds");
|
|
25657
25806
|
ensureDir(dir);
|
|
25658
25807
|
const path8 = join20(dir, `${table2}_seeder.ts`);
|
|
@@ -26170,8 +26319,8 @@ var init_generate = __esm({
|
|
|
26170
26319
|
"../cli/src/commands/generate.ts"() {
|
|
26171
26320
|
"use strict";
|
|
26172
26321
|
FIELD_TYPE_MAP = {
|
|
26173
|
-
string: { orm: '"string"', sql: "
|
|
26174
|
-
str: { orm: '"string"', sql: "
|
|
26322
|
+
string: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
|
|
26323
|
+
str: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
|
|
26175
26324
|
int: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
|
|
26176
26325
|
integer: { orm: '"integer"', sql: "INTEGER", defaultVal: "0" },
|
|
26177
26326
|
float: { orm: '"number"', sql: "REAL", defaultVal: "0" },
|
|
@@ -26181,7 +26330,7 @@ var init_generate = __esm({
|
|
|
26181
26330
|
bool: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
|
|
26182
26331
|
boolean: { orm: '"boolean"', sql: "INTEGER", defaultVal: "0" },
|
|
26183
26332
|
text: { orm: '"string"', sql: "TEXT", defaultVal: "''" },
|
|
26184
|
-
datetime: { orm: '"datetime"', sql: "
|
|
26333
|
+
datetime: { orm: '"datetime"', sql: "TIMESTAMP", defaultVal: "NULL" },
|
|
26185
26334
|
blob: { orm: '"string"', sql: "BLOB", defaultVal: "NULL" }
|
|
26186
26335
|
};
|
|
26187
26336
|
SQL_RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -26277,7 +26426,7 @@ var init_generate = __esm({
|
|
|
26277
26426
|
TINA4_EDIT_MARKER = /^\s*(?:\/\/|--|\{#|#)\s*tina4:edit\s+(.+?)(?:\s*#\})?\s*$/;
|
|
26278
26427
|
DEFAULT_FIELDS = [["name", "string"]];
|
|
26279
26428
|
GENERATORS = {
|
|
26280
|
-
model: { handler: generateModel, usage: '<Name> [--fields "name:string,price:float"]', summary: "ORM model + matching migration" },
|
|
26429
|
+
model: { handler: generateModel, usage: '<Name> [--fields "name:string,price:float"] [--table-name <name>]', summary: "ORM model + matching migration" },
|
|
26281
26430
|
route: { handler: generateRoute, usage: "<name> [--model Name] [--public]", summary: "CRUD route file, secure by default (--public opens writes)" },
|
|
26282
26431
|
crud: { handler: generateCrud, usage: '<Name> [--fields "..."] [--public]', summary: "Model + migration + routes + form + view + test" },
|
|
26283
26432
|
migration: { handler: (n, f) => generateMigration(n, f, void 0, void 0, !f["no-test"]), usage: "<description>", summary: "Timestamped migration file (UP/DOWN)" },
|
|
@@ -22,7 +22,10 @@ import { resolve, join } from "node:path";
|
|
|
22
22
|
|
|
23
23
|
// ── Word Banks ───────────────────────────────────────────────────
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
// Exported (not via the barrel) so seeding tests can build the disjoint
|
|
26
|
+
// product-vs-person vocabulary check, mirroring the Python master's
|
|
27
|
+
// `_FIRST_NAMES` import. Internal-but-importable, like Python's underscore name.
|
|
28
|
+
export const FIRST_NAMES = [
|
|
26
29
|
"Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace", "Henry",
|
|
27
30
|
"Ivy", "Jack", "Kate", "Leo", "Mia", "Noah", "Olivia", "Pete",
|
|
28
31
|
"Quinn", "Rose", "Sam", "Tina", "Uma", "Vince", "Wendy", "Xander",
|
|
@@ -32,7 +35,7 @@ const FIRST_NAMES = [
|
|
|
32
35
|
"Zara", "Amber", "Blake", "Clara",
|
|
33
36
|
];
|
|
34
37
|
|
|
35
|
-
const LAST_NAMES = [
|
|
38
|
+
export const LAST_NAMES = [
|
|
36
39
|
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller",
|
|
37
40
|
"Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Wilson",
|
|
38
41
|
"Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin", "Lee",
|
|
@@ -88,6 +91,27 @@ const CURRENCIES = [
|
|
|
88
91
|
"SEK", "NZD", "MXN", "SGD", "HKD", "NOK", "ZAR", "INR",
|
|
89
92
|
];
|
|
90
93
|
|
|
94
|
+
// Product-name vocabulary (adjective + noun). Seeds a generic `name` column on
|
|
95
|
+
// a product-ish table with "Wireless Keyboard" instead of a person name.
|
|
96
|
+
// Exported (not via the barrel) so seeding tests can assert product-vs-person
|
|
97
|
+
// disjointness — mirrors the Python master's `_PRODUCT_ADJECTIVES`. The two
|
|
98
|
+
// vocabularies are disjoint from FIRST_NAMES by construction, so the FIRST word
|
|
99
|
+
// of a generated value tells which generator ran.
|
|
100
|
+
export const PRODUCT_ADJECTIVES = [
|
|
101
|
+
"Wireless", "Organic", "Premium", "Classic", "Eco", "Smart", "Portable",
|
|
102
|
+
"Deluxe", "Compact", "Rustic", "Handcrafted", "Vintage", "Modern",
|
|
103
|
+
"Ergonomic", "Stainless", "Bamboo", "Recycled", "Artisan", "Professional",
|
|
104
|
+
"Ultra", "Insulated", "Lightweight", "Adjustable", "Foldable",
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
export const PRODUCT_NOUNS = [
|
|
108
|
+
"Keyboard", "Coffee Beans", "Backpack", "Water Bottle", "Desk Lamp",
|
|
109
|
+
"Headphones", "Notebook", "Sneakers", "Sunglasses", "Wallet", "Mug",
|
|
110
|
+
"Chair", "Blender", "Speaker", "Charger", "Umbrella", "Toothbrush",
|
|
111
|
+
"Jacket", "Watch", "Kettle", "Picture Frame", "Planter", "Cutlery Set",
|
|
112
|
+
"Yoga Mat", "Phone Case",
|
|
113
|
+
];
|
|
114
|
+
|
|
91
115
|
// ── Seeded PRNG (mulberry32) ─────────────────────────────────────
|
|
92
116
|
|
|
93
117
|
/**
|
|
@@ -193,6 +217,17 @@ export class FakeData {
|
|
|
193
217
|
return this.pick(JOB_TITLES);
|
|
194
218
|
}
|
|
195
219
|
|
|
220
|
+
/**
|
|
221
|
+
* A plausible product name, e.g. "Wireless Keyboard" or "Organic Coffee
|
|
222
|
+
* Beans" — an adjective + noun from the product vocabulary. Deterministic
|
|
223
|
+
* under a seed like every other generator (draws from the same instance
|
|
224
|
+
* PRNG). Used to seed a generic `name` column on a product-ish table instead
|
|
225
|
+
* of a person name (see the ORM FakeData `forField` heuristic).
|
|
226
|
+
*/
|
|
227
|
+
product(): string {
|
|
228
|
+
return `${this.pick(PRODUCT_ADJECTIVES)} ${this.pick(PRODUCT_NOUNS)}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
196
231
|
paragraph(sentences = 4): string {
|
|
197
232
|
const parts: string[] = [];
|
|
198
233
|
for (let i = 0; i < sentences; i++) {
|