@drzl/validation-core 3.20.0 → 3.22.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/README.md +34 -9
- package/dist/index.cjs +1203 -258
- package/dist/index.d.cts +642 -4
- package/dist/index.d.ts +642 -4
- package/dist/index.js +1183 -260
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -95,6 +95,79 @@ declare function resolveBranding(opt: BrandingOption | undefined): ResolvedBrand
|
|
|
95
95
|
*/
|
|
96
96
|
declare function buildBrandPlan(tables: Table$1[], opt: BrandingOption | undefined): BrandPlan | undefined;
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Where a generator's files go, so that "write it" and "tell me what you would write" are the same
|
|
100
|
+
* code path (plan items 68, 80, 81).
|
|
101
|
+
*
|
|
102
|
+
* Three requests turn out to be one mechanism. `--dry-run` needs to know what would be written
|
|
103
|
+
* without writing it, `generate` needs to say which files it created and which it changed rather
|
|
104
|
+
* than only how many it wrote, and `--check` needs to show a diff of the difference. All three are
|
|
105
|
+
* the same question asked per file: what content is about to land here, and what is here now. The
|
|
106
|
+
* only thing a generator has to give up for that is deciding, itself, that the answer goes to disk.
|
|
107
|
+
*
|
|
108
|
+
* ## Why an option and not an interception
|
|
109
|
+
*
|
|
110
|
+
* The tempting version is to leave every generator alone and patch `node:fs/promises` for the
|
|
111
|
+
* duration of the run, since each one reaches it through `await import('node:fs/promises')`. That
|
|
112
|
+
* was measured and rejected. Patching the CommonJS exports object is visible through a later
|
|
113
|
+
* dynamic import, but a module namespace that already exists is a snapshot and never changes:
|
|
114
|
+
*
|
|
115
|
+
* const ns = await import('node:fs/promises'); // namespace built here
|
|
116
|
+
* require('node:fs/promises').writeFile = spy; // patch applied after
|
|
117
|
+
* ns.writeFile === spy // false, measured on Node 22.22
|
|
118
|
+
*
|
|
119
|
+
* The CLI links `chokidar`, which imports `node:fs/promises` at module scope, so by the time any
|
|
120
|
+
* command body runs the namespace usually exists and the patch is invisible. A dry run built on
|
|
121
|
+
* that would write real files whenever an unrelated dependency happened to import first, which is
|
|
122
|
+
* the worst possible failure for this feature: silent, environmental, and destructive. An explicit
|
|
123
|
+
* option cannot fail that way, because a generator that never received a sink is a generator whose
|
|
124
|
+
* call site can be read.
|
|
125
|
+
*
|
|
126
|
+
* ## Shape
|
|
127
|
+
*
|
|
128
|
+
* `fileWriter` returns an object with the two methods every generator already calls on the
|
|
129
|
+
* `node:fs/promises` namespace, with the same signatures, so adopting it is one line per generator
|
|
130
|
+
* and no write site changes at all:
|
|
131
|
+
*
|
|
132
|
+
* const fs = await import('node:fs/promises'); -> const fs = fileWriter(opts.fileSink);
|
|
133
|
+
*
|
|
134
|
+
* That matters more than it looks. Fourteen generators write files, between one and six times
|
|
135
|
+
* each, and a change that touched every one of those sites would be fourteen chances to miss one
|
|
136
|
+
* and ship a dry run that writes.
|
|
137
|
+
*/
|
|
138
|
+
/**
|
|
139
|
+
* Somewhere for a generator's output to go that is not the filesystem.
|
|
140
|
+
*
|
|
141
|
+
* `mkdir` is part of it because a dry run that creates directories is not a dry run: run in an
|
|
142
|
+
* empty project, the honest answer leaves the directory empty, and `fs.mkdir(out, { recursive:
|
|
143
|
+
* true })` is the first thing every generator does.
|
|
144
|
+
*/
|
|
145
|
+
interface FileSink {
|
|
146
|
+
mkdir(dir: string): void | Promise<void>;
|
|
147
|
+
writeFile(file: string, contents: string): void | Promise<void>;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The `node:fs/promises` subset generators use, narrowed to the two calls they actually make.
|
|
151
|
+
*
|
|
152
|
+
* Declared structurally rather than imported from `node:fs` so that the real namespace satisfies
|
|
153
|
+
* it without a cast and a sink can too. The `options` and `encoding` parameters are accepted and
|
|
154
|
+
* ignored by a sink: every call site in this repository passes `{ recursive: true }` and `'utf8'`,
|
|
155
|
+
* and a sink that took different arguments would make the swap a rewrite rather than a rename.
|
|
156
|
+
*/
|
|
157
|
+
interface GeneratorFs {
|
|
158
|
+
mkdir(dir: string, options?: {
|
|
159
|
+
recursive?: boolean;
|
|
160
|
+
}): Promise<unknown>;
|
|
161
|
+
writeFile(file: string, contents: string, encoding?: BufferEncoding): Promise<void>;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The filesystem, or whatever was passed instead of it.
|
|
165
|
+
*
|
|
166
|
+
* Without a sink this is `node:fs/promises` itself, so a run with no sink performs the same calls
|
|
167
|
+
* in the same order as before this existed.
|
|
168
|
+
*/
|
|
169
|
+
declare function fileWriter(sink?: FileSink): GeneratorFs;
|
|
170
|
+
|
|
98
171
|
/**
|
|
99
172
|
* One place that decides what a generated module is called, on disk and in an import.
|
|
100
173
|
*
|
|
@@ -253,6 +326,22 @@ declare function resolveAffix(opts?: {
|
|
|
253
326
|
declare function schemaName(mode: NameMode, tsName: string, affix: ResolvedAffix): string;
|
|
254
327
|
/** Name of the exported type alias, e.g. `InsertusersInput`. */
|
|
255
328
|
declare function typeName(mode: NameMode, tsName: string, affix: ResolvedAffix): string;
|
|
329
|
+
/**
|
|
330
|
+
* The same two rules, as JSON Schema `pattern` strings, for `drzl.config.schema.json`.
|
|
331
|
+
*
|
|
332
|
+
* `z.toJSONSchema` drops refinements without saying so, and the affix rules live in
|
|
333
|
+
* `ConfigSchema`'s only `.superRefine`. A generated schema would therefore have told an editor
|
|
334
|
+
* that `suffix: 'my-schema'` was fine, and the CLI would then have refused to generate from it.
|
|
335
|
+
* Re-encoding the character half here keeps one definition of the rule rather than a regex in
|
|
336
|
+
* this file and a hand-copied pattern in the schema builder.
|
|
337
|
+
*
|
|
338
|
+
* The body is optional because `checkOne` returns early for the empty string: an empty affix
|
|
339
|
+
* means "none" and is always legal. Equivalence with `validateAffix` is not argued from that
|
|
340
|
+
* sentence, it is fuzzed over every printable ASCII position in
|
|
341
|
+
* packages/cli/test/config-json-schema.spec.ts.
|
|
342
|
+
*/
|
|
343
|
+
declare const AFFIX_PREFIX_PATTERN = "^(?:[A-Za-z_$][A-Za-z0-9_$]*)?$";
|
|
344
|
+
declare const AFFIX_SUFFIX_PATTERN = "^(?:[A-Za-z0-9_$]+)?$";
|
|
256
345
|
/**
|
|
257
346
|
* Reject affixes that cannot produce a compilable file, before anything is written:
|
|
258
347
|
* - characters that are not legal in a TypeScript identifier
|
|
@@ -288,6 +377,11 @@ declare function validateAffix(affix?: AffixOptions, schemaSuffix?: string): Aff
|
|
|
288
377
|
* base expression rather than on the whole field.
|
|
289
378
|
* 2. **A multi-column check cannot live on a field.** `start_date < end_date` is a statement
|
|
290
379
|
* about the row, so it is not returned here at all.
|
|
380
|
+
* 3. **A conjunction splits and a disjunction does not.** Every part of an `AND` has to hold on
|
|
381
|
+
* its own, so a list of checks says exactly what it says. An `OR` is *weaker* than either
|
|
382
|
+
* branch: a schema enforcing one branch turns away every row that satisfied the other. So a
|
|
383
|
+
* disjunction is read only where the whole of it collapses to one statement, and refused
|
|
384
|
+
* whole otherwise. See `parseDisjunction`.
|
|
291
385
|
*/
|
|
292
386
|
/** A comparison of one column against one literal, which is the case worth translating. */
|
|
293
387
|
interface ColumnCheck {
|
|
@@ -326,7 +420,8 @@ interface RowCheck {
|
|
|
326
420
|
name?: string;
|
|
327
421
|
}
|
|
328
422
|
/**
|
|
329
|
-
* A constraint on a column's
|
|
423
|
+
* A constraint on a count derived from a column's value, from `CHECK (length(name) > 3)` and
|
|
424
|
+
* `CHECK (octet_length(blob) <= 5)`.
|
|
330
425
|
*
|
|
331
426
|
* Kept apart from `ColumnCheck` because it is not a comparison of the value: it compares a count
|
|
332
427
|
* derived from it, and the count Postgres takes is code points rather than UTF-16 units. See
|
|
@@ -337,8 +432,88 @@ interface LengthCheck {
|
|
|
337
432
|
operator: ColumnCheck['operator'];
|
|
338
433
|
/** Decimal, as text, matching how the other bounds are carried. */
|
|
339
434
|
value: string;
|
|
435
|
+
/**
|
|
436
|
+
* Which count the expression asked for.
|
|
437
|
+
*
|
|
438
|
+
* Absent on a pre-1.x parse, where every count was a character count, so a consumer reading this
|
|
439
|
+
* treats `undefined` as `'characters'`. `lengthMeasure` is the one place that decision is made.
|
|
440
|
+
*
|
|
441
|
+
* This is *not* the whole answer, because the same function means different things on different
|
|
442
|
+
* column types. Measured on PostgreSQL 17.5 through PGlite, on a `text` holding three emoji and
|
|
443
|
+
* a `bytea` holding six bytes:
|
|
444
|
+
*
|
|
445
|
+
* | expression | text | bytea |
|
|
446
|
+
* | ----------------- | ---- | -------------- |
|
|
447
|
+
* | `octet_length(x)` | 12 | 6 |
|
|
448
|
+
* | `length(x)` | 3 | 6 |
|
|
449
|
+
* | `char_length(x)` | 3 | does not exist |
|
|
450
|
+
*
|
|
451
|
+
* So the unit says what the *expression* asked for and `lengthMeasure` says how to answer it for
|
|
452
|
+
* a given column.
|
|
453
|
+
*/
|
|
454
|
+
unit?: 'characters' | 'bytes';
|
|
340
455
|
name?: string;
|
|
341
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* How a `LengthCheck` is answered in JavaScript, once the column it names is known.
|
|
459
|
+
*
|
|
460
|
+
* Three answers, and no two of them agree on the same value:
|
|
461
|
+
*
|
|
462
|
+
* `codePoints` `[...v].length` a character count
|
|
463
|
+
* `utf8Bytes` `new TextEncoder().encode(v).length` a byte count of a string
|
|
464
|
+
* `byteLength` `v.length`, on a Uint8Array a byte count of a binary payload
|
|
465
|
+
*
|
|
466
|
+
* `v.length` on a *string* is none of them: it counts UTF-16 units, which is 6 for the three emoji
|
|
467
|
+
* whose character count is 3 and whose byte count is 12.
|
|
468
|
+
*/
|
|
469
|
+
type LengthMeasure = 'codePoints' | 'utf8Bytes' | 'byteLength';
|
|
470
|
+
/**
|
|
471
|
+
* How to answer a count on this column, or nothing where no expression answers it.
|
|
472
|
+
*
|
|
473
|
+
* The one place the column type and the expression meet, held here rather than in each generator so
|
|
474
|
+
* the emitted predicate and the constraint ledger cannot disagree about what is enforced.
|
|
475
|
+
*
|
|
476
|
+
* Three refusals, each because the count would be a different measurement than the database took:
|
|
477
|
+
*
|
|
478
|
+
* - **An array.** Postgres has no `length(anyarray)`; `cardinality` is the array's own count and is
|
|
479
|
+
* carried separately.
|
|
480
|
+
* - **A byte-string column**, MySQL's `binary(n)`/`varbinary(n)`. It arrives as a string produced by
|
|
481
|
+
* a lossy decode, so neither its code points nor their UTF-8 re-encoding is the server's byte
|
|
482
|
+
* count: `<ff ff ff>` from a `varbinary(3)` comes back as 3 code points that re-encode to 9
|
|
483
|
+
* bytes. See `ColumnShape`.
|
|
484
|
+
* - **Anything that is not a string or a binary payload.** `length` is defined for text, bytea and
|
|
485
|
+
* bit strings, so a count on a number column cannot come from a schema the database accepted, and
|
|
486
|
+
* spreading a number with `[...v]` would throw rather than fail.
|
|
487
|
+
*
|
|
488
|
+
* A `bytea` answers both functions with the same number, which is why the unit is not consulted
|
|
489
|
+
* there. `char_length(bytea)` is the one spelling that would break that, and Postgres does not have
|
|
490
|
+
* it: `function char_length(bytea) does not exist`, measured.
|
|
491
|
+
*/
|
|
492
|
+
declare function lengthMeasure(column: {
|
|
493
|
+
tsType?: string;
|
|
494
|
+
arrayDimensions?: number;
|
|
495
|
+
shape?: {
|
|
496
|
+
kind: string;
|
|
497
|
+
};
|
|
498
|
+
}, check: LengthCheck): LengthMeasure | undefined;
|
|
499
|
+
/**
|
|
500
|
+
* The JavaScript expression that takes one of the three measurements, over a named variable.
|
|
501
|
+
*
|
|
502
|
+
* A function of the variable name rather than a constant, because the five validation generators
|
|
503
|
+
* put the count in three different places: zod and valibot pipe the value itself, while ArkType and
|
|
504
|
+
* TypeBox reach it off the row object as `o["blob"]`. `CODEPOINT_LENGTH` is the fixed-variable
|
|
505
|
+
* spelling of the first arm and stays for the call sites that already use it.
|
|
506
|
+
*/
|
|
507
|
+
declare function measureExpression(measure: LengthMeasure, variable: string): string;
|
|
508
|
+
/**
|
|
509
|
+
* A count constraint as the sentence every emitted schema attaches and the ledger keys on.
|
|
510
|
+
*
|
|
511
|
+
* Held here rather than spelled out in each generator, because the constraint error map matches an
|
|
512
|
+
* issue's message against this string exactly: the two drifting by one character is a map that
|
|
513
|
+
* silently answers nothing. `char_length` is normalised to `length`, as it always has been, since
|
|
514
|
+
* the two are the same function in Postgres.
|
|
515
|
+
*/
|
|
516
|
+
declare function lengthCheckLabel(check: LengthCheck): string;
|
|
342
517
|
/**
|
|
343
518
|
* A constraint on an array column's element count, from `CHECK (cardinality(tags) > 0)`.
|
|
344
519
|
*
|
|
@@ -351,6 +526,24 @@ interface CardinalityCheck {
|
|
|
351
526
|
value: string;
|
|
352
527
|
name?: string;
|
|
353
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* A test of whether a column holds NULL, from `CHECK (col IS NOT NULL)`.
|
|
531
|
+
*
|
|
532
|
+
* Not a comparison: SQL's comparison operators all yield NULL against a NULL operand, and these
|
|
533
|
+
* two are the operators that answer TRUE or FALSE instead. That is why they are a kind of their
|
|
534
|
+
* own rather than another `operator` on `ColumnCheck`, whose whole placement rule is that a check
|
|
535
|
+
* sits *inside* the nullable wrapper because NULL never reaches it.
|
|
536
|
+
*
|
|
537
|
+
* `notNull` is the only direction any emitted schema states, and it states it by not being
|
|
538
|
+
* nullable rather than by carrying a predicate. `IS NULL` is carried so the constraint can be
|
|
539
|
+
* reported precisely rather than as "not understood", and is enforced nowhere.
|
|
540
|
+
*/
|
|
541
|
+
interface NullCheck {
|
|
542
|
+
column: string;
|
|
543
|
+
/** `true` for `IS NOT NULL`, `false` for `IS NULL`. */
|
|
544
|
+
notNull: boolean;
|
|
545
|
+
name?: string;
|
|
546
|
+
}
|
|
354
547
|
/** A check that was understood, or the reason it was not. */
|
|
355
548
|
type ParsedCheck = {
|
|
356
549
|
ok: true;
|
|
@@ -359,6 +552,7 @@ type ParsedCheck = {
|
|
|
359
552
|
rows?: RowCheck[];
|
|
360
553
|
lengths?: LengthCheck[];
|
|
361
554
|
cardinalities?: CardinalityCheck[];
|
|
555
|
+
nulls?: NullCheck[];
|
|
362
556
|
} | {
|
|
363
557
|
ok: false;
|
|
364
558
|
reason: string;
|
|
@@ -369,8 +563,36 @@ type ParsedCheck = {
|
|
|
369
563
|
* Deliberately narrow. `BETWEEN` is included because it is common and means exactly two
|
|
370
564
|
* inclusive bounds; `AND` of arbitrary predicates is not, because getting its scope wrong would
|
|
371
565
|
* silently change what is enforced.
|
|
566
|
+
*
|
|
567
|
+
* **The order below is load bearing, in both directions.** `BETWEEN` is matched before the `AND`
|
|
568
|
+
* split because it *holds* an `AND`; splitting first turned every `BETWEEN` into an unparseable
|
|
569
|
+
* pair and dropped a constraint that had been enforced. The unary `IS` predicates are matched
|
|
570
|
+
* *after* the splits for the mirror-image reason: none of them holds an `AND` or an `OR`, and
|
|
571
|
+
* their trailing operand is greedy, so matching first would let `a IS DISTINCT FROM 5 AND b > 0`
|
|
572
|
+
* swallow the second predicate. `OR` is split before `AND` because SQL binds `AND` tighter, and
|
|
573
|
+
* no SQL operator spells an `OR` inside itself the way `BETWEEN` spells an `AND`.
|
|
372
574
|
*/
|
|
373
575
|
declare function parseCheck(expression: string | undefined, name?: string): ParsedCheck;
|
|
576
|
+
/**
|
|
577
|
+
* A number-kind literal, spelled for the wire type of the column it constrains.
|
|
578
|
+
*
|
|
579
|
+
* `CHECK (big IN (1, 2))` on a `bigint({ mode: 'bigint' })` column pins a set of *bigints*: the
|
|
580
|
+
* driver returns `1n` there, and `1n === 1` is false in JavaScript, so a number literal in the
|
|
581
|
+
* emitted schema rejects every row the database returns. The literal has to be spelled `1n` on
|
|
582
|
+
* that wire, and stay `1` where a number really arrives, which is what `bigint({ mode: 'number' })`
|
|
583
|
+
* does. The wire type is `tsType`, which the analyzer sets per mode from what the driver hands
|
|
584
|
+
* back (see the `PgBigInt53`/`PgBigInt64` arms and `test/decimal-modes.spec.ts` in the analyzer),
|
|
585
|
+
* so the decision keys on the value's real type rather than on the SQL type name.
|
|
586
|
+
*
|
|
587
|
+
* The integer test is load bearing rather than defensive: `1.5n` is a syntax error, so an emitted
|
|
588
|
+
* module carrying it throws at import. A non-integer literal also has nothing to gain from the
|
|
589
|
+
* suffix, because no stored bigint ever equals 1.5; it keeps its number spelling, which no bigint
|
|
590
|
+
* satisfies either, so the schema and the database agree about every value on the wire while the
|
|
591
|
+
* module keeps parsing.
|
|
592
|
+
*/
|
|
593
|
+
declare function wireNumberLiteral(column: {
|
|
594
|
+
tsType?: string;
|
|
595
|
+
}, value: string): string;
|
|
374
596
|
/**
|
|
375
597
|
* A human-readable rendering of a set constraint, for an error message.
|
|
376
598
|
*
|
|
@@ -378,6 +600,343 @@ declare function parseCheck(expression: string | undefined, name?: string): Pars
|
|
|
378
600
|
* schema rather than like its JavaScript translation.
|
|
379
601
|
*/
|
|
380
602
|
declare function describeSet(set: ColumnSet): string;
|
|
603
|
+
/**
|
|
604
|
+
* How the database compares a CHECK literal against this column, and so how an emitted schema
|
|
605
|
+
* must state the comparison. `wireNumberLiteral` above decides a literal's *spelling*; this
|
|
606
|
+
* decides which comparison that spelling takes part in, which is the other half of the same
|
|
607
|
+
* rule: the literal's kind and the column's wire are reconciled by the database's semantics,
|
|
608
|
+
* never by whether the schema happened to quote the literal.
|
|
609
|
+
*
|
|
610
|
+
* `number` the driver hands back a JS number; literals compare with `===`/ranges.
|
|
611
|
+
* `bigint` a JS bigint; same, spelled with the `n` suffix.
|
|
612
|
+
* `numeric-string` the driver hands back *decimal text* and the database compares it as a
|
|
613
|
+
* number: `numeric`/`decimal` in string mode on every dialect, and v1's
|
|
614
|
+
* `bigint({ mode: 'string' })`. Measured on PostgreSQL 17.5 and MySQL
|
|
615
|
+
* 8.4.11: `1 = 1.00` is true and a `numeric(10,2)` returns '1.00' for a
|
|
616
|
+
* stored 1, so neither a number literal nor the literal's own text can be
|
|
617
|
+
* compared to what arrives; only a canonical decimal spelling can.
|
|
618
|
+
* `text` ordinary strings, compared as strings.
|
|
619
|
+
* `opaque` everything else: dates, booleans, arrays, shaped columns. No literal
|
|
620
|
+
* policy applies and the existing arms keep their behaviour.
|
|
621
|
+
*
|
|
622
|
+
* Keyed on `tsType` plus `dbType` rather than on the SQL type name, for the reason
|
|
623
|
+
* `wireNumberLiteral` records: `tsType` is the analyzer's measured wire, and `dbType` is the
|
|
624
|
+
* coarse kind label both majors agree on (`NUMERIC` for every decimal family, `BIGINT` for the
|
|
625
|
+
* one string-mode integer wire).
|
|
626
|
+
*/
|
|
627
|
+
type ComparisonWire = 'number' | 'bigint' | 'numeric-string' | 'text' | 'opaque';
|
|
628
|
+
declare function comparisonWire(c: {
|
|
629
|
+
tsType?: string;
|
|
630
|
+
dbType?: string;
|
|
631
|
+
shape?: {
|
|
632
|
+
kind: string;
|
|
633
|
+
};
|
|
634
|
+
arrayDimensions?: number;
|
|
635
|
+
}): ComparisonWire;
|
|
636
|
+
/**
|
|
637
|
+
* The canonical spelling of plain decimal text, or nothing for anything else.
|
|
638
|
+
*
|
|
639
|
+
* The one name under which '1', '1.00', '01', '+1', '1.' and ' 1 ' are the same value, which is
|
|
640
|
+
* exactly the equality the database applies on a numeric wire: measured through PGlite, a bare
|
|
641
|
+
* `numeric` returns '1.000000' for an inserted 1.000000 and admits it into `CHECK (n IN (1, 2))`,
|
|
642
|
+
* and a `numeric(10,2)` returns '1.00' for a stored 1. Sign is normalised (`numeric` has no
|
|
643
|
+
* negative zero: '-0.00' comes back '0.00'), leading integer zeros and trailing fraction zeros
|
|
644
|
+
* are stripped, and a bare trailing dot is dropped.
|
|
645
|
+
*
|
|
646
|
+
* String arithmetic deliberately: `Number()` is not usable here. A numeric column carries more
|
|
647
|
+
* digits than a double holds, and Number('99999999999999999999') and
|
|
648
|
+
* Number('99999999999999999998') are the same double, so rounding through one would merge
|
|
649
|
+
* members the database keeps distinct.
|
|
650
|
+
*
|
|
651
|
+
* Everything outside plain decimal answers nothing, exponent forms included. Postgres does
|
|
652
|
+
* accept `'1e3'` as numeric *input*, and `CHECK (n IN ('1e3', 2))` is valid DDL that admits
|
|
653
|
+
* 1000, but its output spelling is always plain ('1000' came back, measured), and a member this
|
|
654
|
+
* function cannot name makes the whole constraint unenforceable rather than approximated: see
|
|
655
|
+
* `wireLiteralFit`.
|
|
656
|
+
*/
|
|
657
|
+
declare function canonicalNumericText(text: string): string | undefined;
|
|
658
|
+
/** The canonical forms of a member list, deduplicated, members outside the domain dropped. */
|
|
659
|
+
declare function canonicalMembers(values: string[]): string[];
|
|
660
|
+
/** Name of the canonical helper emitted into a module that compares on a numeric string wire. */
|
|
661
|
+
declare const NUMERIC_CANON_NAME = "DrzlNumericCanon";
|
|
662
|
+
/**
|
|
663
|
+
* The helper itself, emitted once per file that needs it, in every generator from this one
|
|
664
|
+
* string so six copies cannot drift. `test/numeric-wire-literals.spec.ts` evaluates it beside
|
|
665
|
+
* `canonicalNumericText` over the probe corpus to hold the two to one answer.
|
|
666
|
+
*/
|
|
667
|
+
declare const NUMERIC_CANON_SOURCE = "/**\n * The canonical spelling of the plain decimal text a numeric wire carries, or null for anything\n * else. The driver spells one value many ways by declared scale ('1', '1.00', '1.0000000000',\n * measured) and the database compares them as numbers, so equality is decided on this form:\n * sign normalised, leading integer zeros and trailing fraction zeros stripped, a bare trailing\n * dot dropped. String arithmetic on purpose: Number() is not usable here, because a numeric\n * column carries more digits than a double holds and rounding would merge values the database\n * keeps distinct.\n */\nconst DrzlNumericCanon = (s: string): string | null => {\n const m = /^([+-]?)(\\d*)(?:\\.(\\d*))?$/.exec(s.trim());\n if (!m || (!m[2] && !m[3])) return null;\n const int = (m[2] ?? '').replace(/^0+/, '');\n const frac = (m[3] ?? '').replace(/0+$/, '');\n if (!int && !frac) return '0';\n return (m[1] === '-' ? '-' : '') + (int || '0') + (frac ? '.' + frac : '');\n};\n";
|
|
668
|
+
/** What `wireLiteralFit` is asked about: one literal list, one comparison class. */
|
|
669
|
+
interface WireLiteralQuestion {
|
|
670
|
+
kind: 'number' | 'string';
|
|
671
|
+
values: string[];
|
|
672
|
+
/** `=`, `<>` and `IN` are `equality`; the four ordering operators are `range`. */
|
|
673
|
+
comparison: 'equality' | 'range';
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Whether these literals can be enforced against this column, and in what form.
|
|
677
|
+
*
|
|
678
|
+
* `keep` the spelling already follows the wire: nothing changes.
|
|
679
|
+
* `respell` quoted plain decimal on a number or bigint wire. The database coerces the
|
|
680
|
+
* text and compares numerically (`bigint CHECK (big IN ('1','2'))` admitted 1
|
|
681
|
+
* and refused 3, measured), so the literal becomes its canonical number-kind
|
|
682
|
+
* self and every existing number arm applies. Canonical rather than verbatim,
|
|
683
|
+
* because `018` and `018n` are syntax errors in an emitted module.
|
|
684
|
+
* `canonical` any equality on a numeric string wire: the emitted schema compares canonical
|
|
685
|
+
* decimal spellings through `NUMERIC_CANON_NAME`. A range there is `respell`:
|
|
686
|
+
* it is emitted as a coerced numeric compare instead, since ordering needs no
|
|
687
|
+
* exact spelling, only a monotonic one.
|
|
688
|
+
* `unenforced` no exact statement exists. Three shapes land here, each measured: a number
|
|
689
|
+
* literal against a text column (Postgres refuses the DDL outright, MySQL
|
|
690
|
+
* compares through double coercion that admits '1.00' and '2.0'), quoted text
|
|
691
|
+
* that is not plain decimal on a number or bigint wire, and a member outside
|
|
692
|
+
* the canonical domain on a numeric string wire (`'1e3'` is valid DDL whose
|
|
693
|
+
* rows come back '1000'). Enforcing a guess would reject rows the database
|
|
694
|
+
* admits, which is the defect class this policy exists to remove, so these are
|
|
695
|
+
* left to the base schema and reported.
|
|
696
|
+
*/
|
|
697
|
+
type WireLiteralFit = {
|
|
698
|
+
fit: 'keep';
|
|
699
|
+
} | {
|
|
700
|
+
fit: 'respell';
|
|
701
|
+
values: string[];
|
|
702
|
+
} | {
|
|
703
|
+
fit: 'canonical';
|
|
704
|
+
canon: string[];
|
|
705
|
+
} | {
|
|
706
|
+
fit: 'unenforced';
|
|
707
|
+
reason: string;
|
|
708
|
+
};
|
|
709
|
+
declare function wireLiteralFit(c: {
|
|
710
|
+
name?: string;
|
|
711
|
+
tsType?: string;
|
|
712
|
+
dbType?: string;
|
|
713
|
+
shape?: {
|
|
714
|
+
kind: string;
|
|
715
|
+
};
|
|
716
|
+
arrayDimensions?: number;
|
|
717
|
+
}, q: WireLiteralQuestion): WireLiteralFit;
|
|
718
|
+
/** A clause the wire policy left unenforced, with the reason the ledger reports. */
|
|
719
|
+
interface UnenforcedLiteral {
|
|
720
|
+
column: string;
|
|
721
|
+
reason: string;
|
|
722
|
+
check?: ColumnCheck;
|
|
723
|
+
set?: ColumnSet;
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* The wire policy over a whole table's parsed checks, for the generators.
|
|
727
|
+
*
|
|
728
|
+
* Respelled literals come back as the number-kind twins every existing arm already handles,
|
|
729
|
+
* unenforceable clauses are dropped from the lists and returned beside them, and the
|
|
730
|
+
* numeric-string clauses pass through untouched for the canonical vehicles to state. The
|
|
731
|
+
* constraint ledger applies `wireLiteralFit` itself so its texts and reasons cannot drift from
|
|
732
|
+
* what is emitted here.
|
|
733
|
+
*/
|
|
734
|
+
declare function applyWirePolicy(columns: Array<{
|
|
735
|
+
name: string;
|
|
736
|
+
tsType?: string;
|
|
737
|
+
dbType?: string;
|
|
738
|
+
shape?: {
|
|
739
|
+
kind: string;
|
|
740
|
+
};
|
|
741
|
+
arrayDimensions?: number;
|
|
742
|
+
}>, checks: ColumnCheck[], sets: ColumnSet[]): {
|
|
743
|
+
checks: ColumnCheck[];
|
|
744
|
+
sets: ColumnSet[];
|
|
745
|
+
unenforced: UnenforcedLiteral[];
|
|
746
|
+
};
|
|
747
|
+
/**
|
|
748
|
+
* Whether any column of this table needs `NUMERIC_CANON_SOURCE` emitted beside it.
|
|
749
|
+
*
|
|
750
|
+
* One condition shared by every generator's preamble and its field emitter, for the reason the
|
|
751
|
+
* TypeBox generator records on `tbNeedsCapKind`: two copies of one condition drift, and what a
|
|
752
|
+
* drifted copy emits is a reference to a helper the file never defined.
|
|
753
|
+
*/
|
|
754
|
+
declare function needsNumericCanon(columns: Array<{
|
|
755
|
+
name: string;
|
|
756
|
+
tsType?: string;
|
|
757
|
+
dbType?: string;
|
|
758
|
+
shape?: {
|
|
759
|
+
kind: string;
|
|
760
|
+
};
|
|
761
|
+
arrayDimensions?: number;
|
|
762
|
+
}>, checks: ColumnCheck[], sets: ColumnSet[]): boolean;
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Every constraint on a table, as data a consumer can read without holding a validator.
|
|
766
|
+
*
|
|
767
|
+
* `meta` already carries most of these facts, and this is deliberately not a second copy of it.
|
|
768
|
+
* Three differences, each of which is why both exist.
|
|
769
|
+
*
|
|
770
|
+
* **shape** `meta` renders a CHECK as prose: `"age_adult: age >= 18"`. That is the right
|
|
771
|
+
* form for its destination, which is `z.toJSONSchema` and then an OpenAPI
|
|
772
|
+
* viewer, and the wrong form for a form builder, which wants the bound as a
|
|
773
|
+
* number and would otherwise have to parse SQL back out of a sentence. Here the
|
|
774
|
+
* operand is data and the sentence is beside it.
|
|
775
|
+
*
|
|
776
|
+
* **content** `meta` has no foreign keys at all, and its `unique` is a list of column groups
|
|
777
|
+
* with the constraint names dropped. A form cannot render a picker without the
|
|
778
|
+
* first, and, more sharply, nothing can be mapped back to a constraint that has
|
|
779
|
+
* no name, which is what makes the second load bearing rather than cosmetic.
|
|
780
|
+
*
|
|
781
|
+
* **addressing** `meta` is reachable only by holding the emitted schema object and asking each
|
|
782
|
+
* field, per mode. This is a plain record keyed by table, so the answer to "what
|
|
783
|
+
* constrains this table" costs one property read and no validator import.
|
|
784
|
+
*
|
|
785
|
+
* What is *not* here is anything `meta` states about a column rather than a constraint: the SQL
|
|
786
|
+
* type, whether the database defaults the value, the numeric range of the column's own type. Those
|
|
787
|
+
* are facts about a field, `meta` carries them, and restating them here would be exactly the
|
|
788
|
+
* duplication the paragraph above is arguing against.
|
|
789
|
+
*
|
|
790
|
+
* Both halves are built from `classifyTableChecks` below, which `meta` also uses, so the two can
|
|
791
|
+
* disagree about what is enforced only by both being wrong at once.
|
|
792
|
+
*/
|
|
793
|
+
|
|
794
|
+
/** Where one part of a parsed CHECK lands. */
|
|
795
|
+
type CheckPartPlace = 'column' | 'row' | 'none';
|
|
796
|
+
/**
|
|
797
|
+
* One clause of one CHECK, classified by what the generated schemas do with it.
|
|
798
|
+
*
|
|
799
|
+
* A single `CHECK` declaration can split into several of these: `BETWEEN` is two bounds, and a
|
|
800
|
+
* conjunction is one part per operand. They are kept apart here because enforcement is decided
|
|
801
|
+
* per clause, and rejoined into one constraint by `tableConstraints`, because a database has one
|
|
802
|
+
* constraint there and quotes one name back.
|
|
803
|
+
*/
|
|
804
|
+
interface CheckPart {
|
|
805
|
+
/** The clause as text. Also the message the emitted schema attaches, when it attaches one. */
|
|
806
|
+
text: string;
|
|
807
|
+
/** The columns the clause is about, in the order the expression names them. */
|
|
808
|
+
columns: string[];
|
|
809
|
+
place: CheckPartPlace;
|
|
810
|
+
/** Why nothing enforces it, when nothing does. */
|
|
811
|
+
reason?: string;
|
|
812
|
+
/** Present when the clause was folded into the column's range instead of a predicate. */
|
|
813
|
+
bound?: {
|
|
814
|
+
column: string;
|
|
815
|
+
operator: ColumnCheck['operator'];
|
|
816
|
+
value: string;
|
|
817
|
+
};
|
|
818
|
+
/** Present when the clause was folded into a set of literals instead of a predicate. */
|
|
819
|
+
set?: {
|
|
820
|
+
column: string;
|
|
821
|
+
values: string[];
|
|
822
|
+
kind: 'number' | 'string';
|
|
823
|
+
};
|
|
824
|
+
/**
|
|
825
|
+
* Present when the clause is enforced by the field's shape rather than by a predicate.
|
|
826
|
+
*
|
|
827
|
+
* The third fold, after a bound and a set, and the one that leaves *nothing* to match on:
|
|
828
|
+
* `CHECK (col IS NOT NULL)` is enforced by the field not being nullable, and the failure it
|
|
829
|
+
* produces is the library's own "expected string, received null". Marked so `tableConstraints`
|
|
830
|
+
* does not offer the clause text as a message, which would have the error map keying on a
|
|
831
|
+
* string no emitted module writes.
|
|
832
|
+
*/
|
|
833
|
+
shape?: 'notNull';
|
|
834
|
+
}
|
|
835
|
+
/** One declared CHECK, with each of its clauses placed. */
|
|
836
|
+
interface ClassifiedCheck {
|
|
837
|
+
name?: string;
|
|
838
|
+
/** The expression as declared, trimmed. */
|
|
839
|
+
expression: string;
|
|
840
|
+
parts: CheckPart[];
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Every CHECK on a table, split into clauses and placed.
|
|
844
|
+
*
|
|
845
|
+
* The single reading of `parseCheck` plus the shape guards, shared by `meta` and by the constraint
|
|
846
|
+
* ledger so that "the database also checks this and we do not" is one answer rather than two.
|
|
847
|
+
*/
|
|
848
|
+
declare function classifyTableChecks(table: Table$1): ClassifiedCheck[];
|
|
849
|
+
/** What a constraint is. */
|
|
850
|
+
type ConstraintKind = 'primaryKey' | 'unique' | 'foreignKey' | 'check' | 'maxLength' | 'maxBytes';
|
|
851
|
+
/** One clause of a constraint that nothing in the generated schemas checks. */
|
|
852
|
+
interface UnenforcedPart {
|
|
853
|
+
part: string;
|
|
854
|
+
reason: string;
|
|
855
|
+
}
|
|
856
|
+
/** One constraint on one table. */
|
|
857
|
+
interface ConstraintFacts {
|
|
858
|
+
/**
|
|
859
|
+
* Stable identifier, unique within the table.
|
|
860
|
+
*
|
|
861
|
+
* The SQL constraint name where the declaration has one, and a derived name where it does not.
|
|
862
|
+
* This is what an error map keys on and what a caller stores against its own copy for a
|
|
863
|
+
* message, so it has to exist even for the constraints SQL leaves anonymous.
|
|
864
|
+
*/
|
|
865
|
+
id: string;
|
|
866
|
+
/** The SQL constraint name, absent where the declaration did not give one. */
|
|
867
|
+
name?: string;
|
|
868
|
+
kind: ConstraintKind;
|
|
869
|
+
/** The columns the constraint is about, in declaration order. */
|
|
870
|
+
columns: string[];
|
|
871
|
+
/** The rule as a sentence, for a form with nothing better to show. */
|
|
872
|
+
rule: string;
|
|
873
|
+
/** Whether a generated schema can reject a row for this constraint. */
|
|
874
|
+
enforced: boolean;
|
|
875
|
+
/** The clauses nothing enforces, and why. Absent when there are none. */
|
|
876
|
+
unenforced?: UnenforcedPart[];
|
|
877
|
+
/**
|
|
878
|
+
* The exact messages a generated schema attaches for this constraint.
|
|
879
|
+
*
|
|
880
|
+
* Absent where the schema states the constraint in the validator's own vocabulary instead, which
|
|
881
|
+
* is where the constraint name is lost and the error map has to key on something else.
|
|
882
|
+
*/
|
|
883
|
+
messages?: string[];
|
|
884
|
+
/** Bounds folded into a column's range. What a bound-carrying issue is matched against. */
|
|
885
|
+
bounds?: {
|
|
886
|
+
column: string;
|
|
887
|
+
operator: string;
|
|
888
|
+
value: string;
|
|
889
|
+
}[];
|
|
890
|
+
/** A set of literals folded into an enum. */
|
|
891
|
+
values?: {
|
|
892
|
+
column: string;
|
|
893
|
+
values: string[];
|
|
894
|
+
kind: 'number' | 'string';
|
|
895
|
+
};
|
|
896
|
+
/** Where a foreign key points. */
|
|
897
|
+
references?: {
|
|
898
|
+
table: string;
|
|
899
|
+
schema?: string;
|
|
900
|
+
columns: string[];
|
|
901
|
+
onDelete?: string;
|
|
902
|
+
onUpdate?: string;
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
/** Every constraint on one table. */
|
|
906
|
+
interface TableConstraints {
|
|
907
|
+
/** The SQL table name, which is not the Drizzle export name. */
|
|
908
|
+
table: string;
|
|
909
|
+
/** The SQL schema, present only when the table names one. */
|
|
910
|
+
schema?: string;
|
|
911
|
+
constraints: ConstraintFacts[];
|
|
912
|
+
}
|
|
913
|
+
/** Every constraint on a table, as data. */
|
|
914
|
+
declare function tableConstraints(table: Table$1): TableConstraints;
|
|
915
|
+
interface ConstraintsModuleOptions {
|
|
916
|
+
/** Also emit `constraintForIssue`, which is the half that maps a failure back. */
|
|
917
|
+
errorMap?: boolean;
|
|
918
|
+
}
|
|
919
|
+
/** What `constraints` is asking for, once the boolean shorthand is expanded. */
|
|
920
|
+
type ConstraintsOption = boolean | {
|
|
921
|
+
enabled?: boolean;
|
|
922
|
+
errorMap?: boolean;
|
|
923
|
+
};
|
|
924
|
+
declare function resolveConstraints(opt: ConstraintsOption | undefined): ConstraintsModuleOptions | undefined;
|
|
925
|
+
/** The file every generator writes the ledger to. Fixed, like the barrel's own name. */
|
|
926
|
+
declare const CONSTRAINTS_MODULE = "constraints.ts";
|
|
927
|
+
/**
|
|
928
|
+
* The `constraints.ts` module, as source.
|
|
929
|
+
*
|
|
930
|
+
* Plain objects and, optionally, one function. Nothing here imports a validator or any part of
|
|
931
|
+
* DRZL, so a consumer can read the ledger from a script, a form builder or a server route without
|
|
932
|
+
* pulling a schema in. The matcher is a separate export for the same reason a separate option
|
|
933
|
+
* turns it off: a consumer who only renders forms should not carry the code that maps failures.
|
|
934
|
+
*
|
|
935
|
+
* `JSON.stringify` rather than a hand-rolled literal, because every string here comes from a
|
|
936
|
+
* schema the user wrote: a table named `it's` or a CHECK holding a quote has to survive into valid
|
|
937
|
+
* TypeScript. The formatter unquotes the keys that do not need quoting.
|
|
938
|
+
*/
|
|
939
|
+
declare function renderConstraintsModule(tables: Table$1[], opts?: ConstraintsModuleOptions): string;
|
|
381
940
|
|
|
382
941
|
/**
|
|
383
942
|
* The facts a generated schema can carry beside itself.
|
|
@@ -647,7 +1206,7 @@ declare function nestedNodeColumns<T extends {
|
|
|
647
1206
|
*/
|
|
648
1207
|
|
|
649
1208
|
/**
|
|
650
|
-
* `findDuplicate<Table>s` for a table with unique constraints, or nothing.
|
|
1209
|
+
* `findDuplicate<Table>s` for a table with a primary key or unique constraints, or nothing.
|
|
651
1210
|
*
|
|
652
1211
|
* `rowType` is the name of the insert type, which is what a caller has in hand before an insert.
|
|
653
1212
|
* Passed in rather than derived, because each generator names its types differently.
|
|
@@ -661,6 +1220,16 @@ interface Table {
|
|
|
661
1220
|
primaryKey?: {
|
|
662
1221
|
columns: string[];
|
|
663
1222
|
};
|
|
1223
|
+
/**
|
|
1224
|
+
* The declared CHECK constraints, because one of them decides whether a column is nullable.
|
|
1225
|
+
*
|
|
1226
|
+
* Structurally the analyzer's `Check[]`, and here rather than only there because the three
|
|
1227
|
+
* column selectors below have to read it and this is the shape they are declared against.
|
|
1228
|
+
*/
|
|
1229
|
+
checks?: {
|
|
1230
|
+
name?: string;
|
|
1231
|
+
expression?: string;
|
|
1232
|
+
}[];
|
|
664
1233
|
}
|
|
665
1234
|
type ValidationLibrary = 'zod' | 'valibot' | 'arktype' | 'typebox' | 'effect';
|
|
666
1235
|
interface FormatOptions {
|
|
@@ -796,6 +1365,15 @@ interface ValidationGenerateOptions {
|
|
|
796
1365
|
insert?: boolean;
|
|
797
1366
|
update?: boolean;
|
|
798
1367
|
};
|
|
1368
|
+
/**
|
|
1369
|
+
* Where the generated files go, when that is not the filesystem.
|
|
1370
|
+
*
|
|
1371
|
+
* Omitted, they go to disk exactly as before. Passed, every write and every `mkdir` is handed to
|
|
1372
|
+
* the sink instead, which is what `drzl generate --dry-run` and `drzl generate --check` are
|
|
1373
|
+
* built on: both need the content a run would produce without the run producing it. See
|
|
1374
|
+
* `emit.ts` for why this is an option rather than an interception of `node:fs/promises`.
|
|
1375
|
+
*/
|
|
1376
|
+
fileSink?: FileSink;
|
|
799
1377
|
}
|
|
800
1378
|
interface ValidationRenderer<TOptions extends ValidationGenerateOptions = ValidationGenerateOptions> {
|
|
801
1379
|
readonly library: ValidationLibrary;
|
|
@@ -841,7 +1419,7 @@ declare function isGeneratedColumn(c: Column, _primaryKeyColumns?: string[]): bo
|
|
|
841
1419
|
* the real hazard is over-rejection. A check that turns away something Postgres accepts breaks
|
|
842
1420
|
* working code, which is worse than the bare `string` it replaces.
|
|
843
1421
|
*
|
|
844
|
-
*
|
|
1422
|
+
* The list is short for that reason. Candidates for `date`, `timestamp`, `time`, `interval`,
|
|
845
1423
|
* `inet`, `cidr` and `macaddr` were all built and all rejected, each by a value Postgres accepts
|
|
846
1424
|
* and the pattern did not:
|
|
847
1425
|
*
|
|
@@ -850,6 +1428,11 @@ declare function isGeneratedColumn(c: Column, _primaryKeyColumns?: string[]): bo
|
|
|
850
1428
|
* macaddr `2020-01-01`, which Postgres pads into `20:20:00:01:00:01`
|
|
851
1429
|
* inet `10.1/16`, `::ffff:1.2.3.4`
|
|
852
1430
|
* cidr parses as `inet` and then demands zero host bits, which no regex can state
|
|
1431
|
+
*
|
|
1432
|
+
* **One key per dialect where the servers disagree.** `numeric` is Postgres's alone, and the
|
|
1433
|
+
* analyzer withholds it from SQLite for that reason. The two `bigint` entries are the case where
|
|
1434
|
+
* withholding is not enough, because both dialects have a real answer and the answers contradict
|
|
1435
|
+
* each other in both directions. See them below.
|
|
853
1436
|
*/
|
|
854
1437
|
declare const COLUMN_FORMATS: Record<string, string>;
|
|
855
1438
|
/**
|
|
@@ -987,6 +1570,34 @@ declare function nonFiniteAccepted(c: Column): {
|
|
|
987
1570
|
nan: boolean;
|
|
988
1571
|
infinity: boolean;
|
|
989
1572
|
};
|
|
1573
|
+
/**
|
|
1574
|
+
* The non-finite doubles the emitted schema must *refuse*, as the analyzer stated them.
|
|
1575
|
+
*
|
|
1576
|
+
* The other reading of the same two flags, and not the negation of `nonFiniteAccepted`. Three
|
|
1577
|
+
* states, because a column that was measured and refused the value is a different thing from a
|
|
1578
|
+
* column nobody measured: `true` is stored and returned, `false` is offered and refused, absent is
|
|
1579
|
+
* unstated. `nonFiniteAccepted` reads `=== true` and this reads `=== false`, so an unstated column
|
|
1580
|
+
* answers no to both and every generator leaves it exactly as its library renders it.
|
|
1581
|
+
*
|
|
1582
|
+
* The distinction is load-bearing rather than tidy. MySQL and SQLite both leave a `real` unbounded,
|
|
1583
|
+
* and MySQL answers `ER_WARN_DATA_OUT_OF_RANGE` for an infinity where SQLite stores it and hands it
|
|
1584
|
+
* back. Reading "not accepted" as "refuse it" would have made one schema right and the other wrong
|
|
1585
|
+
* from the same reading, and the SQLite half is filed separately because that engine also turns
|
|
1586
|
+
* `NaN` into NULL and a column needs both halves of that answer or none.
|
|
1587
|
+
*
|
|
1588
|
+
* Guarded on `tsType` exactly as its sibling is, so a stale analysis carrying the flags on a string
|
|
1589
|
+
* or a shaped column cannot put a numeric predicate beside a `z.string()`.
|
|
1590
|
+
*
|
|
1591
|
+
* What each generator does with a yes is its own: `z.number()` and `Type.Number()` already refuse
|
|
1592
|
+
* every non-finite number with no bound at all, measured, so they emit nothing and read this only
|
|
1593
|
+
* through the tests that pin that. `v.number()` and ArkType's `number` take both infinities, so
|
|
1594
|
+
* those two add a finite predicate wherever no bound already holds one back. Effect builds on
|
|
1595
|
+
* `Schema.Finite` unconditionally and needs nothing either.
|
|
1596
|
+
*/
|
|
1597
|
+
declare function nonFiniteRefused(c: Column): {
|
|
1598
|
+
nan: boolean;
|
|
1599
|
+
infinity: boolean;
|
|
1600
|
+
};
|
|
990
1601
|
declare function insertColumns(table: Table): Column[];
|
|
991
1602
|
/**
|
|
992
1603
|
* Which columns belong in an update schema.
|
|
@@ -1003,6 +1614,33 @@ declare function insertColumns(table: Table): Column[];
|
|
|
1003
1614
|
*/
|
|
1004
1615
|
declare function updateColumns(table: Table): Column[];
|
|
1005
1616
|
declare function selectColumns(table: Table): Column[];
|
|
1617
|
+
/**
|
|
1618
|
+
* Whether a resolved manifest path belongs to a project's installed dependencies.
|
|
1619
|
+
*
|
|
1620
|
+
* Resolving a package is normally the same question as having it installed, and on Node and Deno
|
|
1621
|
+
* it is: their resolvers walk `node_modules` and answer a missing package with MODULE_NOT_FOUND.
|
|
1622
|
+
* Bun's does not. When nothing is found it auto-installs the package from npm and resolves into
|
|
1623
|
+
* its own global cache, so `require.resolve('@biomejs/biome/package.json')` succeeds under Bun
|
|
1624
|
+
* against a project whose package.json has never mentioned Biome. Measured under Bun 1.3.14:
|
|
1625
|
+
*
|
|
1626
|
+
* node -> MODULE_NOT_FOUND
|
|
1627
|
+
* deno -> MODULE_NOT_FOUND
|
|
1628
|
+
* bun -> /home/<user>/.bun/install/cache/@biomejs/biome@2.5.7@@@1/package.json
|
|
1629
|
+
*
|
|
1630
|
+
* That made `drzl generate` emit Biome-formatted files under Bun and unformatted files under Node
|
|
1631
|
+
* from the same schema and the same config, so `generate --check` under Node called every file
|
|
1632
|
+
* out of date; and it fetched a package, plus a multi-megabyte native binary, from the network in
|
|
1633
|
+
* the middle of codegen. It was not stable within Bun either, since whether the auto-install fired
|
|
1634
|
+
* depended on the state of a cache outside the project.
|
|
1635
|
+
*
|
|
1636
|
+
* A `node_modules` path segment is the discriminator, and it is exact rather than a heuristic:
|
|
1637
|
+
* npm, pnpm's `.pnpm` store and Yarn PnP's zip and unplugged paths all reach a package through
|
|
1638
|
+
* one, and Bun's auto-install cache is the one shape that does not, because it is not a project
|
|
1639
|
+
* install. Under Node and Deno this can never fire, since their resolvers have no other kind of
|
|
1640
|
+
* path to return, so it costs those runtimes nothing. A Bun project that really does install
|
|
1641
|
+
* Biome resolves through its `node_modules` like everyone else and still formats.
|
|
1642
|
+
*/
|
|
1643
|
+
declare function isProjectInstallPath(manifestPath: string): boolean;
|
|
1006
1644
|
/**
|
|
1007
1645
|
* Pretty-print emitted code with whatever formatter the consumer already has.
|
|
1008
1646
|
*
|
|
@@ -1030,4 +1668,4 @@ declare function selectColumns(table: Table): Column[];
|
|
|
1030
1668
|
*/
|
|
1031
1669
|
declare function formatCode(code: string, filePath: string, fmt?: FormatOptions): Promise<any>;
|
|
1032
1670
|
|
|
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 };
|
|
1671
|
+
export { AFFIX_PREFIX_PATTERN, AFFIX_PROBE_TABLE, AFFIX_SUFFIX_PATTERN, type AffixIssue, type AffixOptions, type AffixValue, type BrandAlias, type BrandPlan, type BrandingOption, type BrandingOptions, CODEPOINT_LENGTH, COERCIBLE_DATE_STRING, COLUMN_FORMATS, CONSTRAINTS_MODULE, type CardinalityCheck, type CheckPart, type CheckPartPlace, type ClassifiedCheck, type ColumnCheck, type ColumnMetaFacts, type ColumnSet, type ComparisonWire, type ConstraintFacts, type ConstraintKind, type ConstraintsModuleOptions, type ConstraintsOption, DEFAULT_IMPORT_EXTENSION, DEFAULT_MODE_PREFIX, DEFAULT_NESTED_DEPTH, DEFAULT_SCHEMA_SUFFIX, DEFAULT_TYPE_SUFFIX, type FileSink, type FormatOptions, type GeneratorFs, IMPORT_EXTENSIONS, type ImportExtension, type LengthCheck, type LengthMeasure, MAX_NESTED_DEPTH, type MetaFactOptions, NAME_MODES, NESTED_PREFIX, NUMERIC_CANON_NAME, NUMERIC_CANON_SOURCE, type NameMode, type NestedArm, type NestedMode, type NestedNode, type NullCheck, type ParsedCheck, type ResolvedAffix, type ResolvedBranding, type RowCheck, type Table, type TableCase, type TableConstraints, type TableMetaFacts, type TableMetaOptions, type UnenforcedLiteral, type UnenforcedPart, type ValidationGenerateOptions, type ValidationLibrary, type ValidationRenderer, type WireLiteralFit, type WireLiteralQuestion, applyTableCase, applyWirePolicy, buildBrandPlan, buildNestedPlan, canonicalMembers, canonicalNumericText, classifyTableChecks, columnMetaFacts, comparisonWire, describeSet, fileWriter, formatCode, importSpecifier, insertColumns, isGeneratedColumn, isIntegerColumn, isProjectInstallPath, lengthCheckLabel, lengthMeasure, measureExpression, moduleFileName, moduleSpecifier, needsNumericCanon, nestedArmNotes, nestedNodeColumns, nestedSchemaName, nestedTypeName, nonFiniteAccepted, nonFiniteRefused, parseCheck, parsesToADate, pascalCase, renderConstraintsModule, renderDuplicateFinder, resolveAffix, resolveBranding, resolveConfiguredImport, resolveConstraints, resolveNestedDepth, schemaName, selectColumns, tableConstraints, tableMetaFacts, typeName, updateColumns, validateAffix, wireLiteralFit, wireNumberLiteral };
|