@drzl/validation-core 3.18.0 → 3.20.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/index.cjs +368 -98
- package/dist/index.d.cts +246 -2
- package/dist/index.d.ts +246 -2
- package/dist/index.js +364 -98
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,99 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Table as Table$1, Column, Relation, Analysis } from '@drzl/analyzer';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which columns carry a nominal brand, and what that brand is called.
|
|
5
|
+
*
|
|
6
|
+
* A brand is a type-level marker with no runtime existence at all. Measured on zod 4.4.3,
|
|
7
|
+
* `z.number().brand('X')` returns the *same object* it was called on, and the parsed value of
|
|
8
|
+
* `1` is `1`; the same holds for valibot 1.4.2, arktype 2.2.3 and effect 3.x, each of which
|
|
9
|
+
* hands the value back unchanged. So everything decided here is decided for the benefit of
|
|
10
|
+
* `tsc`, and nothing in it can be checked by parsing a value.
|
|
11
|
+
*
|
|
12
|
+
* That is exactly why the decisions live in one module rather than five. A plan that gives
|
|
13
|
+
* `posts.authorId` the wrong token emits a schema whose types are confidently wrong in every
|
|
14
|
+
* generator at once, and no runtime assertion anywhere in this repository would notice.
|
|
15
|
+
*
|
|
16
|
+
* ## The token
|
|
17
|
+
*
|
|
18
|
+
* `<export name>.<column>`, verbatim: `users.id`, `orgMembers.orgId`. Nothing is transformed,
|
|
19
|
+
* singularised or re-cased, which is the whole reason to spell it this way. Two Drizzle tables
|
|
20
|
+
* cannot share an export name inside one schema module and two columns cannot share a name
|
|
21
|
+
* inside one table, so the token is unique by construction and the question of what to do when
|
|
22
|
+
* two names collide after a transformation does not arise. It also reads back as the thing the
|
|
23
|
+
* user wrote:
|
|
24
|
+
*
|
|
25
|
+
* Type 'number & $brand<"posts.id">' is not assignable to type 'number & $brand<"users.id">'
|
|
26
|
+
*
|
|
27
|
+
* The transformation question does arise for the exported *alias*, which has to be a TypeScript
|
|
28
|
+
* identifier. See `aliasesFor`.
|
|
29
|
+
*
|
|
30
|
+
* ## What a foreign key gets
|
|
31
|
+
*
|
|
32
|
+
* The token of the column it references, not one of its own. `posts.authorId` holds a user's id,
|
|
33
|
+
* so it is a `users.id`; giving it `posts.authorId` would invent a type nothing else in the
|
|
34
|
+
* schema produces and the feature would check nothing. That rule is applied transitively and it
|
|
35
|
+
* beats the column being part of its own table's key, which is what makes a join table keyed on
|
|
36
|
+
* two foreign keys come out right.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
interface BrandingOptions {
|
|
40
|
+
enabled?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Also brand foreign key columns with the brand of the column they reference, so a
|
|
43
|
+
* `Post.authorId` is a `users.id` rather than a `number`.
|
|
44
|
+
*
|
|
45
|
+
* On by default, because it is where nearly all of the value is: a primary key branded alone
|
|
46
|
+
* only stops you passing a `PostId` to something wanting a `UserId`, while branding the
|
|
47
|
+
* foreign keys is what makes the ids flowing between tables carry their origin.
|
|
48
|
+
*/
|
|
49
|
+
foreignKeys?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Also export a named alias per branded key, e.g. `export type UsersId = ...`.
|
|
52
|
+
*
|
|
53
|
+
* On by default. Without one the type is only nameable as an indexed access into the select
|
|
54
|
+
* type, which works and reads badly.
|
|
55
|
+
*/
|
|
56
|
+
aliases?: boolean;
|
|
57
|
+
}
|
|
58
|
+
type BrandingOption = boolean | BrandingOptions;
|
|
59
|
+
interface ResolvedBranding {
|
|
60
|
+
foreignKeys: boolean;
|
|
61
|
+
aliases: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** One `export type <alias> = ...` line a table's module owns. */
|
|
64
|
+
interface BrandAlias {
|
|
65
|
+
/** The identifier, e.g. `UsersId`. */
|
|
66
|
+
alias: string;
|
|
67
|
+
/** The column it is the type of, e.g. `id`. */
|
|
68
|
+
column: string;
|
|
69
|
+
/** The token that column carries, which for an owned alias is `<tsName>.<column>`. */
|
|
70
|
+
token: string;
|
|
71
|
+
}
|
|
72
|
+
interface BrandPlan {
|
|
73
|
+
/** The brand token for one column, or nothing when it carries none. */
|
|
74
|
+
brandOf(tsName: string, columnName: string): string | undefined;
|
|
75
|
+
/** The aliases this table's module exports. Empty when it owns no brand. */
|
|
76
|
+
aliasesFor(tsName: string): BrandAlias[];
|
|
77
|
+
/** Whether anything at all is branded, so a generator can skip its emit path entirely. */
|
|
78
|
+
readonly any: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* What was withheld and why, in the words a user can act on.
|
|
81
|
+
*
|
|
82
|
+
* Withholding is the normal outcome for several shapes here and it is silent in the emitted
|
|
83
|
+
* file, so a run that quietly brands less than the user expects has to say so somewhere.
|
|
84
|
+
*/
|
|
85
|
+
readonly notes: readonly string[];
|
|
86
|
+
}
|
|
87
|
+
/** `{ foreignKeys, aliases }`, or nothing when branding is off. Off is the default. */
|
|
88
|
+
declare function resolveBranding(opt: BrandingOption | undefined): ResolvedBranding | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* Build the plan, or nothing when branding is off.
|
|
91
|
+
*
|
|
92
|
+
* Every path that cannot answer with certainty returns no brand rather than a guess. A wrong
|
|
93
|
+
* brand does not fail loudly: it compiles, and it makes two unrelated ids interchangeable or two
|
|
94
|
+
* related ones incompatible, either of which is worse than the column staying a plain number.
|
|
95
|
+
*/
|
|
96
|
+
declare function buildBrandPlan(tables: Table$1[], opt: BrandingOption | undefined): BrandPlan | undefined;
|
|
2
97
|
|
|
3
98
|
/**
|
|
4
99
|
* One place that decides what a generated module is called, on disk and in an import.
|
|
@@ -284,6 +379,136 @@ declare function parseCheck(expression: string | undefined, name?: string): Pars
|
|
|
284
379
|
*/
|
|
285
380
|
declare function describeSet(set: ColumnSet): string;
|
|
286
381
|
|
|
382
|
+
/**
|
|
383
|
+
* The facts a generated schema can carry beside itself.
|
|
384
|
+
*
|
|
385
|
+
* A validator says what a value must look like. It does not say where the value came from, and a
|
|
386
|
+
* consumer holding only the schema cannot recover that: `z.string()` is a `text`, a `varchar(40)`,
|
|
387
|
+
* a `citext` and a `char(3)` alike, and nothing on it says which, nor whether the database fills
|
|
388
|
+
* it in, nor which columns key the row.
|
|
389
|
+
*
|
|
390
|
+
* Every key here had to pass one test: **it says something the emitted schema does not already
|
|
391
|
+
* say.** Two ways to pass it.
|
|
392
|
+
*
|
|
393
|
+
* the schema never knew it the SQL type, the primary key, the unique constraints, the
|
|
394
|
+
* dialect, whether the database generates or defaults the value.
|
|
395
|
+
*
|
|
396
|
+
* the schema enforces it and a declared width and every CHECK are `.refine()` calls, and
|
|
397
|
+
* cannot show it `z.toJSONSchema` drops a refinement in silence. Measured on zod
|
|
398
|
+
* 4.4.3: `z.object({ s: z.string().refine(...) })` produces
|
|
399
|
+
* `{ s: { type: 'string' } }` with no warning and no trace. So a
|
|
400
|
+
* JSON Schema built from an emitted module is wrong by omission,
|
|
401
|
+
* and nothing in the document says so.
|
|
402
|
+
*
|
|
403
|
+
* Nullability is the counter-example, and its absence is the rule working: `.nullable()` is in the
|
|
404
|
+
* schema and `anyOf: [..., { type: 'null' }]` is in its JSON Schema, so a `nullable` key would be a
|
|
405
|
+
* second copy of an answer the consumer already has. Same for the enum values, the integer-ness of
|
|
406
|
+
* a number and every numeric bound.
|
|
407
|
+
*
|
|
408
|
+
* Nothing here is a user comment, because there are none to carry. Measured against drizzle-orm on
|
|
409
|
+
* both majors: no column, table or builder exposes one, and a `comment` key passed to a column's
|
|
410
|
+
* options object is dropped before the column is built. See the zod generator's documentation.
|
|
411
|
+
*/
|
|
412
|
+
|
|
413
|
+
/** What a column adds beside its schema. Every key is optional; an empty object is a real answer. */
|
|
414
|
+
interface ColumnMetaFacts {
|
|
415
|
+
/** The type as the database declares it: `varchar(255)`, `numeric(10, 2)`, `text[]`. */
|
|
416
|
+
sqlType?: string;
|
|
417
|
+
/**
|
|
418
|
+
* The declared character limit.
|
|
419
|
+
*
|
|
420
|
+
* Also the JSON Schema keyword of the same name, which is why it is spelled this way: the
|
|
421
|
+
* emitted schema enforces the limit inside a `.refine()` closure, `toJSONSchema` drops that, and
|
|
422
|
+
* this key puts the constraint back in the one spelling every JSON Schema validator enforces.
|
|
423
|
+
*
|
|
424
|
+
* On an array column the limit is the *element's*, since that is where the emitted schema
|
|
425
|
+
* applies it.
|
|
426
|
+
*/
|
|
427
|
+
maxLength?: number;
|
|
428
|
+
/** The declared byte limit, which only MySQL's TEXT and BLOB families carry. */
|
|
429
|
+
maxBytes?: number;
|
|
430
|
+
/**
|
|
431
|
+
* The database supplies a value when the write omits one.
|
|
432
|
+
*
|
|
433
|
+
* Not recoverable from the schema: a defaulted column and a nullable one are both `.optional()`
|
|
434
|
+
* on insert, so the wrapper cannot tell them apart, and on select neither leaves a trace.
|
|
435
|
+
*/
|
|
436
|
+
hasDefault?: true;
|
|
437
|
+
/** The database computes the value and refuses to be given one. Absent from the write schemas. */
|
|
438
|
+
generated?: true;
|
|
439
|
+
/**
|
|
440
|
+
* The CHECK constraints this field enforces, named as the failure messages name them.
|
|
441
|
+
*
|
|
442
|
+
* Not a restatement of the bound beside it. DRZL deliberately folds a CHECK into the column's
|
|
443
|
+
* own range, so `minimum: 18` in the JSON Schema is indistinguishable from a type bound, and a
|
|
444
|
+
* set constraint renders as an enum indistinguishable from a declared one. The provenance is
|
|
445
|
+
* what this carries, along with the constraint name a database error will quote back.
|
|
446
|
+
*/
|
|
447
|
+
checks?: string[];
|
|
448
|
+
/** Prose for a reader, from the constraints the schema enforces and cannot show. Opt-in. */
|
|
449
|
+
description?: string;
|
|
450
|
+
}
|
|
451
|
+
/** What a table's schema adds beside itself. */
|
|
452
|
+
interface TableMetaFacts {
|
|
453
|
+
/** The SQL table name, which is not the Drizzle export name the schema is named after. */
|
|
454
|
+
table: string;
|
|
455
|
+
/**
|
|
456
|
+
* The SQL schema the table lives in, present only when it names one.
|
|
457
|
+
*
|
|
458
|
+
* Beside `table` rather than folded into it. `table` is the bare name in every emitted file
|
|
459
|
+
* that exists, and two tables in two schemas publish the same one, so without this a consumer
|
|
460
|
+
* reading the metadata of `reporting.users` cannot tell it from `public.users`. Absent for a
|
|
461
|
+
* table in the default schema, which is what `pgTable` declares and the only thing it can
|
|
462
|
+
* declare: Drizzle refuses `pgSchema('public')`.
|
|
463
|
+
*/
|
|
464
|
+
schema?: string;
|
|
465
|
+
/** Which database this was analysed from. The same declaration means different things across them. */
|
|
466
|
+
dialect?: string;
|
|
467
|
+
/** Which of the three schemas this is. The export name says it; the schema object does not. */
|
|
468
|
+
mode: string;
|
|
469
|
+
/** The primary key columns, in order. A per-field flag cannot carry the order or the grouping. */
|
|
470
|
+
primaryKey?: string[];
|
|
471
|
+
/**
|
|
472
|
+
* The unique constraints.
|
|
473
|
+
*
|
|
474
|
+
* The one constraint a per-row validator structurally cannot check, which is why
|
|
475
|
+
* `duplicateFinder` exists at all. Carrying it lets a consumer see what the schema is silent
|
|
476
|
+
* about rather than assume it is silent because there is nothing to say.
|
|
477
|
+
*/
|
|
478
|
+
unique?: string[][];
|
|
479
|
+
/** The relation refuses writes, which today means a materialized view. */
|
|
480
|
+
readOnly?: true;
|
|
481
|
+
/** Row-level CHECKs, enforced as object refinements and so invisible for the same reason. */
|
|
482
|
+
checks?: string[];
|
|
483
|
+
/**
|
|
484
|
+
* CHECK constraints the database enforces and this schema does not.
|
|
485
|
+
*
|
|
486
|
+
* Either the parser declined the expression, or it understood it and the column's shape has no
|
|
487
|
+
* way to state it. Both mean the same thing to a caller: the database can reject a row this
|
|
488
|
+
* schema accepted. Nothing else in the emitted module mentions these at all.
|
|
489
|
+
*/
|
|
490
|
+
unenforcedChecks?: string[];
|
|
491
|
+
/** Prose for a reader. Opt-in. */
|
|
492
|
+
description?: string;
|
|
493
|
+
}
|
|
494
|
+
interface MetaFactOptions {
|
|
495
|
+
/** Also write a `description`, which is what an OpenAPI reader renders. Off by default. */
|
|
496
|
+
description?: boolean;
|
|
497
|
+
}
|
|
498
|
+
interface TableMetaOptions extends MetaFactOptions {
|
|
499
|
+
mode: string;
|
|
500
|
+
dialect?: string;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* The metadata for one column.
|
|
504
|
+
*
|
|
505
|
+
* `table` is needed because a CHECK is declared on the table and only then attributed to a column,
|
|
506
|
+
* and because whether a constraint is enforced at all depends on the other columns it names.
|
|
507
|
+
*/
|
|
508
|
+
declare function columnMetaFacts(column: Column, table: Table$1, opts?: MetaFactOptions): ColumnMetaFacts;
|
|
509
|
+
/** The metadata for one table's schema, in one mode. */
|
|
510
|
+
declare function tableMetaFacts(table: Table$1, opts: TableMetaOptions): TableMetaFacts;
|
|
511
|
+
|
|
287
512
|
/**
|
|
288
513
|
* What a relations-aware nested schema describes, decided once for all five generators.
|
|
289
514
|
*
|
|
@@ -547,6 +772,25 @@ interface ValidationGenerateOptions {
|
|
|
547
772
|
* what terminates a cycle, since `users -> posts -> users` simply stops here.
|
|
548
773
|
*/
|
|
549
774
|
nestedDepth?: number;
|
|
775
|
+
/**
|
|
776
|
+
* Give every primary key, and every foreign key pointing at one, a nominal type, so a
|
|
777
|
+
* `users.id` cannot be passed where a `posts.id` is wanted.
|
|
778
|
+
*
|
|
779
|
+
* Type level only. The brand is a marker in the inferred type and nothing at all at runtime:
|
|
780
|
+
* measured on zod 4.4.3, `.brand()` returns the same schema object it was called on, and the
|
|
781
|
+
* parsed value of `1` is `1`, so two branded ids holding `1` are still `===`. Nothing about
|
|
782
|
+
* what a schema accepts or rejects changes here, and nothing is added to the bundle.
|
|
783
|
+
*
|
|
784
|
+
* Off by default, because it changes the inferred type of every consumer of the select
|
|
785
|
+
* schemas. Turning it on will produce errors in code that was passing the wrong id around,
|
|
786
|
+
* which is the point, but it is a change to existing call sites rather than an addition.
|
|
787
|
+
*
|
|
788
|
+
* `{ foreignKeys: false }` brands only the keys themselves. See `BrandingOptions`.
|
|
789
|
+
*
|
|
790
|
+
* TypeBox has no brand helper, so the marker there is an intersection carried by
|
|
791
|
+
* `Type.Unsafe<T>`, which leaves the runtime schema byte-identical. See the docs page.
|
|
792
|
+
*/
|
|
793
|
+
branded?: BrandingOption;
|
|
550
794
|
emit?: {
|
|
551
795
|
select?: boolean;
|
|
552
796
|
insert?: boolean;
|
|
@@ -786,4 +1030,4 @@ declare function selectColumns(table: Table): Column[];
|
|
|
786
1030
|
*/
|
|
787
1031
|
declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
|
|
788
1032
|
|
|
789
|
-
export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, CODEPOINT_LENGTH, COERCIBLE_DATE_STRING, COLUMN_FORMATS, type CardinalityCheck, type ColumnCheck, type ColumnSet, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_NESTED_DEPTH, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, type LengthCheck, MAX_NESTED_DEPTH, NAME_MODES, NESTED_PREFIX, type NameMode, type NestedArm, type NestedMode, type NestedNode, type ParsedCheck, type ResolvedAffix, type RowCheck, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, buildNestedPlan, describeSet, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, nestedArmNotes, nestedNodeColumns, nestedSchemaName, nestedTypeName, nonFiniteAccepted, parseCheck, parsesToADate, pascalCase, renderDuplicateFinder, resolveAffix, resolveConfiguredImport, resolveNestedDepth, schemaName, selectColumns, typeName, updateColumns, validateAffix };
|
|
1033
|
+
export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, type BrandAlias, type BrandPlan, type BrandingOption, type BrandingOptions, CODEPOINT_LENGTH, COERCIBLE_DATE_STRING, COLUMN_FORMATS, type CardinalityCheck, type ColumnCheck, type ColumnMetaFacts, type ColumnSet, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_NESTED_DEPTH, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, type LengthCheck, MAX_NESTED_DEPTH, type MetaFactOptions, NAME_MODES, NESTED_PREFIX, type NameMode, type NestedArm, type NestedMode, type NestedNode, type ParsedCheck, type ResolvedAffix, type ResolvedBranding, type RowCheck, type Table, type TableCase, type TableMetaFacts, type TableMetaOptions, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, buildBrandPlan, buildNestedPlan, columnMetaFacts, describeSet, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, nestedArmNotes, nestedNodeColumns, nestedSchemaName, nestedTypeName, nonFiniteAccepted, parseCheck, parsesToADate, pascalCase, renderDuplicateFinder, resolveAffix, resolveBranding, resolveConfiguredImport, resolveNestedDepth, schemaName, selectColumns, tableMetaFacts, typeName, updateColumns, validateAffix };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,99 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Table as Table$1, Column, Relation, Analysis } from '@drzl/analyzer';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which columns carry a nominal brand, and what that brand is called.
|
|
5
|
+
*
|
|
6
|
+
* A brand is a type-level marker with no runtime existence at all. Measured on zod 4.4.3,
|
|
7
|
+
* `z.number().brand('X')` returns the *same object* it was called on, and the parsed value of
|
|
8
|
+
* `1` is `1`; the same holds for valibot 1.4.2, arktype 2.2.3 and effect 3.x, each of which
|
|
9
|
+
* hands the value back unchanged. So everything decided here is decided for the benefit of
|
|
10
|
+
* `tsc`, and nothing in it can be checked by parsing a value.
|
|
11
|
+
*
|
|
12
|
+
* That is exactly why the decisions live in one module rather than five. A plan that gives
|
|
13
|
+
* `posts.authorId` the wrong token emits a schema whose types are confidently wrong in every
|
|
14
|
+
* generator at once, and no runtime assertion anywhere in this repository would notice.
|
|
15
|
+
*
|
|
16
|
+
* ## The token
|
|
17
|
+
*
|
|
18
|
+
* `<export name>.<column>`, verbatim: `users.id`, `orgMembers.orgId`. Nothing is transformed,
|
|
19
|
+
* singularised or re-cased, which is the whole reason to spell it this way. Two Drizzle tables
|
|
20
|
+
* cannot share an export name inside one schema module and two columns cannot share a name
|
|
21
|
+
* inside one table, so the token is unique by construction and the question of what to do when
|
|
22
|
+
* two names collide after a transformation does not arise. It also reads back as the thing the
|
|
23
|
+
* user wrote:
|
|
24
|
+
*
|
|
25
|
+
* Type 'number & $brand<"posts.id">' is not assignable to type 'number & $brand<"users.id">'
|
|
26
|
+
*
|
|
27
|
+
* The transformation question does arise for the exported *alias*, which has to be a TypeScript
|
|
28
|
+
* identifier. See `aliasesFor`.
|
|
29
|
+
*
|
|
30
|
+
* ## What a foreign key gets
|
|
31
|
+
*
|
|
32
|
+
* The token of the column it references, not one of its own. `posts.authorId` holds a user's id,
|
|
33
|
+
* so it is a `users.id`; giving it `posts.authorId` would invent a type nothing else in the
|
|
34
|
+
* schema produces and the feature would check nothing. That rule is applied transitively and it
|
|
35
|
+
* beats the column being part of its own table's key, which is what makes a join table keyed on
|
|
36
|
+
* two foreign keys come out right.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
interface BrandingOptions {
|
|
40
|
+
enabled?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Also brand foreign key columns with the brand of the column they reference, so a
|
|
43
|
+
* `Post.authorId` is a `users.id` rather than a `number`.
|
|
44
|
+
*
|
|
45
|
+
* On by default, because it is where nearly all of the value is: a primary key branded alone
|
|
46
|
+
* only stops you passing a `PostId` to something wanting a `UserId`, while branding the
|
|
47
|
+
* foreign keys is what makes the ids flowing between tables carry their origin.
|
|
48
|
+
*/
|
|
49
|
+
foreignKeys?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Also export a named alias per branded key, e.g. `export type UsersId = ...`.
|
|
52
|
+
*
|
|
53
|
+
* On by default. Without one the type is only nameable as an indexed access into the select
|
|
54
|
+
* type, which works and reads badly.
|
|
55
|
+
*/
|
|
56
|
+
aliases?: boolean;
|
|
57
|
+
}
|
|
58
|
+
type BrandingOption = boolean | BrandingOptions;
|
|
59
|
+
interface ResolvedBranding {
|
|
60
|
+
foreignKeys: boolean;
|
|
61
|
+
aliases: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** One `export type <alias> = ...` line a table's module owns. */
|
|
64
|
+
interface BrandAlias {
|
|
65
|
+
/** The identifier, e.g. `UsersId`. */
|
|
66
|
+
alias: string;
|
|
67
|
+
/** The column it is the type of, e.g. `id`. */
|
|
68
|
+
column: string;
|
|
69
|
+
/** The token that column carries, which for an owned alias is `<tsName>.<column>`. */
|
|
70
|
+
token: string;
|
|
71
|
+
}
|
|
72
|
+
interface BrandPlan {
|
|
73
|
+
/** The brand token for one column, or nothing when it carries none. */
|
|
74
|
+
brandOf(tsName: string, columnName: string): string | undefined;
|
|
75
|
+
/** The aliases this table's module exports. Empty when it owns no brand. */
|
|
76
|
+
aliasesFor(tsName: string): BrandAlias[];
|
|
77
|
+
/** Whether anything at all is branded, so a generator can skip its emit path entirely. */
|
|
78
|
+
readonly any: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* What was withheld and why, in the words a user can act on.
|
|
81
|
+
*
|
|
82
|
+
* Withholding is the normal outcome for several shapes here and it is silent in the emitted
|
|
83
|
+
* file, so a run that quietly brands less than the user expects has to say so somewhere.
|
|
84
|
+
*/
|
|
85
|
+
readonly notes: readonly string[];
|
|
86
|
+
}
|
|
87
|
+
/** `{ foreignKeys, aliases }`, or nothing when branding is off. Off is the default. */
|
|
88
|
+
declare function resolveBranding(opt: BrandingOption | undefined): ResolvedBranding | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* Build the plan, or nothing when branding is off.
|
|
91
|
+
*
|
|
92
|
+
* Every path that cannot answer with certainty returns no brand rather than a guess. A wrong
|
|
93
|
+
* brand does not fail loudly: it compiles, and it makes two unrelated ids interchangeable or two
|
|
94
|
+
* related ones incompatible, either of which is worse than the column staying a plain number.
|
|
95
|
+
*/
|
|
96
|
+
declare function buildBrandPlan(tables: Table$1[], opt: BrandingOption | undefined): BrandPlan | undefined;
|
|
2
97
|
|
|
3
98
|
/**
|
|
4
99
|
* One place that decides what a generated module is called, on disk and in an import.
|
|
@@ -284,6 +379,136 @@ declare function parseCheck(expression: string | undefined, name?: string): Pars
|
|
|
284
379
|
*/
|
|
285
380
|
declare function describeSet(set: ColumnSet): string;
|
|
286
381
|
|
|
382
|
+
/**
|
|
383
|
+
* The facts a generated schema can carry beside itself.
|
|
384
|
+
*
|
|
385
|
+
* A validator says what a value must look like. It does not say where the value came from, and a
|
|
386
|
+
* consumer holding only the schema cannot recover that: `z.string()` is a `text`, a `varchar(40)`,
|
|
387
|
+
* a `citext` and a `char(3)` alike, and nothing on it says which, nor whether the database fills
|
|
388
|
+
* it in, nor which columns key the row.
|
|
389
|
+
*
|
|
390
|
+
* Every key here had to pass one test: **it says something the emitted schema does not already
|
|
391
|
+
* say.** Two ways to pass it.
|
|
392
|
+
*
|
|
393
|
+
* the schema never knew it the SQL type, the primary key, the unique constraints, the
|
|
394
|
+
* dialect, whether the database generates or defaults the value.
|
|
395
|
+
*
|
|
396
|
+
* the schema enforces it and a declared width and every CHECK are `.refine()` calls, and
|
|
397
|
+
* cannot show it `z.toJSONSchema` drops a refinement in silence. Measured on zod
|
|
398
|
+
* 4.4.3: `z.object({ s: z.string().refine(...) })` produces
|
|
399
|
+
* `{ s: { type: 'string' } }` with no warning and no trace. So a
|
|
400
|
+
* JSON Schema built from an emitted module is wrong by omission,
|
|
401
|
+
* and nothing in the document says so.
|
|
402
|
+
*
|
|
403
|
+
* Nullability is the counter-example, and its absence is the rule working: `.nullable()` is in the
|
|
404
|
+
* schema and `anyOf: [..., { type: 'null' }]` is in its JSON Schema, so a `nullable` key would be a
|
|
405
|
+
* second copy of an answer the consumer already has. Same for the enum values, the integer-ness of
|
|
406
|
+
* a number and every numeric bound.
|
|
407
|
+
*
|
|
408
|
+
* Nothing here is a user comment, because there are none to carry. Measured against drizzle-orm on
|
|
409
|
+
* both majors: no column, table or builder exposes one, and a `comment` key passed to a column's
|
|
410
|
+
* options object is dropped before the column is built. See the zod generator's documentation.
|
|
411
|
+
*/
|
|
412
|
+
|
|
413
|
+
/** What a column adds beside its schema. Every key is optional; an empty object is a real answer. */
|
|
414
|
+
interface ColumnMetaFacts {
|
|
415
|
+
/** The type as the database declares it: `varchar(255)`, `numeric(10, 2)`, `text[]`. */
|
|
416
|
+
sqlType?: string;
|
|
417
|
+
/**
|
|
418
|
+
* The declared character limit.
|
|
419
|
+
*
|
|
420
|
+
* Also the JSON Schema keyword of the same name, which is why it is spelled this way: the
|
|
421
|
+
* emitted schema enforces the limit inside a `.refine()` closure, `toJSONSchema` drops that, and
|
|
422
|
+
* this key puts the constraint back in the one spelling every JSON Schema validator enforces.
|
|
423
|
+
*
|
|
424
|
+
* On an array column the limit is the *element's*, since that is where the emitted schema
|
|
425
|
+
* applies it.
|
|
426
|
+
*/
|
|
427
|
+
maxLength?: number;
|
|
428
|
+
/** The declared byte limit, which only MySQL's TEXT and BLOB families carry. */
|
|
429
|
+
maxBytes?: number;
|
|
430
|
+
/**
|
|
431
|
+
* The database supplies a value when the write omits one.
|
|
432
|
+
*
|
|
433
|
+
* Not recoverable from the schema: a defaulted column and a nullable one are both `.optional()`
|
|
434
|
+
* on insert, so the wrapper cannot tell them apart, and on select neither leaves a trace.
|
|
435
|
+
*/
|
|
436
|
+
hasDefault?: true;
|
|
437
|
+
/** The database computes the value and refuses to be given one. Absent from the write schemas. */
|
|
438
|
+
generated?: true;
|
|
439
|
+
/**
|
|
440
|
+
* The CHECK constraints this field enforces, named as the failure messages name them.
|
|
441
|
+
*
|
|
442
|
+
* Not a restatement of the bound beside it. DRZL deliberately folds a CHECK into the column's
|
|
443
|
+
* own range, so `minimum: 18` in the JSON Schema is indistinguishable from a type bound, and a
|
|
444
|
+
* set constraint renders as an enum indistinguishable from a declared one. The provenance is
|
|
445
|
+
* what this carries, along with the constraint name a database error will quote back.
|
|
446
|
+
*/
|
|
447
|
+
checks?: string[];
|
|
448
|
+
/** Prose for a reader, from the constraints the schema enforces and cannot show. Opt-in. */
|
|
449
|
+
description?: string;
|
|
450
|
+
}
|
|
451
|
+
/** What a table's schema adds beside itself. */
|
|
452
|
+
interface TableMetaFacts {
|
|
453
|
+
/** The SQL table name, which is not the Drizzle export name the schema is named after. */
|
|
454
|
+
table: string;
|
|
455
|
+
/**
|
|
456
|
+
* The SQL schema the table lives in, present only when it names one.
|
|
457
|
+
*
|
|
458
|
+
* Beside `table` rather than folded into it. `table` is the bare name in every emitted file
|
|
459
|
+
* that exists, and two tables in two schemas publish the same one, so without this a consumer
|
|
460
|
+
* reading the metadata of `reporting.users` cannot tell it from `public.users`. Absent for a
|
|
461
|
+
* table in the default schema, which is what `pgTable` declares and the only thing it can
|
|
462
|
+
* declare: Drizzle refuses `pgSchema('public')`.
|
|
463
|
+
*/
|
|
464
|
+
schema?: string;
|
|
465
|
+
/** Which database this was analysed from. The same declaration means different things across them. */
|
|
466
|
+
dialect?: string;
|
|
467
|
+
/** Which of the three schemas this is. The export name says it; the schema object does not. */
|
|
468
|
+
mode: string;
|
|
469
|
+
/** The primary key columns, in order. A per-field flag cannot carry the order or the grouping. */
|
|
470
|
+
primaryKey?: string[];
|
|
471
|
+
/**
|
|
472
|
+
* The unique constraints.
|
|
473
|
+
*
|
|
474
|
+
* The one constraint a per-row validator structurally cannot check, which is why
|
|
475
|
+
* `duplicateFinder` exists at all. Carrying it lets a consumer see what the schema is silent
|
|
476
|
+
* about rather than assume it is silent because there is nothing to say.
|
|
477
|
+
*/
|
|
478
|
+
unique?: string[][];
|
|
479
|
+
/** The relation refuses writes, which today means a materialized view. */
|
|
480
|
+
readOnly?: true;
|
|
481
|
+
/** Row-level CHECKs, enforced as object refinements and so invisible for the same reason. */
|
|
482
|
+
checks?: string[];
|
|
483
|
+
/**
|
|
484
|
+
* CHECK constraints the database enforces and this schema does not.
|
|
485
|
+
*
|
|
486
|
+
* Either the parser declined the expression, or it understood it and the column's shape has no
|
|
487
|
+
* way to state it. Both mean the same thing to a caller: the database can reject a row this
|
|
488
|
+
* schema accepted. Nothing else in the emitted module mentions these at all.
|
|
489
|
+
*/
|
|
490
|
+
unenforcedChecks?: string[];
|
|
491
|
+
/** Prose for a reader. Opt-in. */
|
|
492
|
+
description?: string;
|
|
493
|
+
}
|
|
494
|
+
interface MetaFactOptions {
|
|
495
|
+
/** Also write a `description`, which is what an OpenAPI reader renders. Off by default. */
|
|
496
|
+
description?: boolean;
|
|
497
|
+
}
|
|
498
|
+
interface TableMetaOptions extends MetaFactOptions {
|
|
499
|
+
mode: string;
|
|
500
|
+
dialect?: string;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* The metadata for one column.
|
|
504
|
+
*
|
|
505
|
+
* `table` is needed because a CHECK is declared on the table and only then attributed to a column,
|
|
506
|
+
* and because whether a constraint is enforced at all depends on the other columns it names.
|
|
507
|
+
*/
|
|
508
|
+
declare function columnMetaFacts(column: Column, table: Table$1, opts?: MetaFactOptions): ColumnMetaFacts;
|
|
509
|
+
/** The metadata for one table's schema, in one mode. */
|
|
510
|
+
declare function tableMetaFacts(table: Table$1, opts: TableMetaOptions): TableMetaFacts;
|
|
511
|
+
|
|
287
512
|
/**
|
|
288
513
|
* What a relations-aware nested schema describes, decided once for all five generators.
|
|
289
514
|
*
|
|
@@ -547,6 +772,25 @@ interface ValidationGenerateOptions {
|
|
|
547
772
|
* what terminates a cycle, since `users -> posts -> users` simply stops here.
|
|
548
773
|
*/
|
|
549
774
|
nestedDepth?: number;
|
|
775
|
+
/**
|
|
776
|
+
* Give every primary key, and every foreign key pointing at one, a nominal type, so a
|
|
777
|
+
* `users.id` cannot be passed where a `posts.id` is wanted.
|
|
778
|
+
*
|
|
779
|
+
* Type level only. The brand is a marker in the inferred type and nothing at all at runtime:
|
|
780
|
+
* measured on zod 4.4.3, `.brand()` returns the same schema object it was called on, and the
|
|
781
|
+
* parsed value of `1` is `1`, so two branded ids holding `1` are still `===`. Nothing about
|
|
782
|
+
* what a schema accepts or rejects changes here, and nothing is added to the bundle.
|
|
783
|
+
*
|
|
784
|
+
* Off by default, because it changes the inferred type of every consumer of the select
|
|
785
|
+
* schemas. Turning it on will produce errors in code that was passing the wrong id around,
|
|
786
|
+
* which is the point, but it is a change to existing call sites rather than an addition.
|
|
787
|
+
*
|
|
788
|
+
* `{ foreignKeys: false }` brands only the keys themselves. See `BrandingOptions`.
|
|
789
|
+
*
|
|
790
|
+
* TypeBox has no brand helper, so the marker there is an intersection carried by
|
|
791
|
+
* `Type.Unsafe<T>`, which leaves the runtime schema byte-identical. See the docs page.
|
|
792
|
+
*/
|
|
793
|
+
branded?: BrandingOption;
|
|
550
794
|
emit?: {
|
|
551
795
|
select?: boolean;
|
|
552
796
|
insert?: boolean;
|
|
@@ -786,4 +1030,4 @@ declare function selectColumns(table: Table): Column[];
|
|
|
786
1030
|
*/
|
|
787
1031
|
declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
|
|
788
1032
|
|
|
789
|
-
export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, CODEPOINT_LENGTH, COERCIBLE_DATE_STRING, COLUMN_FORMATS, type CardinalityCheck, type ColumnCheck, type ColumnSet, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_NESTED_DEPTH, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, type LengthCheck, MAX_NESTED_DEPTH, NAME_MODES, NESTED_PREFIX, type NameMode, type NestedArm, type NestedMode, type NestedNode, type ParsedCheck, type ResolvedAffix, type RowCheck, type Table, type TableCase, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, buildNestedPlan, describeSet, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, nestedArmNotes, nestedNodeColumns, nestedSchemaName, nestedTypeName, nonFiniteAccepted, parseCheck, parsesToADate, pascalCase, renderDuplicateFinder, resolveAffix, resolveConfiguredImport, resolveNestedDepth, schemaName, selectColumns, typeName, updateColumns, validateAffix };
|
|
1033
|
+
export { AFFIX_PROBE_TABLE, type AffixIssue, type AffixOptions, type AffixValue, type BrandAlias, type BrandPlan, type BrandingOption, type BrandingOptions, CODEPOINT_LENGTH, COERCIBLE_DATE_STRING, COLUMN_FORMATS, type CardinalityCheck, type ColumnCheck, type ColumnMetaFacts, type ColumnSet, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_NESTED_DEPTH, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FormatOptions, IMPORT_EXTENSIONS, type ImportExtension, type LengthCheck, MAX_NESTED_DEPTH, type MetaFactOptions, NAME_MODES, NESTED_PREFIX, type NameMode, type NestedArm, type NestedMode, type NestedNode, type ParsedCheck, type ResolvedAffix, type ResolvedBranding, type RowCheck, type Table, type TableCase, type TableMetaFacts, type TableMetaOptions, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, applyTableCase, buildBrandPlan, buildNestedPlan, columnMetaFacts, describeSet, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, moduleFileName, moduleSpecifier, nestedArmNotes, nestedNodeColumns, nestedSchemaName, nestedTypeName, nonFiniteAccepted, parseCheck, parsesToADate, pascalCase, renderDuplicateFinder, resolveAffix, resolveBranding, resolveConfiguredImport, resolveNestedDepth, schemaName, selectColumns, tableMetaFacts, typeName, updateColumns, validateAffix };
|