tina4-nodejs 3.13.125 → 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 +413 -81
- package/packages/cli/src/bin.ts +6 -0
- package/packages/cli/src/commands/generate.ts +89 -15
- package/packages/cli/src/commands/lint.ts +360 -0
- package/packages/core/dist/index.js +130 -19
- package/packages/core/src/fakeData.ts +37 -2
- package/packages/orm/dist/index.js +130 -19
- package/packages/orm/src/adapters/mssql.ts +4 -0
- package/packages/orm/src/fakeData.ts +27 -2
- package/packages/orm/src/seeder.ts +8 -3
- 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
|
@@ -8871,6 +8871,7 @@ var init_mssql = __esm({
|
|
|
8871
8871
|
translated = SQLTranslator.concatPipesToFunc(translated);
|
|
8872
8872
|
translated = SQLTranslator.ilikeToLike(translated);
|
|
8873
8873
|
translated = SQLTranslator.autoIncrementSyntax(translated, "mssql");
|
|
8874
|
+
translated = SQLTranslator.booleanToInt(translated);
|
|
8874
8875
|
translated = SQLTranslator.ddlTypes(translated, "mssql");
|
|
8875
8876
|
return translated;
|
|
8876
8877
|
}
|
|
@@ -15455,7 +15456,7 @@ function mulberry32(seed) {
|
|
|
15455
15456
|
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
15456
15457
|
};
|
|
15457
15458
|
}
|
|
15458
|
-
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;
|
|
15459
15460
|
var init_fakeData = __esm({
|
|
15460
15461
|
"src/fakeData.ts"() {
|
|
15461
15462
|
"use strict";
|
|
@@ -15717,6 +15718,59 @@ var init_fakeData = __esm({
|
|
|
15717
15718
|
"ZAR",
|
|
15718
15719
|
"INR"
|
|
15719
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
|
+
];
|
|
15720
15774
|
FakeData = class _FakeData {
|
|
15721
15775
|
rng;
|
|
15722
15776
|
seeded;
|
|
@@ -15788,6 +15842,16 @@ var init_fakeData = __esm({
|
|
|
15788
15842
|
jobTitle() {
|
|
15789
15843
|
return this.pick(JOB_TITLES);
|
|
15790
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
|
+
}
|
|
15791
15855
|
paragraph(sentences = 4) {
|
|
15792
15856
|
const parts = [];
|
|
15793
15857
|
for (let i = 0; i < sentences; i++) {
|
|
@@ -15911,11 +15975,27 @@ var init_fakeData = __esm({
|
|
|
15911
15975
|
});
|
|
15912
15976
|
|
|
15913
15977
|
// ../orm/src/fakeData.ts
|
|
15914
|
-
|
|
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;
|
|
15915
15983
|
var init_fakeData2 = __esm({
|
|
15916
15984
|
"../orm/src/fakeData.ts"() {
|
|
15917
15985
|
"use strict";
|
|
15918
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
|
+
];
|
|
15919
15999
|
FakeData2 = class extends FakeData {
|
|
15920
16000
|
constructor(seed) {
|
|
15921
16001
|
super(seed);
|
|
@@ -15937,8 +16017,11 @@ var init_fakeData2 = __esm({
|
|
|
15937
16017
|
*
|
|
15938
16018
|
* @param fieldDef - An ORM FieldDefinition object
|
|
15939
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).
|
|
15940
16023
|
*/
|
|
15941
|
-
forField(fieldDef, columnName) {
|
|
16024
|
+
forField(fieldDef, columnName, table2) {
|
|
15942
16025
|
if (fieldDef.primaryKey && fieldDef.autoIncrement) {
|
|
15943
16026
|
return void 0;
|
|
15944
16027
|
}
|
|
@@ -15948,7 +16031,9 @@ var init_fakeData2 = __esm({
|
|
|
15948
16031
|
const col = (columnName ?? "").toLowerCase();
|
|
15949
16032
|
if (col.includes("email")) return this.email();
|
|
15950
16033
|
if (col.includes("phone") || col.includes("mobile") || col.includes("tel")) return this.phone();
|
|
15951
|
-
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
|
+
}
|
|
15952
16037
|
if (col === "first_name" || col === "firstname") return this.firstName();
|
|
15953
16038
|
if (col === "last_name" || col === "lastname" || col === "surname") return this.lastName();
|
|
15954
16039
|
if (col.includes("address")) return this.address();
|
|
@@ -16035,7 +16120,7 @@ async function autoFieldMap(db, table2, fake = new FakeData2()) {
|
|
|
16035
16120
|
continue;
|
|
16036
16121
|
}
|
|
16037
16122
|
const fieldType = sqlTypeToFieldType(sqlType);
|
|
16038
|
-
fieldMap[name] = () => fake.forField({ type: fieldType }, name);
|
|
16123
|
+
fieldMap[name] = () => fake.forField({ type: fieldType }, name, table2);
|
|
16039
16124
|
}
|
|
16040
16125
|
return fieldMap;
|
|
16041
16126
|
}
|
|
@@ -16181,7 +16266,7 @@ async function seedOrm(ormClass, count = 10, overrides, seed, opts, fkPools) {
|
|
|
16181
16266
|
} else if (pools[name] && pools[name].length > 0) {
|
|
16182
16267
|
attrs[name] = fake.choice(pools[name]);
|
|
16183
16268
|
} else {
|
|
16184
|
-
attrs[name] = fake.forField(def, name);
|
|
16269
|
+
attrs[name] = fake.forField(def, name, modelName);
|
|
16185
16270
|
}
|
|
16186
16271
|
}
|
|
16187
16272
|
validateTypes(fields, attrs, modelName);
|
|
@@ -24408,6 +24493,7 @@ __export(generate_exports, {
|
|
|
24408
24493
|
parseEvery: () => parseEvery,
|
|
24409
24494
|
parseFields: () => parseFields,
|
|
24410
24495
|
pluralizeReserved: () => pluralizeReserved,
|
|
24496
|
+
resolveTable: () => resolveTable,
|
|
24411
24497
|
toPascal: () => toPascal,
|
|
24412
24498
|
toSnake: () => toSnake,
|
|
24413
24499
|
toTableName: () => toTableName
|
|
@@ -24450,12 +24536,35 @@ function toTableName(name) {
|
|
|
24450
24536
|
from: raw,
|
|
24451
24537
|
to: safe,
|
|
24452
24538
|
reason: `SQL reserved word '${raw}' would break CREATE TABLE`,
|
|
24453
|
-
override: `--table
|
|
24539
|
+
override: `--table-name <name> (table names interpolate unquoted; forcing a reserved name is yours to quote in raw SQL)`
|
|
24454
24540
|
});
|
|
24455
24541
|
return safe;
|
|
24456
24542
|
}
|
|
24457
24543
|
return raw;
|
|
24458
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
|
+
}
|
|
24459
24568
|
function resetResolution(target, input, opts) {
|
|
24460
24569
|
__resolution.target = target;
|
|
24461
24570
|
__resolution.input = input;
|
|
@@ -24554,10 +24663,11 @@ function printResolution() {
|
|
|
24554
24663
|
lines.push(` migration ${b.migration_path}`);
|
|
24555
24664
|
}
|
|
24556
24665
|
const reserved = b.transformations.find((t) => t.kind === "reserved_word_pluralize");
|
|
24557
|
-
if (reserved && reserved.from
|
|
24666
|
+
if (reserved && reserved.from) {
|
|
24558
24667
|
lines.push("");
|
|
24559
|
-
lines.push(` To
|
|
24560
|
-
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.`);
|
|
24561
24671
|
}
|
|
24562
24672
|
if (b.test_paths && b.test_paths.length > 0) {
|
|
24563
24673
|
lines.push("");
|
|
@@ -24693,6 +24803,7 @@ async function generate(what, name, extraArgs = []) {
|
|
|
24693
24803
|
console.error(" Usage: tina4nodejs generate <what> <name> [options]");
|
|
24694
24804
|
console.error(` Generators: ${GENERATOR_LIST}`);
|
|
24695
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)");
|
|
24696
24807
|
console.error(" --public open a route's writes (default: secure)");
|
|
24697
24808
|
console.error(' --every 5m | --cron "\u2026" service schedule');
|
|
24698
24809
|
console.error(" --json emit machine-readable resolution envelope on stdout");
|
|
@@ -24743,7 +24854,7 @@ async function generateProgrammatic(what, name, extraArgs = []) {
|
|
|
24743
24854
|
}
|
|
24744
24855
|
function generateModel(name, flags, emitTest = true) {
|
|
24745
24856
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
24746
|
-
const table2 =
|
|
24857
|
+
const table2 = resolveTable(name, flags, { announce: true });
|
|
24747
24858
|
const dir = resolve10("src/models");
|
|
24748
24859
|
ensureDir(dir);
|
|
24749
24860
|
const path8 = join20(dir, `${name}.ts`);
|
|
@@ -24794,7 +24905,7 @@ function generateRoute(name, flags, emitTest = true) {
|
|
|
24794
24905
|
if (__resolution.target === "route") {
|
|
24795
24906
|
setResolutionField("file_path", `src/routes/api/${routePath}/get.ts`);
|
|
24796
24907
|
}
|
|
24797
|
-
const table2 = model ?
|
|
24908
|
+
const table2 = model ? resolveTable(model, flags, { announce: false }) : "";
|
|
24798
24909
|
const modelImportBase = model ? `import ${model} from "../../../models/${model}.js";
|
|
24799
24910
|
` : "";
|
|
24800
24911
|
const modelImportId = model ? `import ${model} from "../../../../models/${model}.js";
|
|
@@ -25015,7 +25126,7 @@ ${aiFill(`delete_${singular}`, {
|
|
|
25015
25126
|
}
|
|
25016
25127
|
}
|
|
25017
25128
|
function generateCrud(name, flags) {
|
|
25018
|
-
const table2 =
|
|
25129
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
25019
25130
|
const routeName = toPlural(table2);
|
|
25020
25131
|
const isPublic = Boolean(flags.public);
|
|
25021
25132
|
if (!__resolution.jsonMode) console.log(`
|
|
@@ -25042,7 +25153,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
|
|
|
25042
25153
|
table2 = tableOverride;
|
|
25043
25154
|
} else {
|
|
25044
25155
|
const raw = name.replace(/^create_/, "").replace(/^add_/, "").replace(/^drop_/, "");
|
|
25045
|
-
table2 =
|
|
25156
|
+
table2 = resolveTable(raw, flags, { announce: false });
|
|
25046
25157
|
}
|
|
25047
25158
|
if (__resolution.target === "migration") {
|
|
25048
25159
|
setResolutionField("table_name", table2);
|
|
@@ -25268,7 +25379,7 @@ void test${titleName};
|
|
|
25268
25379
|
}
|
|
25269
25380
|
function generateForm(name, flags) {
|
|
25270
25381
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
25271
|
-
const table2 =
|
|
25382
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
25272
25383
|
const routeName = toPlural(table2);
|
|
25273
25384
|
const inputTypes = {
|
|
25274
25385
|
string: "text",
|
|
@@ -25334,7 +25445,7 @@ function generateForm(name, flags) {
|
|
|
25334
25445
|
}
|
|
25335
25446
|
function generateView(name, flags) {
|
|
25336
25447
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
25337
|
-
const table2 =
|
|
25448
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
25338
25449
|
const routeName = toPlural(table2);
|
|
25339
25450
|
const cols = fields.map(([f]) => f);
|
|
25340
25451
|
const dir = resolve10("src/templates/pages");
|
|
@@ -25689,8 +25800,8 @@ ${rules} validator.required("name"); // starter rule (matches the model's def
|
|
|
25689
25800
|
writeFileSafe(path8, content);
|
|
25690
25801
|
emitValidatorTest(name, toSnake(name), toPascal(name));
|
|
25691
25802
|
}
|
|
25692
|
-
function generateSeeder(name,
|
|
25693
|
-
const table2 =
|
|
25803
|
+
function generateSeeder(name, flags) {
|
|
25804
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
25694
25805
|
const dir = resolve10("src/seeds");
|
|
25695
25806
|
ensureDir(dir);
|
|
25696
25807
|
const path8 = join20(dir, `${table2}_seeder.ts`);
|
|
@@ -26315,7 +26426,7 @@ var init_generate = __esm({
|
|
|
26315
26426
|
TINA4_EDIT_MARKER = /^\s*(?:\/\/|--|\{#|#)\s*tina4:edit\s+(.+?)(?:\s*#\})?\s*$/;
|
|
26316
26427
|
DEFAULT_FIELDS = [["name", "string"]];
|
|
26317
26428
|
GENERATORS = {
|
|
26318
|
-
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" },
|
|
26319
26430
|
route: { handler: generateRoute, usage: "<name> [--model Name] [--public]", summary: "CRUD route file, secure by default (--public opens writes)" },
|
|
26320
26431
|
crud: { handler: generateCrud, usage: '<Name> [--fields "..."] [--public]', summary: "Model + migration + routes + form + view + test" },
|
|
26321
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++) {
|
|
@@ -12761,6 +12761,7 @@ __export(generate_exports, {
|
|
|
12761
12761
|
parseEvery: () => parseEvery,
|
|
12762
12762
|
parseFields: () => parseFields,
|
|
12763
12763
|
pluralizeReserved: () => pluralizeReserved,
|
|
12764
|
+
resolveTable: () => resolveTable,
|
|
12764
12765
|
toPascal: () => toPascal,
|
|
12765
12766
|
toSnake: () => toSnake,
|
|
12766
12767
|
toTableName: () => toTableName
|
|
@@ -12803,12 +12804,35 @@ function toTableName(name) {
|
|
|
12803
12804
|
from: raw,
|
|
12804
12805
|
to: safe,
|
|
12805
12806
|
reason: `SQL reserved word '${raw}' would break CREATE TABLE`,
|
|
12806
|
-
override: `--table
|
|
12807
|
+
override: `--table-name <name> (table names interpolate unquoted; forcing a reserved name is yours to quote in raw SQL)`
|
|
12807
12808
|
});
|
|
12808
12809
|
return safe;
|
|
12809
12810
|
}
|
|
12810
12811
|
return raw;
|
|
12811
12812
|
}
|
|
12813
|
+
function resolveTable(name, flags, opts = {}) {
|
|
12814
|
+
const announce = opts.announce ?? false;
|
|
12815
|
+
const override = (flags ?? {})["table-name"];
|
|
12816
|
+
if (typeof override === "string" && override) {
|
|
12817
|
+
if (announce && SQL_RESERVED_TABLE_NAMES.has(toSnake(override))) {
|
|
12818
|
+
console.error(
|
|
12819
|
+
` ! 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.`
|
|
12820
|
+
);
|
|
12821
|
+
}
|
|
12822
|
+
return override;
|
|
12823
|
+
}
|
|
12824
|
+
const bare = toSnake(name);
|
|
12825
|
+
if (!SQL_RESERVED_TABLE_NAMES.has(bare)) {
|
|
12826
|
+
return bare;
|
|
12827
|
+
}
|
|
12828
|
+
const table2 = toTableName(name);
|
|
12829
|
+
if (announce) {
|
|
12830
|
+
console.error(
|
|
12831
|
+
` \xB7 '${bare}' is a SQL reserved word; using table_name '${table2}' (Tina4 interpolates table names unquoted). Override with --table-name <name>.`
|
|
12832
|
+
);
|
|
12833
|
+
}
|
|
12834
|
+
return table2;
|
|
12835
|
+
}
|
|
12812
12836
|
function resetResolution(target, input, opts) {
|
|
12813
12837
|
__resolution.target = target;
|
|
12814
12838
|
__resolution.input = input;
|
|
@@ -12907,10 +12931,11 @@ function printResolution() {
|
|
|
12907
12931
|
lines.push(` migration ${b.migration_path}`);
|
|
12908
12932
|
}
|
|
12909
12933
|
const reserved = b.transformations.find((t) => t.kind === "reserved_word_pluralize");
|
|
12910
|
-
if (reserved && reserved.from
|
|
12934
|
+
if (reserved && reserved.from) {
|
|
12911
12935
|
lines.push("");
|
|
12912
|
-
lines.push(` To
|
|
12913
|
-
lines.push(` tina4nodejs generate ${__resolution.target} ${__resolution.input.name}
|
|
12936
|
+
lines.push(` To set the table name yourself:`);
|
|
12937
|
+
lines.push(` tina4nodejs generate ${__resolution.target} ${__resolution.input.name} --table-name <name>`);
|
|
12938
|
+
lines.push(` Tina4 interpolates table names unquoted; if you force the reserved '${reserved.from}', you own the quoting in raw SQL.`);
|
|
12914
12939
|
}
|
|
12915
12940
|
if (b.test_paths && b.test_paths.length > 0) {
|
|
12916
12941
|
lines.push("");
|
|
@@ -13046,6 +13071,7 @@ async function generate(what, name, extraArgs = []) {
|
|
|
13046
13071
|
console.error(" Usage: tina4nodejs generate <what> <name> [options]");
|
|
13047
13072
|
console.error(` Generators: ${GENERATOR_LIST}`);
|
|
13048
13073
|
console.error(' Options: --fields "name:string,price:float" --model ModelName');
|
|
13074
|
+
console.error(" --table-name <name> force the model's table name (else derived; reserved words auto-pluralise)");
|
|
13049
13075
|
console.error(" --public open a route's writes (default: secure)");
|
|
13050
13076
|
console.error(' --every 5m | --cron "\u2026" service schedule');
|
|
13051
13077
|
console.error(" --json emit machine-readable resolution envelope on stdout");
|
|
@@ -13096,7 +13122,7 @@ async function generateProgrammatic(what, name, extraArgs = []) {
|
|
|
13096
13122
|
}
|
|
13097
13123
|
function generateModel(name, flags, emitTest = true) {
|
|
13098
13124
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
13099
|
-
const table2 =
|
|
13125
|
+
const table2 = resolveTable(name, flags, { announce: true });
|
|
13100
13126
|
const dir = resolve6("src/models");
|
|
13101
13127
|
ensureDir(dir);
|
|
13102
13128
|
const path8 = join15(dir, `${name}.ts`);
|
|
@@ -13147,7 +13173,7 @@ function generateRoute(name, flags, emitTest = true) {
|
|
|
13147
13173
|
if (__resolution.target === "route") {
|
|
13148
13174
|
setResolutionField("file_path", `src/routes/api/${routePath}/get.ts`);
|
|
13149
13175
|
}
|
|
13150
|
-
const table2 = model ?
|
|
13176
|
+
const table2 = model ? resolveTable(model, flags, { announce: false }) : "";
|
|
13151
13177
|
const modelImportBase = model ? `import ${model} from "../../../models/${model}.js";
|
|
13152
13178
|
` : "";
|
|
13153
13179
|
const modelImportId = model ? `import ${model} from "../../../../models/${model}.js";
|
|
@@ -13368,7 +13394,7 @@ ${aiFill(`delete_${singular}`, {
|
|
|
13368
13394
|
}
|
|
13369
13395
|
}
|
|
13370
13396
|
function generateCrud(name, flags) {
|
|
13371
|
-
const table2 =
|
|
13397
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
13372
13398
|
const routeName = toPlural(table2);
|
|
13373
13399
|
const isPublic = Boolean(flags.public);
|
|
13374
13400
|
if (!__resolution.jsonMode) console.log(`
|
|
@@ -13395,7 +13421,7 @@ function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest
|
|
|
13395
13421
|
table2 = tableOverride;
|
|
13396
13422
|
} else {
|
|
13397
13423
|
const raw = name.replace(/^create_/, "").replace(/^add_/, "").replace(/^drop_/, "");
|
|
13398
|
-
table2 =
|
|
13424
|
+
table2 = resolveTable(raw, flags, { announce: false });
|
|
13399
13425
|
}
|
|
13400
13426
|
if (__resolution.target === "migration") {
|
|
13401
13427
|
setResolutionField("table_name", table2);
|
|
@@ -13621,7 +13647,7 @@ void test${titleName};
|
|
|
13621
13647
|
}
|
|
13622
13648
|
function generateForm(name, flags) {
|
|
13623
13649
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
13624
|
-
const table2 =
|
|
13650
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
13625
13651
|
const routeName = toPlural(table2);
|
|
13626
13652
|
const inputTypes = {
|
|
13627
13653
|
string: "text",
|
|
@@ -13687,7 +13713,7 @@ function generateForm(name, flags) {
|
|
|
13687
13713
|
}
|
|
13688
13714
|
function generateView(name, flags) {
|
|
13689
13715
|
const fields = fieldsOrDefault(flags.fields || "");
|
|
13690
|
-
const table2 =
|
|
13716
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
13691
13717
|
const routeName = toPlural(table2);
|
|
13692
13718
|
const cols = fields.map(([f]) => f);
|
|
13693
13719
|
const dir = resolve6("src/templates/pages");
|
|
@@ -14042,8 +14068,8 @@ ${rules} validator.required("name"); // starter rule (matches the model's def
|
|
|
14042
14068
|
writeFileSafe(path8, content);
|
|
14043
14069
|
emitValidatorTest(name, toSnake(name), toPascal(name));
|
|
14044
14070
|
}
|
|
14045
|
-
function generateSeeder(name,
|
|
14046
|
-
const table2 =
|
|
14071
|
+
function generateSeeder(name, flags) {
|
|
14072
|
+
const table2 = resolveTable(name, flags, { announce: false });
|
|
14047
14073
|
const dir = resolve6("src/seeds");
|
|
14048
14074
|
ensureDir(dir);
|
|
14049
14075
|
const path8 = join15(dir, `${table2}_seeder.ts`);
|
|
@@ -14668,7 +14694,7 @@ var init_generate = __esm({
|
|
|
14668
14694
|
TINA4_EDIT_MARKER = /^\s*(?:\/\/|--|\{#|#)\s*tina4:edit\s+(.+?)(?:\s*#\})?\s*$/;
|
|
14669
14695
|
DEFAULT_FIELDS = [["name", "string"]];
|
|
14670
14696
|
GENERATORS = {
|
|
14671
|
-
model: { handler: generateModel, usage: '<Name> [--fields "name:string,price:float"]', summary: "ORM model + matching migration" },
|
|
14697
|
+
model: { handler: generateModel, usage: '<Name> [--fields "name:string,price:float"] [--table-name <name>]', summary: "ORM model + matching migration" },
|
|
14672
14698
|
route: { handler: generateRoute, usage: "<name> [--model Name] [--public]", summary: "CRUD route file, secure by default (--public opens writes)" },
|
|
14673
14699
|
crud: { handler: generateCrud, usage: '<Name> [--fields "..."] [--public]', summary: "Model + migration + routes + form + view + test" },
|
|
14674
14700
|
migration: { handler: (n, f) => generateMigration(n, f, void 0, void 0, !f["no-test"]), usage: "<description>", summary: "Timestamped migration file (UP/DOWN)" },
|
|
@@ -28123,7 +28149,7 @@ function mulberry32(seed) {
|
|
|
28123
28149
|
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
28124
28150
|
};
|
|
28125
28151
|
}
|
|
28126
|
-
var FIRST_NAMES, LAST_NAMES, DOMAINS, WORDS, CITIES, COUNTRIES, STREETS, JOB_TITLES, CURRENCIES, FakeData;
|
|
28152
|
+
var FIRST_NAMES, LAST_NAMES, DOMAINS, WORDS, CITIES, COUNTRIES, STREETS, JOB_TITLES, CURRENCIES, PRODUCT_ADJECTIVES, PRODUCT_NOUNS, FakeData;
|
|
28127
28153
|
var init_fakeData = __esm({
|
|
28128
28154
|
"../core/src/fakeData.ts"() {
|
|
28129
28155
|
"use strict";
|
|
@@ -28385,6 +28411,59 @@ var init_fakeData = __esm({
|
|
|
28385
28411
|
"ZAR",
|
|
28386
28412
|
"INR"
|
|
28387
28413
|
];
|
|
28414
|
+
PRODUCT_ADJECTIVES = [
|
|
28415
|
+
"Wireless",
|
|
28416
|
+
"Organic",
|
|
28417
|
+
"Premium",
|
|
28418
|
+
"Classic",
|
|
28419
|
+
"Eco",
|
|
28420
|
+
"Smart",
|
|
28421
|
+
"Portable",
|
|
28422
|
+
"Deluxe",
|
|
28423
|
+
"Compact",
|
|
28424
|
+
"Rustic",
|
|
28425
|
+
"Handcrafted",
|
|
28426
|
+
"Vintage",
|
|
28427
|
+
"Modern",
|
|
28428
|
+
"Ergonomic",
|
|
28429
|
+
"Stainless",
|
|
28430
|
+
"Bamboo",
|
|
28431
|
+
"Recycled",
|
|
28432
|
+
"Artisan",
|
|
28433
|
+
"Professional",
|
|
28434
|
+
"Ultra",
|
|
28435
|
+
"Insulated",
|
|
28436
|
+
"Lightweight",
|
|
28437
|
+
"Adjustable",
|
|
28438
|
+
"Foldable"
|
|
28439
|
+
];
|
|
28440
|
+
PRODUCT_NOUNS = [
|
|
28441
|
+
"Keyboard",
|
|
28442
|
+
"Coffee Beans",
|
|
28443
|
+
"Backpack",
|
|
28444
|
+
"Water Bottle",
|
|
28445
|
+
"Desk Lamp",
|
|
28446
|
+
"Headphones",
|
|
28447
|
+
"Notebook",
|
|
28448
|
+
"Sneakers",
|
|
28449
|
+
"Sunglasses",
|
|
28450
|
+
"Wallet",
|
|
28451
|
+
"Mug",
|
|
28452
|
+
"Chair",
|
|
28453
|
+
"Blender",
|
|
28454
|
+
"Speaker",
|
|
28455
|
+
"Charger",
|
|
28456
|
+
"Umbrella",
|
|
28457
|
+
"Toothbrush",
|
|
28458
|
+
"Jacket",
|
|
28459
|
+
"Watch",
|
|
28460
|
+
"Kettle",
|
|
28461
|
+
"Picture Frame",
|
|
28462
|
+
"Planter",
|
|
28463
|
+
"Cutlery Set",
|
|
28464
|
+
"Yoga Mat",
|
|
28465
|
+
"Phone Case"
|
|
28466
|
+
];
|
|
28388
28467
|
FakeData = class _FakeData {
|
|
28389
28468
|
rng;
|
|
28390
28469
|
seeded;
|
|
@@ -28456,6 +28535,16 @@ var init_fakeData = __esm({
|
|
|
28456
28535
|
jobTitle() {
|
|
28457
28536
|
return this.pick(JOB_TITLES);
|
|
28458
28537
|
}
|
|
28538
|
+
/**
|
|
28539
|
+
* A plausible product name, e.g. "Wireless Keyboard" or "Organic Coffee
|
|
28540
|
+
* Beans" — an adjective + noun from the product vocabulary. Deterministic
|
|
28541
|
+
* under a seed like every other generator (draws from the same instance
|
|
28542
|
+
* PRNG). Used to seed a generic `name` column on a product-ish table instead
|
|
28543
|
+
* of a person name (see the ORM FakeData `forField` heuristic).
|
|
28544
|
+
*/
|
|
28545
|
+
product() {
|
|
28546
|
+
return `${this.pick(PRODUCT_ADJECTIVES)} ${this.pick(PRODUCT_NOUNS)}`;
|
|
28547
|
+
}
|
|
28459
28548
|
paragraph(sentences = 4) {
|
|
28460
28549
|
const parts = [];
|
|
28461
28550
|
for (let i = 0; i < sentences; i++) {
|
|
@@ -37766,6 +37855,7 @@ var init_mssql = __esm({
|
|
|
37766
37855
|
translated = SQLTranslator.concatPipesToFunc(translated);
|
|
37767
37856
|
translated = SQLTranslator.ilikeToLike(translated);
|
|
37768
37857
|
translated = SQLTranslator.autoIncrementSyntax(translated, "mssql");
|
|
37858
|
+
translated = SQLTranslator.booleanToInt(translated);
|
|
37769
37859
|
translated = SQLTranslator.ddlTypes(translated, "mssql");
|
|
37770
37860
|
return translated;
|
|
37771
37861
|
}
|
|
@@ -44338,11 +44428,27 @@ var init_baseModel = __esm({
|
|
|
44338
44428
|
});
|
|
44339
44429
|
|
|
44340
44430
|
// src/fakeData.ts
|
|
44341
|
-
|
|
44431
|
+
function isProductTable(table2) {
|
|
44432
|
+
const t = (table2 ?? "").toLowerCase();
|
|
44433
|
+
return PRODUCT_TABLE_HINTS.some((hint) => t.includes(hint));
|
|
44434
|
+
}
|
|
44435
|
+
var PRODUCT_TABLE_HINTS, FakeData2;
|
|
44342
44436
|
var init_fakeData2 = __esm({
|
|
44343
44437
|
"src/fakeData.ts"() {
|
|
44344
44438
|
"use strict";
|
|
44345
44439
|
init_fakeData();
|
|
44440
|
+
PRODUCT_TABLE_HINTS = [
|
|
44441
|
+
"product",
|
|
44442
|
+
"item",
|
|
44443
|
+
"catalog",
|
|
44444
|
+
"inventory",
|
|
44445
|
+
"goods",
|
|
44446
|
+
"merchandise",
|
|
44447
|
+
"sku",
|
|
44448
|
+
"listing",
|
|
44449
|
+
"stock",
|
|
44450
|
+
"ware"
|
|
44451
|
+
];
|
|
44346
44452
|
FakeData2 = class extends FakeData {
|
|
44347
44453
|
constructor(seed) {
|
|
44348
44454
|
super(seed);
|
|
@@ -44364,8 +44470,11 @@ var init_fakeData2 = __esm({
|
|
|
44364
44470
|
*
|
|
44365
44471
|
* @param fieldDef - An ORM FieldDefinition object
|
|
44366
44472
|
* @param columnName - Optional column name for heuristic matching (e.g. "email", "phone")
|
|
44473
|
+
* @param table - Optional table/model name. A generic `name`/`full_name`
|
|
44474
|
+
* column on a product-ish table (see {@link isProductTable}) gets a
|
|
44475
|
+
* product name; with no table context it stays a person name (back-compat).
|
|
44367
44476
|
*/
|
|
44368
|
-
forField(fieldDef, columnName) {
|
|
44477
|
+
forField(fieldDef, columnName, table2) {
|
|
44369
44478
|
if (fieldDef.primaryKey && fieldDef.autoIncrement) {
|
|
44370
44479
|
return void 0;
|
|
44371
44480
|
}
|
|
@@ -44375,7 +44484,9 @@ var init_fakeData2 = __esm({
|
|
|
44375
44484
|
const col = (columnName ?? "").toLowerCase();
|
|
44376
44485
|
if (col.includes("email")) return this.email();
|
|
44377
44486
|
if (col.includes("phone") || col.includes("mobile") || col.includes("tel")) return this.phone();
|
|
44378
|
-
if (col === "name" || col === "full_name" || col === "fullname")
|
|
44487
|
+
if (col === "name" || col === "full_name" || col === "fullname") {
|
|
44488
|
+
return isProductTable(table2) ? this.product() : this.name();
|
|
44489
|
+
}
|
|
44379
44490
|
if (col === "first_name" || col === "firstname") return this.firstName();
|
|
44380
44491
|
if (col === "last_name" || col === "lastname" || col === "surname") return this.lastName();
|
|
44381
44492
|
if (col.includes("address")) return this.address();
|
|
@@ -44462,7 +44573,7 @@ async function autoFieldMap(db, table2, fake = new FakeData2()) {
|
|
|
44462
44573
|
continue;
|
|
44463
44574
|
}
|
|
44464
44575
|
const fieldType = sqlTypeToFieldType(sqlType);
|
|
44465
|
-
fieldMap[name] = () => fake.forField({ type: fieldType }, name);
|
|
44576
|
+
fieldMap[name] = () => fake.forField({ type: fieldType }, name, table2);
|
|
44466
44577
|
}
|
|
44467
44578
|
return fieldMap;
|
|
44468
44579
|
}
|
|
@@ -44608,7 +44719,7 @@ async function seedOrm(ormClass, count = 10, overrides, seed, opts, fkPools) {
|
|
|
44608
44719
|
} else if (pools[name] && pools[name].length > 0) {
|
|
44609
44720
|
attrs[name] = fake.choice(pools[name]);
|
|
44610
44721
|
} else {
|
|
44611
|
-
attrs[name] = fake.forField(def, name);
|
|
44722
|
+
attrs[name] = fake.forField(def, name, modelName);
|
|
44612
44723
|
}
|
|
44613
44724
|
}
|
|
44614
44725
|
validateTypes(fields, attrs, modelName);
|