@vibeorm/schema 5.0.0 → 5.2.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/capabilities.d.ts +15 -0
- package/dist/date-truncation.d.ts +37 -0
- package/dist/errors.d.ts +4 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +62 -15
- package/dist/index.js.map +8 -7
- package/package.json +1 -1
package/dist/capabilities.d.ts
CHANGED
|
@@ -90,6 +90,14 @@ export type DialectCapabilities = {
|
|
|
90
90
|
readonly partialIndexes: boolean;
|
|
91
91
|
/** Named-constraint conflict targets (`ON CONFLICT ON CONSTRAINT …`). */
|
|
92
92
|
readonly conflictTargetConstraint: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* A guard on the conflict's update arm: `ON CONFLICT (…) DO UPDATE SET …
|
|
95
|
+
* WHERE <guard>` — the existing row is updated (and returned) only when the
|
|
96
|
+
* guard admits it, in the same atomic statement. Tenant-scoped (`$scoped`)
|
|
97
|
+
* and `$withPolicy` upserts use it; MySQL's `ON DUPLICATE KEY UPDATE` has no
|
|
98
|
+
* WHERE, so there they keep the transactional find-then-write path.
|
|
99
|
+
*/
|
|
100
|
+
readonly conflictUpdateWhere: boolean;
|
|
93
101
|
/** DDL participates in transactions (mysql auto-commits DDL). */
|
|
94
102
|
readonly transactionalDdl: boolean;
|
|
95
103
|
/** createManyAndReturn support (requires multi-row RETURNING). */
|
|
@@ -134,6 +142,13 @@ export type DialectCapabilities = {
|
|
|
134
142
|
readonly uuidColumns: "native" | "text" | "unsupported";
|
|
135
143
|
/** String-backed Gregorian calendar days: native DATE, or TEXT with an owned CHECK on SQLite. */
|
|
136
144
|
readonly localDateColumns: boolean;
|
|
145
|
+
/**
|
|
146
|
+
* Calendar truncation of a timestamp in a named time zone — the `groupBy`
|
|
147
|
+
* key `{ field, truncate, timeZone }` (PostgreSQL `date_trunc` with a zone,
|
|
148
|
+
* PostgreSQL 12 or later). Without it the key is refused when the query is
|
|
149
|
+
* built.
|
|
150
|
+
*/
|
|
151
|
+
readonly dateTruncation: boolean;
|
|
137
152
|
};
|
|
138
153
|
export declare const POSTGRES_CAPABILITIES: DialectCapabilities;
|
|
139
154
|
export declare const SQLITE_CAPABILITIES: DialectCapabilities;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar truncation of a timestamp — the `groupBy` key form
|
|
3
|
+
* `{ field, truncate, timeZone }` (one group per hour, day, ISO week, month
|
|
4
|
+
* or year of a `DateTime` field, in a named time zone).
|
|
5
|
+
*
|
|
6
|
+
* ONE rule decides which fields can take it: the runtime's refusal, the
|
|
7
|
+
* renderer's expression and the generator's `by` type all call
|
|
8
|
+
* {@link dateTruncationStorage}, so they can never disagree.
|
|
9
|
+
*/
|
|
10
|
+
import type { FieldType } from "./ir.ts";
|
|
11
|
+
/** A calendar period a timestamp can be truncated to (PostgreSQL `date_trunc` units; `week` is the ISO week, starting on Monday). */
|
|
12
|
+
export type DateTruncationUnit = "hour" | "day" | "week" | "month" | "year";
|
|
13
|
+
/** Every {@link DateTruncationUnit}, in increasing length. */
|
|
14
|
+
export declare const DATE_TRUNCATION_UNITS: readonly DateTruncationUnit[];
|
|
15
|
+
/**
|
|
16
|
+
* How a truncatable field stores its value on PostgreSQL. `"timestamptz"`: an
|
|
17
|
+
* instant (the canonical `DateTime` mapping, `@db.Timestamptz(n)`).
|
|
18
|
+
* `"timestamp"`: a zone-less column (`@db.Timestamp(n)`), which by VibeORM's
|
|
19
|
+
* contract holds the UTC wall-clock of the instant on every postgres driver.
|
|
20
|
+
*/
|
|
21
|
+
export type DateTruncationStorage = "timestamptz" | "timestamp";
|
|
22
|
+
/**
|
|
23
|
+
* The storage form of a field a timestamp truncation can read, or `undefined`
|
|
24
|
+
* when the field cannot be truncated: not a `DateTime`, a list, an
|
|
25
|
+
* unsupported type, or a native type that is not a timestamp (`@db.Date`,
|
|
26
|
+
* `@db.Time`, `@db.Timetz`). A `DateTime` without a native type takes the
|
|
27
|
+
* canonical mapping, `timestamptz`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function dateTruncationStorage(params: {
|
|
30
|
+
readonly field: {
|
|
31
|
+
readonly type: FieldType;
|
|
32
|
+
readonly nativeType?: string;
|
|
33
|
+
readonly isList: boolean;
|
|
34
|
+
readonly isUnsupported?: boolean;
|
|
35
|
+
};
|
|
36
|
+
}): DateTruncationStorage | undefined;
|
|
37
|
+
//# sourceMappingURL=date-truncation.d.ts.map
|
package/dist/errors.d.ts
CHANGED
|
@@ -130,8 +130,11 @@ export declare function internalError(params: {
|
|
|
130
130
|
* Stable lifecycle categories; metadata never includes identity values.
|
|
131
131
|
* `"null-claim"`: a key listed in `nativeRls.refuseNull` is bound to `null` and
|
|
132
132
|
* the operation reaches a model whose rule reads it (or runs raw SQL).
|
|
133
|
+
* `"context-switched"`: the handle's transaction is running a `tx.$inContext`
|
|
134
|
+
* switch under another identity; the handle (and every handle derived from it)
|
|
135
|
+
* is refused until the switch has restored its claims.
|
|
133
136
|
*/
|
|
134
|
-
export declare const RLS_CONTEXT_REASONS: readonly ["missing", "invalid", "expired", "conflicting", "null-claim"];
|
|
137
|
+
export declare const RLS_CONTEXT_REASONS: readonly ["missing", "invalid", "expired", "conflicting", "null-claim", "context-switched"];
|
|
135
138
|
export type RlsContextReason = (typeof RLS_CONTEXT_REASONS)[number];
|
|
136
139
|
/** Native RLS binding/lifecycle failure, distinct from database permissions. */
|
|
137
140
|
export declare class RlsContextError extends VibeError {
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export { assertMigrationObjects, isSqlObjectDefinition, isSqlObjectIdentity, sql
|
|
|
26
26
|
export type { MigrationObjectIR, QualifiedSqlName, SqlObjectDefinition, SqlObjectDefinitionFormat, SqlObjectFacts, SqlObjectIdentity } from "./migration-objects.ts";
|
|
27
27
|
export type { AdvisoryLockMode, DialectCapabilities, FullTextEngine, RowLockStrength, RowLockWaitPolicy, TimeOfDayType, } from "./capabilities.ts";
|
|
28
28
|
export { timeOfDayType } from "./time-of-day.ts";
|
|
29
|
+
export { DATE_TRUNCATION_UNITS, dateTruncationStorage } from "./date-truncation.ts";
|
|
30
|
+
export type { DateTruncationStorage, DateTruncationUnit } from "./date-truncation.ts";
|
|
29
31
|
export { isLocalDateField, isCanonicalLocalDate, localDateCheckName } from "./local-date.ts";
|
|
30
32
|
export { READ_ONLY_REFUSAL_REASONS, ReadOnlyClientError, SCOPED_REFUSAL_REASONS, ScopedQueryError, VIBE_ERROR_CODES, VibeError, internalError, invalidStoredData, unsupportedCapability, } from "./errors.ts";
|
|
31
33
|
export type { ReadOnlyRefusalReason, ScopedRefusalReason, VibeErrorCode } from "./errors.ts";
|
package/dist/index.js
CHANGED
|
@@ -71,7 +71,8 @@ var RLS_CONTEXT_REASONS = Object.freeze([
|
|
|
71
71
|
"invalid",
|
|
72
72
|
"expired",
|
|
73
73
|
"conflicting",
|
|
74
|
-
"null-claim"
|
|
74
|
+
"null-claim",
|
|
75
|
+
"context-switched"
|
|
75
76
|
]);
|
|
76
77
|
|
|
77
78
|
class RlsContextError extends VibeError {
|
|
@@ -447,6 +448,7 @@ var POSTGRES_CAPABILITIES = Object.freeze({
|
|
|
447
448
|
distinctOn: true,
|
|
448
449
|
partialIndexes: true,
|
|
449
450
|
conflictTargetConstraint: true,
|
|
451
|
+
conflictUpdateWhere: true,
|
|
450
452
|
transactionalDdl: true,
|
|
451
453
|
createManyAndReturn: true,
|
|
452
454
|
jsonColumnType: true,
|
|
@@ -457,7 +459,8 @@ var POSTGRES_CAPABILITIES = Object.freeze({
|
|
|
457
459
|
advisoryLocks: true,
|
|
458
460
|
timeOfDayTypes: Object.freeze(["time", "timetz"]),
|
|
459
461
|
uuidColumns: "native",
|
|
460
|
-
localDateColumns: true
|
|
462
|
+
localDateColumns: true,
|
|
463
|
+
dateTruncation: true
|
|
461
464
|
});
|
|
462
465
|
var SQLITE_CAPABILITIES = Object.freeze({
|
|
463
466
|
rowRuleAppendOnly: true,
|
|
@@ -485,6 +488,7 @@ var SQLITE_CAPABILITIES = Object.freeze({
|
|
|
485
488
|
distinctOn: false,
|
|
486
489
|
partialIndexes: true,
|
|
487
490
|
conflictTargetConstraint: false,
|
|
491
|
+
conflictUpdateWhere: true,
|
|
488
492
|
transactionalDdl: true,
|
|
489
493
|
createManyAndReturn: true,
|
|
490
494
|
jsonColumnType: false,
|
|
@@ -495,7 +499,8 @@ var SQLITE_CAPABILITIES = Object.freeze({
|
|
|
495
499
|
advisoryLocks: false,
|
|
496
500
|
timeOfDayTypes: Object.freeze([]),
|
|
497
501
|
uuidColumns: "text",
|
|
498
|
-
localDateColumns: true
|
|
502
|
+
localDateColumns: true,
|
|
503
|
+
dateTruncation: false
|
|
499
504
|
});
|
|
500
505
|
var MYSQL_CAPABILITIES = Object.freeze({
|
|
501
506
|
rowRuleAppendOnly: true,
|
|
@@ -523,6 +528,7 @@ var MYSQL_CAPABILITIES = Object.freeze({
|
|
|
523
528
|
distinctOn: false,
|
|
524
529
|
partialIndexes: false,
|
|
525
530
|
conflictTargetConstraint: false,
|
|
531
|
+
conflictUpdateWhere: false,
|
|
526
532
|
transactionalDdl: false,
|
|
527
533
|
createManyAndReturn: false,
|
|
528
534
|
jsonColumnType: true,
|
|
@@ -533,7 +539,8 @@ var MYSQL_CAPABILITIES = Object.freeze({
|
|
|
533
539
|
advisoryLocks: false,
|
|
534
540
|
timeOfDayTypes: Object.freeze(["time"]),
|
|
535
541
|
uuidColumns: "unsupported",
|
|
536
|
-
localDateColumns: true
|
|
542
|
+
localDateColumns: true,
|
|
543
|
+
dateTruncation: false
|
|
537
544
|
});
|
|
538
545
|
var DIALECT_CAPABILITIES = Object.freeze({
|
|
539
546
|
postgres: POSTGRES_CAPABILITIES,
|
|
@@ -1936,6 +1943,11 @@ function validateRowRules(params) {
|
|
|
1936
1943
|
function wellFormedName(params) {
|
|
1937
1944
|
return [...params.name].every((character) => character.length !== 1 || character.charCodeAt(0) < 55296 || character.charCodeAt(0) > 57343);
|
|
1938
1945
|
}
|
|
1946
|
+
var REPEATED_RULE_ADVICE = Object.freeze({
|
|
1947
|
+
appendOnly: "Keep one: it already refuses every UPDATE and DELETE.",
|
|
1948
|
+
rowFixed: "Merge them into one rule whose except list holds only the fields both leave mutable.",
|
|
1949
|
+
frozen: "Merge them into one rule, or protect the fields of a later stage with a fixed rule gated by onceSet."
|
|
1950
|
+
});
|
|
1939
1951
|
function validateRowRulesWithLookup(params) {
|
|
1940
1952
|
const { model, dialect } = params;
|
|
1941
1953
|
const rules = model.rowRules;
|
|
@@ -1947,7 +1959,7 @@ function validateRowRulesWithLookup(params) {
|
|
|
1947
1959
|
const add = (params) => {
|
|
1948
1960
|
issues.push({ rule: params.rule ?? "row-rule-invalid", model: model.name, ...params });
|
|
1949
1961
|
};
|
|
1950
|
-
const
|
|
1962
|
+
const firstOfKind = new Map;
|
|
1951
1963
|
const seenDefinitions = new Set;
|
|
1952
1964
|
const seenNames = new Set;
|
|
1953
1965
|
for (const value of rules) {
|
|
@@ -1958,18 +1970,22 @@ function validateRowRulesWithLookup(params) {
|
|
|
1958
1970
|
}
|
|
1959
1971
|
const rule = value;
|
|
1960
1972
|
const generated = rule.kind === "appendOnly" ? new Set : params.generatedFields({ model });
|
|
1961
|
-
|
|
1973
|
+
const first = rule.kind === "fixed" ? undefined : firstOfKind.get(rule.kind);
|
|
1974
|
+
if (first !== undefined && rule.kind !== "fixed")
|
|
1975
|
+
add({ message: `Model "${model.name}" declares more than one ${rule.kind} row rule (${first}, ${rule.name}). A model takes at most one ${rule.kind} rule; only fixed rules may repeat. ${REPEATED_RULE_ADVICE[rule.kind]}` });
|
|
1976
|
+
else if (seenNames.has(rule.name))
|
|
1962
1977
|
add({ rule: "row-rule-name-collision", message: `Row rules share the physical stem ${rule.name}; supply an explicit, distinct name stem` });
|
|
1963
1978
|
seenNames.add(rule.name);
|
|
1964
1979
|
if (model.isView)
|
|
1965
1980
|
add({ message: "Row rules require a stored table, not a view" });
|
|
1966
|
-
if (rule.kind !== "fixed" &&
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1981
|
+
if (rule.kind !== "fixed" && first === undefined)
|
|
1982
|
+
firstOfKind.set(rule.kind, rule.name);
|
|
1983
|
+
if (rule.kind === "fixed") {
|
|
1984
|
+
const definition = JSON.stringify([rule.kind, [...rule.columns].sort(), rule.when]);
|
|
1985
|
+
if (seenDefinitions.has(definition))
|
|
1986
|
+
add({ message: "Duplicate fixed row-rule declaration" });
|
|
1987
|
+
seenDefinitions.add(definition);
|
|
1988
|
+
}
|
|
1973
1989
|
const pair = rowRuleObjectNames({ rule });
|
|
1974
1990
|
const names = dialect === "postgres" ? [pair.functionName, pair.triggerName] : (rule.kind === "appendOnly" ? ["update", "delete"] : ["update"]).map((event) => rowRuleTriggerName({ rule, event }));
|
|
1975
1991
|
for (const name of names) {
|
|
@@ -2004,7 +2020,7 @@ function validateRowRulesWithLookup(params) {
|
|
|
2004
2020
|
if (rule.kind !== "appendOnly" && (rule.kind === "fixed" && rule.columns.length === 0 || protectedFieldsWithGenerated({ model, rule, generated }).length === 0))
|
|
2005
2021
|
add({ message: `${rule.kind} requires at least one protected stored field` });
|
|
2006
2022
|
}
|
|
2007
|
-
if (
|
|
2023
|
+
if (firstOfKind.has("appendOnly") && rules.length > 1)
|
|
2008
2024
|
add({ message: "appendOnly cannot combine with other row rules; it already refuses every UPDATE and DELETE" });
|
|
2009
2025
|
return issues;
|
|
2010
2026
|
}
|
|
@@ -2414,6 +2430,23 @@ function timeOfDayType(params) {
|
|
|
2414
2430
|
return "timetz";
|
|
2415
2431
|
return "time";
|
|
2416
2432
|
}
|
|
2433
|
+
// packages/schema/src/date-truncation.ts
|
|
2434
|
+
var DATE_TRUNCATION_UNITS = Object.freeze(["hour", "day", "week", "month", "year"]);
|
|
2435
|
+
var ZONED_NATIVE_TYPE = /^timestamptz(?:\(\d+\))?$|^timestamp(?:\(\d+\))?withtimezone$/;
|
|
2436
|
+
var ZONE_LESS_NATIVE_TYPE = /^timestamp(?:\(\d+\))?(?:withouttimezone)?$/;
|
|
2437
|
+
function dateTruncationStorage(params) {
|
|
2438
|
+
const { field } = params;
|
|
2439
|
+
if (field.type !== "DateTime" || field.isList || field.isUnsupported === true)
|
|
2440
|
+
return;
|
|
2441
|
+
if (field.nativeType === undefined)
|
|
2442
|
+
return "timestamptz";
|
|
2443
|
+
const native = field.nativeType.toLowerCase().replace(/\s+/g, "");
|
|
2444
|
+
if (ZONED_NATIVE_TYPE.test(native))
|
|
2445
|
+
return "timestamptz";
|
|
2446
|
+
if (ZONE_LESS_NATIVE_TYPE.test(native))
|
|
2447
|
+
return "timestamp";
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2417
2450
|
// packages/schema/src/computed.ts
|
|
2418
2451
|
function buildComputedManifest(params) {
|
|
2419
2452
|
const { computed } = params;
|
|
@@ -2778,6 +2811,8 @@ var RESERVED_ENUM_NAMES = [
|
|
|
2778
2811
|
"KeysetPage",
|
|
2779
2812
|
"KeysetOptions",
|
|
2780
2813
|
"RowLockRequest",
|
|
2814
|
+
"GroupByTruncateUnit",
|
|
2815
|
+
"GroupByKeyName",
|
|
2781
2816
|
"JsonValue",
|
|
2782
2817
|
"InputJsonValue",
|
|
2783
2818
|
"InputJsonArray",
|
|
@@ -2810,6 +2845,7 @@ var RESERVED_ENUM_NAMES = [
|
|
|
2810
2845
|
"Skip",
|
|
2811
2846
|
"TransactionOptions",
|
|
2812
2847
|
"CallbackTransactionOptions",
|
|
2848
|
+
"RawStatement",
|
|
2813
2849
|
"AdvisoryLockOptions",
|
|
2814
2850
|
"StatementDiagnosticsOptions",
|
|
2815
2851
|
"SchemaIR",
|
|
@@ -2849,6 +2885,11 @@ var GENERATED_TYPE_NAMES = {
|
|
|
2849
2885
|
"ScalarOrderByInput",
|
|
2850
2886
|
"RelationOrderByInput",
|
|
2851
2887
|
"RelationFindManyArgs",
|
|
2888
|
+
"RelationAggregateOrderByInput",
|
|
2889
|
+
"RelationAvgOrderByInput",
|
|
2890
|
+
"RelationSumOrderByInput",
|
|
2891
|
+
"RelationMinOrderByInput",
|
|
2892
|
+
"RelationMaxOrderByInput",
|
|
2852
2893
|
"CreateInput",
|
|
2853
2894
|
"UncheckedCreateInput",
|
|
2854
2895
|
"CreateManyInput",
|
|
@@ -2872,6 +2913,7 @@ var GENERATED_TYPE_NAMES = {
|
|
|
2872
2913
|
"MaxAggregateOrderByInput",
|
|
2873
2914
|
"HavingInput",
|
|
2874
2915
|
"GroupByOrderByInput",
|
|
2916
|
+
"GroupByTruncatedKey",
|
|
2875
2917
|
"FindManyArgs",
|
|
2876
2918
|
"FindPageArgs",
|
|
2877
2919
|
"FindFirstArgs",
|
|
@@ -3004,7 +3046,10 @@ function validateGeneratedNames(params) {
|
|
|
3004
3046
|
owners.set("BoundVibeClientInstance", "native RLS client");
|
|
3005
3047
|
owners.set("BoundVibeTransactionClientInstance", "native RLS client");
|
|
3006
3048
|
owners.set("OperatorVibeClientInstance", "native RLS client");
|
|
3049
|
+
owners.set("OperatorVibeTransactionClientInstance", "native RLS client");
|
|
3007
3050
|
}
|
|
3051
|
+
if (schema.models.some((model) => model.policy !== undefined))
|
|
3052
|
+
owners.set("PolicyClientInstance", "policy client");
|
|
3008
3053
|
if (schema.models.some((model) => model.fields.some((field) => isLocalDateField({ field })))) {
|
|
3009
3054
|
for (const name of ["LocalDate", "LocalDateFilter", "LocalDateNullableFilter"])
|
|
3010
3055
|
owners.set(name, "local-date filters");
|
|
@@ -4368,6 +4413,7 @@ function validRlsForeignKey(params) {
|
|
|
4368
4413
|
}
|
|
4369
4414
|
export {
|
|
4370
4415
|
CHECK_RULE_GROUP_MAX,
|
|
4416
|
+
DATE_TRUNCATION_UNITS,
|
|
4371
4417
|
DIALECTS,
|
|
4372
4418
|
DIALECT_CAPABILITIES,
|
|
4373
4419
|
EMPTY_EXTENSION_REGISTRY,
|
|
@@ -4412,6 +4458,7 @@ export {
|
|
|
4412
4458
|
buildComputedManifest,
|
|
4413
4459
|
buildExtensionRegistry,
|
|
4414
4460
|
checkRulePredicate,
|
|
4461
|
+
dateTruncationStorage,
|
|
4415
4462
|
decodePrismaString,
|
|
4416
4463
|
defaultExpressionIndexName,
|
|
4417
4464
|
defaultForeignKeyName,
|
|
@@ -4502,4 +4549,4 @@ export {
|
|
|
4502
4549
|
validatorRefIssue
|
|
4503
4550
|
};
|
|
4504
4551
|
|
|
4505
|
-
//# debugId=
|
|
4552
|
+
//# debugId=6A81F5FFEA4B498F64756E2164756E21
|