@drzl/cli 4.16.0 → 4.18.0
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/{chunk-K4J4XIFO.js → chunk-I4KMDCQR.js} +30 -1
- package/dist/chunk-I4KMDCQR.js.map +1 -0
- package/dist/cli.cjs +741 -133
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +403 -99
- package/dist/cli.js.map +1 -1
- package/dist/config.cjs +29 -0
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts +38 -2
- package/dist/config.d.ts +38 -2
- package/dist/config.js +1 -1
- package/dist/dist-TRJLPIWT.js +603 -0
- package/dist/dist-TRJLPIWT.js.map +1 -0
- package/package.json +5 -5
- package/dist/chunk-K4J4XIFO.js.map +0 -1
- package/dist/dist-UE55LTXV.js +0 -327
- package/dist/dist-UE55LTXV.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -261,7 +261,7 @@ ${entries}
|
|
|
261
261
|
const decided = [...declared, body].join("\n\n");
|
|
262
262
|
const imports = [];
|
|
263
263
|
if (useShared) {
|
|
264
|
-
const sharedAffix = (0,
|
|
264
|
+
const sharedAffix = (0, import_validation_core3.resolveAffix)({
|
|
265
265
|
affix: opts.validation?.affix,
|
|
266
266
|
schemaSuffix: opts.validation?.schemaSuffix
|
|
267
267
|
});
|
|
@@ -271,21 +271,21 @@ ${entries}
|
|
|
271
271
|
["select", selectName]
|
|
272
272
|
].filter(([, local]) => decided.includes(local));
|
|
273
273
|
if (wanted.length) {
|
|
274
|
-
const spec = (0,
|
|
274
|
+
const spec = (0, import_validation_core3.resolveConfiguredImport)(
|
|
275
275
|
opts.validation.importPath,
|
|
276
276
|
ctx.out,
|
|
277
277
|
process.cwd(),
|
|
278
278
|
opts.importExtension
|
|
279
279
|
);
|
|
280
280
|
const names = wanted.map(([mode, local]) => {
|
|
281
|
-
const exported = (0,
|
|
281
|
+
const exported = (0, import_validation_core3.schemaName)(mode, table.tsName, sharedAffix);
|
|
282
282
|
return exported === local ? local : `${exported} as ${local}`;
|
|
283
283
|
}).join(", ");
|
|
284
284
|
imports.push(`import { ${names} } from '${spec}';`);
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
imports.push(
|
|
288
|
-
`import { ${[builder, "router"].sort().join(", ")} } from '${(0,
|
|
288
|
+
`import { ${[builder, "router"].sort().join(", ")} } from '${(0, import_validation_core3.importSpecifier)(
|
|
289
289
|
`./${BASE_MODULE}.ts`,
|
|
290
290
|
opts.importExtension
|
|
291
291
|
)}';`
|
|
@@ -334,7 +334,7 @@ function relationProcedures(table, lib, selectSchemaName, taken, service) {
|
|
|
334
334
|
return out;
|
|
335
335
|
}
|
|
336
336
|
function renderBarrel(routers, ctx, path6, opts) {
|
|
337
|
-
const baseSpec = (0,
|
|
337
|
+
const baseSpec = (0, import_validation_core3.importSpecifier)(`./${BASE_MODULE}.ts`, opts.importExtension);
|
|
338
338
|
const reExports = `export { createCallerFactory, publicProcedure, router } from '${baseSpec}';
|
|
339
339
|
` + (opts.databaseInjection?.enabled === true ? `export { dbProcedure } from '${baseSpec}';
|
|
340
340
|
` : "") + `export type { Context } from '${baseSpec}';
|
|
@@ -352,7 +352,7 @@ export type AppRouter = typeof appRouter;
|
|
|
352
352
|
${reExports}`;
|
|
353
353
|
}
|
|
354
354
|
const entries = routers.map(({ filePath, exportName, table }) => ({
|
|
355
|
-
rel: (0,
|
|
355
|
+
rel: (0, import_validation_core3.importSpecifier)(
|
|
356
356
|
"./" + path6.relative(ctx.out, filePath).replace(/\\/g, "/"),
|
|
357
357
|
opts.importExtension
|
|
358
358
|
),
|
|
@@ -393,7 +393,7 @@ function routerExportName(table, naming) {
|
|
|
393
393
|
function serviceImportSpecifier(table, ctx, opts) {
|
|
394
394
|
const rel = relativePosix(ctx.out, ctx.services);
|
|
395
395
|
const dir = !rel ? "." : rel.startsWith(".") ? rel : `./${rel}`;
|
|
396
|
-
return (0,
|
|
396
|
+
return (0, import_validation_core3.importSpecifier)(`${dir}/${singularize(table.tsName)}Service.ts`, opts.importExtension);
|
|
397
397
|
}
|
|
398
398
|
function relativePosix(from, to) {
|
|
399
399
|
const norm = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
@@ -413,11 +413,11 @@ function buildHeader(h) {
|
|
|
413
413
|
];
|
|
414
414
|
return lines.join("\n") + "\n\n";
|
|
415
415
|
}
|
|
416
|
-
var
|
|
416
|
+
var import_validation_core3, TRPC_MAJOR, q, LIB_IMPORTS, LIB_USAGE, LIBS, cap, singularize, isIdent, BASE_MODULE, TRPCGenerator, index_default;
|
|
417
417
|
var init_dist = __esm({
|
|
418
418
|
"../generator-trpc/dist/index.js"() {
|
|
419
419
|
"use strict";
|
|
420
|
-
|
|
420
|
+
import_validation_core3 = require("@drzl/validation-core");
|
|
421
421
|
TRPC_MAJOR = 11;
|
|
422
422
|
q = (v) => JSON.stringify(v);
|
|
423
423
|
LIB_IMPORTS = {
|
|
@@ -510,7 +510,7 @@ ${body}
|
|
|
510
510
|
await fs3.mkdir(out, { recursive: true });
|
|
511
511
|
const files = [];
|
|
512
512
|
const write = async (filePath, content) => {
|
|
513
|
-
const formatted = await (0,
|
|
513
|
+
const formatted = await (0, import_validation_core3.formatCode)(
|
|
514
514
|
buildHeader(opts.outputHeader) + content,
|
|
515
515
|
filePath,
|
|
516
516
|
opts.format
|
|
@@ -547,9 +547,11 @@ ${body}
|
|
|
547
547
|
// ../generator-json-schema/dist/index.js
|
|
548
548
|
var dist_exports2 = {};
|
|
549
549
|
__export(dist_exports2, {
|
|
550
|
+
DRAFT: () => DRAFT,
|
|
550
551
|
JsonSchemaGenerator: () => JsonSchemaGenerator,
|
|
551
552
|
componentsDocument: () => componentsDocument,
|
|
552
553
|
default: () => index_default2,
|
|
554
|
+
openApiDocument: () => openApiDocument,
|
|
553
555
|
tableSchemas: () => tableSchemas
|
|
554
556
|
});
|
|
555
557
|
function baseSchema(c, mode, target, checks, sets, lengths) {
|
|
@@ -561,7 +563,7 @@ function baseSchema(c, mode, target, checks, sets, lengths) {
|
|
|
561
563
|
case "custom":
|
|
562
564
|
return {};
|
|
563
565
|
case "buffer":
|
|
564
|
-
return
|
|
566
|
+
return base64(target);
|
|
565
567
|
case "tuple":
|
|
566
568
|
return target === "openapi-3.0" ? { type: "array", items: { type: "number" }, minItems: s.length, maxItems: s.length } : {
|
|
567
569
|
type: "array",
|
|
@@ -596,19 +598,22 @@ function baseSchema(c, mode, target, checks, sets, lengths) {
|
|
|
596
598
|
if (c.enumValues && c.enumValues.length) return { enum: [...c.enumValues] };
|
|
597
599
|
const mine = c.arrayDimensions ? [] : checks.filter((k) => k.column === c.name);
|
|
598
600
|
const eq = mine.find((k) => k.operator === "=");
|
|
599
|
-
if (eq)
|
|
601
|
+
if (eq) {
|
|
602
|
+
const only = eq.kind === "string" ? eq.value : Number(eq.value);
|
|
603
|
+
return target === "openapi-3.0" ? { enum: [only] } : { const: only };
|
|
604
|
+
}
|
|
600
605
|
switch (c.tsType) {
|
|
601
606
|
case "string": {
|
|
602
607
|
const out = { type: "string" };
|
|
603
608
|
if (c.format === "uuid") out.format = UUID_FORMAT;
|
|
604
|
-
else if (c.format &&
|
|
609
|
+
else if (c.format && import_validation_core5.COLUMN_FORMATS[c.format]) out.pattern = import_validation_core5.COLUMN_FORMATS[c.format];
|
|
605
610
|
if (c.maxLength !== void 0) out.maxLength = c.maxLength;
|
|
606
611
|
applyByteCap(out, c);
|
|
607
612
|
applyLengths(out, c, lengths);
|
|
608
613
|
return out;
|
|
609
614
|
}
|
|
610
615
|
case "number": {
|
|
611
|
-
const out = { type: (0,
|
|
616
|
+
const out = { type: (0, import_validation_core5.isIntegerColumn)(c) ? "integer" : "number" };
|
|
612
617
|
if (!c.arrayDimensions) applyNumericBounds(out, c, checks, target);
|
|
613
618
|
return out;
|
|
614
619
|
}
|
|
@@ -619,7 +624,7 @@ function baseSchema(c, mode, target, checks, sets, lengths) {
|
|
|
619
624
|
case "Date":
|
|
620
625
|
return { type: "string", format: "date-time" };
|
|
621
626
|
case "Uint8Array":
|
|
622
|
-
return
|
|
627
|
+
return base64(target);
|
|
623
628
|
default:
|
|
624
629
|
return {};
|
|
625
630
|
}
|
|
@@ -745,7 +750,7 @@ function tableSchema(table, cols, mode, target, applyDefaults, parsed) {
|
|
|
745
750
|
};
|
|
746
751
|
}
|
|
747
752
|
function collect(table) {
|
|
748
|
-
const parsed = (table.checks ?? []).map((k) => (0,
|
|
753
|
+
const parsed = (table.checks ?? []).map((k) => (0, import_validation_core5.parseCheck)(k.expression, k.name));
|
|
749
754
|
return {
|
|
750
755
|
checks: parsed.flatMap((p) => p.ok ? p.checks : []),
|
|
751
756
|
sets: parsed.flatMap((p) => p.ok ? p.sets ?? [] : []),
|
|
@@ -757,11 +762,11 @@ function collect(table) {
|
|
|
757
762
|
function tableSchemas(table, opts = {}) {
|
|
758
763
|
const target = opts.target ?? "draft-2020-12";
|
|
759
764
|
const parsed = collect(table);
|
|
760
|
-
const
|
|
765
|
+
const build2 = (cols, mode) => tableSchema(table, cols, mode, target, !!opts.applyDefaults, parsed);
|
|
761
766
|
return {
|
|
762
|
-
insert:
|
|
763
|
-
update:
|
|
764
|
-
select:
|
|
767
|
+
insert: build2((0, import_validation_core5.insertColumns)(table), "insert"),
|
|
768
|
+
update: build2((0, import_validation_core5.updateColumns)(table), "update"),
|
|
769
|
+
select: build2((0, import_validation_core5.selectColumns)(table), "select")
|
|
765
770
|
};
|
|
766
771
|
}
|
|
767
772
|
function componentsDocument(tables, opts = {}) {
|
|
@@ -776,14 +781,231 @@ function componentsDocument(tables, opts = {}) {
|
|
|
776
781
|
}
|
|
777
782
|
return { schemas };
|
|
778
783
|
}
|
|
784
|
+
function keyColumns2(table) {
|
|
785
|
+
const names = table.primaryKey?.columns ?? [];
|
|
786
|
+
if (!names.length) return null;
|
|
787
|
+
const cols = names.map((n) => table.columns.find((c) => c.name === n));
|
|
788
|
+
if (cols.some((c) => !c)) return null;
|
|
789
|
+
return cols;
|
|
790
|
+
}
|
|
791
|
+
function foreignKeysOf(table) {
|
|
792
|
+
if (table.foreignKeys?.length) return table.foreignKeys;
|
|
793
|
+
return table.columns.filter((c) => c.references).map((c) => ({
|
|
794
|
+
columns: [c.name],
|
|
795
|
+
foreignTable: c.references.table,
|
|
796
|
+
foreignColumns: [c.references.column]
|
|
797
|
+
}));
|
|
798
|
+
}
|
|
799
|
+
function build(tables, opts) {
|
|
800
|
+
const target = opts.target ?? "draft-2020-12";
|
|
801
|
+
const schemaTarget = target === "openapi-3.0" ? "openapi-3.0" : "openapi-3.1";
|
|
802
|
+
const failure = String(opts.validationStatus ?? 400);
|
|
803
|
+
const paths = {};
|
|
804
|
+
const schemas = {};
|
|
805
|
+
const tags = [];
|
|
806
|
+
const operationIds = /* @__PURE__ */ new Map();
|
|
807
|
+
const owner = /* @__PURE__ */ new Map();
|
|
808
|
+
const claim = (path6, by, label) => {
|
|
809
|
+
const taken = owner.get(path6);
|
|
810
|
+
if (taken !== void 0 && taken.by !== by) {
|
|
811
|
+
throw new Error(
|
|
812
|
+
`@drzl/generator-json-schema: the OpenAPI path "${path6}" is claimed twice: by table "${taken.label}" (exported as ${taken.by}) and by table "${label}" (exported as ${by}). A path names one resource, so one of the two has to be left out of this generator with the config's "exclude" list.`
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
owner.set(path6, { by, label });
|
|
816
|
+
};
|
|
817
|
+
const operation = (id, table, rest) => {
|
|
818
|
+
const clash = operationIds.get(id);
|
|
819
|
+
if (clash !== void 0) {
|
|
820
|
+
throw new Error(
|
|
821
|
+
`@drzl/generator-json-schema: the operationId "${id}" would be emitted for both "${clash}" and "${table.name}". An operationId is the method name a client generator derives, and the specification requires it to be unique across the document.`
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
operationIds.set(id, table.name);
|
|
825
|
+
return { operationId: id, tags: [table.name], ...rest };
|
|
826
|
+
};
|
|
827
|
+
const built = tables.map((table) => ({
|
|
828
|
+
table,
|
|
829
|
+
key: keyColumns2(table),
|
|
830
|
+
segment: resourceSegment(table),
|
|
831
|
+
schemas: tableSchemas(table, { target: schemaTarget, applyDefaults: opts.applyDefaults })
|
|
832
|
+
}));
|
|
833
|
+
for (const { table, key, segment, schemas: built3 } of built) {
|
|
834
|
+
for (const mode of modesFor(table, key)) {
|
|
835
|
+
const { $schema: _dialect, $id: _id, ...rest } = built3[mode];
|
|
836
|
+
schemas[componentName(table, mode)] = rest;
|
|
837
|
+
}
|
|
838
|
+
const notes = [];
|
|
839
|
+
if (!key) notes.push("It has no primary key, so no path addresses a single row.");
|
|
840
|
+
if (table.readOnly) {
|
|
841
|
+
notes.push("It refuses every write, so only reads are described.");
|
|
842
|
+
}
|
|
843
|
+
tags.push({ name: table.name, description: [`Table "${table.name}".`, ...notes].join(" ") });
|
|
844
|
+
const T = pascal(table.tsName);
|
|
845
|
+
const select = ref(componentName(table, "select"));
|
|
846
|
+
const validationFailed = {
|
|
847
|
+
description: "The request does not match the schema for this operation.",
|
|
848
|
+
...jsonBody(ref(ERROR_SCHEMA))
|
|
849
|
+
};
|
|
850
|
+
const collidable = [
|
|
851
|
+
...table.primaryKey ? [`primary key (${table.primaryKey.columns.join(", ")})`] : [],
|
|
852
|
+
...table.unique.map((u) => `${u.name ? `${u.name} ` : ""}(${u.columns.join(", ")})`)
|
|
853
|
+
];
|
|
854
|
+
const conflict = (constraints) => ({
|
|
855
|
+
description: `The row collides with an existing one on ${constraints.join("; ")}.`,
|
|
856
|
+
...jsonBody(ref(ERROR_SCHEMA))
|
|
857
|
+
});
|
|
858
|
+
const collection = `/${segment}`;
|
|
859
|
+
claim(collection, table.tsName, table.name);
|
|
860
|
+
const item = {
|
|
861
|
+
get: operation(`list${T}`, table, {
|
|
862
|
+
summary: `List every ${table.name} row.`,
|
|
863
|
+
// No pagination parameters. Whether the server implements a limit, an offset or a cursor is
|
|
864
|
+
// not something a Drizzle schema states, and a declared parameter nothing honours is worse
|
|
865
|
+
// than an undeclared one.
|
|
866
|
+
responses: {
|
|
867
|
+
"200": {
|
|
868
|
+
description: `Every ${table.name} row.`,
|
|
869
|
+
...jsonBody({ type: "array", items: select })
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
})
|
|
873
|
+
};
|
|
874
|
+
if (!table.readOnly) {
|
|
875
|
+
item.post = operation(`create${T}`, table, {
|
|
876
|
+
summary: `Create a ${table.name} row.`,
|
|
877
|
+
requestBody: { required: true, ...jsonBody(ref(componentName(table, "insert"))) },
|
|
878
|
+
responses: {
|
|
879
|
+
"201": { description: `The ${table.name} row that was created.`, ...jsonBody(select) },
|
|
880
|
+
[failure]: validationFailed,
|
|
881
|
+
...collidable.length ? { "409": conflict(collidable) } : {}
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
paths[collection] = item;
|
|
886
|
+
if (!key) continue;
|
|
887
|
+
const itemPath = `${collection}/${key.map((c) => `{${c.name}}`).join("/")}`;
|
|
888
|
+
claim(itemPath, table.tsName, table.name);
|
|
889
|
+
const parameters = key.map((c) => ({
|
|
890
|
+
name: c.name,
|
|
891
|
+
in: "path",
|
|
892
|
+
required: true,
|
|
893
|
+
description: `${c.name}, from the primary key of ${table.name}.`,
|
|
894
|
+
// The column's own schema rather than a string, so an integer key is declared as one and a
|
|
895
|
+
// uuid key carries its format. This is the whole point of reading the real key.
|
|
896
|
+
schema: built3.select.properties[c.name] ?? {}
|
|
897
|
+
}));
|
|
898
|
+
const missing = {
|
|
899
|
+
description: `No ${table.name} row has that ${key.map((c) => c.name).join(" and ")}.`,
|
|
900
|
+
...jsonBody(ref(ERROR_SCHEMA))
|
|
901
|
+
};
|
|
902
|
+
const byId = {
|
|
903
|
+
parameters,
|
|
904
|
+
get: operation(`get${T}`, table, {
|
|
905
|
+
summary: `Read one ${table.name} row.`,
|
|
906
|
+
responses: {
|
|
907
|
+
"200": { description: `The requested ${table.name} row.`, ...jsonBody(select) },
|
|
908
|
+
[failure]: validationFailed,
|
|
909
|
+
"404": missing
|
|
910
|
+
}
|
|
911
|
+
})
|
|
912
|
+
};
|
|
913
|
+
if (!table.readOnly) {
|
|
914
|
+
byId.patch = operation(`update${T}`, table, {
|
|
915
|
+
summary: `Patch one ${table.name} row.`,
|
|
916
|
+
requestBody: { required: true, ...jsonBody(ref(componentName(table, "update"))) },
|
|
917
|
+
responses: {
|
|
918
|
+
"200": { description: `The ${table.name} row after the patch.`, ...jsonBody(select) },
|
|
919
|
+
[failure]: validationFailed,
|
|
920
|
+
"404": missing,
|
|
921
|
+
// The primary key is not in the update schema, so a patch cannot collide on it. Only a
|
|
922
|
+
// unique constraint over other columns can.
|
|
923
|
+
...table.unique.length ? {
|
|
924
|
+
"409": conflict(
|
|
925
|
+
table.unique.map((u) => `${u.name ? `${u.name} ` : ""}(${u.columns.join(", ")})`)
|
|
926
|
+
)
|
|
927
|
+
} : {}
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
byId.delete = operation(`delete${T}`, table, {
|
|
931
|
+
summary: `Delete one ${table.name} row.`,
|
|
932
|
+
responses: {
|
|
933
|
+
// No body. Handing back the deleted row is the alternative and it is not a true statement
|
|
934
|
+
// on every dialect DRZL supports: RETURNING is Postgres and SQLite, and MySQL has no such
|
|
935
|
+
// clause, so an implementation there has nothing to send.
|
|
936
|
+
"204": { description: `The ${table.name} row was deleted. No content is returned.` },
|
|
937
|
+
[failure]: validationFailed,
|
|
938
|
+
"404": missing
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
paths[itemPath] = byId;
|
|
943
|
+
if (!opts.includeRelations) continue;
|
|
944
|
+
for (const child of built) {
|
|
945
|
+
if (child.table === table) continue;
|
|
946
|
+
const matching = foreignKeysOf(child.table).filter(
|
|
947
|
+
(fk) => fk.foreignTable === table.name && fk.foreignColumns.length === key.length && fk.foreignColumns.every((c, i) => c === key[i].name)
|
|
948
|
+
);
|
|
949
|
+
if (matching.length !== 1) continue;
|
|
950
|
+
const subPath = `${itemPath}/${child.segment}`;
|
|
951
|
+
claim(
|
|
952
|
+
subPath,
|
|
953
|
+
`${table.tsName} -> ${child.table.tsName}`,
|
|
954
|
+
`${table.name} -> ${child.table.name}`
|
|
955
|
+
);
|
|
956
|
+
paths[subPath] = {
|
|
957
|
+
parameters,
|
|
958
|
+
get: operation(`list${T}${pascal(child.table.tsName)}`, child.table, {
|
|
959
|
+
summary: `List the ${child.table.name} rows belonging to one ${table.name} row.`,
|
|
960
|
+
responses: {
|
|
961
|
+
"200": {
|
|
962
|
+
description: `The ${child.table.name} rows whose ${matching[0].columns.join(", ")} names this ${table.name} row.`,
|
|
963
|
+
...jsonBody({ type: "array", items: ref(componentName(child.table, "select")) })
|
|
964
|
+
},
|
|
965
|
+
[failure]: validationFailed,
|
|
966
|
+
"404": missing
|
|
967
|
+
}
|
|
968
|
+
})
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return { paths, schemas, tags };
|
|
973
|
+
}
|
|
974
|
+
function openApiDocument(tables, opts = {}) {
|
|
975
|
+
const target = opts.target ?? "draft-2020-12";
|
|
976
|
+
const { paths, schemas, tags } = build(tables, opts);
|
|
977
|
+
if (ERROR_SCHEMA in schemas) {
|
|
978
|
+
throw new Error(
|
|
979
|
+
`@drzl/generator-json-schema: a table produced the component schema name "${ERROR_SCHEMA}", which the document already uses for its error responses.`
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
return {
|
|
983
|
+
openapi: target === "openapi-3.0" ? "3.0.3" : "3.1.1",
|
|
984
|
+
info: {
|
|
985
|
+
title: opts.info?.title ?? "API",
|
|
986
|
+
version: opts.info?.version ?? "0.0.0",
|
|
987
|
+
description: opts.info?.description ?? "Generated by DRZL from a Drizzle schema. Paths, request bodies and response bodies are derived from the schema alone; nothing here has been checked against a running server."
|
|
988
|
+
},
|
|
989
|
+
...opts.servers?.length ? { servers: opts.servers } : {},
|
|
990
|
+
paths,
|
|
991
|
+
components: { schemas: { ...schemas, [ERROR_SCHEMA]: errorSchema() } },
|
|
992
|
+
tags
|
|
993
|
+
};
|
|
994
|
+
}
|
|
779
995
|
function renderTableModule(table, affix, target, applyDefaults) {
|
|
780
996
|
const T = table.tsName;
|
|
781
997
|
const schemas = tableSchemas(table, { target, applyDefaults });
|
|
782
|
-
const decl = (mode) => `export const ${(0,
|
|
998
|
+
const decl = (mode) => `export const ${(0, import_validation_core4.schemaName)(mode, T, affix)} = ${JSON.stringify(schemas[mode], null, 2)} as const;
|
|
783
999
|
|
|
784
|
-
export type ${(0,
|
|
1000
|
+
export type ${(0, import_validation_core4.typeName)(mode, T, affix)} = typeof ${(0, import_validation_core4.schemaName)(mode, T, affix)};`;
|
|
785
1001
|
return [decl("insert"), decl("update"), decl("select")].join("\n\n") + "\n";
|
|
786
1002
|
}
|
|
1003
|
+
function resolveDocument(opt) {
|
|
1004
|
+
if (!opt) return null;
|
|
1005
|
+
const o = opt === true ? {} : opt;
|
|
1006
|
+
if (o.enabled === false) return null;
|
|
1007
|
+
return { ...o, format: o.format ?? "ts" };
|
|
1008
|
+
}
|
|
787
1009
|
function buildHeader2(h) {
|
|
788
1010
|
if (h?.enabled === false) return "";
|
|
789
1011
|
const text = h?.text ?? "// Generated by DRZL. Do not edit by hand.";
|
|
@@ -791,14 +1013,38 @@ function buildHeader2(h) {
|
|
|
791
1013
|
|
|
792
1014
|
`;
|
|
793
1015
|
}
|
|
794
|
-
var
|
|
1016
|
+
var import_validation_core4, import_validation_core5, DRAFT, UUID_FORMAT, base64, ERROR_SCHEMA, componentName, ref, pascal, modesFor, resourceSegment, jsonBody, errorSchema, DEFAULT_FILE_SUFFIX, JsonSchemaGenerator, index_default2;
|
|
795
1017
|
var init_dist2 = __esm({
|
|
796
1018
|
"../generator-json-schema/dist/index.js"() {
|
|
797
1019
|
"use strict";
|
|
798
|
-
|
|
799
|
-
|
|
1020
|
+
import_validation_core4 = require("@drzl/validation-core");
|
|
1021
|
+
import_validation_core5 = require("@drzl/validation-core");
|
|
800
1022
|
DRAFT = "https://json-schema.org/draft/2020-12/schema";
|
|
801
1023
|
UUID_FORMAT = "uuid";
|
|
1024
|
+
base64 = (target) => target === "openapi-3.0" ? { type: "string", format: "byte" } : { type: "string", contentEncoding: "base64" };
|
|
1025
|
+
ERROR_SCHEMA = "Error";
|
|
1026
|
+
componentName = (table, mode) => `${table.tsName}${mode[0].toUpperCase()}${mode.slice(1)}`;
|
|
1027
|
+
ref = (name) => ({ $ref: `#/components/schemas/${name}` });
|
|
1028
|
+
pascal = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
1029
|
+
modesFor = (table, key) => [
|
|
1030
|
+
...table.readOnly ? [] : ["insert"],
|
|
1031
|
+
...table.readOnly || !key ? [] : ["update"],
|
|
1032
|
+
"select"
|
|
1033
|
+
];
|
|
1034
|
+
resourceSegment = (table) => encodeURIComponent(table.name);
|
|
1035
|
+
jsonBody = (schema) => ({ content: { "application/json": { schema } } });
|
|
1036
|
+
errorSchema = () => ({
|
|
1037
|
+
title: "error",
|
|
1038
|
+
description: "What an operation returns when it does not return the row.",
|
|
1039
|
+
type: "object",
|
|
1040
|
+
properties: {
|
|
1041
|
+
message: { type: "string" },
|
|
1042
|
+
code: { type: "string" }
|
|
1043
|
+
},
|
|
1044
|
+
required: ["message"],
|
|
1045
|
+
additionalProperties: true
|
|
1046
|
+
});
|
|
1047
|
+
DEFAULT_FILE_SUFFIX = ".schema.ts";
|
|
802
1048
|
JsonSchemaGenerator = class {
|
|
803
1049
|
constructor(analysis) {
|
|
804
1050
|
this.analysis = analysis;
|
|
@@ -810,13 +1056,14 @@ var init_dist2 = __esm({
|
|
|
810
1056
|
const out = path6.resolve(process.cwd(), opts.outDir);
|
|
811
1057
|
const files = [];
|
|
812
1058
|
await fs3.mkdir(out, { recursive: true });
|
|
813
|
-
const affix = (0,
|
|
1059
|
+
const affix = (0, import_validation_core4.resolveAffix)(opts);
|
|
814
1060
|
const fileSuffix = opts.fileSuffix ?? DEFAULT_FILE_SUFFIX;
|
|
815
1061
|
const target = opts.target ?? "draft-2020-12";
|
|
1062
|
+
const document = resolveDocument(opts.document);
|
|
816
1063
|
for (const table of this.analysis.tables) {
|
|
817
|
-
const filePath = path6.join(out, (0,
|
|
1064
|
+
const filePath = path6.join(out, (0, import_validation_core4.moduleFileName)(table.tsName, fileSuffix));
|
|
818
1065
|
const code = renderTableModule(table, affix, target, !!opts.applyDefaults);
|
|
819
|
-
const formatted = await (0,
|
|
1066
|
+
const formatted = await (0, import_validation_core4.formatCode)(
|
|
820
1067
|
buildHeader2(opts.outputHeader) + code,
|
|
821
1068
|
filePath,
|
|
822
1069
|
opts.format
|
|
@@ -834,16 +1081,44 @@ var init_dist2 = __esm({
|
|
|
834
1081
|
`;
|
|
835
1082
|
await fs3.writeFile(
|
|
836
1083
|
componentsPath,
|
|
837
|
-
await (0,
|
|
1084
|
+
await (0, import_validation_core4.formatCode)(buildHeader2(opts.outputHeader) + code, componentsPath, opts.format),
|
|
838
1085
|
"utf8"
|
|
839
1086
|
);
|
|
840
1087
|
files.push(componentsPath);
|
|
841
1088
|
}
|
|
1089
|
+
if (document) {
|
|
1090
|
+
const built = openApiDocument(this.analysis.tables, {
|
|
1091
|
+
target,
|
|
1092
|
+
applyDefaults: !!opts.applyDefaults,
|
|
1093
|
+
includeRelations: !!opts.includeRelations,
|
|
1094
|
+
info: document.info,
|
|
1095
|
+
servers: document.servers,
|
|
1096
|
+
validationStatus: document.validationStatus
|
|
1097
|
+
});
|
|
1098
|
+
const body = JSON.stringify(built, null, 2);
|
|
1099
|
+
if (document.format !== "json") {
|
|
1100
|
+
const tsPath = path6.join(out, "openapi.ts");
|
|
1101
|
+
const code = `export const openapi = ${body} as const;
|
|
1102
|
+
`;
|
|
1103
|
+
await fs3.writeFile(
|
|
1104
|
+
tsPath,
|
|
1105
|
+
await (0, import_validation_core4.formatCode)(buildHeader2(opts.outputHeader) + code, tsPath, opts.format),
|
|
1106
|
+
"utf8"
|
|
1107
|
+
);
|
|
1108
|
+
files.push(tsPath);
|
|
1109
|
+
}
|
|
1110
|
+
if (document.format !== "ts") {
|
|
1111
|
+
const jsonPath = path6.join(out, "openapi.json");
|
|
1112
|
+
await fs3.writeFile(jsonPath, body + "\n", "utf8");
|
|
1113
|
+
files.push(jsonPath);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
const ext = opts.importExtension === "none" ? "" : ".js";
|
|
842
1117
|
const indexPath = path6.join(out, "index.ts");
|
|
843
|
-
const index = this.analysis.tables.map(
|
|
844
|
-
|
|
845
|
-
).join("\n") + "\n";
|
|
846
|
-
const indexFormatted = await (0,
|
|
1118
|
+
const index = this.analysis.tables.map(
|
|
1119
|
+
(t) => `export * from '${(0, import_validation_core4.moduleSpecifier)(t.tsName, fileSuffix, opts.importExtension)}';`
|
|
1120
|
+
).concat(opts.components ? [`export * from './components${ext}';`] : []).concat(document && document.format !== "json" ? [`export * from './openapi${ext}';`] : []).join("\n") + "\n";
|
|
1121
|
+
const indexFormatted = await (0, import_validation_core4.formatCode)(
|
|
847
1122
|
buildHeader2(opts.outputHeader) + index,
|
|
848
1123
|
indexPath,
|
|
849
1124
|
opts.format
|
|
@@ -855,7 +1130,7 @@ var init_dist2 = __esm({
|
|
|
855
1130
|
renderTable(table, opts) {
|
|
856
1131
|
return renderTableModule(
|
|
857
1132
|
table,
|
|
858
|
-
(0,
|
|
1133
|
+
(0, import_validation_core4.resolveAffix)(opts),
|
|
859
1134
|
opts?.target ?? "draft-2020-12",
|
|
860
1135
|
!!opts?.applyDefaults
|
|
861
1136
|
);
|
|
@@ -868,13 +1143,53 @@ var init_dist2 = __esm({
|
|
|
868
1143
|
// src/cli.ts
|
|
869
1144
|
var import_analyzer = require("@drzl/analyzer");
|
|
870
1145
|
var import_generator_orpc = require("@drzl/generator-orpc");
|
|
871
|
-
var
|
|
1146
|
+
var import_chalk3 = __toESM(require("chalk"), 1);
|
|
872
1147
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
873
1148
|
var import_cli_progress = __toESM(require("cli-progress"), 1);
|
|
874
1149
|
var import_commander = require("commander");
|
|
875
1150
|
var path5 = __toESM(require("path"), 1);
|
|
876
1151
|
var import_ora = __toESM(require("ora"), 1);
|
|
877
1152
|
|
|
1153
|
+
// src/validation-options.ts
|
|
1154
|
+
function validationOptions(g, cfg, outDir, caps = {}) {
|
|
1155
|
+
return {
|
|
1156
|
+
outDir,
|
|
1157
|
+
outputHeader: g.outputHeader,
|
|
1158
|
+
format: g.format,
|
|
1159
|
+
schemaSuffix: g.schemaSuffix,
|
|
1160
|
+
fileSuffix: g.fileSuffix,
|
|
1161
|
+
importExtension: g.importExtension,
|
|
1162
|
+
affix: g.affix,
|
|
1163
|
+
coerceDates: g.coerceDates,
|
|
1164
|
+
applyDefaults: g.applyDefaults,
|
|
1165
|
+
duplicateFinder: g.duplicateFinder,
|
|
1166
|
+
nestedSchemas: g.nestedSchemas,
|
|
1167
|
+
nestedDepth: g.nestedDepth,
|
|
1168
|
+
// Only where the generator can act on them, so an unsupported option is absent rather than
|
|
1169
|
+
// present and ignored.
|
|
1170
|
+
...caps.schemaTypes ? {
|
|
1171
|
+
// Needed by both: the reference is resolved relative to the emitted file.
|
|
1172
|
+
schemaPath: cfg.schema,
|
|
1173
|
+
typedJson: g.typedJson,
|
|
1174
|
+
typedColumns: g.typedColumns
|
|
1175
|
+
} : {}
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// src/json-schema-options.ts
|
|
1180
|
+
function jsonSchemaOptions(g, cfg, outDir) {
|
|
1181
|
+
return {
|
|
1182
|
+
// JSON Schema is data, so nothing it emits references a type from the schema module.
|
|
1183
|
+
...validationOptions(g, cfg, outDir, { schemaTypes: false }),
|
|
1184
|
+
target: g.target,
|
|
1185
|
+
components: g.components,
|
|
1186
|
+
document: g.document,
|
|
1187
|
+
// Read only while emitting a document, where it adds `/users/{id}/posts`. The per-table
|
|
1188
|
+
// schemas are flat whatever it says.
|
|
1189
|
+
includeRelations: g.includeRelations
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
|
|
878
1193
|
// src/config.ts
|
|
879
1194
|
var import_validation_core = require("@drzl/validation-core");
|
|
880
1195
|
var fs = __toESM(require("fs"), 1);
|
|
@@ -988,6 +1303,35 @@ var GeneratorSchema = import_zod.z.object({
|
|
|
988
1303
|
target: import_zod.z.enum(["draft-2020-12", "openapi-3.1", "openapi-3.0"]).optional(),
|
|
989
1304
|
/** Also emit `components.ts` for the `json-schema` generator, ready for an OpenAPI document. */
|
|
990
1305
|
components: import_zod.z.boolean().optional(),
|
|
1306
|
+
/**
|
|
1307
|
+
* Also emit the whole OpenAPI document for the `json-schema` generator: paths, verbs, request and
|
|
1308
|
+
* response bodies per table, with `components.schemas` embedded so the file stands alone.
|
|
1309
|
+
*
|
|
1310
|
+
* `true` is the short form. The object form carries the three things a Drizzle schema genuinely
|
|
1311
|
+
* cannot say: what the API is called, where it is served, and which status code that particular
|
|
1312
|
+
* server answers a request that fails its schema with.
|
|
1313
|
+
*/
|
|
1314
|
+
document: import_zod.z.union([
|
|
1315
|
+
import_zod.z.boolean(),
|
|
1316
|
+
import_zod.z.object({
|
|
1317
|
+
enabled: import_zod.z.boolean().optional(),
|
|
1318
|
+
/** `ts` (default) writes a module, `json` the file OpenAPI tooling reads directly. */
|
|
1319
|
+
format: import_zod.z.enum(["ts", "json", "both"]).optional(),
|
|
1320
|
+
info: import_zod.z.object({
|
|
1321
|
+
title: import_zod.z.string().optional(),
|
|
1322
|
+
version: import_zod.z.string().optional(),
|
|
1323
|
+
description: import_zod.z.string().optional()
|
|
1324
|
+
}).strict().optional(),
|
|
1325
|
+
/**
|
|
1326
|
+
* Omitted by default, which the specification reads as a single server at `/`: the
|
|
1327
|
+
* document describes whatever is serving it. A placeholder host would be a fabrication
|
|
1328
|
+
* that tooling then follows.
|
|
1329
|
+
*/
|
|
1330
|
+
servers: import_zod.z.array(import_zod.z.object({ url: import_zod.z.string(), description: import_zod.z.string().optional() }).strict()).optional(),
|
|
1331
|
+
/** 400 by default. 422 is the other defensible reading; exactly one is emitted. */
|
|
1332
|
+
validationStatus: import_zod.z.union([import_zod.z.literal(400), import_zod.z.literal(422)]).optional()
|
|
1333
|
+
}).strict()
|
|
1334
|
+
]).optional(),
|
|
991
1335
|
// service generator specific options
|
|
992
1336
|
path: import_zod.z.string().optional(),
|
|
993
1337
|
dataAccess: import_zod.z.enum(["stub", "drizzle"]).default("stub").optional(),
|
|
@@ -1291,31 +1635,263 @@ function trpcOptions(g, cfg, servicesDir) {
|
|
|
1291
1635
|
};
|
|
1292
1636
|
}
|
|
1293
1637
|
|
|
1294
|
-
// src/
|
|
1295
|
-
|
|
1638
|
+
// src/doctor.ts
|
|
1639
|
+
var import_validation_core2 = require("@drzl/validation-core");
|
|
1640
|
+
var import_chalk = __toESM(require("chalk"), 1);
|
|
1641
|
+
var HANDLED_CODES = /* @__PURE__ */ new Set(["DRZL_ANL_UNKNOWN_COLUMN"]);
|
|
1642
|
+
function splitPath(path6) {
|
|
1643
|
+
if (!path6) return {};
|
|
1644
|
+
const dot = path6.lastIndexOf(".");
|
|
1645
|
+
if (dot <= 0) return { table: path6 };
|
|
1646
|
+
return { table: path6.slice(0, dot), column: path6.slice(dot + 1) };
|
|
1647
|
+
}
|
|
1648
|
+
function namedColumns(parsed) {
|
|
1649
|
+
const out = [];
|
|
1650
|
+
for (const c of parsed.checks) out.push({ column: c.column, scalar: true });
|
|
1651
|
+
for (const s of parsed.sets ?? []) out.push({ column: s.column, scalar: true });
|
|
1652
|
+
for (const l of parsed.lengths ?? []) out.push({ column: l.column, scalar: false });
|
|
1653
|
+
for (const c of parsed.cardinalities ?? []) out.push({ column: c.column, scalar: false });
|
|
1654
|
+
for (const r of parsed.rows ?? []) {
|
|
1655
|
+
out.push({ column: r.left, scalar: false });
|
|
1656
|
+
out.push({ column: r.right, scalar: false });
|
|
1657
|
+
}
|
|
1658
|
+
return out;
|
|
1659
|
+
}
|
|
1660
|
+
function describeShape(c) {
|
|
1661
|
+
if (c.arrayDimensions) return "an array";
|
|
1662
|
+
switch (c.shape?.kind) {
|
|
1663
|
+
case "json":
|
|
1664
|
+
return "a JSON";
|
|
1665
|
+
case "buffer":
|
|
1666
|
+
return "a binary";
|
|
1667
|
+
case "tuple":
|
|
1668
|
+
case "numberObject":
|
|
1669
|
+
return "a structured";
|
|
1670
|
+
case "numberVector":
|
|
1671
|
+
return "a vector";
|
|
1672
|
+
case "bitstring":
|
|
1673
|
+
return "a bit-string";
|
|
1674
|
+
case "byteString":
|
|
1675
|
+
return "a byte-string";
|
|
1676
|
+
case "custom":
|
|
1677
|
+
return "a customType";
|
|
1678
|
+
default:
|
|
1679
|
+
return "a structured";
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
function checkFindings(table) {
|
|
1683
|
+
const out = [];
|
|
1684
|
+
const byName = new Map(table.columns.map((c) => [c.name, c]));
|
|
1685
|
+
for (const k of table.checks ?? []) {
|
|
1686
|
+
const label = k.name ? `"${k.name}"` : "an unnamed constraint";
|
|
1687
|
+
const raw = k.expression ?? "";
|
|
1688
|
+
const expr = raw.trim() ? raw : "(empty)";
|
|
1689
|
+
const parsed = (0, import_validation_core2.parseCheck)(raw, k.name);
|
|
1690
|
+
if (!parsed.ok) {
|
|
1691
|
+
out.push({
|
|
1692
|
+
kind: "check-declined",
|
|
1693
|
+
level: "warn",
|
|
1694
|
+
table: table.tsName,
|
|
1695
|
+
constraint: k.name,
|
|
1696
|
+
message: `CHECK ${label} on "${table.tsName}" is not translated: ${parsed.reason}. Expression: ${expr}`,
|
|
1697
|
+
hint: "Only constraints whose meaning is unambiguous are translated, because a validator enforcing a guess rejects rows the database accepts. Your database still enforces this one; nothing DRZL emits does."
|
|
1698
|
+
});
|
|
1699
|
+
continue;
|
|
1700
|
+
}
|
|
1701
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1702
|
+
for (const { column, scalar } of namedColumns(parsed)) {
|
|
1703
|
+
if (seen.has(column)) continue;
|
|
1704
|
+
seen.add(column);
|
|
1705
|
+
const col = byName.get(column);
|
|
1706
|
+
if (!col) {
|
|
1707
|
+
out.push({
|
|
1708
|
+
kind: "check-unknown-column",
|
|
1709
|
+
level: "warn",
|
|
1710
|
+
table: table.tsName,
|
|
1711
|
+
column,
|
|
1712
|
+
constraint: k.name,
|
|
1713
|
+
message: `CHECK ${label} on "${table.tsName}" names "${column}", which is not a column of that table, so nothing enforces it. Expression: ${expr}`,
|
|
1714
|
+
hint: "A constraint is attached to the field it names. Check the spelling, or move a constraint spanning two tables out of the schema."
|
|
1715
|
+
});
|
|
1716
|
+
continue;
|
|
1717
|
+
}
|
|
1718
|
+
if (scalar && (col.arrayDimensions || col.shape)) {
|
|
1719
|
+
out.push({
|
|
1720
|
+
kind: "check-not-scalar",
|
|
1721
|
+
level: "warn",
|
|
1722
|
+
table: table.tsName,
|
|
1723
|
+
column,
|
|
1724
|
+
constraint: k.name,
|
|
1725
|
+
message: `CHECK ${label} on "${table.tsName}" compares ${describeShape(col)} column "${column}" against a scalar literal, which does not describe it, so it is not translated. Expression: ${expr}`,
|
|
1726
|
+
hint: "On an array column only cardinality(col) is read, since it is the one comparison that is about the array rather than about an element."
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
return out;
|
|
1732
|
+
}
|
|
1733
|
+
function primaryKeyFindings(table) {
|
|
1734
|
+
if (table.readOnly) return [];
|
|
1735
|
+
const pk = table.primaryKey?.columns ?? [];
|
|
1736
|
+
if (!pk.length) {
|
|
1737
|
+
const hasId = table.columns.some((c) => c.name === "id");
|
|
1738
|
+
return [
|
|
1739
|
+
{
|
|
1740
|
+
kind: "no-primary-key",
|
|
1741
|
+
level: "warn",
|
|
1742
|
+
table: table.tsName,
|
|
1743
|
+
message: hasId ? `Table "${table.tsName}" declares no primary key. The service and router generators fall back to a column named "id", which this table happens to have, so they work by coincidence.` : `Table "${table.tsName}" declares no primary key. The service and router generators fall back to a column named "id", which this table does not have, so the generated service will not compile.`,
|
|
1744
|
+
hint: "Declare a primary key, or leave this table out with the config table filter."
|
|
1745
|
+
}
|
|
1746
|
+
];
|
|
1747
|
+
}
|
|
1748
|
+
if (pk.length > 1) {
|
|
1749
|
+
return [
|
|
1750
|
+
{
|
|
1751
|
+
kind: "partial-primary-key",
|
|
1752
|
+
level: "warn",
|
|
1753
|
+
table: table.tsName,
|
|
1754
|
+
message: `Table "${table.tsName}" has a composite primary key (${pk.join(", ")}). The service and router generators key getById, update and delete on "${pk[0]}" alone, so those operations match on part of the key.`,
|
|
1755
|
+
hint: "Treat the generated service as a starting point for this table and widen the key by hand."
|
|
1756
|
+
}
|
|
1757
|
+
];
|
|
1758
|
+
}
|
|
1759
|
+
return [];
|
|
1760
|
+
}
|
|
1761
|
+
function buildDoctorReport(analysis, schemaPath) {
|
|
1762
|
+
const findings = [];
|
|
1763
|
+
const errors = analysis.issues.filter((i) => i.level === "error");
|
|
1764
|
+
for (const i of errors) {
|
|
1765
|
+
findings.push({
|
|
1766
|
+
kind: "analyzer",
|
|
1767
|
+
level: "error",
|
|
1768
|
+
...splitPath(i.path),
|
|
1769
|
+
message: i.message,
|
|
1770
|
+
hint: i.hint
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
for (const i of analysis.issues) {
|
|
1774
|
+
if (i.code !== "DRZL_ANL_UNKNOWN_COLUMN") continue;
|
|
1775
|
+
findings.push({
|
|
1776
|
+
kind: "unknown-column",
|
|
1777
|
+
level: "warn",
|
|
1778
|
+
...splitPath(i.path),
|
|
1779
|
+
message: i.message,
|
|
1780
|
+
hint: i.hint
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
for (const t of analysis.tables) findings.push(...checkFindings(t));
|
|
1784
|
+
for (const t of analysis.tables) findings.push(...primaryKeyFindings(t));
|
|
1785
|
+
for (const i of analysis.issues) {
|
|
1786
|
+
if (i.level === "error" || HANDLED_CODES.has(i.code)) continue;
|
|
1787
|
+
findings.push({
|
|
1788
|
+
kind: "analyzer",
|
|
1789
|
+
level: "warn",
|
|
1790
|
+
...splitPath(i.path),
|
|
1791
|
+
message: i.message,
|
|
1792
|
+
hint: i.hint
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
const columns = analysis.tables.reduce((n, t) => n + t.columns.length, 0);
|
|
1796
|
+
const checks = analysis.tables.reduce((n, t) => n + (t.checks?.length ?? 0), 0);
|
|
1296
1797
|
return {
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
importExtension: g.importExtension,
|
|
1303
|
-
affix: g.affix,
|
|
1304
|
-
coerceDates: g.coerceDates,
|
|
1305
|
-
applyDefaults: g.applyDefaults,
|
|
1306
|
-
duplicateFinder: g.duplicateFinder,
|
|
1307
|
-
nestedSchemas: g.nestedSchemas,
|
|
1308
|
-
nestedDepth: g.nestedDepth,
|
|
1309
|
-
// Only where the generator can act on them, so an unsupported option is absent rather than
|
|
1310
|
-
// present and ignored.
|
|
1311
|
-
...caps.schemaTypes ? {
|
|
1312
|
-
// Needed by both: the reference is resolved relative to the emitted file.
|
|
1313
|
-
schemaPath: cfg.schema,
|
|
1314
|
-
typedJson: g.typedJson,
|
|
1315
|
-
typedColumns: g.typedColumns
|
|
1316
|
-
} : {}
|
|
1798
|
+
schema: schemaPath,
|
|
1799
|
+
dialect: analysis.dialect,
|
|
1800
|
+
ok: findings.length === 0,
|
|
1801
|
+
counts: { tables: analysis.tables.length, columns, checks, findings: findings.length },
|
|
1802
|
+
findings
|
|
1317
1803
|
};
|
|
1318
1804
|
}
|
|
1805
|
+
var SECTIONS = [
|
|
1806
|
+
{
|
|
1807
|
+
kinds: ["unknown-column"],
|
|
1808
|
+
title: "Columns DRZL cannot type",
|
|
1809
|
+
why: "These get a validator that accepts any value."
|
|
1810
|
+
},
|
|
1811
|
+
{
|
|
1812
|
+
kinds: ["check-declined", "check-unknown-column", "check-not-scalar"],
|
|
1813
|
+
title: "CHECK constraints DRZL does not enforce",
|
|
1814
|
+
why: "Your database still enforces these. Nothing DRZL generates does."
|
|
1815
|
+
},
|
|
1816
|
+
{
|
|
1817
|
+
kinds: ["no-primary-key", "partial-primary-key"],
|
|
1818
|
+
title: "Primary keys the generators cannot use",
|
|
1819
|
+
why: "The generated getById, update and delete are keyed on one column."
|
|
1820
|
+
},
|
|
1821
|
+
{
|
|
1822
|
+
kinds: ["analyzer"],
|
|
1823
|
+
title: "Other findings",
|
|
1824
|
+
why: "Reported by the analyzer while reading the schema."
|
|
1825
|
+
}
|
|
1826
|
+
];
|
|
1827
|
+
function wrap(text, indent, first = indent, width = 96) {
|
|
1828
|
+
const lines = [];
|
|
1829
|
+
let line = "";
|
|
1830
|
+
for (const word of text.split(/\s+/)) {
|
|
1831
|
+
if (line && `${line} ${word}`.length + indent.length > width) {
|
|
1832
|
+
lines.push(line);
|
|
1833
|
+
line = word;
|
|
1834
|
+
} else {
|
|
1835
|
+
line = line ? `${line} ${word}` : word;
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
if (line) lines.push(line);
|
|
1839
|
+
return lines.map((l, i) => (i === 0 ? first : indent) + l).join("\n");
|
|
1840
|
+
}
|
|
1841
|
+
function renderDoctorReport(report) {
|
|
1842
|
+
const out = [];
|
|
1843
|
+
const plural = (n, one) => `${n} ${one}${n === 1 ? "" : "s"}`;
|
|
1844
|
+
out.push(import_chalk.default.bold(`DRZL doctor ${report.schema}`));
|
|
1845
|
+
out.push(
|
|
1846
|
+
import_chalk.default.dim(
|
|
1847
|
+
`${report.dialect}, ${plural(report.counts.tables, "table")}, ${plural(report.counts.columns, "column")}, ${plural(report.counts.checks, "CHECK constraint")}`
|
|
1848
|
+
)
|
|
1849
|
+
);
|
|
1850
|
+
out.push("");
|
|
1851
|
+
if (report.ok) {
|
|
1852
|
+
out.push(import_chalk.default.green("Nothing to report."));
|
|
1853
|
+
out.push(import_chalk.default.dim(" Every column has a type DRZL can describe."));
|
|
1854
|
+
out.push(import_chalk.default.dim(" Every CHECK constraint is translated into the generated validators."));
|
|
1855
|
+
out.push(import_chalk.default.dim(" Every table has a primary key the generators can use."));
|
|
1856
|
+
return out.join("\n");
|
|
1857
|
+
}
|
|
1858
|
+
const fatal = report.findings.filter((f) => f.level === "error");
|
|
1859
|
+
if (fatal.length) {
|
|
1860
|
+
out.push(import_chalk.default.red("DRZL could not read this schema"));
|
|
1861
|
+
out.push(import_chalk.default.dim(" Nothing else could be checked."));
|
|
1862
|
+
out.push("");
|
|
1863
|
+
for (const f of fatal) {
|
|
1864
|
+
out.push(wrap(f.message, " ", ` ${import_chalk.default.dim("-")} `));
|
|
1865
|
+
if (f.hint) out.push(import_chalk.default.dim(wrap(f.hint, " ")));
|
|
1866
|
+
}
|
|
1867
|
+
out.push("");
|
|
1868
|
+
}
|
|
1869
|
+
for (const section of SECTIONS) {
|
|
1870
|
+
const mine = report.findings.filter(
|
|
1871
|
+
(f) => f.level !== "error" && section.kinds.includes(f.kind)
|
|
1872
|
+
);
|
|
1873
|
+
if (!mine.length) continue;
|
|
1874
|
+
out.push(import_chalk.default.yellow(`${section.title} (${mine.length})`));
|
|
1875
|
+
out.push(import_chalk.default.dim(` ${section.why}`));
|
|
1876
|
+
out.push("");
|
|
1877
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1878
|
+
for (const f of mine) {
|
|
1879
|
+
const key = f.hint ?? "";
|
|
1880
|
+
groups.set(key, [...groups.get(key) ?? [], f]);
|
|
1881
|
+
}
|
|
1882
|
+
for (const [hint, items] of groups) {
|
|
1883
|
+
for (const f of items) out.push(wrap(f.message, " ", ` ${import_chalk.default.dim("-")} `));
|
|
1884
|
+
if (hint) out.push(import_chalk.default.dim(wrap(hint, " ")));
|
|
1885
|
+
out.push("");
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
out.push(
|
|
1889
|
+
import_chalk.default.bold(`${plural(report.counts.findings, "finding")} in ${report.schema}.`) + import_chalk.default.dim(
|
|
1890
|
+
fatal.length ? " Fix the error above and run this again." : " None of these stop DRZL generating; they are what it will not check for you."
|
|
1891
|
+
)
|
|
1892
|
+
);
|
|
1893
|
+
return out.join("\n");
|
|
1894
|
+
}
|
|
1319
1895
|
|
|
1320
1896
|
// src/drift.ts
|
|
1321
1897
|
var import_node_fs = require("fs");
|
|
@@ -1393,7 +1969,7 @@ async function loadGenerator(specifier, load) {
|
|
|
1393
1969
|
}
|
|
1394
1970
|
|
|
1395
1971
|
// src/sponsor.ts
|
|
1396
|
-
var
|
|
1972
|
+
var import_chalk2 = __toESM(require("chalk"), 1);
|
|
1397
1973
|
var import_node_fs2 = require("fs");
|
|
1398
1974
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
1399
1975
|
var CACHE_DIR = import_node_path2.default.join(process.cwd(), "node_modules", ".cache", "@drzl");
|
|
@@ -1407,9 +1983,9 @@ var tips = [
|
|
|
1407
1983
|
"Need JSON Schema or OpenAPI? The json-schema generator emits both, with no runtime dependency.",
|
|
1408
1984
|
"Use output headers to track generated files and trim noisy diffs."
|
|
1409
1985
|
];
|
|
1410
|
-
var green = (msg) =>
|
|
1411
|
-
var cyan = (msg) =>
|
|
1412
|
-
var gray = (msg) =>
|
|
1986
|
+
var green = (msg) => import_chalk2.default.hex("#6ee7b7")(msg);
|
|
1987
|
+
var cyan = (msg) => import_chalk2.default.cyan(msg);
|
|
1988
|
+
var gray = (msg) => import_chalk2.default.gray(msg);
|
|
1413
1989
|
function maybeShowSponsorMessage({
|
|
1414
1990
|
reason = "generate",
|
|
1415
1991
|
minIntervalMs = DEFAULT_INTERVAL_MS,
|
|
@@ -1498,13 +2074,13 @@ var CLI_VERSION = readCliVersion();
|
|
|
1498
2074
|
function reportGeneratorFailure(kind, e) {
|
|
1499
2075
|
if (e instanceof GeneratorNotInstalledError) {
|
|
1500
2076
|
console.error(
|
|
1501
|
-
|
|
1502
|
-
|
|
2077
|
+
import_chalk3.default.red(`The ${kind} generator is not installed.`),
|
|
2078
|
+
import_chalk3.default.yellow(`
|
|
1503
2079
|
Install with: npm install ${e.specifier}`)
|
|
1504
2080
|
);
|
|
1505
2081
|
return;
|
|
1506
2082
|
}
|
|
1507
|
-
console.error(
|
|
2083
|
+
console.error(import_chalk3.default.red(`The ${kind} generator failed:`), e?.message ?? e);
|
|
1508
2084
|
}
|
|
1509
2085
|
var program = new import_commander.Command();
|
|
1510
2086
|
program.name("drzl").description("DRZL - Drizzle Developer Toolkit").version(CLI_VERSION);
|
|
@@ -1531,9 +2107,9 @@ program.command("analyze").argument("<schema>", "path to drizzle schema (TS)").o
|
|
|
1531
2107
|
} else if (opts.out) {
|
|
1532
2108
|
const fs3 = await import("fs/promises");
|
|
1533
2109
|
await fs3.writeFile(opts.out, json, "utf8");
|
|
1534
|
-
spinner?.succeed(
|
|
2110
|
+
spinner?.succeed(import_chalk3.default.green(`Analysis written to ${opts.out} in ${ms}ms`));
|
|
1535
2111
|
} else {
|
|
1536
|
-
spinner?.succeed(
|
|
2112
|
+
spinner?.succeed(import_chalk3.default.green(`Analyzed in ${ms}ms`));
|
|
1537
2113
|
console.log(json);
|
|
1538
2114
|
}
|
|
1539
2115
|
process.exit(res.issues.some((i) => i.level === "error") ? 2 : 0);
|
|
@@ -1543,7 +2119,48 @@ program.command("analyze").argument("<schema>", "path to drizzle schema (TS)").o
|
|
|
1543
2119
|
console.log(JSON.stringify({ event: "error", code: "DRZL_CLI_ANALYZE", message: msg }));
|
|
1544
2120
|
else
|
|
1545
2121
|
console.error(
|
|
1546
|
-
|
|
2122
|
+
import_chalk3.default.red("Analyze failed (DRZL_CLI_ANALYZE):"),
|
|
2123
|
+
msg,
|
|
2124
|
+
"\nTip: run with --json for structured output."
|
|
2125
|
+
);
|
|
2126
|
+
process.exit(1);
|
|
2127
|
+
}
|
|
2128
|
+
});
|
|
2129
|
+
program.command("doctor").description("Report what DRZL cannot type or enforce in your schema, and why").argument("[schema]", "path to drizzle schema (TS); defaults to the schema in drzl.config").option("-c, --config <path>", "path to drzl.config, read when no schema argument is given").option("--json", "print the report as JSON instead of prose", false).option("--strict", "exit 2 when anything is reported", false).action(async (schema, opts) => {
|
|
2130
|
+
try {
|
|
2131
|
+
let target = schema;
|
|
2132
|
+
if (!target) {
|
|
2133
|
+
const cfg = await loadConfig(opts.config);
|
|
2134
|
+
target = cfg?.schema;
|
|
2135
|
+
}
|
|
2136
|
+
if (!target) {
|
|
2137
|
+
const msg = "No schema given. Pass a path, or run from a directory with a drzl.config.";
|
|
2138
|
+
if (opts.json)
|
|
2139
|
+
console.log(JSON.stringify({ event: "error", code: "DRZL_CLI_DOCTOR", message: msg }));
|
|
2140
|
+
else console.error(import_chalk3.default.red("Doctor failed (DRZL_CLI_DOCTOR):"), msg);
|
|
2141
|
+
process.exit(1);
|
|
2142
|
+
return;
|
|
2143
|
+
}
|
|
2144
|
+
const analyzer = new import_analyzer.SchemaAnalyzer(target);
|
|
2145
|
+
const analysis = await analyzer.analyze({
|
|
2146
|
+
includeRelations: true,
|
|
2147
|
+
validateConstraints: true
|
|
2148
|
+
});
|
|
2149
|
+
const report = buildDoctorReport(analysis, target);
|
|
2150
|
+
if (opts.json) console.log(JSON.stringify(report, null, 2));
|
|
2151
|
+
else console.log(renderDoctorReport(report));
|
|
2152
|
+
if (report.findings.some((f) => f.level === "error")) {
|
|
2153
|
+
process.exit(1);
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
process.exit(opts.strict && report.findings.length ? 2 : 0);
|
|
2157
|
+
} catch (e) {
|
|
2158
|
+
const msg = e?.message ?? String(e);
|
|
2159
|
+
if (opts.json)
|
|
2160
|
+
console.log(JSON.stringify({ event: "error", code: "DRZL_CLI_DOCTOR", message: msg }));
|
|
2161
|
+
else
|
|
2162
|
+
console.error(
|
|
2163
|
+
import_chalk3.default.red("Doctor failed (DRZL_CLI_DOCTOR):"),
|
|
1547
2164
|
msg,
|
|
1548
2165
|
"\nTip: run with --json for structured output."
|
|
1549
2166
|
);
|
|
@@ -1558,7 +2175,7 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
1558
2175
|
const cfg = await loadConfig(opts.config);
|
|
1559
2176
|
if (!cfg) {
|
|
1560
2177
|
console.error(
|
|
1561
|
-
|
|
2178
|
+
import_chalk3.default.red("No config found (DRZL_CFG_001). Create drzl.config.ts or pass --config.")
|
|
1562
2179
|
);
|
|
1563
2180
|
process.exit(2);
|
|
1564
2181
|
return;
|
|
@@ -1603,8 +2220,8 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
1603
2220
|
onProgress: ({ index }) => progress.update(index)
|
|
1604
2221
|
});
|
|
1605
2222
|
progress.stop();
|
|
1606
|
-
(0, import_ora.default)().succeed(
|
|
1607
|
-
files.forEach((f) => console.log(" -",
|
|
2223
|
+
(0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (${g.kind}): ${files.length} files`));
|
|
2224
|
+
files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
|
|
1608
2225
|
} else if (g.kind === "trpc") {
|
|
1609
2226
|
try {
|
|
1610
2227
|
const { TRPCGenerator: TRPCGenerator2 } = await loadGenerator(
|
|
@@ -1617,8 +2234,8 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
1617
2234
|
onProgress: ({ index }) => progress.update(index)
|
|
1618
2235
|
});
|
|
1619
2236
|
progress.stop();
|
|
1620
|
-
(0, import_ora.default)().succeed(
|
|
1621
|
-
files.forEach((f) => console.log(" -",
|
|
2237
|
+
(0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (trpc): ${files.length} files`));
|
|
2238
|
+
files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
|
|
1622
2239
|
} catch (e) {
|
|
1623
2240
|
progress.stop();
|
|
1624
2241
|
reportGeneratorFailure(g.kind, e);
|
|
@@ -1647,8 +2264,8 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
1647
2264
|
databaseInjection: g.databaseInjection
|
|
1648
2265
|
});
|
|
1649
2266
|
progress.stop();
|
|
1650
|
-
(0, import_ora.default)().succeed(
|
|
1651
|
-
files.forEach((f) => console.log(" -",
|
|
2267
|
+
(0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (service): ${files.length} files`));
|
|
2268
|
+
files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
|
|
1652
2269
|
} catch (e) {
|
|
1653
2270
|
progress.stop();
|
|
1654
2271
|
reportGeneratorFailure(g.kind, e);
|
|
@@ -1666,8 +2283,8 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
1666
2283
|
validationOptions(g, cfg, target, { schemaTypes: true })
|
|
1667
2284
|
);
|
|
1668
2285
|
progress.stop();
|
|
1669
|
-
(0, import_ora.default)().succeed(
|
|
1670
|
-
files.forEach((f) => console.log(" -",
|
|
2286
|
+
(0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (zod): ${files.length} files`));
|
|
2287
|
+
files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
|
|
1671
2288
|
} catch (e) {
|
|
1672
2289
|
progress.stop();
|
|
1673
2290
|
reportGeneratorFailure(g.kind, e);
|
|
@@ -1685,8 +2302,8 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
1685
2302
|
validationOptions(g, cfg, target, { schemaTypes: true })
|
|
1686
2303
|
);
|
|
1687
2304
|
progress.stop();
|
|
1688
|
-
(0, import_ora.default)().succeed(
|
|
1689
|
-
files.forEach((f) => console.log(" -",
|
|
2305
|
+
(0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (valibot): ${files.length} files`));
|
|
2306
|
+
files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
|
|
1690
2307
|
} catch (e) {
|
|
1691
2308
|
progress.stop();
|
|
1692
2309
|
reportGeneratorFailure(g.kind, e);
|
|
@@ -1704,8 +2321,8 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
1704
2321
|
validationOptions(g, cfg, target, { schemaTypes: false })
|
|
1705
2322
|
);
|
|
1706
2323
|
progress.stop();
|
|
1707
|
-
(0, import_ora.default)().succeed(
|
|
1708
|
-
files.forEach((f) => console.log(" -",
|
|
2324
|
+
(0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (arktype): ${files.length} files`));
|
|
2325
|
+
files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
|
|
1709
2326
|
} catch (e) {
|
|
1710
2327
|
progress.stop();
|
|
1711
2328
|
reportGeneratorFailure(g.kind, e);
|
|
@@ -1719,15 +2336,10 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
1719
2336
|
);
|
|
1720
2337
|
const gen = new JsonSchemaGenerator2(analysis);
|
|
1721
2338
|
const target = g.path ?? "src/validators/json-schema";
|
|
1722
|
-
const files = await gen.generate(
|
|
1723
|
-
// JSON Schema is data, so nothing here references a type from the schema module.
|
|
1724
|
-
...validationOptions(g, cfg, target, { schemaTypes: false }),
|
|
1725
|
-
target: g.target,
|
|
1726
|
-
components: g.components
|
|
1727
|
-
});
|
|
2339
|
+
const files = await gen.generate(jsonSchemaOptions(g, cfg, target));
|
|
1728
2340
|
progress.stop();
|
|
1729
|
-
(0, import_ora.default)().succeed(
|
|
1730
|
-
files.forEach((f) => console.log(" -",
|
|
2341
|
+
(0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (json-schema): ${files.length} files`));
|
|
2342
|
+
files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
|
|
1731
2343
|
} catch (e) {
|
|
1732
2344
|
progress.stop();
|
|
1733
2345
|
reportGeneratorFailure(g.kind, e);
|
|
@@ -1745,8 +2357,8 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
1745
2357
|
validationOptions(g, cfg, target, { schemaTypes: true })
|
|
1746
2358
|
);
|
|
1747
2359
|
progress.stop();
|
|
1748
|
-
(0, import_ora.default)().succeed(
|
|
1749
|
-
files.forEach((f) => console.log(" -",
|
|
2360
|
+
(0, import_ora.default)().succeed(import_chalk3.default.green(`Generated (typebox): ${files.length} files`));
|
|
2361
|
+
files.forEach((f) => console.log(" -", import_chalk3.default.cyan(f)));
|
|
1750
2362
|
} catch (e) {
|
|
1751
2363
|
progress.stop();
|
|
1752
2364
|
reportGeneratorFailure(g.kind, e);
|
|
@@ -1759,22 +2371,22 @@ program.command("generate").description("Run configured generators (drzl.config.
|
|
|
1759
2371
|
const drift = diffSnapshots(driftBefore, after);
|
|
1760
2372
|
await restoreSnapshot(driftBefore, after);
|
|
1761
2373
|
if (drift.length) {
|
|
1762
|
-
console.error(
|
|
2374
|
+
console.error(import_chalk3.default.red(`
|
|
1763
2375
|
Generated output is out of date (${drift.length} file(s)):`));
|
|
1764
2376
|
for (const d of drift) {
|
|
1765
2377
|
const mark = d.status === "added" ? "+" : d.status === "removed" ? "-" : "~";
|
|
1766
2378
|
console.error(
|
|
1767
|
-
` ${mark} ${
|
|
2379
|
+
` ${mark} ${import_chalk3.default.yellow(d.status.padEnd(8))} ${path5.relative(process.cwd(), d.file)}`
|
|
1768
2380
|
);
|
|
1769
2381
|
}
|
|
1770
2382
|
console.error(
|
|
1771
|
-
|
|
2383
|
+
import_chalk3.default.dim(
|
|
1772
2384
|
"\nRun `drzl generate` and commit the result. Nothing was written by this check."
|
|
1773
2385
|
)
|
|
1774
2386
|
);
|
|
1775
2387
|
process.exit(1);
|
|
1776
2388
|
}
|
|
1777
|
-
console.log(
|
|
2389
|
+
console.log(import_chalk3.default.green("Generated output is up to date."));
|
|
1778
2390
|
return;
|
|
1779
2391
|
}
|
|
1780
2392
|
if (cfg.generators.length) {
|
|
@@ -1782,7 +2394,7 @@ Generated output is out of date (${drift.length} file(s)):`));
|
|
|
1782
2394
|
}
|
|
1783
2395
|
} catch (e) {
|
|
1784
2396
|
console.error(
|
|
1785
|
-
|
|
2397
|
+
import_chalk3.default.red("Generate failed (DRZL_GEN_001):"),
|
|
1786
2398
|
e?.message ?? e,
|
|
1787
2399
|
"\nTip: check your drzl.config.ts and template path."
|
|
1788
2400
|
);
|
|
@@ -1802,10 +2414,10 @@ program.command("generate:orpc").argument("<schema>", "path to drizzle schema (T
|
|
|
1802
2414
|
template: opts.template,
|
|
1803
2415
|
includeRelations: !!opts.includeRelations
|
|
1804
2416
|
});
|
|
1805
|
-
console.log(
|
|
2417
|
+
console.log(import_chalk3.default.green(`Generated:`), files.map((f) => import_chalk3.default.cyan(f)).join(", "));
|
|
1806
2418
|
maybeShowSponsorMessage({ reason: "generate:orpc" });
|
|
1807
2419
|
} catch (e) {
|
|
1808
|
-
console.error(
|
|
2420
|
+
console.error(import_chalk3.default.red("Generate orpc failed:"), e?.message ?? e);
|
|
1809
2421
|
process.exit(1);
|
|
1810
2422
|
}
|
|
1811
2423
|
});
|
|
@@ -1829,7 +2441,7 @@ program.command("generate:trpc").argument("<schema>", "path to drizzle schema (T
|
|
|
1829
2441
|
// cannot become the branch that forgets it.
|
|
1830
2442
|
servicesDir: opts.servicesDir
|
|
1831
2443
|
});
|
|
1832
|
-
console.log(
|
|
2444
|
+
console.log(import_chalk3.default.green(`Generated:`), files.map((f) => import_chalk3.default.cyan(f)).join(", "));
|
|
1833
2445
|
maybeShowSponsorMessage({ reason: "generate:trpc" });
|
|
1834
2446
|
} catch (e) {
|
|
1835
2447
|
reportGeneratorFailure("trpc", e);
|
|
@@ -1839,7 +2451,7 @@ program.command("generate:trpc").argument("<schema>", "path to drizzle schema (T
|
|
|
1839
2451
|
program.command("watch").description("Watch schema and regenerate on changes").option("-c, --config <path>", "path to drzl.config").option("--pipeline <name>", "all | analyze | generate-orpc | generate-trpc", "all").option("--debounce <ms>", "debounce ms", "200").option("--json", "emit JSON logs", false).option("--poll", "force polling (helps WSL/Docker/remote FS)", false).action(async (opts) => {
|
|
1840
2452
|
let cfg = await loadConfig(opts.config);
|
|
1841
2453
|
if (!cfg) {
|
|
1842
|
-
console.error(
|
|
2454
|
+
console.error(import_chalk3.default.red("No config found. Create drzl.config.ts or pass --config."));
|
|
1843
2455
|
process.exit(2);
|
|
1844
2456
|
return;
|
|
1845
2457
|
}
|
|
@@ -1931,7 +2543,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
1931
2543
|
})
|
|
1932
2544
|
);
|
|
1933
2545
|
} else {
|
|
1934
|
-
console.log(
|
|
2546
|
+
console.log(import_chalk3.default.green("Analyze complete."));
|
|
1935
2547
|
}
|
|
1936
2548
|
return;
|
|
1937
2549
|
}
|
|
@@ -1961,8 +2573,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
1961
2573
|
servicesDir
|
|
1962
2574
|
});
|
|
1963
2575
|
opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
|
|
1964
|
-
|
|
1965
|
-
files.map((f) =>
|
|
2576
|
+
import_chalk3.default.green(`Generated (${g.kind}):`),
|
|
2577
|
+
files.map((f) => import_chalk3.default.cyan(f)).join(", ")
|
|
1966
2578
|
);
|
|
1967
2579
|
newFiles.push(...files);
|
|
1968
2580
|
} else if (g.kind === "trpc") {
|
|
@@ -1974,8 +2586,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
1974
2586
|
const gen = new TRPCGenerator2(analysis);
|
|
1975
2587
|
const { files } = await gen.generate(trpcOptions(g, cfg, servicesDir));
|
|
1976
2588
|
opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
|
|
1977
|
-
|
|
1978
|
-
files.map((f) =>
|
|
2589
|
+
import_chalk3.default.green(`Generated (trpc): ${files.length} files`),
|
|
2590
|
+
files.map((f) => import_chalk3.default.cyan(f)).join(", ")
|
|
1979
2591
|
);
|
|
1980
2592
|
newFiles.push(...files);
|
|
1981
2593
|
} catch (e) {
|
|
@@ -2001,8 +2613,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
2001
2613
|
databaseInjection: g.databaseInjection
|
|
2002
2614
|
});
|
|
2003
2615
|
opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
|
|
2004
|
-
|
|
2005
|
-
files.map((f) =>
|
|
2616
|
+
import_chalk3.default.green(`Generated (service): ${files.length} files`),
|
|
2617
|
+
files.map((f) => import_chalk3.default.cyan(f)).join(", ")
|
|
2006
2618
|
);
|
|
2007
2619
|
newFiles.push(...files);
|
|
2008
2620
|
} catch (e) {
|
|
@@ -2021,8 +2633,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
2021
2633
|
validationOptions(g, cfg, target, { schemaTypes: true })
|
|
2022
2634
|
);
|
|
2023
2635
|
opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
|
|
2024
|
-
|
|
2025
|
-
files.map((f) =>
|
|
2636
|
+
import_chalk3.default.green(`Generated (zod): ${files.length} files`),
|
|
2637
|
+
files.map((f) => import_chalk3.default.cyan(f)).join(", ")
|
|
2026
2638
|
);
|
|
2027
2639
|
newFiles.push(...files);
|
|
2028
2640
|
} catch (e) {
|
|
@@ -2041,8 +2653,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
2041
2653
|
validationOptions(g, cfg, target, { schemaTypes: true })
|
|
2042
2654
|
);
|
|
2043
2655
|
opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
|
|
2044
|
-
|
|
2045
|
-
files.map((f) =>
|
|
2656
|
+
import_chalk3.default.green(`Generated (valibot): ${files.length} files`),
|
|
2657
|
+
files.map((f) => import_chalk3.default.cyan(f)).join(", ")
|
|
2046
2658
|
);
|
|
2047
2659
|
newFiles.push(...files);
|
|
2048
2660
|
} catch (e) {
|
|
@@ -2061,8 +2673,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
2061
2673
|
validationOptions(g, cfg, target, { schemaTypes: false })
|
|
2062
2674
|
);
|
|
2063
2675
|
opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
|
|
2064
|
-
|
|
2065
|
-
files.map((f) =>
|
|
2676
|
+
import_chalk3.default.green(`Generated (arktype): ${files.length} files`),
|
|
2677
|
+
files.map((f) => import_chalk3.default.cyan(f)).join(", ")
|
|
2066
2678
|
);
|
|
2067
2679
|
newFiles.push(...files);
|
|
2068
2680
|
} catch (e) {
|
|
@@ -2081,8 +2693,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
2081
2693
|
validationOptions(g, cfg, target, { schemaTypes: true })
|
|
2082
2694
|
);
|
|
2083
2695
|
opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
|
|
2084
|
-
|
|
2085
|
-
files.map((f) =>
|
|
2696
|
+
import_chalk3.default.green(`Generated (typebox): ${files.length} files`),
|
|
2697
|
+
files.map((f) => import_chalk3.default.cyan(f)).join(", ")
|
|
2086
2698
|
);
|
|
2087
2699
|
newFiles.push(...files);
|
|
2088
2700
|
} catch (e) {
|
|
@@ -2097,15 +2709,10 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
2097
2709
|
);
|
|
2098
2710
|
const gen = new JsonSchemaGenerator2(analysis);
|
|
2099
2711
|
const target = g.path ?? "src/validators/json-schema";
|
|
2100
|
-
const files = await gen.generate(
|
|
2101
|
-
// JSON Schema is data, so nothing here references a type from the schema module.
|
|
2102
|
-
...validationOptions(g, cfg, target, { schemaTypes: false }),
|
|
2103
|
-
target: g.target,
|
|
2104
|
-
components: g.components
|
|
2105
|
-
});
|
|
2712
|
+
const files = await gen.generate(jsonSchemaOptions(g, cfg, target));
|
|
2106
2713
|
opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
|
|
2107
|
-
|
|
2108
|
-
files.map((f) =>
|
|
2714
|
+
import_chalk3.default.green(`Generated (json-schema): ${files.length} files`),
|
|
2715
|
+
files.map((f) => import_chalk3.default.cyan(f)).join(", ")
|
|
2109
2716
|
);
|
|
2110
2717
|
newFiles.push(...files);
|
|
2111
2718
|
} catch (e) {
|
|
@@ -2117,8 +2724,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
2117
2724
|
const added = newFiles.filter((f) => !lastFiles.includes(f));
|
|
2118
2725
|
const removed = lastFiles.filter((f) => !newFiles.includes(f));
|
|
2119
2726
|
opts.json ? console.log(JSON.stringify({ event: "diff", added, removed })) : (() => {
|
|
2120
|
-
if (added.length) console.log(
|
|
2121
|
-
if (removed.length) console.log(
|
|
2727
|
+
if (added.length) console.log(import_chalk3.default.blue(`Added: ${added.join(", ")}`));
|
|
2728
|
+
if (removed.length) console.log(import_chalk3.default.yellow(`Removed: ${removed.join(", ")}`));
|
|
2122
2729
|
})();
|
|
2123
2730
|
if (newFiles.length && !opts.json) {
|
|
2124
2731
|
const reason = opts.pipeline && opts.pipeline !== "all" ? `watch:${opts.pipeline}` : "watch";
|
|
@@ -2126,7 +2733,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
2126
2733
|
}
|
|
2127
2734
|
lastFiles = newFiles;
|
|
2128
2735
|
} catch (e) {
|
|
2129
|
-
opts.json ? console.log(JSON.stringify({ event: "error", message: String(e?.message ?? e) })) : console.error(
|
|
2736
|
+
opts.json ? console.log(JSON.stringify({ event: "error", message: String(e?.message ?? e) })) : console.error(import_chalk3.default.red("Watch pipeline failed:"), e?.message ?? e);
|
|
2130
2737
|
}
|
|
2131
2738
|
};
|
|
2132
2739
|
const debounced = Number(opts.debounce) || 200;
|
|
@@ -2151,12 +2758,12 @@ program.command("watch").description("Watch schema and regenerate on changes").o
|
|
|
2151
2758
|
);
|
|
2152
2759
|
} else {
|
|
2153
2760
|
console.log(
|
|
2154
|
-
|
|
2761
|
+
import_chalk3.default.gray(
|
|
2155
2762
|
"Watching:\n " + Array.from(currentTargets).map((p) => path5.relative(process.cwd(), p)).join("\n ")
|
|
2156
2763
|
)
|
|
2157
2764
|
);
|
|
2158
2765
|
}
|
|
2159
|
-
watcher.on("add", (p) => trigger(p)).on("change", (p) => trigger(p)).on("unlink", (p) => trigger(p)).on("error", (err) => console.error(
|
|
2766
|
+
watcher.on("add", (p) => trigger(p)).on("change", (p) => trigger(p)).on("unlink", (p) => trigger(p)).on("error", (err) => console.error(import_chalk3.default.red("Watcher error:"), err));
|
|
2160
2767
|
await run();
|
|
2161
2768
|
});
|
|
2162
2769
|
program.command("init").description("Scaffold a drzl.config.ts").option("-y, --yes", "accept defaults").action(async (_opts) => {
|
|
@@ -2176,9 +2783,9 @@ program.command("init").description("Scaffold a drzl.config.ts").option("-y, --y
|
|
|
2176
2783
|
`;
|
|
2177
2784
|
try {
|
|
2178
2785
|
await fs3.writeFile(target, template, { flag: "wx" });
|
|
2179
|
-
console.log(
|
|
2786
|
+
console.log(import_chalk3.default.green(`Created ${target}`));
|
|
2180
2787
|
} catch (e) {
|
|
2181
|
-
console.error(
|
|
2788
|
+
console.error(import_chalk3.default.red("Init failed:"), e?.message ?? e);
|
|
2182
2789
|
process.exit(1);
|
|
2183
2790
|
}
|
|
2184
2791
|
});
|
|
@@ -2186,13 +2793,14 @@ function reportWideColumns(issues) {
|
|
|
2186
2793
|
const wide = issues.filter((i) => i.code === "DRZL_ANL_UNKNOWN_COLUMN");
|
|
2187
2794
|
if (!wide.length) return;
|
|
2188
2795
|
console.warn(
|
|
2189
|
-
|
|
2796
|
+
import_chalk3.default.yellow(`
|
|
2190
2797
|
${wide.length} column${wide.length === 1 ? "" : "s"} could not be typed:`)
|
|
2191
2798
|
);
|
|
2192
|
-
for (const i of wide.slice(0, 10)) console.warn(
|
|
2193
|
-
if (wide.length > 10) console.warn(
|
|
2799
|
+
for (const i of wide.slice(0, 10)) console.warn(import_chalk3.default.gray(` - ${i.message}`));
|
|
2800
|
+
if (wide.length > 10) console.warn(import_chalk3.default.gray(` ... and ${wide.length - 10} more`));
|
|
2194
2801
|
const hints = [...new Set(wide.map((i) => i.hint).filter(Boolean))];
|
|
2195
|
-
for (const h of hints) console.warn(
|
|
2802
|
+
for (const h of hints) console.warn(import_chalk3.default.gray(` ${h}`));
|
|
2803
|
+
console.warn(import_chalk3.default.gray(" Run `drzl doctor` for the full report."));
|
|
2196
2804
|
}
|
|
2197
2805
|
program.parseAsync(process.argv);
|
|
2198
2806
|
//# sourceMappingURL=cli.cjs.map
|