From b51d33c41ca1baa84d9f880b0093eb9354506a1f Mon Sep 17 00:00:00 2001 From: sanny-io <3054653+sanny-io@users.noreply.github.com> Date: Mon, 5 Oct 2026 08:07:41 -0700 Subject: [PATCH 01/18] feat(language): implicitly convert `enum` references to arrays (#2807) Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 --- packages/cli/test/ts-schema-gen.test.ts | 120 +++++++++++++++ packages/language/src/generated/ast.ts | 4 +- packages/language/src/generated/grammar.ts | 6 + .../src/validators/expression-validator.ts | 14 ++ packages/language/src/zmodel-linker.ts | 6 + packages/language/src/zmodel.langium | 2 +- packages/language/test/enum.test.ts | 92 ++++++++++++ .../orm/src/client/executor/name-mapper.ts | 17 ++- .../policy/src/expression-transformer.ts | 24 ++- packages/sdk/src/ts-schema-generator.ts | 10 ++ packages/zod/test/factory.test.ts | 140 ++++++++++++++++-- packages/zod/test/schema/schema-lite.ts | 12 ++ packages/zod/test/schema/schema.ts | 12 ++ packages/zod/test/schema/schema.zmodel | 7 + tests/e2e/orm/client-api/enum.test.ts | 63 ++++++++ tests/e2e/orm/schemas/enum/schema.ts | 66 +++++++++ tests/e2e/orm/schemas/enum/schema.zmodel | 21 +++ tests/regression/test/issue-1211.test.ts | 89 +++++++++++ 18 files changed, 683 insertions(+), 22 deletions(-) create mode 100644 packages/language/test/enum.test.ts create mode 100644 tests/e2e/orm/client-api/enum.test.ts create mode 100644 tests/e2e/orm/schemas/enum/schema.ts create mode 100644 tests/e2e/orm/schemas/enum/schema.zmodel create mode 100644 tests/regression/test/issue-1211.test.ts diff --git a/packages/cli/test/ts-schema-gen.test.ts b/packages/cli/test/ts-schema-gen.test.ts index 06a1cab57..e84b1e054 100644 --- a/packages/cli/test/ts-schema-gen.test.ts +++ b/packages/cli/test/ts-schema-gen.test.ts @@ -852,6 +852,126 @@ model Post { }); }); + it('supports implicit conversions from enums to arrays', async () => { + const { schema } = await generateTsSchema(` +enum PostStatus { + DRAFT + ACTIVE + CANCELLED +} + +model User { + id Int @id @default(autoincrement()) +} + +model Post { + id String @id + status String + + @@validate(status in PostStatus) +} + `); + + expect(schema.models['Post']?.attributes).toMatchObject([ + { + name: '@@validate', + args: [ + { + name: 'value', + value: { + kind: 'binary', + op: 'in', + left: { + kind: 'field', + field: 'status', + }, + right: { + kind: 'array', + type: 'PostStatus', + items: [ + { + kind: 'literal', + value: 'DRAFT', + }, + { + kind: 'literal', + value: 'ACTIVE', + }, + { + kind: 'literal', + value: 'CANCELLED', + }, + ], + }, + binding: undefined, + }, + }, + ], + }, + ]); + }); + + it('emits enum names (not @map values) when converting enums to arrays', async () => { + // enum values are represented by their names at runtime (TS values, auth(), Zod); + // the ORM's name mapper translates them to `@map`-ed values at SQL execution time + const { schema } = await generateTsSchema(` +enum PostStatus { + DRAFT @map('draft') + ACTIVE @map('active') + CANCELLED @map('cancelled') +} + +model User { + id Int @id @default(autoincrement()) +} + +model Post { + id String @id + status String + + @@validate(status in PostStatus) +} + `); + + expect(schema.models['Post']?.attributes).toMatchObject([ + { + name: '@@validate', + args: [ + { + name: 'value', + value: { + kind: 'binary', + op: 'in', + left: { + kind: 'field', + field: 'status', + }, + right: { + kind: 'array', + type: 'PostStatus', + items: [ + { + kind: 'literal', + value: 'DRAFT', + }, + { + kind: 'literal', + value: 'ACTIVE', + }, + { + kind: 'literal', + value: 'CANCELLED', + }, + ], + }, + binding: undefined, + }, + }, + ], + }, + ]); + }); + it('supports @@strict for type defs', async () => { const { schema } = await generateTsSchema(` model User { diff --git a/packages/language/src/generated/ast.ts b/packages/language/src/generated/ast.ts index 545fdb944..c5ecbe0fd 100644 --- a/packages/language/src/generated/ast.ts +++ b/packages/language/src/generated/ast.ts @@ -902,7 +902,7 @@ export function isReferenceExpr(item: unknown): item is ReferenceExpr { return reflection.isInstance(item, ReferenceExpr.$type); } -export type ReferenceTarget = CollectionPredicateBinding | DataField | EnumField | FunctionParam; +export type ReferenceTarget = CollectionPredicateBinding | DataField | Enum | EnumField | FunctionParam; export const ReferenceTarget = { $type: 'ReferenceTarget' @@ -1427,7 +1427,7 @@ export class ZModelAstReflection extends langium.AbstractAstReflection { name: Enum.name } }, - superTypes: [AbstractDeclaration.$type, TypeDeclaration.$type] + superTypes: [AbstractDeclaration.$type, ReferenceTarget.$type, TypeDeclaration.$type] }, EnumField: { name: EnumField.$type, diff --git a/packages/language/src/generated/grammar.ts b/packages/language/src/generated/grammar.ts index 0f7f341a4..ac3ae2b27 100644 --- a/packages/language/src/generated/grammar.ts +++ b/packages/language/src/generated/grammar.ts @@ -4008,6 +4008,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "typeRef": { "$ref": "#/rules@30" } + }, + { + "$type": "SimpleType", + "typeRef": { + "$ref": "#/rules@46" + } } ] } diff --git a/packages/language/src/validators/expression-validator.ts b/packages/language/src/validators/expression-validator.ts index 3efe6d916..a3b3bcc59 100644 --- a/packages/language/src/validators/expression-validator.ts +++ b/packages/language/src/validators/expression-validator.ts @@ -4,6 +4,7 @@ import { Expression, isArrayExpr, isCollectionPredicateBinding, + isDataFieldAttribute, isDataModel, isDataModelAttribute, isEnum, @@ -87,6 +88,11 @@ export default class ExpressionValidator implements AstValidator { node: expr, }); } + if (isEnum(expr.target.ref) && !this.isInPolicyOrValidationAttribute(expr)) { + accept('error', 'Enum reference can only be used with policy and validation attributes', { + node: expr, + }); + } } private validateMemberAccessExpr(expr: MemberAccessExpr, accept: ValidationAcceptor) { @@ -281,6 +287,14 @@ export default class ExpressionValidator implements AstValidator { return findUpAst(node, (n) => isDataModelAttribute(n) && n.decl.$refText === '@@validate'); } + private isInPolicyOrValidationAttribute(node: AstNode) { + const attrs = ['@allow', '@@allow', '@deny', '@@deny', '@@validate']; + return findUpAst( + node, + (n) => (isDataModelAttribute(n) || isDataFieldAttribute(n)) && attrs.includes(n.decl.$refText), + ); + } + private isNotModelFieldExpr(expr: Expression): boolean { return ( // literal diff --git a/packages/language/src/zmodel-linker.ts b/packages/language/src/zmodel-linker.ts index 6766a6afc..0908f7c20 100644 --- a/packages/language/src/zmodel-linker.ts +++ b/packages/language/src/zmodel-linker.ts @@ -265,6 +265,12 @@ export class ZModelLinker extends DefaultLinker { } else if (isDataField(target) || isFunctionParam(target)) { // other references are resolved to their declared type this.resolveToDeclaredType(node, target.type); + } else if (isEnum(target)) { + node.$resolvedType = { + decl: target, + array: true, + nullable: false, + }; } } } diff --git a/packages/language/src/zmodel.langium b/packages/language/src/zmodel.langium index 4f39ea28e..6856e6183 100644 --- a/packages/language/src/zmodel.langium +++ b/packages/language/src/zmodel.langium @@ -66,7 +66,7 @@ ConfigArrayExpr: ConfigExpr: LiteralExpr | InvocationExpr | ConfigArrayExpr; -type ReferenceTarget = FunctionParam | DataField | EnumField | CollectionPredicateBinding; +type ReferenceTarget = FunctionParam | DataField | EnumField | CollectionPredicateBinding | Enum; ThisExpr: value='this'; diff --git a/packages/language/test/enum.test.ts b/packages/language/test/enum.test.ts new file mode 100644 index 000000000..bea678dff --- /dev/null +++ b/packages/language/test/enum.test.ts @@ -0,0 +1,92 @@ +import { describe, it } from 'vitest'; +import { loadSchema, loadSchemaWithError } from './utils'; + +describe('Enum tests', () => { + describe('implicit array conversions', () => { + it('supports usage with policy and validation attributes', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + enum PostStatus { + DRAFT + ACTIVE + CANCELLED + } + + model User { + id Int @id @default(autoincrement()) + } + + model Post { + id String @id + status String @allow('update', status in PostStatus) @deny('read', !(status in PostStatus)) + + @@allow('read', status in PostStatus) + @@deny('read', status in PostStatus) + @@validate(status in PostStatus) + } + `); + }); + + it('rejects usage with non-policy and non-validation attributes', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + enum PostStatus { + DRAFT + ACTIVE + CANCELLED + } + + model User { + id Int @id @default(autoincrement()) + } + + model Post { + id String @id + status PostStatus[] @default(PostStatus) + } + `, + /Enum reference can only be used with policy and validation attributes/, + ); + }); + + it('rejects usage in other contexts', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + enum PostStatus { + DRAFT + ACTIVE + CANCELLED + } + + model User { + id Int @id @default(autoincrement()) + } + + model Post { + id String @id + status String + } + + function Test(status: String): Void { + status in PostStatus + } + `, + /Enum reference can only be used with policy and validation attributes/, + ); + }); + }); +}); diff --git a/packages/orm/src/client/executor/name-mapper.ts b/packages/orm/src/client/executor/name-mapper.ts index 7cb5c72ec..f26374275 100644 --- a/packages/orm/src/client/executor/name-mapper.ts +++ b/packages/orm/src/client/executor/name-mapper.ts @@ -283,7 +283,9 @@ export class QueryNameMapper extends OperationNodeTransformer { if ( ReferenceNode.is(node.leftOperand) && ColumnNode.is(node.leftOperand.column) && - (ValueNode.is(node.rightOperand) || PrimitiveValueListNode.is(node.rightOperand)) + (ValueNode.is(node.rightOperand) || + PrimitiveValueListNode.is(node.rightOperand) || + ValueListNode.is(node.rightOperand)) ) { const columnNode = node.leftOperand.column; @@ -311,6 +313,19 @@ export class QueryNameMapper extends OperationNodeTransformer { valueNode.values, ), ); + } else if (ValueListNode.is(valueNode)) { + // list value: column IN (EnumValue, EnumValue2) + resultValue = ValueListNode.create( + valueNode.values.map((v) => + ValueNode.is(v) + ? (this.processEnumMappingForValue( + resolvedScope.model!, + columnNode, + v, + ) as OperationNode) + : v, + ), + ); } return super.transformBinaryOperation( diff --git a/packages/plugins/policy/src/expression-transformer.ts b/packages/plugins/policy/src/expression-transformer.ts index c885b504c..ea5d0f1ee 100644 --- a/packages/plugins/policy/src/expression-transformer.ts +++ b/packages/plugins/policy/src/expression-transformer.ts @@ -274,14 +274,29 @@ export class ExpressionTransformer { const { normalizedLeft, normalizedRight } = this.normalizeBinaryOperationOperands(expr, context); const left = this.transform(normalizedLeft, context); - const right = this.transform(normalizedRight, context); if (op === 'in') { if (this.isNullNode(left)) { return this.transformValue(false, 'Boolean'); } else { + if (this.isLiteralArray(normalizedRight)) { + // `in` with a list of literal values, e.g. `field in [1, 2, 3]` or + // `field in [ENUM_A, ENUM_B]`: emit a plain SQL `IN (...)` list on every + // dialect (instead of a dialect-specific array value) so that it stays + // index-friendly and the name mapper can translate `@map`-ed enum values + if (normalizedRight.items.length === 0) { + return this.transformValue(false, 'Boolean'); + } + return BinaryOperationNode.create( + left, + OperatorNode.create('in'), + ValueListNode.create(normalizedRight.items.map((item) => this.transform(item, context))), + ); + } + + const right = this.transform(normalizedRight, context); if (ValueListNode.is(right)) { - // simple `in` operator with a list of values, e.g. `field in [1, 2, 3]` + // simple `in` operator with a list of values, e.g. `field in [auth().x, 2]` return BinaryOperationNode.create(left, OperatorNode.create('in'), right); } else { // array contains @@ -317,6 +332,7 @@ export class ExpressionTransformer { } } + const right = this.transform(normalizedRight, context); if (this.isNullNode(right)) { return this.transformNullCheck(left, expr.op); } else if (this.isNullNode(left)) { @@ -358,6 +374,10 @@ export class ExpressionTransformer { } } + private isLiteralArray(expr: Expression): expr is ArrayExpression { + return expr.kind === 'array' && expr.items.every((item) => item.kind === 'literal'); + } + private normalizeBinaryOperationOperands(expr: BinaryExpression, context: ExpressionTransformerContext) { // If relation fields are used directly in a comparison, normalize both sides to the // first id field (used for multiple). This applies whether the relation is SQL-backed diff --git a/packages/sdk/src/ts-schema-generator.ts b/packages/sdk/src/ts-schema-generator.ts index b3c70eaca..5ed8467c9 100644 --- a/packages/sdk/src/ts-schema-generator.ts +++ b/packages/sdk/src/ts-schema-generator.ts @@ -1368,6 +1368,16 @@ export class TsSchemaGenerator { .when(isCollectionPredicateBinding, () => this.createExpressionUtilsCall('binding', [this.createLiteralNode(expr.target.$refText)]), ) + .when(isEnum, () => + this.createExpressionUtilsCall('array', [ + this.createLiteralNode(expr.target.$refText), + ts.factory.createArrayLiteralExpression( + (target as Enum).fields.map((field) => + this.createLiteralExpression('StringLiteral', field.name), + ), + ), + ]), + ) .otherwise(() => { throw Error(`Unsupported reference type: ${expr.target.$refText}`); }); diff --git a/packages/zod/test/factory.test.ts b/packages/zod/test/factory.test.ts index 8a21945f5..33d21d011 100644 --- a/packages/zod/test/factory.test.ts +++ b/packages/zod/test/factory.test.ts @@ -92,6 +92,7 @@ describe.each([ expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf
(); // relation fields are NOT present by default — use include/select to opt in @@ -460,7 +461,13 @@ describe.each([ const userSchema = factory.makeModelSchema('User'); const result = userSchema.safeParse({ ...validUser, - address: { residents: [], street: '123 Main St', city: 'Springfield', zip: null }, + address: { + residents: [], + street: '123 Main St', + city: 'Springfield', + zip: null, + type: 'RESIDENTIAL', + }, }); expect(result.success).toBe(true); }); @@ -469,7 +476,13 @@ describe.each([ const userSchema = factory.makeModelSchema('User'); const result = userSchema.safeParse({ ...validUser, - address: { residents: [], street: '123 Main St', city: 'Springfield', zip: '12345' }, + address: { + residents: [], + street: '123 Main St', + city: 'Springfield', + zip: '12345', + type: 'RESIDENTIAL', + }, }); expect(result.success).toBe(true); }); @@ -478,7 +491,14 @@ describe.each([ const userSchema = factory.makeModelSchema('User'); const result = userSchema.safeParse({ ...validUser, - address: { residents: [], street: '123 Main St', city: 'Springfield', zip: null, extra: 'field' }, + address: { + residents: [], + street: '123 Main St', + city: 'Springfield', + zip: null, + type: 'RESIDENTIAL', + extra: 'field', + }, }); expect(result.success).toBe(false); }); @@ -487,7 +507,7 @@ describe.each([ const userSchema = factory.makeModelSchema('User'); const result = userSchema.safeParse({ ...validUser, - address: { residents: [], street: '123 Main St' }, + address: { residents: [], street: '123 Main St', type: 'RESIDENTIAL' }, }); expect(result.success).toBe(false); }); @@ -539,7 +559,13 @@ describe.each([ it('generates schema for Address typedef', () => { const addressSchema = factory.makeTypeSchema('Address'); expect( - addressSchema.safeParse({ residents: [], street: '123 Main', city: 'Springfield', zip: null }).success, + addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: null, + type: 'RESIDENTIAL', + }).success, ).toBe(true); }); @@ -557,6 +583,7 @@ describe.each([ city: 'Springfield', zip: null, extra: 'field', + type: 'RESIDENTIAL', }); expect(result.success).toBe(false); }); @@ -564,15 +591,26 @@ describe.each([ it('accepts Address with optional zip as null', () => { const addressSchema = factory.makeTypeSchema('Address'); expect( - addressSchema.safeParse({ residents: [], street: '123 Main', city: 'Springfield', zip: null }).success, + addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: null, + type: 'RESIDENTIAL', + }).success, ).toBe(true); }); it('accepts Address with optional zip as a string', () => { const addressSchema = factory.makeTypeSchema('Address'); expect( - addressSchema.safeParse({ residents: [], street: '123 Main', city: 'Springfield', zip: '12345' }) - .success, + addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: '12345', + type: 'RESIDENTIAL', + }).success, ).toBe(true); }); @@ -580,23 +618,38 @@ describe.each([ it('passes when zip is null', () => { const addressSchema = factory.makeTypeSchema('Address'); expect( - addressSchema.safeParse({ residents: [], street: '123 Main', city: 'Springfield', zip: null }) - .success, + addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: null, + type: 'RESIDENTIAL', + }).success, ).toBe(true); }); it('passes when zip is omitted', () => { const addressSchema = factory.makeTypeSchema('Address'); expect( - addressSchema.safeParse({ residents: [], street: '123 Main', city: 'Springfield' }).success, + addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + type: 'RESIDENTIAL', + }).success, ).toBe(true); }); it('passes when zip is exactly 5 characters', () => { const addressSchema = factory.makeTypeSchema('Address'); expect( - addressSchema.safeParse({ residents: [], street: '123 Main', city: 'Springfield', zip: '90210' }) - .success, + addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: '90210', + type: 'RESIDENTIAL', + }).success, ).toBe(true); }); @@ -607,6 +660,7 @@ describe.each([ street: '123 Main', city: 'Springfield', zip: '123', + type: 'RESIDENTIAL', }); expect(result.success).toBe(false); }); @@ -618,6 +672,7 @@ describe.each([ street: '123 Main', city: 'Springfield', zip: '123456', + type: 'RESIDENTIAL', }); expect(result.success).toBe(false); }); @@ -629,6 +684,7 @@ describe.each([ street: '123 Main', city: 'Springfield', zip: '123', + type: 'RESIDENTIAL', }); expect(result.success).toBe(false); if (!result.success) { @@ -645,6 +701,7 @@ describe.each([ street: '123 Main', city: 'Springfield', zip: '123', + type: 'RESIDENTIAL', }); expect(result.success).toBe(false); if (!result.success) { @@ -654,7 +711,13 @@ describe.each([ it('fails when city is too short', () => { const addressSchema = factory.makeTypeSchema('Address'); - const result = addressSchema.safeParse({ residents: [], street: '123 Main', city: '', zip: '12345' }); + const result = addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: '', + zip: '12345', + type: 'RESIDENTIAL', + }); expect(result.success).toBe(false); }); @@ -678,16 +741,61 @@ describe.each([ avatar: null, metadata: null, status: 'ACTIVE', - address: { residents: [], street: '123 Main', city: 'Springfield', zip: '90210' }, + address: { + residents: [], + street: '123 Main', + city: 'Springfield', + zip: '90210', + type: 'RESIDENTIAL', + }, }; expect(userSchema.safeParse(validUser).success).toBe(true); expect( userSchema.safeParse({ ...validUser, - address: { residents: ['Alice'], street: '123 Main', city: 'Springfield', zip: '123' }, + address: { + residents: ['Alice'], + street: '123 Main', + city: 'Springfield', + zip: '123', + type: 'RESIDENTIAL', + }, }).success, ).toBe(false); }); + + it('passes when field value matches implicitly converted enum', () => { + const addressSchema = factory.makeTypeSchema('Address'); + let result = addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: '12345', + type: 'RESIDENTIAL', + }); + expect(result.success).toBe(true); + + result = addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: '12345', + type: 'COMMERCIAL', + }); + expect(result.success).toBe(true); + }); + + it('fails when field value does not match implicitly converted enum', () => { + const addressSchema = factory.makeTypeSchema('Address'); + const result = addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: '12345', + type: 'UNKNOWN', + }); + expect(result.success).toBe(false); + }); }); }); diff --git a/packages/zod/test/schema/schema-lite.ts b/packages/zod/test/schema/schema-lite.ts index 1be47cfca..29472e365 100644 --- a/packages/zod/test/schema/schema-lite.ts +++ b/packages/zod/test/schema/schema-lite.ts @@ -336,9 +336,14 @@ export class SchemaType implements SchemaDef { name: "zip", type: "String", optional: true + }, + type: { + name: "type", + type: "String" } }, attributes: [ + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.field("type"), "in", ExpressionUtils.array("AddressType", [ExpressionUtils.literal("RESIDENTIAL"), ExpressionUtils.literal("COMMERCIAL")])) }] }, { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.binary(ExpressionUtils.field("zip"), "==", ExpressionUtils._null()), "||", ExpressionUtils.binary(ExpressionUtils.call("length", [ExpressionUtils.field("zip")]), "==", ExpressionUtils.literal(5))) }, { name: "message", value: ExpressionUtils.literal("Zip code must be exactly 5 characters") }, { name: "path", value: ExpressionUtils.array("String", [ExpressionUtils.literal("zip")]) }] }, { name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("A mailing address") }] } ] as readonly AttributeApplication[] @@ -355,6 +360,13 @@ export class SchemaType implements SchemaDef { attributes: [ { name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("User account status") }] } ] as readonly AttributeApplication[] + }, + AddressType: { + name: "AddressType", + values: { + RESIDENTIAL: "RESIDENTIAL", + COMMERCIAL: "COMMERCIAL" + } } } as const; authType = "User" as const; diff --git a/packages/zod/test/schema/schema.ts b/packages/zod/test/schema/schema.ts index 3442d3920..275b25dcf 100644 --- a/packages/zod/test/schema/schema.ts +++ b/packages/zod/test/schema/schema.ts @@ -342,9 +342,14 @@ export class SchemaType implements SchemaDef { name: "zip", type: "String", optional: true + }, + type: { + name: "type", + type: "String" } }, attributes: [ + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.field("type"), "in", ExpressionUtils.array("AddressType", [ExpressionUtils.literal("RESIDENTIAL"), ExpressionUtils.literal("COMMERCIAL")])) }] }, { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.binary(ExpressionUtils.field("zip"), "==", ExpressionUtils._null()), "||", ExpressionUtils.binary(ExpressionUtils.call("length", [ExpressionUtils.field("zip")]), "==", ExpressionUtils.literal(5))) }, { name: "message", value: ExpressionUtils.literal("Zip code must be exactly 5 characters") }, { name: "path", value: ExpressionUtils.array("String", [ExpressionUtils.literal("zip")]) }] }, { name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("A mailing address") }] } ] as readonly AttributeApplication[] @@ -361,6 +366,13 @@ export class SchemaType implements SchemaDef { attributes: [ { name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("User account status") }] } ] as readonly AttributeApplication[] + }, + AddressType: { + name: "AddressType", + values: { + RESIDENTIAL: "RESIDENTIAL", + COMMERCIAL: "COMMERCIAL" + } } } as const; authType = "User" as const; diff --git a/packages/zod/test/schema/schema.zmodel b/packages/zod/test/schema/schema.zmodel index 6aa265ad0..3ceba9c35 100644 --- a/packages/zod/test/schema/schema.zmodel +++ b/packages/zod/test/schema/schema.zmodel @@ -10,12 +10,19 @@ enum Status { @@meta("description", "User account status") } +enum AddressType { + RESIDENTIAL + COMMERCIAL +} + type Address { residents String[] street String @meta("description", "Street address line") city String @length(2) zip String? + type String + @@validate(type in AddressType) @@validate(zip == null || length(zip) == 5, "Zip code must be exactly 5 characters", ["zip"]) @@meta("description", "A mailing address") } diff --git a/tests/e2e/orm/client-api/enum.test.ts b/tests/e2e/orm/client-api/enum.test.ts new file mode 100644 index 000000000..e7b8a4705 --- /dev/null +++ b/tests/e2e/orm/client-api/enum.test.ts @@ -0,0 +1,63 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import type { ClientContract } from '@zenstackhq/orm'; +import { schema } from '../schemas/enum/schema'; +import { createTestClient } from '@zenstackhq/testtools'; + +describe('Enum tests', () => { + let client: ClientContract; + + beforeEach(async () => { + client = await createTestClient(schema); + }); + + afterEach(async () => { + await client?.$disconnect(); + }); + + it('works when implicitly converted to arrays', async () => { + await expect( + client.post.create({ + data: { + id: '1', + status: 'ACTIVE', + }, + }), + ).resolves.toMatchObject({ + id: '1', + status: 'ACTIVE', + }); + + await expect( + client.post.create({ + data: { + id: '2', + status: 'DRAFT', + }, + }), + ).resolves.toMatchObject({ + id: '2', + status: 'DRAFT', + }); + + await expect( + client.post.create({ + data: { + id: '3', + status: 'CANCELLED', + }, + }), + ).resolves.toMatchObject({ + id: '3', + status: 'CANCELLED', + }); + + await expect( + client.post.create({ + data: { + id: '4', + status: 'UNKNOWN', + }, + }), + ).rejects.toThrow(/Validation error/); + }); +}); diff --git a/tests/e2e/orm/schemas/enum/schema.ts b/tests/e2e/orm/schemas/enum/schema.ts new file mode 100644 index 000000000..99cb511be --- /dev/null +++ b/tests/e2e/orm/schemas/enum/schema.ts @@ -0,0 +1,66 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +/* eslint-disable */ + +import { type SchemaDef, type AttributeApplication, type FieldDefault, ExpressionUtils } from "@zenstackhq/schema"; +export class SchemaType implements SchemaDef { + provider = { + type: "sqlite" + } as const; + models = { + User: { + name: "User", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }] as readonly AttributeApplication[], + default: ExpressionUtils.call("autoincrement") as FieldDefault + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "Int" } + } + }, + Post: { + name: "Post", + fields: { + id: { + name: "id", + type: "String", + id: true, + attributes: [{ name: "@id" }] as readonly AttributeApplication[] + }, + status: { + name: "status", + type: "String" + } + }, + attributes: [ + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.field("status"), "in", ExpressionUtils.array("PostStatus", [ExpressionUtils.literal("DRAFT"), ExpressionUtils.literal("ACTIVE"), ExpressionUtils.literal("CANCELLED")])) }] } + ] as readonly AttributeApplication[], + idFields: ["id"], + uniqueFields: { + id: { type: "String" } + } + } + } as const; + enums = { + PostStatus: { + name: "PostStatus", + values: { + DRAFT: "DRAFT", + ACTIVE: "ACTIVE", + CANCELLED: "CANCELLED" + } + } + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/orm/schemas/enum/schema.zmodel b/tests/e2e/orm/schemas/enum/schema.zmodel new file mode 100644 index 000000000..d95f40bdd --- /dev/null +++ b/tests/e2e/orm/schemas/enum/schema.zmodel @@ -0,0 +1,21 @@ +datasource db { + provider = 'sqlite' + url = 'file:./dev.db' +} + +enum PostStatus { + DRAFT + ACTIVE + CANCELLED +} + +model User { + id Int @id @default(autoincrement()) +} + +model Post { + id String @id + status String + + @@validate(status in PostStatus) +} \ No newline at end of file diff --git a/tests/regression/test/issue-1211.test.ts b/tests/regression/test/issue-1211.test.ts new file mode 100644 index 000000000..6b5dd5d0f --- /dev/null +++ b/tests/regression/test/issue-1211.test.ts @@ -0,0 +1,89 @@ +import { createPolicyTestClient, createTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +// https://github.com/zenstackhq/zenstack/issues/1211 +describe('Regression for issue 1211', () => { + const ENUM = ` +enum PostStatus { + DRAFT @map('draft') + ACTIVE @map('active') + CANCELLED @map('cancelled') +} +`; + + it('validates enum-typed field against an enum with @map-ed values', async () => { + const db = await createTestClient(` +${ENUM} +model Post { + id Int @id @default(autoincrement()) + status PostStatus + @@validate(status in PostStatus) +} +`); + await expect(db.post.create({ data: { status: 'DRAFT' } })).resolves.toMatchObject({ status: 'DRAFT' }); + }); + + it('validates string field against an enum', async () => { + const db = await createTestClient(` +enum PostStatus { + DRAFT + ACTIVE +} +model Post { + id Int @id @default(autoincrement()) + status String + @@validate(status in PostStatus) +} +`); + await expect(db.post.create({ data: { status: 'ACTIVE' } })).resolves.toMatchObject({ status: 'ACTIVE' }); + await expect(db.post.create({ data: { status: 'UNKNOWN' } })).toBeRejectedByValidation(); + }); + + it('checks enum-typed field against an enum with @map-ed values in policies', async () => { + const db = await createPolicyTestClient(` +${ENUM} +model Post { + id Int @id @default(autoincrement()) + status PostStatus + @@allow('create', true) + @@allow('read', status in PostStatus) +} +`); + await db.$unuseAll().post.create({ data: { status: 'DRAFT' } }); + await expect(db.post.findMany()).resolves.toMatchObject([{ status: 'DRAFT' }]); + }); + + it('checks enum-typed field against an explicit enum array with @map-ed values in policies', async () => { + const db = await createPolicyTestClient(` +${ENUM} +model Post { + id Int @id @default(autoincrement()) + status PostStatus + @@allow('create', true) + @@allow('read', status in [DRAFT, ACTIVE]) +} +`); + await db.$unuseAll().post.create({ data: { status: 'DRAFT' } }); + await db.$unuseAll().post.create({ data: { status: 'CANCELLED' } }); + await expect(db.post.findMany()).resolves.toMatchObject([{ status: 'DRAFT' }]); + }); + + it('checks auth() enum member against an enum with @map-ed values in policies', async () => { + const db = await createPolicyTestClient(` +${ENUM} +model User { + id Int @id @default(autoincrement()) + status PostStatus + @@allow('all', true) +} +model Post { + id Int @id @default(autoincrement()) + @@allow('create', true) + @@allow('read', auth().status in PostStatus) +} +`); + await db.$unuseAll().post.create({ data: {} }); + await expect(db.$setAuth({ id: 1, status: 'DRAFT' }).post.findMany()).resolves.toHaveLength(1); + await expect(db.$setAuth({ id: 1, status: 'UNKNOWN' }).post.findMany()).resolves.toHaveLength(0); + }); +}); From c46fbaddc3c09366075d9e371c31f8a85d1b0f9f Mon Sep 17 00:00:00 2001 From: ludovicmotte Date: Sat, 10 Oct 2026 03:12:53 +0200 Subject: [PATCH 02/18] feat(policy): field-level update policies on M2M relation fields (#2858) Co-authored-by: Ludovic Motte Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 --- packages/language/src/utils.ts | 48 ++ .../attribute-application-validator.ts | 17 +- .../test/attribute-application.test.ts | 195 ++++- packages/plugins/policy/src/policy-handler.ts | 107 ++- packages/sdk/src/ts-schema-generator.ts | 19 +- tests/e2e/orm/policy/crud/update.test.ts | 6 +- .../migrated/connect-disconnect.test.ts | 677 ++++++++++++++++++ .../migrated/field-level-policy.test.ts | 2 +- tests/regression/test/issue-2382.test.ts | 68 ++ 9 files changed, 1086 insertions(+), 53 deletions(-) create mode 100644 tests/regression/test/issue-2382.test.ts diff --git a/packages/language/src/utils.ts b/packages/language/src/utils.ts index aa2ecfe3e..0e2f690c1 100644 --- a/packages/language/src/utils.ts +++ b/packages/language/src/utils.ts @@ -166,6 +166,54 @@ export function isRelationshipField(field: DataField) { return isDataModel(field.type.reference?.ref); } +/** + * Returns the name of the relation the given field belongs to, as declared in its `@relation` + * attribute, or `undefined` if the field has no `@relation` attribute or no explicit name. + * + * The name argument is matched by its literal name (or absence of a name for the positional + * argument) rather than by `$resolvedParam`, because this function is also used during schema + * validation, where `$resolvedParam` may not have been resolved yet for attributes that have + * not been validated. + */ +export function getRelationName(field: DataField): string | undefined { + const relation = getAttribute(field, '@relation'); + if (!relation) { + return undefined; + } + for (const arg of relation.args) { + if (!arg.name || arg.name === 'name') { + if (isStringLiteral(arg.value)) { + return arg.value.value; + } + } + } + return undefined; +} + +/** + * Returns if the given field is a many-to-many relation field, i.e. a relation field that is an + * array and whose opposite relation field on the referenced model (belonging to the same relation) + * is also an array referencing back to the containing model. + */ +export function isManyToManyField(field: DataField) { + if (!isRelationshipField(field) || !field.type.array) { + return false; + } + + const oppositeModel = field.type.reference!.ref as DataModel; + const containingModel = field.$container as DataModel; + const relationName = getRelationName(field); + + return getAllFields(oppositeModel).some((f) => { + if (f === field || !f.type.array || f.type.reference?.ref?.name !== containingModel.name) { + return false; + } + // the opposite field must belong to the same relation: either both declare the same + // explicit relation name, or both are unnamed + return getRelationName(f) === relationName; + }); +} + /** * Returns if the given field is a computed field. */ diff --git a/packages/language/src/validators/attribute-application-validator.ts b/packages/language/src/validators/attribute-application-validator.ts index cdb0ed969..1be7fc6c8 100644 --- a/packages/language/src/validators/attribute-application-validator.ts +++ b/packages/language/src/validators/attribute-application-validator.ts @@ -38,6 +38,7 @@ import { isComputedField, isDataFieldReference, isDelegateModel, + isManyToManyField, isNativeTypeMappingAttribute, isRelationshipField, mapBuiltinTypeToExpressionType, @@ -348,18 +349,28 @@ export default class AttributeApplicationValidator implements AstValidator isBeforeInvocation(node))) { accept('error', `"before()" is not allowed in field-level policies`, { node: expr }); } - // relation fields are not allowed + // relation fields are not allowed, except for many-to-many fields which only support 'update' const field = attr.$container as DataField; if (isRelationshipField(field)) { - accept('error', `Field-level policies are not allowed for relation fields.`, { node: attr }); + if (isManyToManyField(field)) { + if (kinds.some((k) => k !== 'update')) { + accept('error', `Only 'update' policies are allowed on many-to-many relation fields`, { + node: attr, + }); + } + } else { + accept('error', `Field-level policies are only allowed for implicit many-to-many relation fields`, { + node: attr, + }); + } } if (isComputedField(field)) { diff --git a/packages/language/test/attribute-application.test.ts b/packages/language/test/attribute-application.test.ts index 9abe8ffe9..c1d7a6311 100644 --- a/packages/language/test/attribute-application.test.ts +++ b/packages/language/test/attribute-application.test.ts @@ -366,7 +366,200 @@ describe('Attribute application validation tests', () => { @@allow('all', true) } `, - `Field-level policies are not allowed for relation fields`, + `Field-level policies are only allowed for implicit many-to-many relation fields`, + ); + }); + + it('accepts update field-level policy on many-to-many relation fields', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + bars Bar[] + @@allow('all', true) + } + + model Bar { + id Int @id @default(autoincrement()) + foos Foo[] @allow('update', true) + @@allow('all', true) + } + `); + }); + + it('accepts deny update field-level policy on many-to-many relation fields', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + bars Bar[] + @@allow('all', true) + } + + model Bar { + id Int @id @default(autoincrement()) + foos Foo[] @deny('update', false) + @@allow('all', true) + } + `); + }); + + it('rejects read field-level policy on many-to-many relation fields', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + bars Bar[] + @@allow('all', true) + } + + model Bar { + id Int @id @default(autoincrement()) + foos Foo[] @allow('read', true) + @@allow('all', true) + } + `, + `Only 'update' policies are allowed on many-to-many relation fields`, + ); + }); + + it('rejects all field-level policy on many-to-many relation fields', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + bars Bar[] + @@allow('all', true) + } + + model Bar { + id Int @id @default(autoincrement()) + foos Foo[] @allow('all', true) + @@allow('all', true) + } + `, + `Only 'update' policies are allowed on many-to-many relation fields`, + ); + }); + + it('rejects comma-separated kinds including non-update on many-to-many relation fields', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + bars Bar[] + @@allow('all', true) + } + + model Bar { + id Int @id @default(autoincrement()) + foos Foo[] @allow('read, update', true) + @@allow('all', true) + } + `, + `Only 'update' policies are allowed on many-to-many relation fields`, + ); + }); + + it('rejects update field-level policy on one-to-many relation fields', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + bar Bar @relation(fields: [barId], references: [id]) + barId Int + @@allow('all', true) + } + + model Bar { + id Int @id @default(autoincrement()) + foos Foo[] @allow('update', true) + @@allow('all', true) + } + `, + `Field-level policies are only allowed for implicit many-to-many relation fields`, + ); + }); + + it('rejects update field-level policy on one-to-many relation fields when a separate m2m relation exists', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + bar Bar @relation("one-to-many", fields: [barId], references: [id]) @allow('update', true) + barId Int + bars Bar[] @relation("m2m") + @@allow('all', true) + } + + model Bar { + id Int @id @default(autoincrement()) + foos Foo[] @relation("m2m") + @@allow('all', true) + } + `, + `Field-level policies are only allowed for implicit many-to-many relation fields`, + ); + }); + + it('rejects update field-level policy on a one-to-many field that is not many-to-many when a differently-named relation exists on the opposite model', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + bars Bar[] @allow('update', true) + bar2 Bar @relation("other", fields: [bar2Id], references: [id]) + bar2Id Int + @@allow('all', true) + } + + model Bar { + id Int @id @default(autoincrement()) + foo Foo @relation(fields: [fooId], references: [id]) + fooId Int + foos Foo[] @relation("other") + @@allow('all', true) + } + `, + `Field-level policies are only allowed for implicit many-to-many relation fields`, ); }); diff --git a/packages/plugins/policy/src/policy-handler.ts b/packages/plugins/policy/src/policy-handler.ts index b192d72b5..ddc58ee7a 100644 --- a/packages/plugins/policy/src/policy-handler.ts +++ b/packages/plugins/policy/src/policy-handler.ts @@ -262,16 +262,22 @@ export class PolicyHandler extends OperationNodeTransf // the join table's fk columns are constrained by literal values for the sides that the // delete explicitly targets; only those sides can be checked upfront const sides = [ - { column: 'A', model: m2m.firstModel, idField: m2m.firstIdField }, - { column: 'B', model: m2m.secondModel, idField: m2m.secondIdField }, + { column: 'A', model: m2m.firstModel, field: m2m.firstField, idField: m2m.firstIdField }, + { column: 'B', model: m2m.secondModel, field: m2m.secondField, idField: m2m.secondIdField }, ] - .map((side) => ({ ...side, value: this.extractEqualityValue(node.where?.where, side.column) })) - .filter((side) => side.value !== undefined); + .map((side) => ({ ...side, values: this.extractConstrainedValues(node.where?.where, side.column) })) + .filter((side) => side.values !== undefined); if (sides.length === 0) { return; } + // For each side, count the constrained participants that are updatable. A plain + // `SELECT ... IN (...)` would return one row per matching participant, which + // scalar subquery positions reject on some databases and which would only verify a single + // participant on others. Aggregating to a single `COUNT(*)` row verifies every participant: + // a participant is only counted if it exists and is updatable, so any missing or + // non-updatable participant lowers the count below the number of distinct values. const result = await proceed({ kind: 'SelectQueryNode', selections: sides.map((side, index) => @@ -279,10 +285,9 @@ export class PolicyHandler extends OperationNodeTransf AliasNode.create( this.eb .selectFrom(side.model) - .where(this.eb(this.eb.ref(`${side.model}.${side.idField}`), '=', side.value)) - .select(() => - new ExpressionWrapper(this.buildPolicyFilter(side.model, undefined, 'update')).as('_'), - ) + .where(this.eb(this.eb.ref(`${side.model}.${side.idField}`), 'in', side.values!)) + .where(() => new ExpressionWrapper(this.buildM2mSidePolicyFilter(side.model, side.field))) + .select((eb) => eb.fn('COUNT', [eb.lit(1)]).as('_')) .toOperationNode(), IdentifierNode.create(`$condition${index}`), ), @@ -291,7 +296,9 @@ export class PolicyHandler extends OperationNodeTransf } satisfies SelectQueryNode); for (const [index, side] of sides.entries()) { - if (!result.rows[0]?.[`$condition${index}`]) { + const distinctValues = new Set(side.values!).size; + const updatableCount = Number(result.rows[0]?.[`$condition${index}`] ?? 0); + if (updatableCount < distinctValues) { throw createRejectedByPolicyError( side.model, RejectedByPolicyReason.NO_ACCESS, @@ -302,30 +309,33 @@ export class PolicyHandler extends OperationNodeTransf } /** - * Finds the literal value that `column` is constrained to by a top-level conjunction of the given - * where clause, or `undefined` if there's no such constraint. + * Finds the literal values that `column` is constrained to by a top-level conjunction of the + * given where clause, or `undefined` if there's no such constraint. Supports both `=` and `IN` + * operators. * - * E.g., given the where clause `("A" = 1 AND "B" IN (2, 3))`, extracting column "A" returns `1`, - * while extracting column "B" returns `undefined` since it's not an equality constraint. + * E.g., given the where clause `("A" = 1 AND "B" IN (2, 3))`, extracting column "A" returns + * `[1]`, and extracting column "B" returns `[2, 3]`. */ - private extractEqualityValue(node: OperationNode | undefined, column: string): unknown { + private extractConstrainedValues(node: OperationNode | undefined, column: string): unknown[] | undefined { if (!node) { return undefined; } if (ParensNode.is(node)) { - return this.extractEqualityValue(node.node, column); + return this.extractConstrainedValues(node.node, column); } if (AndNode.is(node)) { - return this.extractEqualityValue(node.left, column) ?? this.extractEqualityValue(node.right, column); + return ( + this.extractConstrainedValues(node.left, column) ?? this.extractConstrainedValues(node.right, column) + ); } if (!BinaryOperationNode.is(node)) { return undefined; } - if (!OperatorNode.is(node.operator) || node.operator.operator !== '=') { + if (!OperatorNode.is(node.operator)) { return undefined; } @@ -336,10 +346,29 @@ export class PolicyHandler extends OperationNodeTransf if (leftOperand.column.column.name !== column) { return undefined; } - if (!ValueNode.is(rightOperand) || rightOperand.value === null || rightOperand.value === undefined) { + + if (node.operator.operator === '=') { + if (!ValueNode.is(rightOperand) || rightOperand.value === null || rightOperand.value === undefined) { + return undefined; + } + return [rightOperand.value]; + } + + if (node.operator.operator === 'in') { + if (PrimitiveValueListNode.is(rightOperand)) { + const values = rightOperand.values.filter((v) => v !== null && v !== undefined); + return values.length > 0 ? values : undefined; + } + if (ValueListNode.is(rightOperand)) { + const values = rightOperand.values.filter( + (v): v is ValueNode => ValueNode.is(v) && v.value !== null && v.value !== undefined, + ); + return values.length > 0 ? values.map((v) => v.value) : undefined; + } return undefined; } - return rightOperand.value; + + return undefined; } private async postUpdateCheck( @@ -885,6 +914,21 @@ export class PolicyHandler extends OperationNodeTransf return combinedPolicy; } + /** + * Builds the update policy filter for one side of an implicit many-to-many relation. + * + * If the relation field declares field-level `update` policies, they take precedence over + * the model-level policy for this side; otherwise the model-level `update` policy applies + * (preserving the pre-existing behavior). + */ + private buildM2mSidePolicyFilter(model: string, field: string): OperationNode { + const fieldPolicies = this.getFieldPolicies(model, field, 'update'); + if (fieldPolicies.length > 0) { + return this.buildFieldPolicyFilter(model, field, 'update'); + } + return this.buildPolicyFilter(model, undefined, 'update'); + } + // #endregion // #region helpers @@ -977,13 +1021,13 @@ export class PolicyHandler extends OperationNodeTransf const eb = expressionBuilder(); - const filterA = this.buildPolicyFilter(m2m.firstModel, undefined, 'update'); + const filterA = this.buildM2mSidePolicyFilter(m2m.firstModel, m2m.firstField); const queryA = eb .selectFrom(m2m.firstModel) .where(eb(eb.ref(`${m2m.firstModel}.${m2m.firstIdField}`), '=', aValue)) .select(() => new ExpressionWrapper(filterA).as('_')); - const filterB = this.buildPolicyFilter(m2m.secondModel, undefined, 'update'); + const filterB = this.buildM2mSidePolicyFilter(m2m.secondModel, m2m.secondField); const queryB = eb .selectFrom(m2m.secondModel) .where(eb(eb.ref(`${m2m.secondModel}.${m2m.secondIdField}`), '=', bValue)) @@ -1464,27 +1508,32 @@ export class PolicyHandler extends OperationNodeTransf // join table's permission: // - read: requires both sides to be readable - // - mutation: requires both sides to be updatable + // - mutation: requires both sides to be updatable, honoring field-level update policies + // on the relation fields when declared (see buildM2mSidePolicyFilter) - const checkForOperation = operation === 'read' ? 'read' : 'update'; + const isRead = operation === 'read'; const joinTable = alias ?? tableName; const aQuery = this.eb .selectFrom(m2m.firstModel) .whereRef(`${m2m.firstModel}.${m2m.firstIdField}`, '=', `${joinTable}.A`) .select(() => - new ExpressionWrapper(this.buildPolicyFilter(m2m.firstModel, undefined, checkForOperation)).as( - '$conditionA', - ), + new ExpressionWrapper( + isRead + ? this.buildPolicyFilter(m2m.firstModel, undefined, 'read') + : this.buildM2mSidePolicyFilter(m2m.firstModel, m2m.firstField), + ).as('$conditionA'), ); const bQuery = this.eb .selectFrom(m2m.secondModel) .whereRef(`${m2m.secondModel}.${m2m.secondIdField}`, '=', `${joinTable}.B`) .select(() => - new ExpressionWrapper(this.buildPolicyFilter(m2m.secondModel, undefined, checkForOperation)).as( - '$conditionB', - ), + new ExpressionWrapper( + isRead + ? this.buildPolicyFilter(m2m.secondModel, undefined, 'read') + : this.buildM2mSidePolicyFilter(m2m.secondModel, m2m.secondField), + ).as('$conditionB'), ); return this.eb.and([aQuery, bQuery]).toOperationNode(); diff --git a/packages/sdk/src/ts-schema-generator.ts b/packages/sdk/src/ts-schema-generator.ts index 5ed8467c9..d94393a8c 100644 --- a/packages/sdk/src/ts-schema-generator.ts +++ b/packages/sdk/src/ts-schema-generator.ts @@ -41,6 +41,7 @@ import { getAllAttributes, getAllFields, getAttributeArg, + getRelationName, isDataFieldReference, isLiteAttribute, } from '@zenstackhq/language/utils'; @@ -869,7 +870,7 @@ export class TsSchemaGenerator { ); } - const relationName = this.getRelationName(field); + const relationName = getRelationName(field); if (relationName) { relationFields.push( ts.factory.createPropertyAssignment('name', ts.factory.createStringLiteral(relationName)), @@ -959,7 +960,7 @@ export class TsSchemaGenerator { const sourceModel = isTypeDef(field.$container) ? contextModel : (field.$container as DataModel); const targetModel = field.type.reference.ref as DataModel; - const relationName = this.getRelationName(field); + const relationName = getRelationName(field); for (const otherField of getAllFields(targetModel)) { if (otherField === field) { // backlink field is never self @@ -969,7 +970,7 @@ export class TsSchemaGenerator { // relation names must match on both sides, including the case where neither side // is named - otherwise an unnamed relation can be paired with a named one that // happens to be declared first - if (this.getRelationName(otherField) === relationName) { + if (getRelationName(otherField) === relationName) { return otherField; } } @@ -977,18 +978,6 @@ export class TsSchemaGenerator { return undefined; } - private getRelationName(field: DataField) { - const relation = getAttribute(field, '@relation'); - if (relation) { - const nameArg = relation.args.find((arg) => arg.$resolvedParam?.name === 'name'); - if (nameArg) { - invariant(isLiteralExpr(nameArg.value), 'name must be a literal'); - return nameArg.value.value as string; - } - } - return undefined; - } - private createUniqueFieldsObject(dm: DataModel) { const properties: ts.PropertyAssignment[] = []; diff --git a/tests/e2e/orm/policy/crud/update.test.ts b/tests/e2e/orm/policy/crud/update.test.ts index d7f3a8a21..feab4fe0d 100644 --- a/tests/e2e/orm/policy/crud/update.test.ts +++ b/tests/e2e/orm/policy/crud/update.test.ts @@ -1041,7 +1041,7 @@ model Group { db.user.update({ where: { id: 2 }, data: { groups: { connect: { id: 2 } } } }), ).toBeRejectedByPolicy(); - // disconnect rejected + // disconnect rejected because group is not updatable await db.$unuseAll().user.update({ where: { id: 2 }, data: { groups: { connect: { id: 2 } } } }); await expect( db.user.update({ @@ -1049,9 +1049,7 @@ model Group { data: { groups: { disconnect: { id: 2 } } }, include: { groups: true }, }), - ).resolves.toMatchObject({ - groups: [{ id: 2 }], // verify not disconnected - }); + ).toBeRejectedByPolicy(); // delete rejected await expect( diff --git a/tests/e2e/orm/policy/migrated/connect-disconnect.test.ts b/tests/e2e/orm/policy/migrated/connect-disconnect.test.ts index ec3387271..d630b5c6e 100644 --- a/tests/e2e/orm/policy/migrated/connect-disconnect.test.ts +++ b/tests/e2e/orm/policy/migrated/connect-disconnect.test.ts @@ -377,4 +377,681 @@ describe('connect and disconnect tests', () => { }), ).toBeRejectedByPolicy(); }); + + it('inherits model-level update policy when no field-level policy is declared', async () => { + const db = await createPolicyTestClient( + ` + model M1 { + id String @id @default(uuid()) + value Int @default(0) + m2 M2[] + + @@allow('all', true) + } + + model M2 { + id String @id @default(uuid()) + value Int + deleted Boolean @default(false) + m1 M1[] + + @@allow('read,create', true) + @@allow('update', !deleted) + } + `, + { usePrismaPush: true }, + ); + const rawDb = db.$unuseAll(); + + await rawDb.m1.create({ data: { id: 'm1-1', value: 1 } }); + await rawDb.m2.create({ data: { id: 'm2-1', value: 1, deleted: false } }); + // both sides updatable -> connect allowed + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { connect: { id: 'm2-1' } } }, + }), + ).toResolveTruthy(); + + await rawDb.m2.create({ data: { id: 'm2-2', value: 1, deleted: true } }); + // m2-2 not updatable -> connect rejected + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { connect: { id: 'm2-2' } } }, + }), + ).toBeRejectedByPolicy(); + + // disconnect of an updatable side is allowed + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { disconnect: { id: 'm2-1' } } }, + }), + ).toResolveTruthy(); + }); + + it('field-level allow overrides restrictive model-level update policy', async () => { + const db = await createPolicyTestClient( + ` + model M1 { + id String @id @default(uuid()) + value Int @default(0) + m2 M2[] + + @@allow('all', true) + } + + model M2 { + id String @id @default(uuid()) + value Int + deleted Boolean @default(false) + m1 M1[] @allow('update', true) + + @@allow('read,create', true) + @@allow('update', !deleted) + } + `, + { usePrismaPush: true }, + ); + const rawDb = db.$unuseAll(); + + await rawDb.m1.create({ data: { id: 'm1-1', value: 1 } }); + await rawDb.m2.create({ data: { id: 'm2-1', value: 1, deleted: true } }); + // model-level policy would reject (deleted), but field-level allow overrides it + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { connect: { id: 'm2-1' } } }, + }), + ).toResolveTruthy(); + + // the override applies to disconnect as well + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { disconnect: { id: 'm2-1' } } }, + }), + ).toResolveTruthy(); + }); + + it('field-level deny overrides permissive model-level update policy', async () => { + const db = await createPolicyTestClient( + ` + model M1 { + id String @id @default(uuid()) + value Int @default(0) + m2 M2[] + + @@allow('all', true) + } + + model M2 { + id String @id @default(uuid()) + value Int + deleted Boolean @default(false) + m1 M1[] @deny('update', deleted) + + @@allow('read,create', true) + @@allow('update', !deleted) + } + `, + { usePrismaPush: true }, + ); + const rawDb = db.$unuseAll(); + + await rawDb.m1.create({ data: { id: 'm1-1', value: 1 } }); + await rawDb.m2.create({ data: { id: 'm2-1', value: 1, deleted: false } }); + // not deleted -> field-level deny not triggered, connect allowed + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { connect: { id: 'm2-1' } } }, + }), + ).toResolveTruthy(); + + await rawDb.m2.create({ data: { id: 'm2-2', value: 1, deleted: true } }); + // deleted -> field-level deny triggers, connect rejected + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { connect: { id: 'm2-2' } } }, + }), + ).toBeRejectedByPolicy(); + + // mark m2-1 deleted after connecting -> field-level deny triggers on disconnect + await rawDb.m2.update({ + where: { id: 'm2-1' }, + data: { deleted: true }, + }); + // disconnect is also rejected for the denied side + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { disconnect: { id: 'm2-1' } } }, + }), + ).toBeRejectedByPolicy(); + }); + + it('batch disconnect verifies every participant in the delete precheck', async () => { + const db = await createPolicyTestClient( + ` + model M1 { + id String @id @default(uuid()) + value Int @default(0) + m2 M2[] + + @@allow('all', true) + } + + model M2 { + id String @id @default(uuid()) + value Int + deleted Boolean @default(false) + m1 M1[] + + @@allow('read,create', true) + @@allow('update', !deleted) + } + `, + { usePrismaPush: true }, + ); + const rawDb = db.$unuseAll(); + + await rawDb.m1.create({ data: { id: 'm1-1', value: 1 } }); + await rawDb.m2.create({ data: { id: 'm2-1', value: 1, deleted: false } }); + await rawDb.m2.create({ data: { id: 'm2-2', value: 1, deleted: false } }); + + // connect both + await db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { connect: [{ id: 'm2-1' }, { id: 'm2-2' }] } }, + }); + + // both updatable -> batch disconnect succeeds (the precheck must verify every participant + // without the scalar subquery returning more than one row) + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { disconnect: [{ id: 'm2-1' }, { id: 'm2-2' }] } }, + }), + ).toResolveTruthy(); + + // reconnect both + await db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { connect: [{ id: 'm2-1' }, { id: 'm2-2' }] } }, + }); + + // mark one deleted -> batch disconnect rejected because a participant is not updatable + await rawDb.m2.update({ + where: { id: 'm2-2' }, + data: { deleted: true }, + }); + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { disconnect: [{ id: 'm2-1' }, { id: 'm2-2' }] } }, + }), + ).toBeRejectedByPolicy(); + }); + + it('field-level allow on a read-only model enables connect (issue #2382)', async () => { + const db = await createPolicyTestClient( + ` + model M1 { + id String @id @default(uuid()) + value Int @default(0) + m2 M2[] + + @@allow('all', true) + } + + model M2 { + id String @id @default(uuid()) + value Int + m1 M1[] @allow('update', true) + + @@allow('read,create', true) + } + `, + { usePrismaPush: true }, + ); + const rawDb = db.$unuseAll(); + + await rawDb.m1.create({ data: { id: 'm1-1', value: 1 } }); + await rawDb.m2.create({ data: { id: 'm2-1', value: 1 } }); + // M2 has no model-level update policy, but the field-level allow on m1 enables connect + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { connect: { id: 'm2-1' } } }, + }), + ).toResolveTruthy(); + + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { disconnect: { id: 'm2-1' } } }, + }), + ).toResolveTruthy(); + }); + + it('checks both sides of the relation', async () => { + const db = await createPolicyTestClient( + ` + model M1 { + id String @id @default(uuid()) + value Int @default(0) + m2 M2[] @deny('update', value > 0) + + @@allow('all', true) + } + + model M2 { + id String @id @default(uuid()) + value Int + m1 M1[] + + @@allow('all', true) + } + `, + { usePrismaPush: true }, + ); + const rawDb = db.$unuseAll(); + + await rawDb.m1.create({ data: { id: 'm1-1', value: 0 } }); + await rawDb.m2.create({ data: { id: 'm2-1', value: 1 } }); + // m1-1 value is 0 -> field-level deny not triggered, connect allowed + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { connect: { id: 'm2-1' } } }, + }), + ).toResolveTruthy(); + + await rawDb.m1.create({ data: { id: 'm1-2', value: 1 } }); + // m1-2 value is 1 -> field-level deny on the "source" side triggers, connect rejected + await expect( + db.m1.update({ + where: { id: 'm1-2' }, + data: { m2: { connect: { id: 'm2-1' } } }, + }), + ).toBeRejectedByPolicy(); + }); + it('field-level deny-only policy allows by default on a model without update allow', async () => { + const db = await createPolicyTestClient( + ` + model M1 { + id String @id @default(uuid()) + value Int @default(0) + m2 M2[] + + @@allow('all', true) + } + + model M2 { + id String @id @default(uuid()) + value Int + deleted Boolean @default(false) + m1 M1[] @deny('update', deleted) + + @@allow('read,create', true) + } + `, + { usePrismaPush: true }, + ); + const rawDb = db.$unuseAll(); + + await rawDb.m1.create({ data: { id: 'm1-1', value: 1 } }); + await rawDb.m2.create({ data: { id: 'm2-1', value: 1, deleted: false } }); + await rawDb.m2.create({ data: { id: 'm2-2', value: 1, deleted: true } }); + + // M2 has no model-level update allow, but the field-level policy overrides it and, having + // only deny rules, allows by default when the deny is not triggered + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { connect: { id: 'm2-1' } } }, + }), + ).toResolveTruthy(); + + // deny triggered -> rejected + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { connect: { id: 'm2-2' } } }, + }), + ).toBeRejectedByPolicy(); + + // same from the other side + await expect( + db.m2.update({ + where: { id: 'm2-1' }, + data: { m1: { disconnect: { id: 'm1-1' } } }, + }), + ).toResolveTruthy(); + await expect( + db.m2.update({ + where: { id: 'm2-2' }, + data: { m1: { connect: { id: 'm1-1' } } }, + }), + ).toBeRejectedByPolicy(); + }); + + it('field-level mixed allow and deny policies: allow must match and deny wins', async () => { + const db = await createPolicyTestClient( + ` + model M1 { + id String @id @default(uuid()) + value Int @default(0) + m2 M2[] + + @@allow('all', true) + } + + model M2 { + id String @id @default(uuid()) + value Int + deleted Boolean @default(false) + m1 M1[] @allow('update', value > 0) @deny('update', deleted) + + @@allow('all', true) + } + `, + { usePrismaPush: true }, + ); + const rawDb = db.$unuseAll(); + + await rawDb.m1.create({ data: { id: 'm1-1', value: 1 } }); + await rawDb.m2.create({ data: { id: 'm2-allowed', value: 1, deleted: false } }); + await rawDb.m2.create({ data: { id: 'm2-not-allowed', value: 0, deleted: false } }); + await rawDb.m2.create({ data: { id: 'm2-denied', value: 1, deleted: true } }); + + // allow matches, deny not triggered -> connect allowed + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { connect: { id: 'm2-allowed' } } }, + }), + ).toResolveTruthy(); + + // allow does not match: with an explicit allow present, access is no longer granted by + // default even though the model-level policy is permissive + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { connect: { id: 'm2-not-allowed' } } }, + }), + ).toBeRejectedByPolicy(); + + // allow matches but deny triggers -> deny wins + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { connect: { id: 'm2-denied' } } }, + }), + ).toBeRejectedByPolicy(); + + // disconnect follows the same combination + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { disconnect: { id: 'm2-allowed' } } }, + }), + ).toResolveTruthy(); + await rawDb.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { connect: [{ id: 'm2-not-allowed' }, { id: 'm2-denied' }] } }, + }); + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { disconnect: { id: 'm2-not-allowed' } } }, + }), + ).toBeRejectedByPolicy(); + await expect( + db.m1.update({ + where: { id: 'm1-1' }, + data: { m2: { disconnect: { id: 'm2-denied' } } }, + }), + ).toBeRejectedByPolicy(); + }); + + it('field-level policies on both sides must both pass, regardless of which side initiates', async () => { + const db = await createPolicyTestClient( + ` + model M1 { + id String @id @default(uuid()) + locked Boolean @default(false) + m2 M2[] @allow('update', !locked) + + @@allow('all', true) + } + + model M2 { + id String @id @default(uuid()) + deleted Boolean @default(false) + m1 M1[] @deny('update', deleted) + + @@allow('all', true) + } + `, + { usePrismaPush: true }, + ); + const rawDb = db.$unuseAll(); + + await rawDb.m1.create({ data: { id: 'm1-ok', locked: false } }); + await rawDb.m1.create({ data: { id: 'm1-locked', locked: true } }); + await rawDb.m2.create({ data: { id: 'm2-ok', deleted: false } }); + await rawDb.m2.create({ data: { id: 'm2-deleted', deleted: true } }); + + // both pass + await expect( + db.m1.update({ where: { id: 'm1-ok' }, data: { m2: { connect: { id: 'm2-ok' } } } }), + ).toResolveTruthy(); + await expect( + db.m2.update({ where: { id: 'm2-ok' }, data: { m1: { disconnect: { id: 'm1-ok' } } } }), + ).toResolveTruthy(); + await expect( + db.m2.update({ where: { id: 'm2-ok' }, data: { m1: { connect: { id: 'm1-ok' } } } }), + ).toResolveTruthy(); + + // M1 side fails (initiated from M1, then from M2) + await expect( + db.m1.update({ where: { id: 'm1-locked' }, data: { m2: { connect: { id: 'm2-ok' } } } }), + ).toBeRejectedByPolicy(); + await expect( + db.m2.update({ where: { id: 'm2-ok' }, data: { m1: { connect: { id: 'm1-locked' } } } }), + ).toBeRejectedByPolicy(); + + // M2 side fails (initiated from M1, then from M2) + await expect( + db.m1.update({ where: { id: 'm1-ok' }, data: { m2: { connect: { id: 'm2-deleted' } } } }), + ).toBeRejectedByPolicy(); + await expect( + db.m2.update({ where: { id: 'm2-deleted' }, data: { m1: { connect: { id: 'm1-ok' } } } }), + ).toBeRejectedByPolicy(); + + // both fail + await expect( + db.m1.update({ where: { id: 'm1-locked' }, data: { m2: { connect: { id: 'm2-deleted' } } } }), + ).toBeRejectedByPolicy(); + + // disconnect: one side failing is enough to reject, from either direction + await rawDb.m1.update({ + where: { id: 'm1-locked' }, + data: { m2: { connect: [{ id: 'm2-ok' }, { id: 'm2-deleted' }] } }, + }); + await rawDb.m1.update({ where: { id: 'm1-ok' }, data: { m2: { connect: { id: 'm2-deleted' } } } }); + await expect( + db.m1.update({ where: { id: 'm1-locked' }, data: { m2: { disconnect: { id: 'm2-ok' } } } }), + ).toBeRejectedByPolicy(); + await expect( + db.m2.update({ where: { id: 'm2-ok' }, data: { m1: { disconnect: { id: 'm1-locked' } } } }), + ).toBeRejectedByPolicy(); + await expect( + db.m1.update({ where: { id: 'm1-ok' }, data: { m2: { disconnect: { id: 'm2-deleted' } } } }), + ).toBeRejectedByPolicy(); + await expect( + db.m2.update({ where: { id: 'm2-deleted' }, data: { m1: { disconnect: { id: 'm1-ok' } } } }), + ).toBeRejectedByPolicy(); + }); + + it('field-level override on one side does not relax the model-level policy of the other side', async () => { + const db = await createPolicyTestClient( + ` + model M1 { + id String @id @default(uuid()) + archived Boolean @default(false) + m2 M2[] + + @@allow('read,create', true) + @@allow('update', !archived) + } + + model M2 { + id String @id @default(uuid()) + m1 M1[] @allow('update', true) + + @@allow('read,create', true) + } + `, + { usePrismaPush: true }, + ); + const rawDb = db.$unuseAll(); + + await rawDb.m1.create({ data: { id: 'm1-ok', archived: false } }); + await rawDb.m1.create({ data: { id: 'm1-archived', archived: true } }); + await rawDb.m2.create({ data: { id: 'm2-1' } }); + + // M2 is read-only at model level but its field-level allow opens the relation; M1 is + // governed by its model-level update policy + await expect( + db.m2.update({ where: { id: 'm2-1' }, data: { m1: { connect: { id: 'm1-ok' } } } }), + ).toResolveTruthy(); + await expect( + db.m1.update({ where: { id: 'm1-ok' }, data: { m2: { disconnect: { id: 'm2-1' } } } }), + ).toResolveTruthy(); + + // archived M1 fails its model-level update policy, from either direction + await expect( + db.m2.update({ where: { id: 'm2-1' }, data: { m1: { connect: { id: 'm1-archived' } } } }), + ).toBeRejectedByPolicy(); + // (initiating from M1 fails the top-level update check itself, also a policy rejection) + await expect( + db.m1.update({ where: { id: 'm1-archived' }, data: { m2: { connect: { id: 'm2-1' } } } }), + ).toBeRejectedByPolicy(); + + await rawDb.m1.update({ where: { id: 'm1-archived' }, data: { m2: { connect: { id: 'm2-1' } } } }); + await expect( + db.m2.update({ where: { id: 'm2-1' }, data: { m1: { disconnect: { id: 'm1-archived' } } } }), + ).toBeRejectedByPolicy(); + }); + + it('field-level update policy on m2m field inherited from a delegate base model', async () => { + const db = await createPolicyTestClient( + ` + model Tag { + id String @id @default(uuid()) + name String + items Item[] + + @@allow('all', true) + } + + model Item { + id String @id @default(uuid()) + type String + deleted Boolean @default(false) + tags Tag[] @allow('update', !deleted) + + @@delegate(type) + @@allow('read,create', true) + } + + model Image extends Item { + width Int + } + + model Video extends Item { + url String + videoType String + @@delegate(videoType) + } + + model Clip extends Video { + duration Int + } + `, + { usePrismaPush: true }, + ); + const rawDb = db.$unuseAll(); + + await rawDb.tag.create({ data: { id: 'tag-1', name: 'tag1' } }); + await rawDb.image.create({ data: { id: 'image-1', width: 100, deleted: false } }); + await rawDb.image.create({ data: { id: 'image-2', width: 200, deleted: true } }); + await rawDb.clip.create({ data: { id: 'clip-1', url: 'c1', duration: 10, deleted: false } }); + await rawDb.clip.create({ data: { id: 'clip-2', url: 'c2', duration: 20, deleted: true } }); + + // direct sub-model: the base model has no model-level update allow, the inherited field-level + // allow enables connect for non-deleted items + await expect( + db.image.update({ + where: { id: 'image-1' }, + data: { tags: { connect: { id: 'tag-1' } } }, + }), + ).toResolveTruthy(); + await expect( + db.image.update({ + where: { id: 'image-2' }, + data: { tags: { connect: { id: 'tag-1' } } }, + }), + ).toBeRejectedByPolicy(); + + // chained sub-model + await expect( + db.clip.update({ + where: { id: 'clip-1' }, + data: { tags: { connect: { id: 'tag-1' } } }, + }), + ).toResolveTruthy(); + await expect( + db.clip.update({ + where: { id: 'clip-2' }, + data: { tags: { connect: { id: 'tag-1' } } }, + }), + ).toBeRejectedByPolicy(); + + // from the other side of the relation, the field-level policy of the delegate side still applies + await expect( + db.tag.update({ + where: { id: 'tag-1' }, + data: { items: { connect: { id: 'image-2' } } }, + }), + ).toBeRejectedByPolicy(); + await expect( + db.tag.update({ + where: { id: 'tag-1' }, + data: { items: { connect: { id: 'clip-2' } } }, + }), + ).toBeRejectedByPolicy(); + + // disconnect follows the same rule + await expect( + db.image.update({ + where: { id: 'image-1' }, + data: { tags: { disconnect: { id: 'tag-1' } } }, + }), + ).toResolveTruthy(); + await expect( + db.tag.update({ + where: { id: 'tag-1' }, + data: { items: { disconnect: { id: 'clip-1' } } }, + }), + ).toResolveTruthy(); + await expect(db.tag.findUnique({ where: { id: 'tag-1' }, include: { items: true } })).resolves.toMatchObject({ + items: [], + }); + }); }); diff --git a/tests/e2e/orm/policy/migrated/field-level-policy.test.ts b/tests/e2e/orm/policy/migrated/field-level-policy.test.ts index 429b0f70e..a912a91b5 100644 --- a/tests/e2e/orm/policy/migrated/field-level-policy.test.ts +++ b/tests/e2e/orm/policy/migrated/field-level-policy.test.ts @@ -669,7 +669,7 @@ describe('field-level policy tests migrated from v2', () => { } `, ), - ).rejects.toThrow(/Field-level policies are not allowed for relation fields/); + ).rejects.toThrow(/Field-level policies are only allowed for implicit many-to-many relation fields/); }); it('evaluates computed field to null when based on non-readable field', async () => { diff --git a/tests/regression/test/issue-2382.test.ts b/tests/regression/test/issue-2382.test.ts new file mode 100644 index 000000000..915b5e422 --- /dev/null +++ b/tests/regression/test/issue-2382.test.ts @@ -0,0 +1,68 @@ +import { createPolicyTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +// https://github.com/zenstackhq/zenstack/issues/2382 +describe('Regression for issue #2382', () => { + it('allows club owner to link/unlink read-only activities', async () => { + const db = await createPolicyTestClient( + ` +model User { + id String @id @default(uuid()) + clubs Club[] +} + +model Club { + id String @id @default(uuid()) + name String + ownerId String + owner User @relation(fields: [ownerId], references: [id]) + activities Activity[] + + @@allow('all', auth().id == ownerId) +} + +model Activity { + id String @id @default(uuid()) + name String + clubs Club[] @allow('update', auth() != null) + + @@allow('read', true) +} +`, + { usePrismaPush: true }, + ); + const rawDb = db.$unuseAll(); + + await rawDb.user.create({ data: { id: 'user-1' } }); + await rawDb.club.create({ data: { id: 'club-1', name: 'Chess Club', ownerId: 'user-1' } }); + await rawDb.activity.create({ data: { id: 'act-1', name: 'Tournament' } }); + + const ownerDb = db.$setAuth({ id: 'user-1' }); + + // Activity is read-only (no model-level update policy), but the field-level + // allow on `clubs` enables the club owner to link it — this was rejected before the fix + await expect( + ownerDb.club.update({ + where: { id: 'club-1' }, + data: { activities: { connect: { id: 'act-1' } } }, + }), + ).toResolveTruthy(); + + // disconnect also works + await expect( + ownerDb.club.update({ + where: { id: 'club-1' }, + data: { activities: { disconnect: { id: 'act-1' } } }, + }), + ).toResolveTruthy(); + + // a non-owner cannot link (Club is not visible to them — read policy filters it out) + const otherDb = db.$setAuth({ id: 'user-2' }); + await expect( + otherDb.club.update({ + where: { id: 'club-1' }, + data: { activities: { connect: { id: 'act-1' } } }, + }), + ).toBeRejectedNotFound(); + }); +}); From b6e0a8f4eccc84b3ac1c38a2793394bb92c473ba Mon Sep 17 00:00:00 2001 From: sanny-io <3054653+sanny-io@users.noreply.github.com> Date: Sat, 10 Oct 2026 09:28:14 -0700 Subject: [PATCH 03/18] fix(language): semantic highlighting issues (#2800) --- packages/language/package.json | 4 +- .../language/scripts/patch-generated-tm.ts | 70 +++++++++ packages/language/src/zmodel-semantic.ts | 137 +++++++++++++++--- 3 files changed, 192 insertions(+), 19 deletions(-) create mode 100644 packages/language/scripts/patch-generated-tm.ts diff --git a/packages/language/package.json b/packages/language/package.json index deaf5c9f0..648e1880b 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -23,8 +23,8 @@ "watch": "tsdown --watch", "lint": "eslint src --ext ts", "test": "vitest run", - "langium:generate": "langium generate", - "langium:generate:production": "langium generate --mode=production", + "langium:generate": "langium generate && tsx scripts/patch-generated-tm.ts", + "langium:generate:production": "langium generate --mode=production && tsx scripts/patch-generated-tm.ts", "pack": "pnpm pack" }, "exports": { diff --git a/packages/language/scripts/patch-generated-tm.ts b/packages/language/scripts/patch-generated-tm.ts new file mode 100644 index 000000000..0a57d6e8b --- /dev/null +++ b/packages/language/scripts/patch-generated-tm.ts @@ -0,0 +1,70 @@ +/** + * Post-processes syntaxes/zmodel.tmLanguage.json after `langium generate` to correct + * highlighting for some keywords. + */ + +import tm from '../syntaxes/zmodel.tmLanguage.json'; +import { writeFileSync } from 'fs'; +import { join } from 'path'; + +const tmPath = join(import.meta.dirname, '../syntaxes/zmodel.tmLanguage.json'); +const control = tm.patterns.find((pattern) => pattern.name === 'keyword.control.zmodel-v3'); + +if (!control?.match) { + throw new Error('Could not find control pattern/match'); +} + +const keywordsToRemove = new Set([ + 'this', + 'null', + 'true', + 'false', + 'in', + 'type', + 'datasource', + 'model', + 'enum', + 'generator', + 'attribute', + 'view', + 'function', + 'mutation', + 'procedure', + 'plugin', + 'abstract', + 'extends', + 'with', +]); +const matchPattern = /\((.+)\)/g; +const [, keywordsMatch] = matchPattern.exec(control.match) as RegExpExecArray; +const keywordsArray = keywordsMatch.split('|').filter((keyword) => !keywordsToRemove.has(keyword)); + +control.match = control.match.replace(keywordsMatch, keywordsArray.join('|')); + +tm.patterns.push({ + name: 'constant.language', + match: '\\b(true|false|null)\\b', +}); + +tm.patterns.push({ + name: 'variable.language.this', + match: '\\b(this)\\b', +}); + +tm.patterns.push({ + name: 'storage.modifier', + match: '\\b(mutation|abstract|extends|with)\\b', +}); + +tm.patterns.push({ + name: 'storage.type', + match: '\\b(type|datasource|model|enum|generator|attribute|view|function|procedure|plugin)\\b', +}); + +tm.patterns.push({ + name: 'keyword.operator.expression.in', + match: '\\b(in)\\b', +}); + +writeFileSync(tmPath, JSON.stringify(tm, null, 2)); +console.log('Patched syntaxes/zmodel.tmLanguage.json'); diff --git a/packages/language/src/zmodel-semantic.ts b/packages/language/src/zmodel-semantic.ts index 7910c78af..8a26d4120 100644 --- a/packages/language/src/zmodel-semantic.ts +++ b/packages/language/src/zmodel-semantic.ts @@ -21,7 +21,18 @@ import { isPluginField, isReferenceExpr, isTypeDef, + isProcedure, + isFunctionParamType, + isAttributeParamType, + isUnaryExpr, + isBinaryExpr, + isNumberLiteral, + isCollectionPredicateBinding, + isFunctionParam, + isAttributeParam, + isProcedureParam, type AstNode, + isDataFieldParam, } from './ast'; export class ZModelSemanticTokenProvider extends AbstractSemanticTokenProvider { @@ -44,25 +55,31 @@ export class ZModelSemanticTokenProvider extends AbstractSemanticTokenProvider { property: 'baseModel', type: SemanticTokenTypes.type, }); - } else if (isDataSource(node) || isGeneratorDecl(node) || isPlugin(node) || isEnum(node) || isTypeDef(node)) { + } else if (isTypeDef(node)) { acceptor({ node, property: 'name', type: SemanticTokenTypes.type, }); - } else if ( - isDataField(node) || - isConfigField(node) || - isAttributeArg(node) || - isPluginField(node) || - isEnumField(node) - ) { + + acceptor({ + node, + property: 'mixins', + type: SemanticTokenTypes.type, + }); + } else if (isDataSource(node) || isGeneratorDecl(node) || isPlugin(node)) { acceptor({ node, property: 'name', - type: SemanticTokenTypes.variable, + type: SemanticTokenTypes.type, }); - } else if (isDataFieldType(node)) { + } else if (isDataField(node) || isConfigField(node) || isPluginField(node)) { + acceptor({ + node, + property: 'name', + type: SemanticTokenTypes.property, + }); + } else if (isDataFieldType(node) || isFunctionParamType(node) || isAttributeParamType(node)) { if (node.type) { acceptor({ node, @@ -80,7 +97,21 @@ export class ZModelSemanticTokenProvider extends AbstractSemanticTokenProvider { acceptor({ node, property: 'decl', - type: SemanticTokenTypes.function, + type: SemanticTokenTypes.decorator, + }); + + if (node.decl.$refText === '@regex' && node.args[0]) { + acceptor({ + node: node.args[0], + property: 'value', + type: SemanticTokenTypes.regexp, + }); + } + } else if (isAttribute(node)) { + acceptor({ + node, + property: 'name', + type: SemanticTokenTypes.decorator, }); } else if (isInvocationExpr(node)) { acceptor({ @@ -88,24 +119,96 @@ export class ZModelSemanticTokenProvider extends AbstractSemanticTokenProvider { property: 'function', type: SemanticTokenTypes.function, }); - } else if (isFunctionDecl(node) || isAttribute(node)) { + } else if (isFunctionDecl(node) || isProcedure(node)) { acceptor({ node, property: 'name', type: SemanticTokenTypes.function, }); + + if ('mutation' in node && node.mutation) { + acceptor({ + node, + property: 'mutation', + type: SemanticTokenTypes.modifier, + }); + } } else if (isReferenceExpr(node)) { - acceptor({ - node, - property: 'target', - type: SemanticTokenTypes.variable, - }); + if (isEnumField(node.target.ref)) { + acceptor({ + node, + property: 'target', + type: SemanticTokenTypes.enumMember, + }); + } else if (isFunctionParam(node.target.ref)) { + acceptor({ + node, + property: 'target', + type: SemanticTokenTypes.parameter, + }); + } else if (isCollectionPredicateBinding(node.target.ref)) { + acceptor({ + node, + property: 'target', + type: SemanticTokenTypes.variable, + }); + } else { + acceptor({ + node, + property: 'target', + type: SemanticTokenTypes.property, + }); + } } else if (isMemberAccessExpr(node)) { acceptor({ node, property: 'member', type: SemanticTokenTypes.property, }); + } else if (isNumberLiteral(node)) { + acceptor({ + node, + property: 'value', + type: SemanticTokenTypes.number, + }); + } else if (isUnaryExpr(node) || isBinaryExpr(node)) { + if (node.operator !== 'in') { + acceptor({ + node, + property: 'operator', + type: SemanticTokenTypes.operator, + }); + } + } else if (isEnumField(node)) { + acceptor({ + node, + property: 'name', + type: SemanticTokenTypes.enumMember, + }); + } else if (isEnum(node)) { + acceptor({ + node, + property: 'name', + type: SemanticTokenTypes.enum, + }); + } else if ( + isFunctionParam(node) || + isAttributeArg(node) || + isAttributeParam(node) || + isProcedureParam(node) || + isDataFieldParam(node) + ) { + acceptor({ + node, + property: 'name', + type: SemanticTokenTypes.parameter, + }); + } else if (isCollectionPredicateBinding(node)) { + acceptor({ + node, + property: 'name', + type: SemanticTokenTypes.variable, + }); } } } From 9b9a1795925c511bce61668f4e25ce595c986aee Mon Sep 17 00:00:00 2001 From: Eugen Istoc Date: Mon, 12 Oct 2026 20:43:07 -0400 Subject: [PATCH 04/18] feat(orm)!: Add typed typed mutation hooks #2365 (#2740) --- BREAKINGCHANGES.md | 1 + .../executor/zenstack-query-executor.ts | 64 +- packages/orm/src/client/plugin.ts | 34 +- .../entity-mutation-hooks.test.ts | 554 ++++++++++++------ tests/regression/test/issue-586.test.ts | 8 +- 5 files changed, 452 insertions(+), 209 deletions(-) diff --git a/BREAKINGCHANGES.md b/BREAKINGCHANGES.md index 96c3d629d..8612ff374 100644 --- a/BREAKINGCHANGES.md +++ b/BREAKINGCHANGES.md @@ -4,3 +4,4 @@ 1. `@omit` and `@password` attributes have been removed 1. SWR plugin is removed 1. `makeModelSchema()` no longer includes relation fields by default — use `include` or `select` options to opt in, mirroring ORM behaviour +1. ORM entity mutation catch-all hooks now need to be registered under `onEntityMutation.$all` diff --git a/packages/orm/src/client/executor/zenstack-query-executor.ts b/packages/orm/src/client/executor/zenstack-query-executor.ts index 2b5083c2c..ebd0842bf 100644 --- a/packages/orm/src/client/executor/zenstack-query-executor.ts +++ b/packages/orm/src/client/executor/zenstack-query-executor.ts @@ -125,7 +125,12 @@ export class ZenStackQueryExecutor extends DefaultQueryExecutor { } private get hasEntityMutationPluginsWithAfterMutationHooks() { - return (this.client.$options.plugins ?? []).some((plugin) => plugin.onEntityMutation?.afterEntityMutation); + return (this.client.$options.plugins ?? []).some((plugin) => { + if (!plugin.onEntityMutation) return false; + if (plugin.onEntityMutation['$all']?.afterEntityMutation) return true; + const models = Object.keys(this.client.$schema.models); + return models.some((model) => (plugin.onEntityMutation as any)?.[model]?.afterEntityMutation); + }); } private get hasOnKyselyHooks() { @@ -353,18 +358,34 @@ export class ZenStackQueryExecutor extends DefaultQueryExecutor { if (this.options.plugins) { for (const plugin of this.options.plugins) { const onEntityMutation = plugin.onEntityMutation; - if (!onEntityMutation?.beforeEntityMutation) { + if (!onEntityMutation) { continue; } - await onEntityMutation.beforeEntityMutation({ - model: mutationInfo.model, - action: mutationInfo.action, - queryNode, - loadBeforeMutationEntities, - client, - queryId, - }); + // all-model hook + if (onEntityMutation['$all']?.beforeEntityMutation) { + await onEntityMutation['$all'].beforeEntityMutation({ + model: mutationInfo.model, + action: mutationInfo.action, + queryNode, + loadBeforeMutationEntities, + client, + queryId, + }); + } + + // per-model hook + const modelHooks = (onEntityMutation as Record)[mutationInfo.model]; + if (modelHooks?.beforeEntityMutation) { + await modelHooks.beforeEntityMutation({ + model: mutationInfo.model, + action: mutationInfo.action, + queryNode, + loadBeforeMutationEntities, + client, + queryId, + }); + } } } } @@ -375,22 +396,29 @@ export class ZenStackQueryExecutor extends DefaultQueryExecutor { const hooks: AfterEntityMutationCallback[] = []; - // tsc perf for (const plugin of this.options.plugins ?? []) { const onEntityMutation = plugin.onEntityMutation; - if (!onEntityMutation?.afterEntityMutation) { - continue; - } - if (filterFor === 'inTx' && !onEntityMutation.runAfterMutationWithinTransaction) { + if (!onEntityMutation) { continue; } - if (filterFor === 'outTx' && onEntityMutation.runAfterMutationWithinTransaction) { - continue; + // all-model hook + if (onEntityMutation['$all']?.afterEntityMutation) { + const runInTx = onEntityMutation['$all'].runAfterMutationWithinTransaction ?? false; + if (filterFor === 'all' || (filterFor === 'inTx' && runInTx) || (filterFor === 'outTx' && !runInTx)) { + hooks.push(onEntityMutation['$all'].afterEntityMutation.bind(plugin)); + } } - hooks.push(onEntityMutation.afterEntityMutation.bind(plugin)); + // per-model hook + const modelHooks = (onEntityMutation as Record)[mutationInfo.model]; + if (modelHooks?.afterEntityMutation) { + const runInTx = modelHooks.runAfterMutationWithinTransaction ?? false; + if (filterFor === 'all' || (filterFor === 'inTx' && runInTx) || (filterFor === 'outTx' && !runInTx)) { + hooks.push(modelHooks.afterEntityMutation.bind(plugin)); + } + } } if (hooks.length === 0) { diff --git a/packages/orm/src/client/plugin.ts b/packages/orm/src/client/plugin.ts index 2a2431637..c9f0d791a 100644 --- a/packages/orm/src/client/plugin.ts +++ b/packages/orm/src/client/plugin.ts @@ -5,6 +5,7 @@ import type { GetModelFields, GetModels, NonRelationFields, SchemaDef } from '@z import type { MaybePromise } from '../utils/type-utils'; import type { MapModelFieldType } from './crud-types'; import type { AllCrudOperations, CoreCrudOperations } from './crud/operations/base'; +import type { DefaultModelResult } from './crud-types'; type AllowedExtQueryArgKeys = CoreCrudOperations | '$create' | '$read' | '$update' | '$delete' | '$all'; @@ -287,6 +288,12 @@ type OnQueryHookContext = { // #region OnEntityMutation hooks export type EntityMutationHooksDef = { + [M in '$all' | GetModels]?: M extends '$all' + ? AllEntityMutationHooksDef + : ModelEntityMutationHooksDef>; +}; + +export type AllEntityMutationHooksDef = { /** * Called before entities are mutated. */ @@ -374,13 +381,38 @@ export type PluginAfterEntityMutationArgs = MutationHo /** * The ZenStack client you can use to perform additional operations. - * See {@link EntityMutationHooksDef.runAfterMutationWithinTransaction} for detailed transaction behavior. + * See {@link AllEntityMutationHooksDef.runAfterMutationWithinTransaction} for detailed transaction behavior. * * Mutations initiated from this client will NOT trigger entity mutation hooks to avoid infinite loops. */ client: ClientContract; }; +export type PluginModelBeforeEntityMutationArgs> = Omit< + MutationHooksArgs, + 'model' +> & { + model: Model; + loadBeforeMutationEntities(): Promise[] | undefined>; + client: ClientContract; +}; + +export type PluginModelAfterEntityMutationArgs> = Omit< + MutationHooksArgs, + 'model' +> & { + model: Model; + loadAfterMutationEntities(): Promise[] | undefined>; + beforeMutationEntities?: DefaultModelResult[]; + client: ClientContract; +}; + +export type ModelEntityMutationHooksDef> = { + beforeEntityMutation?: (args: PluginModelBeforeEntityMutationArgs) => MaybePromise; + afterEntityMutation?: (args: PluginModelAfterEntityMutationArgs) => MaybePromise; + runAfterMutationWithinTransaction?: boolean; +}; + // #endregion // #region OnKyselyQuery hooks diff --git a/tests/e2e/orm/plugin-infra/entity-mutation-hooks.test.ts b/tests/e2e/orm/plugin-infra/entity-mutation-hooks.test.ts index 8ddb35f7e..0bc5514e3 100644 --- a/tests/e2e/orm/plugin-infra/entity-mutation-hooks.test.ts +++ b/tests/e2e/orm/plugin-infra/entity-mutation-hooks.test.ts @@ -22,20 +22,22 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - beforeEntityMutation(args) { - beforeCalled[args.action] = true; - if (args.action === 'create') { - expect(InsertQueryNode.is(args.queryNode)).toBe(true); - } - if (args.action === 'update') { - expect(UpdateQueryNode.is(args.queryNode)).toBe(true); - } - if (args.action === 'delete') { - expect(DeleteQueryNode.is(args.queryNode)).toBe(true); - } - }, - afterEntityMutation(args) { - afterCalled[args.action] = true; + $all: { + beforeEntityMutation(args) { + beforeCalled[args.action] = true; + if (args.action === 'create') { + expect(InsertQueryNode.is(args.queryNode)).toBe(true); + } + if (args.action === 'update') { + expect(UpdateQueryNode.is(args.queryNode)).toBe(true); + } + if (args.action === 'delete') { + expect(DeleteQueryNode.is(args.queryNode)).toBe(true); + } + }, + afterEntityMutation(args) { + afterCalled[args.action] = true; + }, }, }, }); @@ -72,24 +74,26 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - async beforeEntityMutation(args) { - if (args.action === 'update' || args.action === 'delete') { - await expect(args.loadBeforeMutationEntities()).resolves.toEqual([ - expect.objectContaining({ - email: args.action === 'update' ? 'u1@test.com' : 'u3@test.com', - }), - ]); - queryIds[args.action].before = args.queryId.queryId; - } - }, - async afterEntityMutation(args) { - if (args.action === 'update' || args.action === 'delete') { - queryIds[args.action].after = args.queryId.queryId; - } - - if (args.action === 'update') { - beforeMutationEntitiesInAfterHooks = args.beforeMutationEntities; - } + $all: { + async beforeEntityMutation(args) { + if (args.action === 'update' || args.action === 'delete') { + await expect(args.loadBeforeMutationEntities()).resolves.toEqual([ + expect.objectContaining({ + email: args.action === 'update' ? 'u1@test.com' : 'u3@test.com', + }), + ]); + queryIds[args.action].before = args.queryId.queryId; + } + }, + async afterEntityMutation(args) { + if (args.action === 'update' || args.action === 'delete') { + queryIds[args.action].after = args.queryId.queryId; + } + + if (args.action === 'update') { + beforeMutationEntitiesInAfterHooks = args.beforeMutationEntities; + } + }, }, }, }); @@ -122,22 +126,24 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - async afterEntityMutation(args) { - if (args.action === 'create' || args.action === 'update') { - if (args.action === 'create') { - userCreateIntercepted = true; - } - if (args.action === 'update') { - userUpdateIntercepted = true; + $all: { + async afterEntityMutation(args) { + if (args.action === 'create' || args.action === 'update') { + if (args.action === 'create') { + userCreateIntercepted = true; + } + if (args.action === 'update') { + userUpdateIntercepted = true; + } + await expect(args.loadAfterMutationEntities()).resolves.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + email: args.action === 'create' ? 'u1@test.com' : 'u2@test.com', + }), + ]), + ); } - await expect(args.loadAfterMutationEntities()).resolves.toEqual( - expect.arrayContaining([ - expect.objectContaining({ - email: args.action === 'create' ? 'u1@test.com' : 'u2@test.com', - }), - ]), - ); - } + }, }, }, }); @@ -162,38 +168,40 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - async afterEntityMutation(args) { - if (args.action === 'create') { - userCreateIntercepted = true; - await expect(args.loadAfterMutationEntities()).resolves.toEqual( - expect.arrayContaining([ - expect.objectContaining({ email: 'u1@test.com' }), - expect.objectContaining({ email: 'u2@test.com' }), - ]), - ); - } else if (args.action === 'update') { - userUpdateIntercepted = true; - await expect(args.loadAfterMutationEntities()).resolves.toEqual( - expect.arrayContaining([ - expect.objectContaining({ - email: 'u1@test.com', - name: 'A user', - }), - expect.objectContaining({ - email: 'u2@test.com', - name: 'A user', - }), - ]), - ); - } else if (args.action === 'delete') { - userDeleteIntercepted = true; - await expect(args.loadAfterMutationEntities()).resolves.toEqual( - expect.arrayContaining([ - expect.objectContaining({ email: 'u1@test.com' }), - expect.objectContaining({ email: 'u2@test.com' }), - ]), - ); - } + $all: { + async afterEntityMutation(args) { + if (args.action === 'create') { + userCreateIntercepted = true; + await expect(args.loadAfterMutationEntities()).resolves.toEqual( + expect.arrayContaining([ + expect.objectContaining({ email: 'u1@test.com' }), + expect.objectContaining({ email: 'u2@test.com' }), + ]), + ); + } else if (args.action === 'update') { + userUpdateIntercepted = true; + await expect(args.loadAfterMutationEntities()).resolves.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + email: 'u1@test.com', + name: 'A user', + }), + expect.objectContaining({ + email: 'u2@test.com', + name: 'A user', + }), + ]), + ); + } else if (args.action === 'delete') { + userDeleteIntercepted = true; + await expect(args.loadAfterMutationEntities()).resolves.toEqual( + expect.arrayContaining([ + expect.objectContaining({ email: 'u1@test.com' }), + expect.objectContaining({ email: 'u2@test.com' }), + ]), + ); + } + }, }, }, }); @@ -216,18 +224,20 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - async afterEntityMutation(args) { - if (args.action === 'create') { - if (args.model === 'Post') { - const afterEntities = await args.loadAfterMutationEntities(); - if ((afterEntities![0] as any).title === 'Post1') { - post1Intercepted = true; - } - if ((afterEntities![0] as any).title === 'Post2') { - post2Intercepted = true; + $all: { + async afterEntityMutation(args) { + if (args.action === 'create') { + if (args.model === 'Post') { + const afterEntities = await args.loadAfterMutationEntities(); + if ((afterEntities![0] as any).title === 'Post1') { + post1Intercepted = true; + } + if ((afterEntities![0] as any).title === 'Post2') { + post2Intercepted = true; + } } } - } + }, }, }, }); @@ -256,12 +266,14 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - async afterEntityMutation(args) { - triggered.push({ - action: args.action, - model: args.model, - afterMutationEntities: await args.loadAfterMutationEntities(), - }); + $all: { + async afterEntityMutation(args) { + triggered.push({ + action: args.action, + model: args.model, + afterMutationEntities: await args.loadAfterMutationEntities(), + }); + }, }, }, }); @@ -303,17 +315,19 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - async beforeEntityMutation(ctx) { - await ctx.client.profile.create({ - data: { bio: 'Bio1' }, - }); - }, - async afterEntityMutation(ctx) { - intercepted = true; - await ctx.client.user.update({ - where: { email: 'u1@test.com' }, - data: { email: 'u2@test.com' }, - }); + $all: { + async beforeEntityMutation(ctx) { + await ctx.client.profile.create({ + data: { bio: 'Bio1' }, + }); + }, + async afterEntityMutation(ctx) { + intercepted = true; + await ctx.client.user.update({ + where: { email: 'u1@test.com' }, + data: { email: 'u2@test.com' }, + }); + }, }, }, }); @@ -333,18 +347,20 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - runAfterMutationWithinTransaction: true, - async beforeEntityMutation(ctx) { - await ctx.client.profile.create({ - data: { bio: 'Bio1' }, - }); - }, - async afterEntityMutation(ctx) { - intercepted = true; - await ctx.client.user.update({ - where: { email: 'u1@test.com' }, - data: { email: 'u2@test.com' }, - }); + $all: { + runAfterMutationWithinTransaction: true, + async beforeEntityMutation(ctx) { + await ctx.client.profile.create({ + data: { bio: 'Bio1' }, + }); + }, + async afterEntityMutation(ctx) { + intercepted = true; + await ctx.client.user.update({ + where: { email: 'u1@test.com' }, + data: { email: 'u2@test.com' }, + }); + }, }, }, }); @@ -362,8 +378,10 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - async beforeEntityMutation() { - throw new Error('trigger failure'); + $all: { + async beforeEntityMutation() { + throw new Error('trigger failure'); + }, }, }, }); @@ -384,9 +402,11 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - async afterEntityMutation() { - intercepted = true; - throw new Error('trigger rollback'); + $all: { + async afterEntityMutation() { + intercepted = true; + throw new Error('trigger rollback'); + }, }, }, }); @@ -406,11 +426,13 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - runAfterMutationWithinTransaction: true, - async afterEntityMutation(ctx) { - intercepted = true; - await ctx.client.user.create({ data: { email: 'u2@test.com' } }); - throw new Error('trigger rollback'); + $all: { + runAfterMutationWithinTransaction: true, + async afterEntityMutation(ctx) { + intercepted = true; + await ctx.client.user.create({ data: { email: 'u2@test.com' } }); + throw new Error('trigger rollback'); + }, }, }, }); @@ -432,9 +454,11 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - async afterEntityMutation(ctx) { - intercepted = true; - await ctx.client.user.create({ data: { email: 'u2@test.com' } }); + $all: { + async afterEntityMutation(ctx) { + intercepted = true; + await ctx.client.user.create({ data: { email: 'u2@test.com' } }); + }, }, }, }); @@ -461,10 +485,12 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - runAfterMutationWithinTransaction: true, - async afterEntityMutation(ctx) { - intercepted = true; - await ctx.client.user.create({ data: { email: 'u2@test.com' } }); + $all: { + runAfterMutationWithinTransaction: true, + async afterEntityMutation(ctx) { + intercepted = true; + await ctx.client.user.create({ data: { email: 'u2@test.com' } }); + }, }, }, }); @@ -492,13 +518,15 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - async beforeEntityMutation(args) { - if (args.action === 'update') { - intercepted = true; - await expect(args.loadBeforeMutationEntities()).resolves.toEqual([ - expect.objectContaining({ email: 'u1@test.com' }), - ]); - } + $all: { + async beforeEntityMutation(args) { + if (args.action === 'update') { + intercepted = true; + await expect(args.loadBeforeMutationEntities()).resolves.toEqual([ + expect.objectContaining({ email: 'u1@test.com' }), + ]); + } + }, }, }, }); @@ -519,11 +547,13 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - async beforeEntityMutation(ctx) { - intercepted = true; - await ctx.client.profile.create({ - data: { bio: 'Bio1' }, - }); + $all: { + async beforeEntityMutation(ctx) { + intercepted = true; + await ctx.client.profile.create({ + data: { bio: 'Bio1' }, + }); + }, }, }, }); @@ -549,27 +579,29 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - async beforeEntityMutation(ctx) { - const r = await ctx.client.user.findUnique({ where: { email: 'u1@test.com' } }); - if (r) { - // second create - txVisible = true; - } else { - // first create - await ctx.client.profile.create({ - data: { bio: 'Bio1' }, + $all: { + async beforeEntityMutation(ctx) { + const r = await ctx.client.user.findUnique({ where: { email: 'u1@test.com' } }); + if (r) { + // second create + txVisible = true; + } else { + // first create + await ctx.client.profile.create({ + data: { bio: 'Bio1' }, + }); + } + }, + async afterEntityMutation(ctx) { + if (intercepted) { + return; + } + intercepted = true; + await ctx.client.user.update({ + where: { email: 'u1@test.com' }, + data: { email: 'u3@test.com' }, }); - } - }, - async afterEntityMutation(ctx) { - if (intercepted) { - return; - } - intercepted = true; - await ctx.client.user.update({ - where: { email: 'u1@test.com' }, - data: { email: 'u3@test.com' }, - }); + }, }, }, }); @@ -602,16 +634,18 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - runAfterMutationWithinTransaction: true, - async afterEntityMutation(ctx) { - if (intercepted) { - return; - } - intercepted = true; - await ctx.client.user.update({ - where: { email: 'u1@test.com' }, - data: { email: 'u3@test.com' }, - }); + $all: { + runAfterMutationWithinTransaction: true, + async afterEntityMutation(ctx) { + if (intercepted) { + return; + } + intercepted = true; + await ctx.client.user.update({ + where: { email: 'u1@test.com' }, + data: { email: 'u3@test.com' }, + }); + }, }, }, }); @@ -642,10 +676,12 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - async afterEntityMutation(ctx) { - intercepted = true; - await ctx.client.user.create({ data: { email: 'u2@test.com' } }); - throw new Error('trigger error'); + $all: { + async afterEntityMutation(ctx) { + intercepted = true; + await ctx.client.user.create({ data: { email: 'u2@test.com' } }); + throw new Error('trigger error'); + }, }, }, }); @@ -668,11 +704,13 @@ describe('Entity mutation hooks tests', () => { const client = _client.$use({ id: 'test', onEntityMutation: { - runAfterMutationWithinTransaction: true, - async afterEntityMutation(ctx) { - intercepted = true; - await ctx.client.user.create({ data: { email: 'u2@test.com' } }); - throw new Error('trigger error'); + $all: { + runAfterMutationWithinTransaction: true, + async afterEntityMutation(ctx) { + intercepted = true; + await ctx.client.user.create({ data: { email: 'u2@test.com' } }); + throw new Error('trigger error'); + }, }, }, }); @@ -691,6 +729,146 @@ describe('Entity mutation hooks tests', () => { await expect(client.user.findMany()).toResolveWithLength(0); }); }); + + it('can use per-model hooks with typed entities', async () => { + let userBeforeEntities: any; + let postBeforeEntities: any; + let userAfterEntities: any; + let postAfterEntities: any; + + const client = _client.$use({ + id: 'test', + onEntityMutation: { + User: { + async beforeEntityMutation(args) { + expect(args.model).toBe('User'); + userBeforeEntities = await args.loadBeforeMutationEntities(); + }, + async afterEntityMutation(args) { + expect(args.model).toBe('User'); + userAfterEntities = await args.loadAfterMutationEntities(); + }, + }, + Post: { + async beforeEntityMutation(args) { + expect(args.model).toBe('Post'); + postBeforeEntities = await args.loadBeforeMutationEntities(); + }, + async afterEntityMutation(args) { + expect(args.model).toBe('Post'); + postAfterEntities = await args.loadAfterMutationEntities(); + }, + }, + }, + }); + + const user = await client.user.create({ + data: { email: 'u1@test.com' }, + }); + + // User after hook should have fired, Post hooks should not + expect(userAfterEntities).toEqual([expect.objectContaining({ email: 'u1@test.com' })]); + expect(postAfterEntities).toBeUndefined(); + + await client.user.update({ + where: { id: user.id }, + data: { email: 'u2@test.com' }, + }); + + // User before hook should have captured old entity + expect(userBeforeEntities).toEqual([expect.objectContaining({ email: 'u1@test.com' })]); + // User after hook should have captured new entity + expect(userAfterEntities).toEqual([expect.objectContaining({ email: 'u2@test.com' })]); + // Post hooks still untouched + expect(postBeforeEntities).toBeUndefined(); + }); + + it('can combine per-model and catch-all hooks', async () => { + const catchAllModels: string[] = []; + const perModelModels: string[] = []; + + const client = _client.$use({ + id: 'test', + onEntityMutation: { + $all: { + afterEntityMutation(args) { + catchAllModels.push(args.model); + }, + }, + User: { + afterEntityMutation(args) { + perModelModels.push(args.model); + }, + }, + }, + }); + + await client.user.create({ + data: { email: 'u1@test.com', posts: { create: { title: 'Post1' } } }, + }); + + // catch-all fires for both User and Post + expect(catchAllModels).toContain('User'); + expect(catchAllModels).toContain('Post'); + + // per-model fires only for User + expect(perModelModels).toEqual(['User']); + }); + + it('per-model hooks provide beforeMutationEntities in after hook', async () => { + let beforeEntitiesInAfterHook: any; + + const client = _client.$use({ + id: 'test', + onEntityMutation: { + User: { + async beforeEntityMutation(args) { + await args.loadBeforeMutationEntities(); + }, + async afterEntityMutation(args) { + beforeEntitiesInAfterHook = args.beforeMutationEntities; + }, + }, + }, + }); + + const user = await client.user.create({ + data: { email: 'u1@test.com' }, + }); + await client.user.update({ + where: { id: user.id }, + data: { email: 'u2@test.com' }, + }); + + expect(beforeEntitiesInAfterHook).toEqual([expect.objectContaining({ email: 'u1@test.com' })]); + }); + + it('per-model hooks infer correct entity types', async () => { + const client = _client.$use({ + id: 'test', + onEntityMutation: { + User: { + async afterEntityMutation(args) { + const entities = await args.loadAfterMutationEntities(); + if (entities) { + const entity = entities[0]; + expect(entity).toBeDefined(); + if (!entity) { + return; + } + // This access compiles without casts because entity is a User + const _email: string = entity.email; + expect(_email).toBeTruthy(); + } + }, + }, + }, + }); + + await client.user.create({ + data: { email: 'u1@test.com' }, + }); + }); }); describe('Entity mutation hooks - delegate model interaction', () => { @@ -713,7 +891,9 @@ model Child extends Base { const withPlugin = client.$use({ id: 'test', onEntityMutation: { - afterEntityMutation() {}, + $all: { + afterEntityMutation() {}, + }, }, }); diff --git a/tests/regression/test/issue-586.test.ts b/tests/regression/test/issue-586.test.ts index 0c1244728..3a69d3590 100644 --- a/tests/regression/test/issue-586.test.ts +++ b/tests/regression/test/issue-586.test.ts @@ -46,9 +46,11 @@ describe('Regression for issue #586', () => { name: 'foo', description: 'foo', onEntityMutation: { - afterEntityMutation: async () => Promise.resolve(), - beforeEntityMutation: async () => Promise.resolve(), - runAfterMutationWithinTransaction: true, + $all: { + afterEntityMutation: async () => Promise.resolve(), + beforeEntityMutation: async () => Promise.resolve(), + runAfterMutationWithinTransaction: true, + }, }, }, ], From 5199bb1914a40b31fa24d3e19c5bb80d749ca2a4 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Wed, 14 Oct 2026 10:27:49 -0700 Subject: [PATCH 05/18] fix(orm): follow-ups for typed entity mutation hooks (#2874) Co-authored-by: Claude Fable 5.1 --- BREAKINGCHANGES.md | 2 +- packages/orm/src/client/client-impl.ts | 106 +++++ .../executor/zenstack-query-executor.ts | 7 +- packages/orm/src/client/plugin.ts | 30 +- .../entity-mutation-hooks.test.ts | 425 +++++++++++++++++- 5 files changed, 555 insertions(+), 15 deletions(-) diff --git a/BREAKINGCHANGES.md b/BREAKINGCHANGES.md index 8612ff374..fe0a67e86 100644 --- a/BREAKINGCHANGES.md +++ b/BREAKINGCHANGES.md @@ -4,4 +4,4 @@ 1. `@omit` and `@password` attributes have been removed 1. SWR plugin is removed 1. `makeModelSchema()` no longer includes relation fields by default — use `include` or `select` options to opt in, mirroring ORM behaviour -1. ORM entity mutation catch-all hooks now need to be registered under `onEntityMutation.$all` +1. ORM entity mutation catch-all hooks should now be registered under `onEntityMutation.$all` (the previous shape still works but emits a deprecation warning); entities passed to per-model hooks are typed as raw database rows (same as `$qb` results) diff --git a/packages/orm/src/client/client-impl.ts b/packages/orm/src/client/client-impl.ts index 47dc82c2f..f6d9f9d76 100644 --- a/packages/orm/src/client/client-impl.ts +++ b/packages/orm/src/client/client-impl.ts @@ -45,6 +45,10 @@ import { createZenStackPromise, type ZenStackPromise } from './promise'; import { fieldHasDefaultValue, getField, isUnsupportedField, requireModel } from './query-utils'; import { ResultProcessor } from './result-processor'; +// tracks `onEntityMutation` objects that have already been validated/warned about, +// so that derived clients (e.g., from `$use` or transactions) don't warn repeatedly +const warnedPlugins = new WeakSet(); + type ExtResultFieldDef = { needs: Record; compute: (data: Record) => unknown; @@ -92,6 +96,10 @@ export class ClientImpl { ...this.$options.functions, }; + if (this.$options.plugins) { + this.$options.plugins = this.normalizePlugins(this.$options.plugins); + } + this.validateOptions(baseClient, options); // here we use kysely's props constructor so we can pass a custom query executor @@ -163,6 +171,104 @@ export class ClientImpl { return createClientProxy(this); } + /** + * Normalizes plugin definitions for backward compatibility and validates + * `onEntityMutation` keys. Legacy plugins that define hooks directly under + * `onEntityMutation` (instead of under `$all`) are converted to the new shape + * with a one-time warning. + */ + private normalizePlugins(plugins: AnyPlugin[]): AnyPlugin[] { + let changed = false; + const result = plugins.map((plugin) => { + const normalized = this.normalizeEntityMutationHooks(plugin); + if (normalized !== plugin) { + changed = true; + } + return normalized; + }); + return changed ? result : plugins; + } + + private normalizeEntityMutationHooks(plugin: AnyPlugin): AnyPlugin { + const hooks = plugin.onEntityMutation as Record | undefined; + if (!hooks || typeof hooks !== 'object') { + return plugin; + } + + const legacyKeys = ['beforeEntityMutation', 'afterEntityMutation', 'runAfterMutationWithinTransaction']; + const presentLegacyKeys = legacyKeys.filter((key) => key in hooks); + + const unknownKeys = Object.keys(hooks).filter( + (key) => key !== '$all' && !legacyKeys.includes(key) && !(key in this.$schema.models), + ); + + const warnOnce = (message: string) => { + if (!warnedPlugins.has(hooks)) { + console.warn(message); + } + }; + + if (unknownKeys.length > 0) { + warnOnce( + `Plugin "${plugin.id}" defines "onEntityMutation" hooks for unknown keys: ${unknownKeys + .map((k) => `"${k}"`) + .join(', ')}. Valid keys are "$all" and model names. These hooks will never be invoked.`, + ); + } + + if (presentLegacyKeys.length === 0) { + warnedPlugins.add(hooks); + return plugin; + } + + warnOnce( + `Plugin "${plugin.id}" defines "onEntityMutation" hooks in a deprecated shape. ` + + `Hooks that apply to all models should be nested under "$all": ` + + `{ onEntityMutation: { $all: { beforeEntityMutation, afterEntityMutation, runAfterMutationWithinTransaction } } }. ` + + `The legacy shape is still supported but will be removed in a future release.`, + ); + warnedPlugins.add(hooks); + + const legacy: Record = {}; + const rest: Record = {}; + for (const [key, value] of Object.entries(hooks)) { + if (legacyKeys.includes(key)) { + legacy[key] = value; + } else { + rest[key] = value; + } + } + + // bind legacy callbacks to the original plugin object to preserve `this` + for (const key of ['beforeEntityMutation', 'afterEntityMutation']) { + if (typeof legacy[key] === 'function') { + legacy[key] = (legacy[key] as Function).bind(plugin); + } + } + + const normalizedHooks = { + ...rest, + $all: { + // explicitly nested `$all` (if any) takes precedence over legacy keys + ...legacy, + ...(rest['$all'] as object | undefined), + }, + }; + // mark the normalized hooks as validated so derived clients don't warn again + warnedPlugins.add(normalizedHooks); + + // create a copy that preserves the plugin's prototype chain (plugins may be class + // instances with hooks defined on the prototype), overriding only `onEntityMutation` + const copy = Object.create(Object.getPrototypeOf(plugin), Object.getOwnPropertyDescriptors(plugin)); + Object.defineProperty(copy, 'onEntityMutation', { + value: normalizedHooks, + enumerable: true, + configurable: true, + writable: true, + }); + return copy as AnyPlugin; + } + private validateOptions(baseClient: ClientImpl | undefined, options: ClientOptions) { if (!baseClient && !options.skipValidationForComputedFields) { // validate computed fields configuration once for the root client diff --git a/packages/orm/src/client/executor/zenstack-query-executor.ts b/packages/orm/src/client/executor/zenstack-query-executor.ts index ebd0842bf..c7d9cbadc 100644 --- a/packages/orm/src/client/executor/zenstack-query-executor.ts +++ b/packages/orm/src/client/executor/zenstack-query-executor.ts @@ -450,8 +450,11 @@ export class ZenStackQueryExecutor extends DefaultQueryExecutor { } if (this.dialect.supportsReturning) { - // entities are returned in the query result - return queryResult.rows as Record[]; + // entities are returned in the query result; shallow-copy the rows so that + // hooks (which may run after the ORM has post-processed the query result + // in place, e.g., when deferred to after transaction commit) always see + // raw database rows + return (queryResult.rows as Record[]).map((row) => ({ ...row })); } else { const mutatedIds = InsertQueryNode.is(queryNode) ? this.getInsertIds(mutationInfo.model, queryNode, queryResult) diff --git a/packages/orm/src/client/plugin.ts b/packages/orm/src/client/plugin.ts index c9f0d791a..a706399a1 100644 --- a/packages/orm/src/client/plugin.ts +++ b/packages/orm/src/client/plugin.ts @@ -1,11 +1,11 @@ -import type { OperationNode, QueryId, QueryResult, RootOperationNode, UnknownRow } from 'kysely'; +import type { OperationNode, QueryId, QueryResult, RootOperationNode, Selectable, UnknownRow } from 'kysely'; import type { ZodType } from 'zod'; import type { ClientContract, ZModelFunction } from '.'; import type { GetModelFields, GetModels, NonRelationFields, SchemaDef } from '@zenstackhq/schema'; import type { MaybePromise } from '../utils/type-utils'; import type { MapModelFieldType } from './crud-types'; import type { AllCrudOperations, CoreCrudOperations } from './crud/operations/base'; -import type { DefaultModelResult } from './crud-types'; +import type { ToKyselySchema } from './query-builder'; type AllowedExtQueryArgKeys = CoreCrudOperations | '$create' | '$read' | '$update' | '$delete' | '$all'; @@ -388,12 +388,32 @@ export type PluginAfterEntityMutationArgs = MutationHo client: ClientContract; }; +/** + * The shape of an entity passed to per-model entity mutation hooks. Entities are + * loaded directly from the database table (the same way as `$qb` query results), + * so they contain only physical columns (no relations, no computed fields, and no + * fields inherited from a delegate base model), and values are in the form returned + * by the database driver, not post-processed by the ORM. + * + * For models using delegate inheritance (`@@delegate`), a mutation fires one event + * per physical table written, keyed by that table's model. E.g., creating a sub-model + * entity fires a base-model event followed by a sub-model event; updating only + * inherited fields fires only a base-model event; deleting a sub-model entity fires + * only a base-model event (the sub-model row is removed by cascade). Base-model + * entities carry the discriminator field, which can be used to identify the concrete + * sub-model. To observe all mutations of a sub-model, register hooks on its base model + * (or use `$all`). + */ +export type MutationHookEntity> = Selectable< + ToKyselySchema[Model] +>; + export type PluginModelBeforeEntityMutationArgs> = Omit< MutationHooksArgs, 'model' > & { model: Model; - loadBeforeMutationEntities(): Promise[] | undefined>; + loadBeforeMutationEntities(): Promise[] | undefined>; client: ClientContract; }; @@ -402,8 +422,8 @@ export type PluginModelAfterEntityMutationArgs & { model: Model; - loadAfterMutationEntities(): Promise[] | undefined>; - beforeMutationEntities?: DefaultModelResult[]; + loadAfterMutationEntities(): Promise[] | undefined>; + beforeMutationEntities?: MutationHookEntity[]; client: ClientContract; }; diff --git a/tests/e2e/orm/plugin-infra/entity-mutation-hooks.test.ts b/tests/e2e/orm/plugin-infra/entity-mutation-hooks.test.ts index 0bc5514e3..a6bc7f297 100644 --- a/tests/e2e/orm/plugin-infra/entity-mutation-hooks.test.ts +++ b/tests/e2e/orm/plugin-infra/entity-mutation-hooks.test.ts @@ -1,8 +1,11 @@ -import { type ClientContract } from '@zenstackhq/orm'; +import { type ClientContract, type MutationHookEntity } from '@zenstackhq/orm'; import { createTestClient } from '@zenstackhq/testtools'; import { DeleteQueryNode, InsertQueryNode, UpdateQueryNode } from 'kysely'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { sql } from 'kysely'; +import { afterEach, beforeEach, describe, expect, expectTypeOf, it, vi } from 'vitest'; import { schema } from '../schemas/basic'; +import { schema as delegateSchemaTyped } from '../schemas/delegate/schema'; +import { schema as typingSchema } from '../schemas/typing/schema'; describe('Entity mutation hooks tests', () => { let _client: ClientContract; @@ -869,12 +872,266 @@ describe('Entity mutation hooks tests', () => { data: { email: 'u1@test.com' }, }); }); + + it('after-mutation entities match $qb rows', async () => { + let postEntity: any; + let userEntity: any; + + const client = _client.$use({ + id: 'test', + onEntityMutation: { + Post: { + async afterEntityMutation(args) { + const entities = await args.loadAfterMutationEntities(); + postEntity = entities?.[0]; + // entity type must be identical to a `$qb` selectAll row + expectTypeOf(entities!).items.toEqualTypeOf>>(); + }, + }, + User: { + async afterEntityMutation(args) { + const entities = await args.loadAfterMutationEntities(); + userEntity = entities?.[0]; + }, + }, + }, + }); + + const qbPostQuery = () => client.$qb.selectFrom('Post').selectAll().executeTakeFirstOrThrow(); + + const user = await client.user.create({ + data: { + email: 'u1@test.com', + meta: { foo: 'bar' }, + posts: { create: { title: 'P1', published: true } }, + }, + }); + + const qbPost = await client.$qb + .selectFrom('Post') + .selectAll() + .where('authorId', '=', user.id) + .executeTakeFirstOrThrow(); + const qbUser = await client.$qb + .selectFrom('User') + .selectAll() + .where('id', '=', user.id) + .executeTakeFirstOrThrow(); + + expect(postEntity).toEqual(qbPost); + expect(userEntity).toEqual(qbUser); + }); + + it('before-mutation entities match $qb rows', async () => { + let beforeEntity: any; + let beforeEntityInAfterHook: any; + + const client = _client.$use({ + id: 'test', + onEntityMutation: { + Post: { + async beforeEntityMutation(args) { + beforeEntity = (await args.loadBeforeMutationEntities())?.[0]; + }, + afterEntityMutation(args) { + beforeEntityInAfterHook = args.beforeMutationEntities?.[0]; + }, + }, + }, + }); + + const user = await client.user.create({ data: { email: 'u1@test.com' } }); + const post = await client.post.create({ data: { title: 'P1', published: true, authorId: user.id } }); + const qbPostBefore = await client.$qb + .selectFrom('Post') + .selectAll() + .where('id', '=', post.id) + .executeTakeFirstOrThrow(); + + await client.post.update({ where: { id: post.id }, data: { title: 'P2' } }); + + expect(beforeEntity).toEqual(qbPostBefore); + expect(beforeEntityInAfterHook).toEqual(qbPostBefore); + }); + + it('supports legacy-shaped onEntityMutation (hooks directly under onEntityMutation) with a warning', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + try { + const calls: string[] = []; + let thisArg: unknown; + const legacy = { + id: 'legacy', + beforeEntityMutation(args: any) { + calls.push(`before:${args.model}:${args.action}`); + }, + onEntityMutation: { + beforeEntityMutation(this: any, args: any) { + thisArg = this; + calls.push(`before:${args.model}:${args.action}`); + }, + afterEntityMutation(args: any) { + calls.push(`after:${args.model}:${args.action}`); + }, + runAfterMutationWithinTransaction: true, + }, + }; + + const client = _client.$use(legacy as any); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0]![0]).toContain('deprecated'); + + await client.user.create({ data: { email: 'u1@test.com' } }); + expect(calls).toEqual(['before:User:create', 'after:User:create']); + // `this` is the plugin object + expect(thisArg).toBe(legacy); + + // derived clients (e.g., transactions) should not warn again + await client.$transaction(async (tx) => { + await tx.user.create({ data: { email: 'u2@test.com' } }); + }); + expect(warn).toHaveBeenCalledTimes(1); + expect(calls).toContain('after:User:create'); + } finally { + warn.mockRestore(); + } + }); + + it('preserves prototype-defined hooks of class-based plugins using the legacy shape', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + try { + const calls: string[] = []; + + class LegacyPlugin { + id = 'class-legacy'; + onEntityMutation = { + afterEntityMutation: (args: any) => { + calls.push(`mutation:${args.action}`); + }, + }; + // the following hooks live on the prototype, not as own properties + onQuery(ctx: any) { + calls.push(`query:${ctx.operation}`); + return ctx.proceed(ctx.args); + } + onKyselyQuery(ctx: any) { + calls.push('kysely'); + return ctx.proceed(ctx.query); + } + } + + const client = _client.$use(new LegacyPlugin() as any); + expect(warn).toHaveBeenCalledTimes(1); + + await client.user.create({ data: { email: 'u1@test.com' } }); + expect(calls).toContain('mutation:create'); + expect(calls).toContain('query:create'); + expect(calls).toContain('kysely'); + } finally { + warn.mockRestore(); + } + }); + + it('explicit $all takes precedence over legacy keys when both are present', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + try { + const calls: string[] = []; + const client = _client.$use({ + id: 'mixed', + onEntityMutation: { + afterEntityMutation() { + calls.push('legacy'); + }, + $all: { + afterEntityMutation() { + calls.push('$all'); + }, + }, + }, + } as any); + + await client.user.create({ data: { email: 'u1@test.com' } }); + expect(calls).toEqual(['$all']); + } finally { + warn.mockRestore(); + } + }); + + it('warns on onEntityMutation keys that are not $all or a known model name', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + try { + const client = _client.$use({ + id: 'typo', + onEntityMutation: { + user: { + afterEntityMutation() {}, + }, + }, + } as any); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0]![0]).toContain('"user"'); + + // no repeated warnings for derived clients + await client.$transaction(async (tx) => { + await tx.user.create({ data: { email: 'u1@test.com' } }); + }); + expect(warn).toHaveBeenCalledTimes(1); + } finally { + warn.mockRestore(); + } + }); }); -describe('Entity mutation hooks - delegate model interaction', () => { - it('update on child model succeeds with afterEntityMutation plugin', async () => { - const client = await createTestClient( +describe('Entity mutation hooks - computed fields', () => { + it('hook entities exclude computed fields in both type and value', async () => { + const db = await createTestClient( ` +model User { + id Int @id @default(autoincrement()) + firstName String + lastName String + fullName String @computed +} +`, + { + computedFields: { + User: { + fullName: (eb: any) => eb.fn('concat', [eb.ref('firstName'), sql.lit(' '), eb.ref('lastName')]), + }, + }, + } as any, + ); + + let entity: any; + const client = db.$use({ + id: 'test', + onEntityMutation: { + User: { + async afterEntityMutation(args: any) { + entity = (await args.loadAfterMutationEntities())?.[0]; + }, + }, + }, + }); + + await client.user.create({ data: { firstName: 'Alex', lastName: 'Smith' } }); + + // type-level: computed field `postCount` is not part of the hook entity type + type UserEntity = MutationHookEntity; + expectTypeOf().toHaveProperty('email'); + expectTypeOf().not.toHaveProperty('postCount'); + // relations are not part of the entity either + expectTypeOf().not.toHaveProperty('posts'); + + expect(entity).toBeDefined(); + expect(entity).toMatchObject({ firstName: 'Alex', lastName: 'Smith' }); + expect(entity).not.toHaveProperty('fullName'); + + await db.$disconnect(); + }); +}); + +describe('Entity mutation hooks - delegate model interaction', () => { + const delegateSchema = ` model Base { id Int @id @default(autoincrement()) orgId Int @@ -885,8 +1142,162 @@ model Base { model Child extends Base { childField Int } -`, - ); +`; + + it('hook entity types match per-table rows (no inherited or descendant fields)', () => { + // Asset (root base): only its own columns + type AssetEntity = MutationHookEntity; + expectTypeOf().toHaveProperty('assetType'); + expectTypeOf().toHaveProperty('ownerId'); + expectTypeOf().not.toHaveProperty('duration'); + expectTypeOf().not.toHaveProperty('rating'); + expectTypeOf().not.toHaveProperty('owner'); + + // Video (sub-model and delegate base): own columns, no inherited ones + type VideoEntity = MutationHookEntity; + expectTypeOf().toHaveProperty('id'); + expectTypeOf().toHaveProperty('duration'); + expectTypeOf().toHaveProperty('videoType'); + expectTypeOf().not.toHaveProperty('assetType'); + expectTypeOf().not.toHaveProperty('viewCount'); + expectTypeOf().not.toHaveProperty('rating'); + + // RatedVideo (chained sub-model): own columns only + type RatedVideoEntity = MutationHookEntity; + expectTypeOf().toHaveProperty('id'); + expectTypeOf().toHaveProperty('rating'); + expectTypeOf().toHaveProperty('userId'); + expectTypeOf().not.toHaveProperty('duration'); + expectTypeOf().not.toHaveProperty('assetType'); + }); + + it('Child hook entities contain only child-table columns; Base hook entities only base-table columns', async () => { + const db = await createTestClient(delegateSchema); + let childEntity: any; + let baseEntity: any; + + const client = db.$use({ + id: 'test', + onEntityMutation: { + Child: { + async afterEntityMutation(args: any) { + childEntity = (await args.loadAfterMutationEntities())?.[0]; + }, + }, + Base: { + async afterEntityMutation(args: any) { + baseEntity = (await args.loadAfterMutationEntities())?.[0]; + }, + }, + }, + }); + + const created = await client.child.create({ data: { orgId: 1, childField: 10 } }); + + const qbChild = await db.$qb + .selectFrom('Child') + .selectAll() + .where('id', '=', created.id) + .executeTakeFirstOrThrow(); + const qbBase = await db.$qb + .selectFrom('Base') + .selectAll() + .where('id', '=', created.id) + .executeTakeFirstOrThrow(); + + expect(childEntity).toEqual(qbChild); + expect(childEntity).toEqual({ id: created.id, childField: 10 }); + + expect(baseEntity).toEqual(qbBase); + expect(baseEntity).toEqual({ id: created.id, orgId: 1, type: 'Child' }); + + await db.$disconnect(); + }); + + it("fires per-model hooks once per table written, keyed by that table's model", async () => { + const db = await createTestClient(delegateSchema); + const events: string[] = []; + + const client = db.$use({ + id: 'test', + onEntityMutation: { + Base: { + afterEntityMutation(args: any) { + events.push(`Base:${args.action}`); + }, + }, + Child: { + afterEntityMutation(args: any) { + events.push(`Child:${args.action}`); + }, + }, + }, + }); + + // create writes both tables + const created = await client.child.create({ data: { orgId: 1, childField: 10 } }); + expect(events).toEqual(['Base:create', 'Child:create']); + + // updating only a sub-model field writes only the sub-model table + events.length = 0; + await client.child.update({ where: { id: created.id }, data: { childField: 11 } }); + expect(events).toEqual(['Child:update']); + + // updating only an inherited field writes only the base table, so only + // the base model's hook fires + events.length = 0; + await client.child.update({ where: { id: created.id }, data: { orgId: 2 } }); + expect(events).toEqual(['Base:update']); + + // updating both writes both tables + events.length = 0; + await client.child.update({ where: { id: created.id }, data: { orgId: 3, childField: 12 } }); + expect(events).toEqual(['Base:update', 'Child:update']); + + // deleting a sub-model entity deletes the base row (sub-model row is cascaded), + // so only the base model's hook fires + events.length = 0; + await client.child.delete({ where: { id: created.id } }); + expect(events).toEqual(['Base:delete']); + + await db.$disconnect(); + }); + + it('base model hook entities carry the discriminator so the concrete sub-model can be identified', async () => { + const db = await createTestClient(delegateSchema); + const types: string[] = []; + + const client = db.$use({ + id: 'test', + onEntityMutation: { + Base: { + async beforeEntityMutation(args: any) { + const entities = await args.loadBeforeMutationEntities(); + if (entities) { + types.push(...entities.map((e: any) => `${args.action}:${e.type}`)); + } + }, + async afterEntityMutation(args: any) { + const entities = await args.loadAfterMutationEntities(); + if (entities) { + types.push(...entities.map((e: any) => `${args.action}:${e.type}`)); + } + }, + }, + }, + }); + + const created = await client.child.create({ data: { orgId: 1, childField: 10 } }); + await client.child.update({ where: { id: created.id }, data: { orgId: 2 } }); + await client.child.delete({ where: { id: created.id } }); + + expect(types).toEqual(['create:Child', 'update:Child', 'update:Child', 'delete:Child']); + + await db.$disconnect(); + }); + + it('update on child model succeeds with afterEntityMutation plugin', async () => { + const client = await createTestClient(delegateSchema); const withPlugin = client.$use({ id: 'test', From 8cbe1b0f50772de829839c19c13a8b2e116a9452 Mon Sep 17 00:00:00 2001 From: Erik Dakoda Date: Wed, 14 Oct 2026 14:05:07 -0400 Subject: [PATCH 06/18] fix(clients): keep the ORM runtime out of client bundles (#2871) Co-authored-by: Claude Opus 5.5 --- packages/clients/client-helpers/src/types.ts | 2 +- .../tanstack-query/src/common/transaction.ts | 2 +- packages/orm/package.json | 10 ++ packages/orm/src/client/constants.ts | 125 ++++++++++++++++ .../orm/src/client/crud/operations/base.ts | 137 ++---------------- packages/orm/src/constants.ts | 12 ++ packages/orm/test/constants.test.ts | 22 +++ packages/orm/tsdown.config.ts | 1 + 8 files changed, 184 insertions(+), 127 deletions(-) create mode 100644 packages/orm/src/constants.ts create mode 100644 packages/orm/test/constants.test.ts diff --git a/packages/clients/client-helpers/src/types.ts b/packages/clients/client-helpers/src/types.ts index a4f4f68cc..247b8344a 100644 --- a/packages/clients/client-helpers/src/types.ts +++ b/packages/clients/client-helpers/src/types.ts @@ -1,4 +1,4 @@ -import { ExtQueryArgsMarker, ExtResultMarker, type QueryOptions } from '@zenstackhq/orm'; +import type { ExtQueryArgsMarker, ExtResultMarker, QueryOptions } from '@zenstackhq/orm'; import type { SchemaDef } from '@zenstackhq/schema'; /** diff --git a/packages/clients/tanstack-query/src/common/transaction.ts b/packages/clients/tanstack-query/src/common/transaction.ts index 396c5b3d4..31d09fb40 100644 --- a/packages/clients/tanstack-query/src/common/transaction.ts +++ b/packages/clients/tanstack-query/src/common/transaction.ts @@ -2,7 +2,7 @@ import type { Logger } from '@zenstackhq/client-helpers'; import { createInvalidator, TRANSACTION_ROUTE_PREFIX, type InvalidateFunc } from '@zenstackhq/client-helpers'; import { fetcher, marshal, type FetchFn } from '@zenstackhq/client-helpers/fetch'; import type { TransactionOperation } from '@zenstackhq/client-helpers'; -import { CoreReadOperations } from '@zenstackhq/orm'; +import { CoreReadOperations } from '@zenstackhq/orm/constants'; import type { SchemaDef } from '@zenstackhq/schema'; /** diff --git a/packages/orm/package.json b/packages/orm/package.json index 6f4021380..71e437f5c 100644 --- a/packages/orm/package.json +++ b/packages/orm/package.json @@ -106,6 +106,16 @@ "default": "./dist/common-types.cjs" } }, + "./constants": { + "import": { + "types": "./dist/constants.d.mts", + "default": "./dist/constants.mjs" + }, + "require": { + "types": "./dist/constants.d.cts", + "default": "./dist/constants.cjs" + } + }, "./package.json": { "import": "./package.json", "require": "./package.json" diff --git a/packages/orm/src/client/constants.ts b/packages/orm/src/client/constants.ts index eeb7e65b6..582e7df72 100644 --- a/packages/orm/src/client/constants.ts +++ b/packages/orm/src/client/constants.ts @@ -85,3 +85,128 @@ export const FILTER_PROPERTY_TO_KIND = { * Mapping of filter operators to their corresponding filter kind categories. */ export type FilterPropertyToKind = typeof FILTER_PROPERTY_TO_KIND; + +/** + * List of core CRUD operations. It excludes the 'orThrow' variants. + */ +export const CoreCrudOperations = [ + 'findMany', + 'findUnique', + 'findFirst', + 'create', + 'createMany', + 'createManyAndReturn', + 'update', + 'updateMany', + 'updateManyAndReturn', + 'upsert', + 'delete', + 'deleteMany', + 'count', + 'aggregate', + 'groupBy', + 'exists', +] as const; + +/** + * List of core CRUD operations. It excludes the 'orThrow' variants. + */ +export type CoreCrudOperations = (typeof CoreCrudOperations)[number]; + +/** + * List of core read operations. It excludes the 'orThrow' variants. + */ +export const CoreReadOperations = [ + 'findMany', + 'findUnique', + 'findFirst', + 'count', + 'aggregate', + 'groupBy', + 'exists', +] as const; + +/** + * List of core read operations. It excludes the 'orThrow' variants. + */ +export type CoreReadOperations = (typeof CoreReadOperations)[number]; + +/** + * List of core write operations. + */ +export const CoreWriteOperations = [ + 'create', + 'createMany', + 'createManyAndReturn', + 'update', + 'updateMany', + 'updateManyAndReturn', + 'upsert', + 'delete', + 'deleteMany', +] as const; + +/** + * List of core write operations. + */ +export type CoreWriteOperations = (typeof CoreWriteOperations)[number]; + +/** + * List of core create operations. + */ +export const CoreCreateOperations = ['create', 'createMany', 'createManyAndReturn', 'upsert'] as const; + +/** + * List of core create operations. + */ +export type CoreCreateOperations = (typeof CoreCreateOperations)[number]; + +/** + * List of core update operations. + */ +export const CoreUpdateOperations = ['update', 'updateMany', 'updateManyAndReturn', 'upsert'] as const; + +/** + * List of core update operations. + */ +export type CoreUpdateOperations = (typeof CoreUpdateOperations)[number]; + +/** + * List of core delete operations. + */ +export const CoreDeleteOperations = ['delete', 'deleteMany'] as const; + +/** + * List of core delete operations. + */ +export type CoreDeleteOperations = (typeof CoreDeleteOperations)[number]; + +/** + * List of all CRUD operations, including 'orThrow' variants. + */ +export const AllCrudOperations = [...CoreCrudOperations, 'findUniqueOrThrow', 'findFirstOrThrow'] as const; + +/** + * List of all CRUD operations, including 'orThrow' variants. + */ +export type AllCrudOperations = (typeof AllCrudOperations)[number]; + +/** + * List of all read operations, including 'orThrow' variants. + */ +export const AllReadOperations = [...CoreReadOperations, 'findUniqueOrThrow', 'findFirstOrThrow'] as const; + +/** + * List of all read operations, including 'orThrow' variants. + */ +export type AllReadOperations = (typeof AllReadOperations)[number]; + +/** + * List of all write operations - simply an alias of CoreWriteOperations. + */ +export const AllWriteOperations = CoreWriteOperations; + +/** + * List of all write operations - simply an alias of CoreWriteOperations. + */ +export type AllWriteOperations = CoreWriteOperations; diff --git a/packages/orm/src/client/crud/operations/base.ts b/packages/orm/src/client/crud/operations/base.ts index a9b9e572e..ce8b651e7 100644 --- a/packages/orm/src/client/crud/operations/base.ts +++ b/packages/orm/src/client/crud/operations/base.ts @@ -19,7 +19,7 @@ import { ulid } from 'ulid'; import * as uuid from 'uuid'; import type { AnyKysely } from '../../../utils/kysely-utils'; import { extractFields, fieldsToSelectObject, isEmptyObject } from '../../../utils/object-utils'; -import { NUMERIC_FIELD_TYPES } from '../../constants'; +import { NUMERIC_FIELD_TYPES, type CoreCrudOperations } from '../../constants'; import { TransactionIsolationLevel, type ClientContract, type CRUD } from '../../contract'; import type { FindArgs, SelectIncludeOmit, WhereInput } from '../../crud-types'; import { @@ -54,130 +54,17 @@ import { getCrudDialect } from '../dialects'; import type { BaseCrudDialect } from '../dialects/base-dialect'; import { InputValidator } from '../validator'; -/** - * List of core CRUD operations. It excludes the 'orThrow' variants. - */ -export const CoreCrudOperations = [ - 'findMany', - 'findUnique', - 'findFirst', - 'create', - 'createMany', - 'createManyAndReturn', - 'update', - 'updateMany', - 'updateManyAndReturn', - 'upsert', - 'delete', - 'deleteMany', - 'count', - 'aggregate', - 'groupBy', - 'exists', -] as const; - -/** - * List of core CRUD operations. It excludes the 'orThrow' variants. - */ -export type CoreCrudOperations = (typeof CoreCrudOperations)[number]; - -/** - * List of core read operations. It excludes the 'orThrow' variants. - */ -export const CoreReadOperations = [ - 'findMany', - 'findUnique', - 'findFirst', - 'count', - 'aggregate', - 'groupBy', - 'exists', -] as const; - -/** - * List of core read operations. It excludes the 'orThrow' variants. - */ -export type CoreReadOperations = (typeof CoreReadOperations)[number]; - -/** - * List of core write operations. - */ -export const CoreWriteOperations = [ - 'create', - 'createMany', - 'createManyAndReturn', - 'update', - 'updateMany', - 'updateManyAndReturn', - 'upsert', - 'delete', - 'deleteMany', -] as const; - -/** - * List of core write operations. - */ -export type CoreWriteOperations = (typeof CoreWriteOperations)[number]; - -/** - * List of core create operations. - */ -export const CoreCreateOperations = ['create', 'createMany', 'createManyAndReturn', 'upsert'] as const; - -/** - * List of core create operations. - */ -export type CoreCreateOperations = (typeof CoreCreateOperations)[number]; - -/** - * List of core update operations. - */ -export const CoreUpdateOperations = ['update', 'updateMany', 'updateManyAndReturn', 'upsert'] as const; - -/** - * List of core update operations. - */ -export type CoreUpdateOperations = (typeof CoreUpdateOperations)[number]; - -/** - * List of core delete operations. - */ -export const CoreDeleteOperations = ['delete', 'deleteMany'] as const; - -/** - * List of core delete operations. - */ -export type CoreDeleteOperations = (typeof CoreDeleteOperations)[number]; - -/** - * List of all CRUD operations, including 'orThrow' variants. - */ -export const AllCrudOperations = [...CoreCrudOperations, 'findUniqueOrThrow', 'findFirstOrThrow'] as const; - -/** - * List of all CRUD operations, including 'orThrow' variants. - */ -export type AllCrudOperations = (typeof AllCrudOperations)[number]; - -/** - * List of all read operations, including 'orThrow' variants. - */ -export const AllReadOperations = [...CoreReadOperations, 'findUniqueOrThrow', 'findFirstOrThrow'] as const; - -/** - * List of all read operations, including 'orThrow' variants. - */ -export type AllReadOperations = (typeof AllReadOperations)[number]; - -/** - * List of all write operations - simply an alias of CoreWriteOperations. - */ -export const AllWriteOperations = CoreWriteOperations; - -/** - * List of all write operations - simply an alias of CoreWriteOperations. - */ -export type AllWriteOperations = CoreWriteOperations; +export { + AllCrudOperations, + AllReadOperations, + AllWriteOperations, + CoreCreateOperations, + CoreCrudOperations, + CoreDeleteOperations, + CoreReadOperations, + CoreUpdateOperations, + CoreWriteOperations, +} from '../../constants'; // context for nested relation operations export type FromRelationContext = { diff --git a/packages/orm/src/constants.ts b/packages/orm/src/constants.ts new file mode 100644 index 000000000..8164f7a0a --- /dev/null +++ b/packages/orm/src/constants.ts @@ -0,0 +1,12 @@ +// Dependency-free constants, importable without pulling in the ORM runtime (e.g. from browser clients). +export { + AllCrudOperations, + AllReadOperations, + AllWriteOperations, + CoreCreateOperations, + CoreCrudOperations, + CoreDeleteOperations, + CoreReadOperations, + CoreUpdateOperations, + CoreWriteOperations, +} from './client/constants'; diff --git a/packages/orm/test/constants.test.ts b/packages/orm/test/constants.test.ts new file mode 100644 index 000000000..0270c23d4 --- /dev/null +++ b/packages/orm/test/constants.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest'; +import * as constants from '../src/constants'; +import * as orm from '../src/index'; + +describe('constants entry', () => { + it('exports the operation lists of the main entry', () => { + expect(Object.keys(constants).sort()).toEqual([ + 'AllCrudOperations', + 'AllReadOperations', + 'AllWriteOperations', + 'CoreCreateOperations', + 'CoreCrudOperations', + 'CoreDeleteOperations', + 'CoreReadOperations', + 'CoreUpdateOperations', + 'CoreWriteOperations', + ]); + for (const [name, value] of Object.entries(constants)) { + expect(orm[name as keyof typeof orm]).toBe(value); + } + }); +}); diff --git a/packages/orm/tsdown.config.ts b/packages/orm/tsdown.config.ts index 39118a802..af397a1cf 100644 --- a/packages/orm/tsdown.config.ts +++ b/packages/orm/tsdown.config.ts @@ -6,6 +6,7 @@ export default createConfig({ schema: 'src/schema.ts', helpers: 'src/helpers.ts', 'common-types': 'src/common-types.ts', + constants: 'src/constants.ts', 'dialects/sqlite': 'src/dialects/sqlite.ts', 'dialects/postgres': 'src/dialects/postgres.ts', 'dialects/mysql': 'src/dialects/mysql.ts', From 2c81cbd89f07de0dc64250bdcc811926f4ba12f7 Mon Sep 17 00:00:00 2001 From: Erik Dakoda Date: Sun, 18 Oct 2026 21:59:49 -0400 Subject: [PATCH 07/18] fix(orm): stop delegate cascade delete recursion and simulate sub model cascades (#2872) Co-authored-by: Claude Opus 5.5 --- .../orm/src/client/crud/operations/base.ts | 234 +++++++++++++--- .../orm/src/client/crud/operations/delete.ts | 16 +- .../delegate-cascade-delete.test.ts | 262 ++++++++++++++++++ 3 files changed, 468 insertions(+), 44 deletions(-) create mode 100644 tests/e2e/orm/client-api/delegate-cascade-delete.test.ts diff --git a/packages/orm/src/client/crud/operations/base.ts b/packages/orm/src/client/crud/operations/base.ts index ce8b651e7..73ffd4a8d 100644 --- a/packages/orm/src/client/crud/operations/base.ts +++ b/packages/orm/src/client/crud/operations/base.ts @@ -36,6 +36,7 @@ import { ensureArray, extractIdFields, flattenCompoundUniqueFilters, + getDelegateDescendantModels, getDelegateDiscriminatorValue, getDiscriminatorField, getField, @@ -66,6 +67,16 @@ export { CoreWriteOperations, } from '../../constants'; +/** + * Maximum number of rows identified by id in a single statement during cascade delete simulation. + */ +const DELETE_ID_BATCH_SIZE = 1000; + +/** + * Same as `DELETE_ID_BATCH_SIZE`, for compound ids, which are matched with one `OR` branch per row. + */ +const DELETE_COMPOUND_ID_BATCH_SIZE = 100; + // context for nested relation operations export type FromRelationContext = { // the model where the relation field is defined @@ -2263,8 +2274,10 @@ export abstract class BaseOperationHandler { limit?: number, filterModel?: string, fieldsToReturn?: readonly string[], + visited?: Set, ): Promise> { filterModel ??= model; + visited ??= new Set(); const modelDef = this.requireModel(model); @@ -2273,9 +2286,109 @@ export abstract class BaseOperationHandler { throw createNotSupportedError('Deleting with a limit is not supported for polymorphic models'); } // just delete base and it'll cascade back to this model - return this.processBaseModelDelete(kysely, modelDef.baseModel, where, limit, filterModel); + return this.processBaseModelDelete(kysely, modelDef.baseModel, where, limit, filterModel, visited); + } + + // if the model being deleted has a relation to a model that extends a delegate model, and if that + // relation is set to trigger a cascade delete from this model, the deletion will not automatically + // clean up the base hierarchy of the relation side (because polymorphic model's cascade deletion + // works downward not upward). We need to take care of the base deletions manually here. + const cascadeRelations = this.getDelegateCascadeRelations(model, filterModel); + if (cascadeRelations.length === 0) { + return this.executeDelete(kysely, model, where, limit, filterModel, fieldsToReturn); + } + + if (!modelDef.isDelegate) { + if (limit !== undefined) { + throw createNotSupportedError('Deleting with a limit is not supported for polymorphic models'); + } + for (const { fieldDef } of cascadeRelations) { + await this.delete( + kysely, + fieldDef.type, + { [fieldDef.relation!.opposite!]: where }, + undefined, + undefined, + undefined, + visited, + ); + } + return this.executeDelete(kysely, model, where, limit, filterModel, fieldsToReturn); + } + + // for a delegate model, resolve the rows being deleted first: `where` can filter on a sub model or on + // related rows, which the cascades below may delete + const discriminator = getDiscriminatorField(this.schema, model)!; + const deletedRows = await this.read(kysely, filterModel, { + where, + select: { ...this.makeIdSelect(filterModel), [discriminator]: true }, + ...(limit !== undefined ? { take: limit } : {}), + }); + + // rows already being deleted in this chain are skipped, otherwise cyclic data recurses forever + deletedRows.forEach((row) => visited.add(this.makeVisitedKey(model, row))); + + for (const { fieldDef, discriminatorValue } of cascadeRelations) { + const parentRows = + discriminatorValue === undefined + ? deletedRows + : deletedRows.filter((row) => row[discriminator] === discriminatorValue); + for (const batch of this.batchRows(model, parentRows)) { + const childModel = fieldDef.type; + const childRows = await this.read(kysely, childModel, { + where: this.makeChildFilter(model, fieldDef, batch) as any, + select: this.makeIdSelect(childModel), + }); + const pendingRows = childRows.filter((row) => { + const key = this.makeVisitedKey(childModel, row); + if (visited.has(key)) { + return false; + } + visited.add(key); + return true; + }); + + // the deletion will propagate upward to the base model chain + for (const childBatch of this.batchRows(childModel, pendingRows)) { + await this.delete( + kysely, + childModel, + this.makeIdsFilter(childModel, childBatch), + undefined, + undefined, + undefined, + visited, + ); + } + } } + let numAffectedRows = 0n; + const rows: unknown[] = []; + for (const batch of this.batchRows(model, deletedRows)) { + const result = await this.executeDelete( + kysely, + model, + this.makeIdsFilter(model, batch), + undefined, + model, + fieldsToReturn, + ); + numAffectedRows += result.numAffectedRows ?? 0n; + rows.push(...result.rows); + } + return { numAffectedRows, rows }; + } + + private async executeDelete( + kysely: AnyKysely, + model: string, + where: any, + limit: number | undefined, + filterModel: string, + fieldsToReturn: readonly string[] | undefined, + ): Promise> { + const modelDef = this.requireModel(model); fieldsToReturn = fieldsToReturn ?? requireIdFields(this.schema, model); let needIdFilter = false; @@ -2315,13 +2428,6 @@ export abstract class BaseOperationHandler { ) : () => this.dialect.buildFilter(model, model, where); - // if the model being deleted has a relation to a model that extends a delegate model, and if that - // relation is set to trigger a cascade delete from this model, the deletion will not automatically - // clean up the base hierarchy of the relation side (because polymorphic model's cascade deletion - // works downward not upward). We need to take care of the base deletions manually here. - - await this.processDelegateRelationDelete(kysely, modelDef, where, limit); - const query = kysely .deleteFrom(model) .where(deleteFilter) @@ -2332,32 +2438,97 @@ export abstract class BaseOperationHandler { return this.executeQuery(kysely, query, 'delete'); } - private async processDelegateRelationDelete( - kysely: ToKysely, - modelDef: ModelDef, - where: any, - limit: number | undefined, - ) { - for (const fieldDef of Object.values(modelDef.fields)) { - if (fieldDef.relation && fieldDef.relation.opposite) { - const oppositeModelDef = this.requireModel(fieldDef.type); - const oppositeRelation = this.requireField(fieldDef.type, fieldDef.relation.opposite); - if (oppositeModelDef.baseModel && oppositeRelation.relation?.onDelete === 'Cascade') { - if (limit !== undefined) { - throw createNotSupportedError('Deleting with a limit is not supported for polymorphic models'); + /** + * Gets the relations whose deletion must be simulated when deleting from the given model: relations + * pointing to a model that extends a delegate model, with cascade delete on the opposite side. For a + * delegate model, this includes relations declared on its sub models, because deleting a base row + * cascades to the sub model rows in the database. `discriminatorValue` is set for a sub model relation: + * it is the base model's discriminator value of the rows the relation can belong to. + */ + protected getDelegateCascadeRelations(model: string, filterModel = model) { + const modelDef = this.requireModel(model); + // when deleting through a sub model, only relations its rows can have apply + const filterDiscriminatorValue = + filterModel === model ? undefined : this.getDiscriminatorValueBelow(model, filterModel); + const candidates = Object.values(modelDef.fields).map((fieldDef) => ({ + fieldDef, + discriminatorValue: undefined as unknown, + })); + if (modelDef.isDelegate) { + for (const subModel of getDelegateDescendantModels(this.schema, model)) { + // the sub model's ancestor directly below `model` gives the discriminator value its rows carry + const discriminatorValue = this.getDiscriminatorValueBelow(model, subModel.name); + if (filterDiscriminatorValue !== undefined && discriminatorValue !== filterDiscriminatorValue) { + continue; + } + for (const fieldDef of Object.values(subModel.fields)) { + if (!fieldDef.originModel) { + candidates.push({ fieldDef, discriminatorValue }); } - // the deletion will propagate upward to the base model chain - await this.delete( - kysely, - fieldDef.type as GetModels, - { - [fieldDef.relation.opposite]: where, - }, - undefined, - ); } } } + + return candidates.filter(({ fieldDef }) => { + if (!fieldDef.relation?.opposite) { + return false; + } + const oppositeModelDef = this.requireModel(fieldDef.type); + const oppositeRelation = this.requireField(fieldDef.type, fieldDef.relation.opposite); + return !!oppositeModelDef.baseModel && oppositeRelation.relation?.onDelete === 'Cascade'; + }); + } + + // gets the discriminator value that `model` stores for rows of `subModel`, which is the value of + // `subModel`'s ancestor directly below `model` + private getDiscriminatorValueBelow(model: string, subModel: string) { + let ancestor = this.requireModel(subModel); + while (ancestor.baseModel !== model) { + ancestor = this.requireModel(ancestor.baseModel!); + } + return getDelegateDiscriminatorValue(this.schema, ancestor.name); + } + + private makeVisitedKey(model: string, row: any) { + // key by the root of the delegate hierarchy, since a row can be reached through any model in it + let root = model; + for (let base = this.requireModel(root).baseModel; base; base = this.requireModel(root).baseModel) { + root = base; + } + return JSON.stringify([root, ...this.requireModel(model).idFields.map((f) => row[f])]); + } + + // filters the children of the given parent rows, by foreign key when it references the parent's id + private makeChildFilter(model: string, fieldDef: FieldDef, parentRows: any[]) { + const opposite = fieldDef.relation!.opposite!; + const { fields, references } = this.requireField(fieldDef.type, opposite).relation ?? {}; + const idFields = this.requireModel(model).idFields; + if (!fields || !references || !references.every((f) => idFields.includes(f))) { + return { [opposite]: this.makeIdsFilter(model, parentRows) }; + } + if (fields.length === 1) { + return { [fields[0]!]: { in: parentRows.map((row) => row[references[0]!]) } }; + } + return { + OR: parentRows.map((row) => Object.fromEntries(fields.map((f, i) => [f, row[references[i]!]]))), + }; + } + + private makeIdsFilter(model: string, rows: any[]) { + const idFields = this.requireModel(model).idFields; + if (idFields.length === 1) { + return { [idFields[0]!]: { in: rows.map((row) => row[idFields[0]!]) } }; + } + return { OR: rows.map((row) => Object.fromEntries(idFields.map((f) => [f, row[f]]))) }; + } + + private *batchRows(model: string, rows: T[]) { + // keeps id lists within the databases' limits on query parameters and expression depth + const size = + this.requireModel(model).idFields.length === 1 ? DELETE_ID_BATCH_SIZE : DELETE_COMPOUND_ID_BATCH_SIZE; + for (let i = 0; i < rows.length; i += size) { + yield rows.slice(i, i + size); + } } private async processBaseModelDelete( @@ -2366,8 +2537,9 @@ export abstract class BaseOperationHandler { where: any, limit: number | undefined, filterModel: string, + visited: Set, ) { - return this.delete(kysely, model, where, limit, filterModel); + return this.delete(kysely, model, where, limit, filterModel, undefined, visited); } protected makeIdSelect(model: string) { diff --git a/packages/orm/src/client/crud/operations/delete.ts b/packages/orm/src/client/crud/operations/delete.ts index 03addadc3..2a1f33bde 100644 --- a/packages/orm/src/client/crud/operations/delete.ts +++ b/packages/orm/src/client/crud/operations/delete.ts @@ -68,18 +68,8 @@ export class DeleteOperationHandler extends BaseOperat return true; } - // Check if any relation points to a delegate sub-model with cascade delete, - // which would trigger processDelegateRelationDelete in BaseOperationHandler.delete() - for (const fieldDef of Object.values(modelDef.fields)) { - if (fieldDef.relation?.opposite) { - const oppositeModelDef = this.requireModel(fieldDef.type); - const oppositeRelation = this.requireField(fieldDef.type, fieldDef.relation.opposite); - if (oppositeModelDef.baseModel && oppositeRelation.relation?.onDelete === 'Cascade') { - return true; - } - } - } - - return false; + // Check if any relation points to a delegate sub-model with cascade delete, which + // BaseOperationHandler.delete() simulates with nested deletes + return this.getDelegateCascadeRelations(this.model).length > 0; } } diff --git a/tests/e2e/orm/client-api/delegate-cascade-delete.test.ts b/tests/e2e/orm/client-api/delegate-cascade-delete.test.ts new file mode 100644 index 000000000..2c50f3a41 --- /dev/null +++ b/tests/e2e/orm/client-api/delegate-cascade-delete.test.ts @@ -0,0 +1,262 @@ +import { createPolicyTestClient, createTestClient } from '@zenstackhq/testtools'; +import { afterEach, describe, expect, it } from 'vitest'; + +describe('Delegate cascade delete', () => { + let db: any; + + afterEach(async () => { + await db?.$disconnect(); + }); + + async function itemIds() { + const rows = await db.$qb.selectFrom('Item').select('id').execute(); + return rows.map((r: { id: string }) => r.id).sort(); + } + + describe('with a base model relation that cascades back into the hierarchy', () => { + const schema = ` +model Item { + id String @id @default(cuid()) + itemKind String + notesAsSource Note[] @relation("NoteSource") + + @@delegate(itemKind) +} + +model Task extends Item {} + +model Note extends Item { + sourceId String? + source Item? @relation("NoteSource", fields: [sourceId], references: [id], onDelete: Cascade) + threadId String? + thread Thread? @relation(fields: [threadId], references: [id], onDelete: Cascade) +} + +model Thread { + id String @id @default(cuid()) + notes Note[] +} +`; + + it('deletes a row with no related rows', async () => { + db = await createTestClient(schema, { usePrismaPush: true }); + const task = await db.task.create({ data: {} }); + await db.task.delete({ where: { id: task.id } }); + expect(await itemIds()).toEqual([]); + }); + + it('cascades through the base model relation', async () => { + db = await createTestClient(schema, { usePrismaPush: true }); + const task = await db.task.create({ data: {} }); + await db.note.create({ data: { sourceId: task.id } }); + await db.task.delete({ where: { id: task.id } }); + expect(await itemIds()).toEqual([]); + }); + + it('cascades through a chain of rows', async () => { + db = await createTestClient(schema, { usePrismaPush: true }); + const task = await db.task.create({ data: {} }); + const parent = await db.note.create({ data: { sourceId: task.id } }); + await db.note.create({ data: { sourceId: parent.id } }); + await db.task.delete({ where: { id: task.id } }); + expect(await itemIds()).toEqual([]); + }); + + it('cascades from a non-delegate model', async () => { + db = await createTestClient(schema, { usePrismaPush: true }); + const thread = await db.thread.create({ data: {} }); + const note = await db.note.create({ data: { threadId: thread.id } }); + await db.note.create({ data: { sourceId: note.id } }); + await db.thread.delete({ where: { id: thread.id } }); + expect(await itemIds()).toEqual([]); + }); + + it('deletes cyclic data through the sub model', async () => { + db = await createTestClient(schema, { usePrismaPush: true }); + const first = await db.note.create({ data: {} }); + const second = await db.note.create({ data: { sourceId: first.id } }); + await db.note.update({ where: { id: first.id }, data: { sourceId: second.id } }); + await db.note.delete({ where: { id: first.id } }); + expect(await itemIds()).toEqual([]); + }); + + it('deletes cyclic data through the base model', async () => { + db = await createTestClient(schema, { usePrismaPush: true }); + const first = await db.note.create({ data: {} }); + const second = await db.note.create({ data: { sourceId: first.id } }); + await db.note.update({ where: { id: first.id }, data: { sourceId: second.id } }); + await expect(db.item.deleteMany({ where: { id: first.id } })).resolves.toEqual({ count: 1 }); + expect(await itemIds()).toEqual([]); + + const third = await db.note.create({ data: {} }); + const fourth = await db.note.create({ data: { sourceId: third.id } }); + await db.note.update({ where: { id: third.id }, data: { sourceId: fourth.id } }); + await db.item.delete({ where: { id: third.id } }); + expect(await itemIds()).toEqual([]); + }); + + it('deletes many rows filtered by a sub model field', async () => { + db = await createTestClient(schema, { usePrismaPush: true }); + const thread = await db.thread.create({ data: {} }); + const task = await db.task.create({ data: {} }); + const kept = await db.note.create({ data: { sourceId: task.id } }); + const deleted = await db.note.create({ data: { threadId: thread.id } }); + await db.note.create({ data: { sourceId: deleted.id } }); + await expect(db.note.deleteMany({ where: { threadId: thread.id } })).resolves.toEqual({ count: 1 }); + expect(await itemIds()).toEqual([kept.id, task.id].sort()); + }); + + it('deletes rows filtered by the related rows it cascades to', async () => { + db = await createTestClient(schema, { usePrismaPush: true }); + const task = await db.task.create({ data: {} }); + await db.note.create({ data: { sourceId: task.id } }); + const kept = await db.task.create({ data: {} }); + await expect(db.task.deleteMany({ where: { notesAsSource: { some: {} } } })).resolves.toEqual({ + count: 1, + }); + expect(await itemIds()).toEqual([kept.id]); + }); + }); + + describe('with a sub model relation', () => { + const schema = ` +model Item { + id String @id @default(cuid()) + itemKind String + + @@delegate(itemKind) +} + +model Task extends Item { + comments Comment[] +} + +model Comment extends Item { + taskId String? + task Task? @relation(fields: [taskId], references: [id], onDelete: Cascade) +} +`; + + it('cascades when deleting through the sub model', async () => { + db = await createTestClient(schema, { usePrismaPush: true }); + const task = await db.task.create({ data: {} }); + await db.comment.create({ data: { taskId: task.id } }); + await db.task.delete({ where: { id: task.id } }); + expect(await itemIds()).toEqual([]); + }); + + it('cascades when deleting through the base model', async () => { + db = await createTestClient(schema, { usePrismaPush: true }); + const task = await db.task.create({ data: {} }); + await db.comment.create({ data: { taskId: task.id } }); + await db.item.delete({ where: { id: task.id } }); + expect(await itemIds()).toEqual([]); + }); + + it('applies a limit when deleting through the base model', async () => { + db = await createTestClient(schema, { usePrismaPush: true }); + for (let i = 0; i < 2; i++) { + const task = await db.task.create({ data: {} }); + await db.comment.create({ data: { taskId: task.id } }); + } + await expect(db.item.deleteMany({ where: { itemKind: 'Task' }, limit: 1 })).resolves.toEqual({ + count: 1, + }); + const [remainingTask] = await db.task.findMany(); + const [remainingComment] = await db.comment.findMany(); + expect(remainingComment.taskId).toBe(remainingTask.id); + expect(await db.task.count()).toBe(1); + expect(await db.comment.count()).toBe(1); + expect(await itemIds()).toHaveLength(2); + }); + + it('deletes more related rows than fit in one statement', async () => { + db = await createTestClient(schema, { usePrismaPush: true }); + const task = await db.task.create({ data: {} }); + const ids = Array.from({ length: 33000 }, (_, i) => `c${String(i).padStart(5, '0')}`); + for (let i = 0; i < ids.length; i += 1000) { + const batch = ids.slice(i, i + 1000); + await db.$qb + .insertInto('Item') + .values(batch.map((id) => ({ id, itemKind: 'Comment' }))) + .execute(); + await db.$qb + .insertInto('Comment') + .values(batch.map((id) => ({ id, taskId: task.id }))) + .execute(); + } + await db.task.delete({ where: { id: task.id } }); + expect(await itemIds()).toEqual([]); + }, 120_000); + }); + + describe('with a compound id', () => { + const schema = ` +model Item { + id String @id @default(cuid()) + itemKind String + + @@delegate(itemKind) +} + +model Note extends Item { + groupA Int + groupB Int + group Group @relation(fields: [groupA, groupB], references: [a, b], onDelete: Cascade) +} + +model Group { + a Int + b Int + notes Note[] + + @@id([a, b]) +} +`; + + it('deletes many rows with compound ids', async () => { + db = await createTestClient(schema, { usePrismaPush: true }); + for (let b = 0; b < 600; b++) { + await db.group.create({ data: { a: 1, b, notes: { create: {} } } }); + } + await expect(db.group.deleteMany({ where: { a: 1 } })).resolves.toEqual({ count: 600 }); + expect(await itemIds()).toEqual([]); + }, 120_000); + }); + + describe('with access policies', () => { + const schema = ` +model Item { + id String @id @default(cuid()) + itemKind String + + @@delegate(itemKind) + @@allow('all', true) +} + +model Note extends Item { + threadId String? + thread Thread? @relation(fields: [threadId], references: [id], onDelete: Cascade) +} + +model Thread { + id String @id @default(cuid()) + hidden Boolean @default(false) + notes Note[] + + @@allow('create,delete', true) + @@allow('read', !hidden) +} +`; + + it('deletes rows that can be deleted but not read', async () => { + db = await createPolicyTestClient(schema, { usePrismaPush: true }); + const raw = db.$unuseAll(); + await raw.thread.create({ data: { id: 'visible', notes: { create: {} } } }); + await raw.thread.create({ data: { id: 'hidden', hidden: true, notes: { create: {} } } }); + await expect(db.thread.deleteMany({})).resolves.toEqual({ count: 2 }); + expect(await raw.thread.count()).toBe(0); + expect(await itemIds()).toEqual([]); + }); + }); +}); From efbdc884b742f1aec5ab640ddd144c3e33afd383 Mon Sep 17 00:00:00 2001 From: sanny-io <3054653+sanny-io@users.noreply.github.com> Date: Thu, 22 Oct 2026 10:50:49 -0700 Subject: [PATCH 08/18] feat: primitive custom types (#2852) Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 --- packages/cli/test/prisma-schema-gen.test.ts | 43 ++ packages/cli/test/ts-schema-gen.test.ts | 57 +++ packages/language/src/generated/ast.ts | 14 +- packages/language/src/generated/grammar.ts | 41 +- packages/language/src/utils.ts | 11 + .../attribute-application-validator.ts | 67 ++- .../src/validators/datamodel-validator.ts | 22 +- .../function-invocation-validator.ts | 3 +- .../src/validators/typedef-validator.ts | 45 +++ packages/language/src/zmodel-linker.ts | 45 ++- packages/language/src/zmodel-semantic.ts | 6 + packages/language/src/zmodel.langium | 6 +- .../test/custom-type-primitive.test.ts | 380 ++++++++++++++++++ .../language/test/function-invocation.test.ts | 22 + packages/orm/src/client/crud-types.ts | 125 ++++-- .../src/client/crud/dialects/base-dialect.ts | 28 +- .../orm/src/client/crud/dialects/mysql.ts | 22 +- .../src/client/crud/dialects/postgresql.ts | 26 +- .../orm/src/client/crud/dialects/sqlite.ts | 19 +- .../src/client/crud/operations/aggregate.ts | 6 +- .../orm/src/client/crud/operations/base.ts | 6 +- .../src/client/crud/operations/group-by.ts | 6 +- .../src/client/helpers/schema-db-pusher.ts | 12 +- packages/orm/src/client/query-utils.ts | 4 + packages/orm/src/client/zod/factory.ts | 47 ++- packages/schema/src/schema.ts | 8 + packages/sdk/src/model-utils.ts | 3 +- .../sdk/src/prisma/prisma-schema-generator.ts | 9 +- packages/sdk/src/ts-schema-generator.ts | 27 +- packages/zod/src/factory.ts | 82 ++-- packages/zod/src/types.ts | 9 +- packages/zod/test/factory.test.ts | 135 +++++++ packages/zod/test/schema/schema-lite.ts | 98 ++++- packages/zod/test/schema/schema.ts | 98 ++++- packages/zod/test/schema/schema.zmodel | 42 +- .../client-api/custom-type-primitive.test.ts | 172 ++++++++ tests/e2e/orm/client-api/procedures.test.ts | 53 +++ .../schemas/custom-type-primitive/schema.ts | 137 +++++++ .../custom-type-primitive/schema.zmodel | 35 ++ tests/e2e/orm/schemas/procedures/schema.ts | 32 ++ .../e2e/orm/schemas/procedures/schema.zmodel | 19 +- 41 files changed, 1829 insertions(+), 193 deletions(-) create mode 100644 packages/language/test/custom-type-primitive.test.ts create mode 100644 tests/e2e/orm/client-api/custom-type-primitive.test.ts create mode 100644 tests/e2e/orm/schemas/custom-type-primitive/schema.ts create mode 100644 tests/e2e/orm/schemas/custom-type-primitive/schema.zmodel diff --git a/packages/cli/test/prisma-schema-gen.test.ts b/packages/cli/test/prisma-schema-gen.test.ts index b5a8ce733..9d5838c6b 100644 --- a/packages/cli/test/prisma-schema-gen.test.ts +++ b/packages/cli/test/prisma-schema-gen.test.ts @@ -91,4 +91,47 @@ model User { expect(prismaSchemaText.includes('@ds.JsonB')).toBe(true); expect(prismaSchemaText.includes('@ds.ByteA')).toBe(true); }); + + it('renames primitive type defs to match the base type', async () => { + const model = await loadSchema(` +model User { + id String @id + name UserName +} + +type UserName with String { + this String +} + `); + + const generator = new PrismaSchemaGenerator(model); + const prismaSchemaText = await generator.generate(); + + expect(prismaSchemaText.includes('name UserName')).toBe(false); + expect(prismaSchemaText.includes('name String')).toBe(true); + }); + + it('renames primitive type defs to match the base type when used with lists', async () => { + const model = await loadSchema(` +datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') +} + +model User { + id String @id + name UserName[] +} + +type UserName with String { + this String +} + `); + + const generator = new PrismaSchemaGenerator(model); + const prismaSchemaText = await generator.generate(); + + expect(prismaSchemaText.includes('name UserName[]')).toBe(false); + expect(prismaSchemaText.includes('name String[]')).toBe(true); + }); }); diff --git a/packages/cli/test/ts-schema-gen.test.ts b/packages/cli/test/ts-schema-gen.test.ts index e84b1e054..14a94eedd 100644 --- a/packages/cli/test/ts-schema-gen.test.ts +++ b/packages/cli/test/ts-schema-gen.test.ts @@ -1015,4 +1015,61 @@ type Profile with Strict { }, }); }); + + it('supports primitive type defs', async () => { + const { schema } = await generateTsSchema(` + model User { + id Int @id + name UserName + } + + type UserName with String { + this String + } + `); + + expect(schema).toMatchObject({ + models: { + User: { + name: 'User', + fields: { + id: { + name: 'id', + type: 'Int', + id: true, + attributes: [ + { + name: '@id', + }, + ], + }, + name: { + name: 'name', + type: 'UserName', + }, + }, + idFields: ['id'], + uniqueFields: { + id: { + type: 'Int', + }, + }, + }, + }, + typeDefs: { + UserName: { + name: 'UserName', + base: 'String', + fields: { + this: { + name: 'this', + type: 'String', + }, + }, + }, + }, + authType: 'User', + plugins: {}, + }); + }); }); diff --git a/packages/language/src/generated/ast.ts b/packages/language/src/generated/ast.ts index c5ecbe0fd..5676b8889 100644 --- a/packages/language/src/generated/ast.ts +++ b/packages/language/src/generated/ast.ts @@ -435,6 +435,7 @@ export interface DataModel extends langium.AstNode { readonly $container: Model; readonly $type: 'DataModel'; attributes: Array; + base?: BuiltinType; baseModel?: langium.Reference; comments: Array; fields: Array; @@ -448,6 +449,7 @@ export interface DataModel extends langium.AstNode { export const DataModel = { $type: 'DataModel', attributes: 'attributes', + base: 'base', baseModel: 'baseModel', comments: 'comments', fields: 'fields', @@ -912,10 +914,10 @@ export function isReferenceTarget(item: unknown): item is ReferenceTarget { return reflection.isInstance(item, ReferenceTarget.$type); } -export type RegularID = 'abstract' | 'attribute' | 'datasource' | 'enum' | 'import' | 'in' | 'model' | 'plugin' | 'type' | 'view' | string; +export type RegularID = 'abstract' | 'attribute' | 'datasource' | 'enum' | 'import' | 'in' | 'model' | 'plugin' | 'this' | 'type' | 'view' | string; export function isRegularID(item: unknown): item is RegularID { - return item === 'model' || item === 'enum' || item === 'attribute' || item === 'datasource' || item === 'plugin' || item === 'abstract' || item === 'in' || item === 'view' || item === 'import' || item === 'type' || (typeof item === 'string' && (/[_a-zA-Z][\w_]*/.test(item))); + return item === 'model' || item === 'enum' || item === 'attribute' || item === 'datasource' || item === 'plugin' || item === 'abstract' || item === 'in' || item === 'view' || item === 'import' || item === 'type' || item === 'this' || (typeof item === 'string' && (/[_a-zA-Z][\w_]*/.test(item))); } export type RegularIDWithTypeNames = 'Any' | 'BigInt' | 'Boolean' | 'Bytes' | 'DateTime' | 'Decimal' | 'Float' | 'Int' | 'Json' | 'Null' | 'Object' | 'String' | 'Unsupported' | 'Void' | RegularID; @@ -968,6 +970,7 @@ export interface TypeDef extends langium.AstNode { readonly $container: Model; readonly $type: 'TypeDef'; attributes: Array; + base?: BuiltinType; comments: Array; fields: Array; mixins: Array>; @@ -977,6 +980,7 @@ export interface TypeDef extends langium.AstNode { export const TypeDef = { $type: 'TypeDef', attributes: 'attributes', + base: 'base', comments: 'comments', fields: 'fields', mixins: 'mixins', @@ -1354,6 +1358,9 @@ export class ZModelAstReflection extends langium.AbstractAstReflection { name: DataModel.attributes, defaultValue: [] }, + base: { + name: DataModel.base + }, baseModel: { name: DataModel.baseModel, referenceType: DataModel.$type @@ -1765,6 +1772,9 @@ export class ZModelAstReflection extends langium.AbstractAstReflection { name: TypeDef.attributes, defaultValue: [] }, + base: { + name: TypeDef.base + }, comments: { name: TypeDef.comments, defaultValue: [] diff --git a/packages/language/src/generated/grammar.ts b/packages/language/src/generated/grammar.ts index ac3ae2b27..14d288069 100644 --- a/packages/language/src/generated/grammar.ts +++ b/packages/language/src/generated/grammar.ts @@ -2013,17 +2013,34 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "value": "with" }, { - "$type": "Assignment", - "feature": "mixins", - "operator": "+=", - "terminal": { - "$type": "CrossReference", - "type": { - "$ref": "#/rules@44" + "$type": "Alternatives", + "elements": [ + { + "$type": "Assignment", + "feature": "mixins", + "operator": "+=", + "terminal": { + "$type": "CrossReference", + "type": { + "$ref": "#/rules@44" + }, + "deprecatedSyntax": false, + "isMulti": false + } }, - "deprecatedSyntax": false, - "isMulti": false - } + { + "$type": "Assignment", + "feature": "base", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@64" + }, + "arguments": [] + } + } + ] }, { "$type": "Group", @@ -3087,6 +3104,10 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "Keyword", "value": "type" + }, + { + "$type": "Keyword", + "value": "this" } ] }, diff --git a/packages/language/src/utils.ts b/packages/language/src/utils.ts index 0e2f690c1..bbe8d16ea 100644 --- a/packages/language/src/utils.ts +++ b/packages/language/src/utils.ts @@ -249,6 +249,13 @@ export function isLiteAttribute(node: AstNode): node is Attribute { return isAttribute(node) && hasAttribute(node, '@@@lite'); } +/** + * Returns if the given node is primitive type def. + */ +export function isPrimitiveTypeDef(node: AstNode): node is TypeDef { + return isTypeDef(node) && !!node.base; +} + /** * Returns the datasource provider literal (e.g. `'postgresql'`) declared in the schema, or undefined * if no datasource is found or its provider is not a literal. @@ -737,6 +744,10 @@ export function getAllFields( return fields; } +export function getPrimitiveTypeDefThisField(td: TypeDef) { + return td.fields.find((f) => f.name === 'this'); +} + /** * Gets all attributes of a data model or type def, including inherited attributes * from base models and mixins. diff --git a/packages/language/src/validators/attribute-application-validator.ts b/packages/language/src/validators/attribute-application-validator.ts index 1be7fc6c8..3ec19831e 100644 --- a/packages/language/src/validators/attribute-application-validator.ts +++ b/packages/language/src/validators/attribute-application-validator.ts @@ -22,6 +22,7 @@ import { isModel, isReferenceExpr, isStringLiteral, + isThisExpr, isTypeDef, } from '../generated/ast'; import { @@ -30,6 +31,7 @@ import { getContainingDataModel, getDataSourceProvider, getNumberLiteral, + getPrimitiveTypeDefThisField, getStringLiteral, hasAttribute, isAuthOrAuthMemberAccess, @@ -40,6 +42,7 @@ import { isDelegateModel, isManyToManyField, isNativeTypeMappingAttribute, + isPrimitiveTypeDef, isRelationshipField, mapBuiltinTypeToExpressionType, resolved, @@ -142,6 +145,14 @@ export default class AttributeApplicationValidator implements AstValidator isDataFieldReference(node) && isDataModel(node.$resolvedType?.decl), - ) - ) { - accept('error', `\`@@validate\` condition cannot use relation fields`, { node: condition }); + if (condition) { + if ( + AstUtils.streamAst(condition).some( + (node) => isDataFieldReference(node) && isDataModel(node.$resolvedType?.decl), + ) + ) { + accept('error', `\`@@validate\` condition cannot use relation fields`, { node: condition }); + return; + } + + if (isPrimitiveTypeDef(attr.$container) && AstUtils.streamAst(condition).some((node) => isThisExpr(node))) { + const thisField = getPrimitiveTypeDefThisField(attr.$container); + if (!thisField) { + accept('error', 'primitive type def cannot reference `this` field without declaring it', { + node: condition, + }); + } + } } } @@ -664,10 +686,16 @@ function assignableToAttributeParam( // attribute parameter type is ContextType, need to infer type from // the attribute's container if (isDataField(attr.$container)) { - if (!attr.$container?.type?.type) { - return genericError; + if (attr.$container.type.reference?.ref && isPrimitiveTypeDef(attr.$container.type.reference.ref)) { + dstType = mapBuiltinTypeToExpressionType(attr.$container.type.reference.ref.base!); + } else { + if (!attr.$container?.type?.type) { + return genericError; + } + + dstType = mapBuiltinTypeToExpressionType(attr.$container.type.type); } - dstType = mapBuiltinTypeToExpressionType(attr.$container.type.type); + dstIsArray = attr.$container.type.array; } else { dstType = 'Any'; @@ -708,34 +736,35 @@ function isValidAttributeTarget(attrDecl: Attribute, targetDecl: DataField) { .filter((name): name is string => !!name); let allowed = false; + const targetDeclType = targetDecl.$resolvedType?.decl ?? targetDecl.type.type; for (const allowedType of fieldTypes) { switch (allowedType) { case 'StringField': - allowed = allowed || targetDecl.type.type === 'String'; + allowed = allowed || targetDeclType === 'String'; break; case 'IntField': - allowed = allowed || targetDecl.type.type === 'Int'; + allowed = allowed || targetDeclType === 'Int'; break; case 'BigIntField': - allowed = allowed || targetDecl.type.type === 'BigInt'; + allowed = allowed || targetDeclType === 'BigInt'; break; case 'FloatField': - allowed = allowed || targetDecl.type.type === 'Float'; + allowed = allowed || targetDeclType === 'Float'; break; case 'DecimalField': - allowed = allowed || targetDecl.type.type === 'Decimal'; + allowed = allowed || targetDeclType === 'Decimal'; break; case 'BooleanField': - allowed = allowed || targetDecl.type.type === 'Boolean'; + allowed = allowed || targetDeclType === 'Boolean'; break; case 'DateTimeField': - allowed = allowed || targetDecl.type.type === 'DateTime'; + allowed = allowed || targetDeclType === 'DateTime'; break; case 'JsonField': - allowed = allowed || targetDecl.type.type === 'Json'; + allowed = allowed || targetDeclType === 'Json'; break; case 'BytesField': - allowed = allowed || targetDecl.type.type === 'Bytes'; + allowed = allowed || targetDeclType === 'Bytes'; break; case 'ModelField': allowed = allowed || isDataModel(targetDecl.type.reference?.ref); diff --git a/packages/language/src/validators/datamodel-validator.ts b/packages/language/src/validators/datamodel-validator.ts index 577599390..a628c7138 100644 --- a/packages/language/src/validators/datamodel-validator.ts +++ b/packages/language/src/validators/datamodel-validator.ts @@ -28,6 +28,7 @@ import { hasAttribute, isDelegateModel, isEnumFieldReference, + isPrimitiveTypeDef, } from '../utils'; import { validateAttributeApplication } from './attribute-application-validator'; import { validateDuplicatedDeclarations, type AstValidator } from './common'; @@ -41,13 +42,12 @@ export default class DataModelValidator implements AstValidator { this.validateAttributes(dm, accept); this.validateFields(dm, accept); this.validateOnceInModelAttributes(dm, accept); - if (dm.mixins.length > 0) { + if (dm.mixins.length > 0 || dm.base) { this.validateMixins(dm, accept); } this.validateInherits(dm, accept); this.validateDelegateMap(dm, accept); } - private validateFields(dm: DataModel, accept: ValidationAcceptor) { const allFields = getAllFields(dm); const idFields = allFields.filter((f) => f.attributes.find((attr) => attr.decl.ref?.name === '@id')); @@ -87,7 +87,9 @@ export default class DataModelValidator implements AstValidator { } const isArray = idField.type.array; - const isScalar = SCALAR_TYPES.includes(idField.type.type as (typeof SCALAR_TYPES)[number]); + const isScalar = SCALAR_TYPES.includes( + (idField.$resolvedType?.decl ?? idField.type.type) as (typeof SCALAR_TYPES)[number], + ); const isValidType = isScalar || isEnum(idField.type.reference?.ref); if (isArray || !isValidType) { @@ -121,7 +123,7 @@ export default class DataModelValidator implements AstValidator { field.attributes.forEach((attr) => validateAttributeApplication(attr, accept)); - if (isTypeDef(field.type.reference?.ref)) { + if (isTypeDef(field.type.reference?.ref) && !isPrimitiveTypeDef(field.type.reference.ref)) { if (!hasAttribute(field, '@json')) { accept('error', 'Custom-typed field must have @json attribute', { node: field }); } @@ -525,6 +527,12 @@ export default class DataModelValidator implements AstValidator { } private validateMixins(dm: DataModel, accept: ValidationAcceptor) { + if (dm.base) { + accept('error', `model "${dm.name}" cannot use a primitive type as a mixin`, { + node: dm, + }); + return; + } const seen: TypeDef[] = []; const todo: TypeDef[] = dm.mixins.map((mixin) => mixin.ref!); while (todo.length > 0) { @@ -537,6 +545,12 @@ export default class DataModelValidator implements AstValidator { } seen.push(current); todo.push(...current.mixins.map((mixin) => mixin.ref!)); + + if (current.base) { + accept('error', `cannot use primitive type def "${current.name}" as a mixin`, { + node: dm, + }); + } } } diff --git a/packages/language/src/validators/function-invocation-validator.ts b/packages/language/src/validators/function-invocation-validator.ts index 9f722b490..ba6c3efc7 100644 --- a/packages/language/src/validators/function-invocation-validator.ts +++ b/packages/language/src/validators/function-invocation-validator.ts @@ -14,6 +14,7 @@ import { isDataModel, isDataModelAttribute, isStringLiteral, + isThisExpr, } from '../generated/ast'; import { getFunctionExpressionContext, @@ -266,7 +267,7 @@ export default class FunctionInvocationValidator implements AstValidator { validateDuplicatedDeclarations(typeDef, typeDef.fields, accept); this.validateAttributes(typeDef, accept); this.validateFields(typeDef, accept); + this.validatePrimitiveTypeDef(typeDef, accept); } private validateAttributes(typeDef: TypeDef, accept: ValidationAcceptor) { @@ -24,4 +26,47 @@ export default class TypeDefValidator implements AstValidator { private validateField(field: DataField, accept: ValidationAcceptor): void { field.attributes.forEach((attr) => validateAttributeApplication(attr, accept)); } + + private validatePrimitiveTypeDef(typeDef: TypeDef, accept: ValidationAcceptor) { + if (typeDef.base) { + if (typeDef.fields.length > 1) { + accept('error', 'primitive type def must only declare 1 field', { + node: typeDef, + }); + } + const thisField = getPrimitiveTypeDefThisField(typeDef); + if (thisField) { + if (thisField.type.type !== typeDef.base) { + accept('error', 'primitive type def\'s "this" field must match the declared type', { + node: thisField, + }); + } + + if (thisField.type.array) { + accept('error', 'primitive type def\'s "this" field must be scalar', { + node: thisField, + }); + } + + if (thisField.type.optional) { + accept('error', 'primitive type def\'s "this" field must not be optional', { + node: thisField, + }); + } + } + + if (typeDef.mixins.length > 0) { + accept('error', `primitive type def cannot use mixins`, { + node: typeDef, + }); + } + } else { + const primitiveTypeDefs = typeDef.mixins.filter((m) => !!m.ref?.base); + for (const primitiveTypeDef of primitiveTypeDefs) { + accept('error', `cannot use primitive type def "${primitiveTypeDef.$refText}" as a mixin`, { + node: typeDef, + }); + } + } + } } diff --git a/packages/language/src/zmodel-linker.ts b/packages/language/src/zmodel-linker.ts index 0908f7c20..7fed902da 100644 --- a/packages/language/src/zmodel-linker.ts +++ b/packages/language/src/zmodel-linker.ts @@ -57,9 +57,11 @@ import { getAllLoadedAndReachableDataModelsAndTypeDefs, getAuthDecl, getContainingDataModel, + getPrimitiveTypeDefThisField, isAuthInvocation, isBeforeInvocation, isMemberContainer, + isPrimitiveTypeDef, mapBuiltinTypeToExpressionType, } from './utils'; @@ -372,12 +374,21 @@ export class ZModelLinker extends DefaultLinker { let decl: AstNode | undefined = node.$container; - while (decl && !isDataModel(decl)) { - decl = decl.$container; - } + while (decl) { + if (isPrimitiveTypeDef(decl)) { + const thisField = getPrimitiveTypeDefThisField(decl); + if (thisField?.type.type) { + this.resolveToBuiltinTypeOrDecl(node, thisField.type.type); + } + return; + } + + if (isDataModel(decl)) { + this.resolveToBuiltinTypeOrDecl(node, decl); + return; + } - if (decl) { - this.resolveToBuiltinTypeOrDecl(node, decl); + decl = decl.$container; } } @@ -497,11 +508,19 @@ export class ZModelLinker extends DefaultLinker { let scopes = extraScopes; - // if the field has enum declaration type, resolve the rest with that enum's fields on top of the scopes - if (node.type.reference?.ref && isEnum(node.type.reference.ref)) { - const contextEnum = node.type.reference.ref as Enum; - const enumScope: ScopeProvider = (name) => contextEnum.fields.find((f) => f.name === name); - scopes = [enumScope, ...scopes]; + if (node.type.reference?.ref) { + // if the field has enum declaration type, resolve the rest with that enum's fields on top of the scopes + if (isEnum(node.type.reference.ref)) { + const contextEnum = node.type.reference.ref as Enum; + const enumScope: ScopeProvider = (name) => contextEnum.fields.find((f) => f.name === name); + scopes = [enumScope, ...scopes]; + } else if (isPrimitiveTypeDef(node.type.reference.ref)) { + node.$resolvedType = { + decl: node.type.reference.ref.base, + array: node.type.array, + nullable: node.type.optional, + }; + } } this.resolveDefault(node, document, scopes); @@ -546,6 +565,12 @@ export class ZModelLinker extends DefaultLinker { array: type.array, nullable: nullable, }; + } else if (type.reference?.ref && isPrimitiveTypeDef(type.reference.ref)) { + node.$resolvedType = { + decl: type.reference.ref.base, + array: type.array, + nullable: nullable, + }; } else if (type.reference) { node.$resolvedType = { decl: type.reference.ref, diff --git a/packages/language/src/zmodel-semantic.ts b/packages/language/src/zmodel-semantic.ts index 8a26d4120..9668f41e3 100644 --- a/packages/language/src/zmodel-semantic.ts +++ b/packages/language/src/zmodel-semantic.ts @@ -67,6 +67,12 @@ export class ZModelSemanticTokenProvider extends AbstractSemanticTokenProvider { property: 'mixins', type: SemanticTokenTypes.type, }); + + acceptor({ + node, + property: 'base', + type: SemanticTokenTypes.type, + }); } else if (isDataSource(node) || isGeneratorDecl(node) || isPlugin(node)) { acceptor({ node, diff --git a/packages/language/src/zmodel.langium b/packages/language/src/zmodel.langium index 6856e6183..c2845aac1 100644 --- a/packages/language/src/zmodel.langium +++ b/packages/language/src/zmodel.langium @@ -179,7 +179,9 @@ DataModel: '}'; fragment WithClause: - 'with' mixins+=[TypeDef] (','? mixins+=[TypeDef])*; + 'with' + (mixins+=[TypeDef] | base=BuiltinType) + (','? mixins+=[TypeDef])*; fragment ExtendsClause: 'extends' baseModel=[DataModel]; @@ -240,7 +242,7 @@ Procedure: // https://github.com/langium/langium/discussions/1012 RegularID returns string: // include keywords that we'd like to work as ID in most places - ID | 'model' | 'enum' | 'attribute' | 'datasource' | 'plugin' | 'abstract' | 'in' | 'view' | 'import' | 'type'; + ID | 'model' | 'enum' | 'attribute' | 'datasource' | 'plugin' | 'abstract' | 'in' | 'view' | 'import' | 'type' | 'this'; RegularIDWithTypeNames returns string: RegularID | 'String' | 'Boolean' | 'Int' | 'BigInt' | 'Float' | 'Decimal' | 'DateTime' | 'Json' | 'Bytes' | 'Null' | 'Object' | 'Any' | 'Void' | 'Unsupported'; diff --git a/packages/language/test/custom-type-primitive.test.ts b/packages/language/test/custom-type-primitive.test.ts new file mode 100644 index 000000000..891fb2d99 --- /dev/null +++ b/packages/language/test/custom-type-primitive.test.ts @@ -0,0 +1,380 @@ +import { describe, it } from 'vitest'; +import { loadSchema, loadSchemaWithError } from './utils'; + +describe('Custom type primitive tests', () => { + it('supports custom type primitives', async () => { + await loadSchema(` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id Int @id + name UserName + } + + type UserName with String { + this String + } + `); + }); + + it('supports custom type primitives in validation', async () => { + await loadSchema(` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id Int @id + age Age + + @@validate(age >= 18) + } + + type Age with Int { + this Int + } + `); + }); + + it('supports custom type primitives with default values', async () => { + await loadSchema(` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + name UserName @default('') + } + + type UserName with String { + this String + } + `); + }); + + it('resolves `this` to the base type', async () => { + await loadSchema(` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + email Email + } + + type Email with String { + this String + + @@validate(isEmail(this)) + } + `); + }); + + it('rejects invalid attributes', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + email Email + } + + type Email with String { + this String @db.Text + } + `, + 'attribute "@db.Text" cannot be used with primitive type defs', + ); + + await loadSchemaWithError( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + email Email @gt(5) + } + + type Email with String { + this String + } + `, + 'cannot be used on this type of field', + ); + }); + + it('accepts attributes on the field declaration', async () => { + await loadSchema(` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + email Email @length(1, 2) @db.Text + } + + type Email with String { + this String + } + `); + }); + + it('accepts attributes on the `this` declaration', async () => { + await loadSchema(` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + email Email + } + + type Email with String { + this String @email + } + `); + }); + + it('does not require a "this" field', async () => { + await loadSchema( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + email Email + } + + type Email with String {} + `, + ); + }); + + it('rejects when there is more than 1 field', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + email Email + } + + type Email with String { + this String + this2 String + } + `, + 'primitive type def must only declare 1 field', + ); + }); + + it('rejects when "this" field does not match declared type', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + email Email + } + + type Email with String { + this Int + } + `, + 'primitive type def\'s "this" field must match the declared type', + ); + }); + + it('rejects when "this" field is not scalar', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + email Email + } + + type Email with String { + this String[] + } + `, + 'primitive type def\'s "this" field must be scalar', + ); + }); + + it('rejects when "this" field is optional', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + email Email + } + + type Email with String { + this String? + } + `, + 'primitive type def\'s "this" field must not be optional', + ); + }); + + it('rejects when trying to use mixins', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + email Email + } + + type Email with String, Mixin { + this String + } + + type Mixin { + } + `, + 'primitive type def cannot use mixins', + ); + }); + + it('rejects when used as a mixin', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User with Email { + id String @id + email Email + } + + type Email with String { + this String + } + `, + 'cannot use primitive type def "Email" as a mixin', + ); + + await loadSchemaWithError( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + email Email + } + + type Email with String { + this String + } + + type Mixin with Email { + } + `, + 'cannot use primitive type def "Email" as a mixin', + ); + + await loadSchemaWithError( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User with String { + id String @id + } + `, + 'model "User" cannot use a primitive type as a mixin', + ); + }); + + it('rejects when using `this` field without declaring it', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + email Email + } + + type Email with String { + @@validate(isEmail(this)) + } + `, + 'primitive type def cannot reference `this` field without declaring it', + ); + + await loadSchemaWithError( + ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + score Score + } + + type Score with Int { + @@validate(this >= 0) + } + `, + 'primitive type def cannot reference `this` field without declaring it', + ); + }); +}); diff --git a/packages/language/test/function-invocation.test.ts b/packages/language/test/function-invocation.test.ts index 68d20a092..e182b697c 100644 --- a/packages/language/test/function-invocation.test.ts +++ b/packages/language/test/function-invocation.test.ts @@ -462,4 +462,26 @@ describe('Function Invocation Tests', () => { ); }); }); + + describe('length()', () => { + it('should accept primitive type defs of String', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model User { + id String @id + name UserName + } + + type UserName with String { + this String + + @@validate(length(this) >= 2) + } + `); + }); + }); }); diff --git a/packages/orm/src/client/crud-types.ts b/packages/orm/src/client/crud-types.ts index d0e9ef940..b4b137e4c 100644 --- a/packages/orm/src/client/crud-types.ts +++ b/packages/orm/src/client/crud-types.ts @@ -17,6 +17,7 @@ import type { GetModelFieldType, GetModels, GetSubModels, + GetTypeDef, GetTypeDefField, GetTypeDefFields, GetTypeDefFieldType, @@ -32,6 +33,7 @@ import type { SchemaDef, TypeDefFieldIsArray, TypeDefFieldIsOptional, + TypeDefIsPrimitive, UpdatedAtInfo, } from '@zenstackhq/schema'; import type { ExpressionBuilder, OperandExpression, SqlBool } from 'kysely'; @@ -318,27 +320,36 @@ export type TypeDefResult< Schema extends SchemaDef, TypeDef extends GetTypeDefs, Partial extends boolean = false, -> = PartialIf< - Optional< - { - [Key in GetTypeDefFields]: MapFieldDefType< - Schema, - GetTypeDefField, - Partial - >; - }, - // optionality - Partial extends true - ? never - : keyof { - [Key in GetTypeDefFields as TypeDefFieldIsOptional extends true - ? Key - : never]: true; - } - >, - Partial -> & - (IsTypeDefStrict extends true ? {} : Record); +> = + Schema['typeDefs'] extends Record + ? Schema['typeDefs'][TypeDef]['base'] extends string + ? TypeMap[Schema['typeDefs'][TypeDef]['base']] + : PartialIf< + Optional< + { + [Key in GetTypeDefFields]: MapFieldDefType< + Schema, + GetTypeDefField, + Partial + >; + }, + // optionality + Partial extends true + ? never + : keyof { + [Key in GetTypeDefFields as TypeDefFieldIsOptional< + Schema, + TypeDef, + Key + > extends true + ? Key + : never]: true; + } + >, + Partial + > & + (IsTypeDefStrict extends true ? {} : Record) + : never; export type IsTypeDefStrict> = Schema['typeDefs'] extends Record @@ -401,14 +412,22 @@ type FieldFilter< AllowedKinds > : GetModelFieldType extends GetTypeDefs - ? // typedef - TypedJsonFilter< - Schema, - GetModelFieldType, - FieldIsArray, - ModelFieldIsOptional, - AllowedKinds - > + ? TypeDefIsPrimitive> extends true + ? // primitive typedef — filter like its base type + PrimitiveFilter< + TypeDefBaseType>, + ModelFieldIsOptional, + WithAggregations, + AllowedKinds + > + : // JSON typedef + TypedJsonFilter< + Schema, + GetModelFieldType, + FieldIsArray, + ModelFieldIsOptional, + AllowedKinds + > : // primitive GetModelFieldType extends 'String' ? // string — additionally consider fuzzy / full-text augmentations @@ -584,9 +603,23 @@ type ArrayFilter< } : {}); +// the base builtin type of a primitive typedef (e.g. `Int` for `type Age with Int`) +type TypeDefBaseType> = GetTypeDef< + Schema, + TypeDef +>['base'] extends infer B extends string + ? B + : never; + // map a scalar type (primitive and enum) to TS type type MapScalarType = - Type extends GetEnums ? keyof GetEnum : MapBaseType; + Type extends GetEnums + ? keyof GetEnum + : Type extends GetTypeDefs + ? TypeDefIsPrimitive extends true + ? MapBaseType> + : MapBaseType + : MapBaseType; type PrimitiveFilter< T extends string, @@ -973,14 +1006,24 @@ type TypedJsonFieldsFilter< TypeDefName, Key > extends GetTypeDefs - ? // nested typedef - recurse - TypedJsonFilter< - Schema, - GetTypeDefFieldType, - TypeDefFieldIsArray, - TypeDefFieldIsOptional, - AllowedKinds - > + ? TypeDefIsPrimitive> extends true + ? // primitive typedef — filter like its base type + TypeDefFieldIsArray extends true + ? ArrayFilter, AllowedKinds> + : PrimitiveFilter< + TypeDefBaseType>, + TypeDefFieldIsOptional, + false, + AllowedKinds + > + : // nested JSON typedef - recurse + TypedJsonFilter< + Schema, + GetTypeDefFieldType, + TypeDefFieldIsArray, + TypeDefFieldIsOptional, + AllowedKinds + > : // array TypeDefFieldIsArray extends true ? ArrayFilter, AllowedKinds> @@ -1485,8 +1528,10 @@ type MapFieldDefType< T['type'] extends GetEnums ? keyof GetEnum : T['type'] extends GetTypeDefs - ? TypeDefResult & - (IsTypeDefStrict extends true ? {} : Record) + ? TypeDefIsPrimitive extends true + ? TypeDefResult + : TypeDefResult & + (IsTypeDefStrict extends true ? {} : Record) : MapBaseType, T['optional'], T['array'] diff --git a/packages/orm/src/client/crud/dialects/base-dialect.ts b/packages/orm/src/client/crud/dialects/base-dialect.ts index e3e852ed2..2fa4b4d37 100644 --- a/packages/orm/src/client/crud/dialects/base-dialect.ts +++ b/packages/orm/src/client/crud/dialects/base-dialect.ts @@ -28,6 +28,7 @@ import { getModelFields, getRelationForeignKeyFieldPairs, isEnum, + isPrimitiveTypeDef, isTypeDef, makeDefaultOrderBy, requireField, @@ -658,15 +659,25 @@ export abstract class BaseCrudDialect { return this.buildEnumFilter(fieldRef, fieldDef, payload); } + let type = fieldDef.type; + if (isTypeDef(this.schema, fieldDef.type)) { - if (payload instanceof DbNullClass || payload instanceof JsonNullClass || payload instanceof AnyNullClass) { - // null sentinel passed directly (e.g. where: { field: DbNull }) — treat like { equals: sentinel } - return this.buildJsonValueFilterClause(fieldRef, payload); + if (isPrimitiveTypeDef(this.schema, fieldDef.type)) { + type = this.schema['typeDefs']![fieldDef.type]!['base']!; + } else { + if ( + payload instanceof DbNullClass || + payload instanceof JsonNullClass || + payload instanceof AnyNullClass + ) { + // null sentinel passed directly (e.g. where: { field: DbNull }) — treat like { equals: sentinel } + return this.buildJsonValueFilterClause(fieldRef, payload); + } + return this.buildJsonFilter(fieldRef, payload, fieldDef); } - return this.buildJsonFilter(fieldRef, payload, fieldDef); } - return match(fieldDef.type as BuiltinType) + return match(type as BuiltinType) .with('String', () => this.buildStringFilter(fieldRef, payload, fieldDef)) .with(P.union('Int', 'Float', 'Decimal', 'BigInt'), (type) => this.buildNumberFilter(fieldRef, type, payload), @@ -839,14 +850,17 @@ export abstract class BaseCrudDialect { const fieldDef = typeDef.fields[key]; invariant(fieldDef, `Field "${key}" not found in type definition "${typeDefName}"`); const fieldReceiver = this.buildJsonPathSelection(receiver, `$.${key}`); - if (isTypeDef(this.schema, fieldDef.type)) { + if (isTypeDef(this.schema, fieldDef.type) && !isPrimitiveTypeDef(this.schema, fieldDef.type)) { clauses.push(this.buildTypedJsonFilter(fieldReceiver, value, fieldDef.type, !!fieldDef.array)); } else { if (fieldDef.array) { clauses.push(this.buildArrayFilter(fieldReceiver, fieldDef, value)); } else { + const effectiveType = isPrimitiveTypeDef(this.schema, fieldDef.type) + ? this.schema.typeDefs![fieldDef.type]!.base! + : fieldDef.type; let _receiver = fieldReceiver; - if (fieldDef.type === 'String' || isEnum(this.schema, fieldDef.type)) { + if (effectiveType === 'String' || isEnum(this.schema, effectiveType)) { // trim quotes for string and enum fields (enums are stored as JSON strings) _receiver = this.trimTextQuotes(this.castText(fieldReceiver)); } diff --git a/packages/orm/src/client/crud/dialects/mysql.ts b/packages/orm/src/client/crud/dialects/mysql.ts index 498a431fc..075f41e17 100644 --- a/packages/orm/src/client/crud/dialects/mysql.ts +++ b/packages/orm/src/client/crud/dialects/mysql.ts @@ -14,7 +14,7 @@ import { import { AnyNullClass, DbNullClass, JsonNullClass } from '../../../common-types'; import type { NullsOrder, SortOrder } from '../../crud-types'; import { createInvalidInputError, createNotSupportedError } from '../../errors'; -import { isTypeDef } from '../../query-utils'; +import { isPrimitiveTypeDef, isTypeDef } from '../../query-utils'; import type { FuzzyFilterOptions } from './base-dialect'; import { LateralJoinDialectBase } from './lateral-join-dialect-base'; @@ -72,15 +72,21 @@ export class MySqlCrudDialect extends LateralJoinDiale } if (isTypeDef(this.schema, type)) { - // type-def fields (regardless array or scalar) are stored as scalar `Json` and - // their input values need to be stringified if not already (i.e., provided in - // default values) - if (typeof value !== 'string') { - return this.transformInput(value, 'Json', forArrayField); + if (isPrimitiveTypeDef(this.schema, type)) { + type = this.schema['typeDefs']![type]!['base']!; } else { - return value; + // type-def fields (regardless array or scalar) are stored as scalar `Json` and + // their input values need to be stringified if not already (i.e., provided in + // default values) + if (typeof value !== 'string') { + return this.transformInput(value, 'Json', forArrayField); + } else { + return value; + } } - } else if (Array.isArray(value)) { + } + + if (Array.isArray(value)) { if (type === 'Json') { // type-def arrays reach here return JSON.stringify(value); diff --git a/packages/orm/src/client/crud/dialects/postgresql.ts b/packages/orm/src/client/crud/dialects/postgresql.ts index 5c6866b23..fb6780af0 100644 --- a/packages/orm/src/client/crud/dialects/postgresql.ts +++ b/packages/orm/src/client/crud/dialects/postgresql.ts @@ -15,7 +15,7 @@ import { parse as parsePostgresArray } from 'postgres-array'; import { AnyNullClass, DbNullClass, JsonNullClass } from '../../../common-types'; import type { NullsOrder, SortOrder } from '../../crud-types'; import { createInvalidInputError } from '../../errors'; -import { isEnum, isTypeDef } from '../../query-utils'; +import { isEnum, isPrimitiveTypeDef, isTypeDef } from '../../query-utils'; import type { CrudDialectArgs, FuzzyFilterOptions } from './base-dialect'; import { LateralJoinDialectBase } from './lateral-join-dialect-base'; @@ -187,15 +187,21 @@ export class PostgresCrudDialect extends LateralJoinDi // https://github.com/brianc/node-postgres/issues/374 if (isTypeDef(this.schema, type)) { - // type-def fields (regardless array or scalar) are stored as scalar `Json` and - // their input values need to be stringified if not already (i.e., provided in - // default values) - if (typeof value !== 'string') { - return JSON.stringify(value); + if (isPrimitiveTypeDef(this.schema, type)) { + type = this.schema['typeDefs']![type]!['base']!; } else { - return value; + // type-def fields (regardless array or scalar) are stored as scalar `Json` and + // their input values need to be stringified if not already (i.e., provided in + // default values) + if (typeof value !== 'string') { + return JSON.stringify(value); + } else { + return value; + } } - } else if (Array.isArray(value)) { + } + + if (Array.isArray(value)) { if (type === 'Json' && !forArrayField) { // scalar `Json` fields need their input stringified return JSON.stringify(value); @@ -500,6 +506,10 @@ export class PostgresCrudDialect extends LateralJoinDi } } } + // a primitive typedef maps to its base type's SQL type + if (isPrimitiveTypeDef(this.schema, zmodelType)) { + zmodelType = this.schema.typeDefs![zmodelType]!.base!; + } if (isEnum(this.schema, zmodelType)) { // reduce enum to text for type compatibility return 'text'; diff --git a/packages/orm/src/client/crud/dialects/sqlite.ts b/packages/orm/src/client/crud/dialects/sqlite.ts index a7ceac7b2..f7e321cc8 100644 --- a/packages/orm/src/client/crud/dialects/sqlite.ts +++ b/packages/orm/src/client/crud/dialects/sqlite.ts @@ -21,6 +21,8 @@ import { getDelegateDescendantModels, getManyToManyRelation, getRelationForeignKeyFieldPairs, + isPrimitiveTypeDef, + isTypeDef, requireField, requireIdFields, requireModel, @@ -81,7 +83,17 @@ export class SqliteCrudDialect extends BaseCrudDialect invariant(false, 'should not reach here: AnyNull is not a valid input value'); } - if (type === 'Json' || (this.schema.typeDefs && type in this.schema.typeDefs)) { + if (isTypeDef(this.schema, type)) { + if (isPrimitiveTypeDef(this.schema, type)) { + // a primitive typedef is stored as its base type + type = this.schema.typeDefs![type]!.base!; + } else { + // JSON data should be stringified + return JSON.stringify(value); + } + } + + if (type === 'Json') { // JSON data should be stringified return JSON.stringify(value); } @@ -108,10 +120,13 @@ export class SqliteCrudDialect extends BaseCrudDialect } } - override transformOutput(value: unknown, type: BuiltinType, array: boolean) { + override transformOutput(value: unknown, type: BuiltinType, array: boolean): unknown { if (value === null || value === undefined) { return value; } else if (this.schema.typeDefs && type in this.schema.typeDefs) { + if (isPrimitiveTypeDef(this.schema, type)) { + return this.transformOutput(value, this.schema.typeDefs[type]!.base!, array); + } // typed JSON field return this.transformOutputJson(value); } else { diff --git a/packages/orm/src/client/crud/operations/aggregate.ts b/packages/orm/src/client/crud/operations/aggregate.ts index 53e8e3640..343117084 100644 --- a/packages/orm/src/client/crud/operations/aggregate.ts +++ b/packages/orm/src/client/crud/operations/aggregate.ts @@ -1,7 +1,7 @@ import type { SchemaDef } from '@zenstackhq/schema'; import { match } from 'ts-pattern'; import { createInvalidInputError } from '../../errors'; -import { getField } from '../../query-utils'; +import { getField, isPrimitiveTypeDef } from '../../query-utils'; import { BaseOperationHandler } from './base'; // stable key for comparing two `args` payloads (bigint-safe) @@ -162,7 +162,9 @@ export class AggregateOperationHandler extends BaseOpe if (typeof value === 'string') { const fieldDef = getField(this.schema, this.model, field); if (fieldDef) { - const type = fieldDef.type; + const type = isPrimitiveTypeDef(this.schema, fieldDef.type) + ? this.schema.typeDefs![fieldDef.type]!.base! + : fieldDef.type; if (op === '_avg') { val = parseFloat(val); } else { diff --git a/packages/orm/src/client/crud/operations/base.ts b/packages/orm/src/client/crud/operations/base.ts index 73ffd4a8d..f68f6139b 100644 --- a/packages/orm/src/client/crud/operations/base.ts +++ b/packages/orm/src/client/crud/operations/base.ts @@ -45,6 +45,7 @@ import { getModel, getRelationForeignKeyFieldPairs, isForeignKeyField, + isPrimitiveTypeDef, isRelationField, isScalarField, requireField, @@ -1459,7 +1460,10 @@ export abstract class BaseOperationHandler { } private isNumericField(fieldDef: FieldDef) { - return NUMERIC_FIELD_TYPES.includes(fieldDef.type) && !fieldDef.array; + const type = isPrimitiveTypeDef(this.schema, fieldDef.type) + ? this.schema.typeDefs![fieldDef.type]!.base! + : fieldDef.type; + return NUMERIC_FIELD_TYPES.includes(type) && !fieldDef.array; } private makeContextComment(_context: { model: string; operation: CRUD }) { diff --git a/packages/orm/src/client/crud/operations/group-by.ts b/packages/orm/src/client/crud/operations/group-by.ts index 377519454..2b819027a 100644 --- a/packages/orm/src/client/crud/operations/group-by.ts +++ b/packages/orm/src/client/crud/operations/group-by.ts @@ -1,7 +1,7 @@ import type { SchemaDef } from '@zenstackhq/schema'; import { sql } from 'kysely'; import { match } from 'ts-pattern'; -import { aggregate, getField } from '../../query-utils'; +import { aggregate, getField, isPrimitiveTypeDef } from '../../query-utils'; import { BaseOperationHandler } from './base'; export class GroupByOperationHandler extends BaseOperationHandler { @@ -132,7 +132,9 @@ export class GroupByOperationHandler extends BaseOpera if (typeof value === 'string') { const fieldDef = getField(this.schema, this.model, field); if (fieldDef) { - const type = fieldDef.type; + const type = isPrimitiveTypeDef(this.schema, fieldDef.type) + ? this.schema.typeDefs![fieldDef.type]!.base! + : fieldDef.type; if (op === '_avg') { val = parseFloat(val); } else { diff --git a/packages/orm/src/client/helpers/schema-db-pusher.ts b/packages/orm/src/client/helpers/schema-db-pusher.ts index 569f65162..bcddf2314 100644 --- a/packages/orm/src/client/helpers/schema-db-pusher.ts +++ b/packages/orm/src/client/helpers/schema-db-pusher.ts @@ -326,11 +326,17 @@ export class SchemaDbPusher { return 'serial'; } - if (this.isCustomType(fieldDef.type)) { - return this.jsonType; + let type = fieldDef.type as BuiltinType; + + if (this.isCustomType(type)) { + const typeDef = Object.values(this.schema.typeDefs!).find((def) => def.name === type)!; + if (typeDef.base) { + type = typeDef.base; + } else { + return this.jsonType; + } } - const type = fieldDef.type as BuiltinType; const result = match>(type) .with('String', () => this.stringType) .with('Boolean', () => this.booleanType) diff --git a/packages/orm/src/client/query-utils.ts b/packages/orm/src/client/query-utils.ts index 7941b7faf..382176834 100644 --- a/packages/orm/src/client/query-utils.ts +++ b/packages/orm/src/client/query-utils.ts @@ -299,6 +299,10 @@ export function isTypeDef(schema: SchemaDef, type: string) { return !!schema.typeDefs?.[type]; } +export function isPrimitiveTypeDef(schema: SchemaDef, type: string) { + return !!schema.typeDefs?.[type]?.base; +} + export function buildJoinPairs( schema: SchemaDef, model: string, diff --git a/packages/orm/src/client/zod/factory.ts b/packages/orm/src/client/zod/factory.ts index 0d335551a..53014d4b1 100644 --- a/packages/orm/src/client/zod/factory.ts +++ b/packages/orm/src/client/zod/factory.ts @@ -48,6 +48,7 @@ import { getTypeDef, getUniqueFields, isEnum, + isPrimitiveTypeDef, isTypeDef, requireField, requireModel, @@ -367,7 +368,10 @@ export class ZodSchemaFactory< private makeScalarSchema(type: string, attributes?: readonly AttributeApplication[]) { if (this.schema.typeDefs && type in this.schema.typeDefs) { - return this.makeTypeDefSchema(type); + // attributes declared on the consuming field only apply to primitive typedefs + return this.schema.typeDefs[type]!.base + ? this.makeTypeDefSchema(type, attributes) + : this.makeTypeDefSchema(type); } else if (this.schema.enums && type in this.schema.enums) { return this.makeEnumSchema(type); } else { @@ -458,10 +462,26 @@ export class ZodSchemaFactory< return schema; } + /** + * Attributes to validate each value of a scalar field against. For typedef list fields, the + * field's own attributes (e.g. `@length`) validate the list rather than its items. + */ + private getItemAttributes(fieldDef: FieldDef) { + return fieldDef.array && isTypeDef(this.schema, fieldDef.type) ? undefined : fieldDef.attributes; + } + @cache() - private makeTypeDefSchema(type: string): ZodType { + private makeTypeDefSchema(type: string, fieldAttributes?: readonly AttributeApplication[]): ZodType { const typeDef = getTypeDef(this.schema, type); invariant(typeDef, `Type definition "${type}" not found in schema`); + if (typeDef.base) { + // validate against both the typedef's own attributes and those of the consuming field + const schema = this.makeScalarSchema(typeDef.base, [ + ...(typeDef.fields['this']?.attributes ?? []), + ...(fieldAttributes ?? []), + ]); + return this.extraValidationsEnabled ? ZodUtils.addCustomValidation(schema, typeDef.attributes) : schema; + } const func = typeDef.strict ? z.strictObject : z.looseObject; const schema = func( Object.fromEntries( @@ -577,7 +597,7 @@ export class ZodSchemaFactory< } else if (fieldDef.array) { // array field fieldSchema = this.makeArrayFilterSchema(fieldDef.type, allowedFilterKinds); - } else if (this.isTypeDefType(fieldDef.type)) { + } else if (this.isTypeDefType(fieldDef.type) && !isPrimitiveTypeDef(this.schema, fieldDef.type)) { fieldSchema = this.makeTypedJsonFilterSchema( fieldDef.type, !!fieldDef.optional, @@ -585,9 +605,9 @@ export class ZodSchemaFactory< allowedFilterKinds, ); } else { - // primitive field + // primitive field (or a primitive typedef, which filters like its base type) fieldSchema = this.makePrimitiveFilterSchema( - fieldDef.type as BuiltinType, + this.getEffectiveFieldType(fieldDef.type) as BuiltinType, !!fieldDef.optional, withAggregations, allowedFilterKinds, @@ -714,7 +734,7 @@ export class ZodSchemaFactory< // fields filter — typedef sub-fields are not model fields, no slicing applies const fieldSchemas: Record = {}; for (const [fieldName, fieldDef] of Object.entries(typeDef.fields)) { - if (this.isTypeDefType(fieldDef.type)) { + if (this.isTypeDefType(fieldDef.type) && !isPrimitiveTypeDef(this.schema, fieldDef.type)) { fieldSchemas[fieldName] = this.makeTypedJsonFilterSchema( fieldDef.type, !!fieldDef.optional, @@ -735,7 +755,7 @@ export class ZodSchemaFactory< fieldSchemas[fieldName] = this.makeArrayFilterSchema(fieldDef.type, undefined).optional(); } else { fieldSchemas[fieldName] = this.makePrimitiveFilterSchema( - fieldDef.type as BuiltinType, + this.getEffectiveFieldType(fieldDef.type) as BuiltinType, !!fieldDef.optional, false, undefined, @@ -815,6 +835,13 @@ export class ZodSchemaFactory< return this.schema.typeDefs && type in this.schema.typeDefs; } + private getEffectiveFieldType(type: string): string { + if (isPrimitiveTypeDef(this.schema, type)) { + return this.schema.typeDefs![type]!.base!; + } + return type; + } + @cache() private makeEnumFilterSchema( enumName: string, @@ -1676,7 +1703,7 @@ export class ZodSchemaFactory< uncheckedVariantFields[field] = fieldSchema; } } else { - let fieldSchema = this.makeScalarSchema(fieldDef.type, fieldDef.attributes); + let fieldSchema = this.makeScalarSchema(fieldDef.type, this.getItemAttributes(fieldDef)); if (fieldDef.array) { fieldSchema = ZodUtils.addListValidation(fieldSchema.array(), fieldDef.attributes); @@ -2034,7 +2061,7 @@ export class ZodSchemaFactory< uncheckedVariantFields[field] = fieldSchema; } } else { - let fieldSchema = this.makeScalarSchema(fieldDef.type, fieldDef.attributes); + let fieldSchema = this.makeScalarSchema(fieldDef.type, this.getItemAttributes(fieldDef)); if (this.isNumericField(fieldDef)) { fieldSchema = z.union([ @@ -2561,7 +2588,7 @@ export class ZodSchemaFactory< } private isNumericField(fieldDef: FieldDef) { - return NUMERIC_FIELD_TYPES.includes(fieldDef.type) && !fieldDef.array; + return NUMERIC_FIELD_TYPES.includes(this.getEffectiveFieldType(fieldDef.type)) && !fieldDef.array; } private get providerSupportsCaseSensitivity() { diff --git a/packages/schema/src/schema.ts b/packages/schema/src/schema.ts index b6689dace..42ca76075 100644 --- a/packages/schema/src/schema.ts +++ b/packages/schema/src/schema.ts @@ -129,6 +129,7 @@ export type EnumDef = { export type TypeDefDef = { name: string; strict?: boolean; + base?: BuiltinType; fields: Record; attributes?: readonly AttributeApplication[]; }; @@ -284,6 +285,13 @@ export type TypeDefFieldIsArray< Field extends GetTypeDefFields, > = GetTypeDefField['array'] extends true ? true : false; +export type TypeDefIsPrimitive> = GetTypeDef< + Schema, + TypeDef +>['base'] extends string + ? true + : false; + export type FieldIsRelation< Schema extends SchemaDef, Model extends GetModels, diff --git a/packages/sdk/src/model-utils.ts b/packages/sdk/src/model-utils.ts index 5c1370370..367d56bc0 100644 --- a/packages/sdk/src/model-utils.ts +++ b/packages/sdk/src/model-utils.ts @@ -77,7 +77,8 @@ export function isDelegateModel(node: AstNode) { */ export function getOwnedFields(model: DataModel | TypeDef): DataField[] { const fields: DataField[] = [...model.fields]; - for (const mixin of model.mixins) { + const mixins = model.mixins.filter((m) => !m.ref?.base); + for (const mixin of mixins) { if (mixin.ref) { fields.push(...getOwnedFields(mixin.ref)); } diff --git a/packages/sdk/src/prisma/prisma-schema-generator.ts b/packages/sdk/src/prisma/prisma-schema-generator.ts index 8034900ad..8cf5757f3 100644 --- a/packages/sdk/src/prisma/prisma-schema-generator.ts +++ b/packages/sdk/src/prisma/prisma-schema-generator.ts @@ -38,6 +38,7 @@ import { getStringLiteral, isAuthInvocation, isDelegateModel, + isPrimitiveTypeDef, } from '@zenstackhq/language/utils'; import { AstUtils } from 'langium'; import { match } from 'ts-pattern'; @@ -286,7 +287,7 @@ export class PrismaSchemaGenerator { } else if (field.type.reference?.ref) { // model, enum, or type-def if (isTypeDef(field.type.reference.ref)) { - fieldType = 'Json'; + fieldType = isPrimitiveTypeDef(field.type.reference.ref) ? field.type.reference.ref.base! : 'Json'; } else { fieldType = field.type.reference.ref.name; } @@ -304,7 +305,11 @@ export class PrismaSchemaGenerator { const isArray = // typed-JSON fields should be translated to scalar Json type - isTypeDef(field.type.reference?.ref) ? false : field.type.array; + isTypeDef(field.type.reference?.ref) + ? isPrimitiveTypeDef(field.type.reference.ref) + ? field.type.array + : false + : field.type.array; const type = new ModelFieldType(fieldType, isArray, field.type.optional); const attributes = field.attributes diff --git a/packages/sdk/src/ts-schema-generator.ts b/packages/sdk/src/ts-schema-generator.ts index d94393a8c..f18e9ca02 100644 --- a/packages/sdk/src/ts-schema-generator.ts +++ b/packages/sdk/src/ts-schema-generator.ts @@ -44,6 +44,7 @@ import { getRelationName, isDataFieldReference, isLiteAttribute, + isPrimitiveTypeDef, } from '@zenstackhq/language/utils'; import fs from 'node:fs'; import path from 'node:path'; @@ -508,6 +509,9 @@ export class TsSchemaGenerator { // name ts.factory.createPropertyAssignment('name', ts.factory.createStringLiteral(td.name)), + // base + ...(td.base ? [ts.factory.createPropertyAssignment('base', ts.factory.createStringLiteral(td.base))] : []), + // fields ts.factory.createPropertyAssignment( 'fields', @@ -813,9 +817,12 @@ export class TsSchemaGenerator { ): string | number | boolean | unknown[] | { call: string; args: any[] } | { authMember: string[] } | null { if (isLiteralExpr(expr)) { const lit = (expr as LiteralExpr).value; - return fieldType.type === 'Boolean' + // resolve the effective base type for primitive typedef references so that + // numeric/boolean defaults are emitted with the correct JS type + const effectiveType = this.getMappedValueBaseType(fieldType); + return effectiveType === 'Boolean' ? (lit as boolean) - : ['Int', 'Float', 'Decimal', 'BigInt'].includes(fieldType.type!) + : ['Int', 'Float', 'Decimal', 'BigInt'].includes(effectiveType!) ? Number(lit) : lit; } else if (isArrayExpr(expr)) { @@ -838,6 +845,19 @@ export class TsSchemaGenerator { } } + /** + * The primitive type used to map a literal default value to its JS representation. For a + * primitive typedef reference (e.g. `type Age with Int`), this resolves to the base type so + * that e.g. an `Int`-based typedef default is emitted as a number instead of a string. + */ + private getMappedValueBaseType(fieldType: DataFieldType): string | undefined { + if (fieldType.type) { + return fieldType.type; + } + const ref = fieldType.reference?.ref; + return ref && isPrimitiveTypeDef(ref) ? ref.base : undefined; + } + private getMemberAccessChain(expr: MemberAccessExpr): string[] { if (!isMemberAccessExpr(expr.operand)) { return [expr.member.$refText]; @@ -1076,8 +1096,7 @@ export class TsSchemaGenerator { ? ts.factory.createStringLiteral(field.type.type) : field.type.reference ? ts.factory.createStringLiteral(field.type.reference.$refText) - : // `Unsupported` type - ts.factory.createStringLiteral('Unsupported'); + : ts.factory.createStringLiteral('Unsupported'); } private createEnumObject(e: Enum) { diff --git a/packages/zod/src/factory.ts b/packages/zod/src/factory.ts index 6d2c50b8f..6d768e1ca 100644 --- a/packages/zod/src/factory.ts +++ b/packages/zod/src/factory.ts @@ -2,6 +2,7 @@ import { ExpressionUtils, SchemaAccessor, type AttributeApplication, + type BuiltinType, type FieldDef, type GetEnum, type GetEnums, @@ -17,13 +18,14 @@ import type { GetModelFieldsShape, GetModelSchemaShapeWithOptions, GetModelUpdateFieldsShape, - GetTypeDefFieldsShape, + MapTypeDefToZod, ModelSchemaOptions, } from './types'; import { addBigIntValidation, addCustomValidation, addDecimalValidation, + addListValidation, addNumberValidation, addStringValidation, } from './utils'; @@ -361,64 +363,70 @@ class SchemaFactory { return z.lazy(() => this.makeModelSchema(relatedModelName)); } - private makeScalarFieldSchema(fieldDef: FieldDef): z.ZodType { - const { type, attributes } = fieldDef; - - // enum - const enumDef = this.schema.getEnum(type); - if (enumDef) { - return this.applyCardinality(this.makeEnumSchema(type as GetEnums), fieldDef); - } - - // typedef - const typedefDef = this.schema.getTypeDef(type); - if (typedefDef) { - return this.applyCardinality(this.makeTypeSchema(type as GetTypeDefs), fieldDef); - } - - let base: z.ZodType; + private makeScalarSchema(type: BuiltinType, attributes: readonly AttributeApplication[] | undefined): z.ZodType { + let schema: z.ZodType; switch (type) { case 'String': - base = addStringValidation(z.string(), attributes); + schema = addStringValidation(z.string(), attributes); break; case 'Int': - base = addNumberValidation(z.number().int(), attributes); + schema = addNumberValidation(z.number().int(), attributes); break; case 'Float': - base = addNumberValidation(z.number(), attributes); + schema = addNumberValidation(z.number(), attributes); break; case 'Boolean': - base = z.boolean(); + schema = z.boolean(); break; case 'BigInt': - base = addBigIntValidation(z.bigint(), attributes); + schema = addBigIntValidation(z.bigint(), attributes); break; case 'Decimal': - base = z.union([ + schema = z.union([ addNumberValidation(z.number(), attributes) as z.ZodNumber, addDecimalValidation(z.string(), attributes, true) as z.ZodString, addDecimalValidation(z.instanceof(Decimal), attributes, true), ]); break; case 'DateTime': - base = z.union([z.date(), z.iso.datetime()]); + schema = z.union([z.date(), z.iso.datetime()]); break; case 'Bytes': - base = z.instanceof(Uint8Array); + schema = z.instanceof(Uint8Array); break; case 'Json': - base = this.makeJsonSchema(); + schema = this.makeJsonSchema(); break; case 'Unsupported': - base = z.unknown(); + schema = z.unknown(); break; default: { const _exhaustive: never = type as never; throw new SchemaFactoryError(`Unsupported field type: ${_exhaustive}`); } } + return schema; + } + + private makeScalarFieldSchema(def: FieldDef): z.ZodType { + const { type } = def; + // for list fields, field-level attributes (e.g. `@length`) validate the list itself + // rather than its items + const itemAttributes = def.array ? undefined : def.attributes; + + // enum + const enumDef = this.schema.getEnum(type); + if (enumDef) { + return this.applyCardinality(this.makeEnumSchema(type as GetEnums), def); + } - return this.applyCardinality(base, fieldDef); + // typedef + const typedefDef = this.schema.getTypeDef(type); + if (typedefDef) { + return this.applyCardinality(this.makeTypeSchema(type as GetTypeDefs, itemAttributes), def); + } + + return this.applyCardinality(this.makeScalarSchema(type as BuiltinType, itemAttributes), def); } private makeJsonSchema(): z.ZodType { @@ -450,7 +458,7 @@ class SchemaFactory { private applyCardinality(schema: z.ZodType, fieldDef: FieldDef): z.ZodType { let result = schema; if (fieldDef.array) { - result = result.array(); + result = addListValidation(result.array(), fieldDef.attributes); } if (fieldDef.optional) { result = result.nullable().optional(); @@ -460,8 +468,20 @@ class SchemaFactory { makeTypeSchema>( type: Type, - ): z.ZodObject, z.core.$strict> { + attributes?: readonly AttributeApplication[], + ): MapTypeDefToZod { const typeDef = this.schema.requireTypeDef(type); + if (typeDef.base) { + return addCustomValidation( + // validate against both the typedef's own attributes and those of the consuming field + this.makeScalarSchema(typeDef.base, [ + ...(typeDef.fields['this']?.attributes ?? []), + ...(attributes ?? []), + ]), + typeDef.attributes, + ) as unknown as MapTypeDefToZod; + } + const fields: Record = {}; for (const [fieldName, fieldDef] of Object.entries(typeDef.fields)) { @@ -472,7 +492,7 @@ class SchemaFactory { return this.applyDescription( addCustomValidation(shape, typeDef.attributes), typeDef.attributes, - ) as unknown as z.ZodObject, z.core.$strict>; + ) as unknown as MapTypeDefToZod; } makeEnumSchema>( diff --git a/packages/zod/src/types.ts b/packages/zod/src/types.ts index 8258363c1..224df0e30 100644 --- a/packages/zod/src/types.ts +++ b/packages/zod/src/types.ts @@ -127,12 +127,19 @@ type MapTypeDefFieldToZod< FieldType = GetTypeDefFieldType, > = MapFieldTypeToZod; +export type MapTypeDefToZod> = + Schema['typeDefs'] extends Record + ? Schema['typeDefs'][Type]['base'] extends keyof FieldTypeZodMap + ? FieldTypeZodMap[Schema['typeDefs'][Type]['base']] + : z.ZodObject, z.core.$strict> + : never; + type MapFieldTypeToZod = FieldType extends keyof FieldTypeZodMap ? FieldTypeZodMap[FieldType] : FieldType extends GetEnums ? EnumZodType : FieldType extends GetTypeDefs - ? z.ZodObject, z.core.$strict> + ? MapTypeDefToZod : z.ZodUnknown; export type JsonValue = string | number | boolean | JsonObject | JsonArray | null; diff --git a/packages/zod/test/factory.test.ts b/packages/zod/test/factory.test.ts index 33d21d011..9f68154f4 100644 --- a/packages/zod/test/factory.test.ts +++ b/packages/zod/test/factory.test.ts @@ -33,6 +33,7 @@ describe.each([ status: 'ACTIVE', address: null, extId: null, + contacts: [], }; // A fully valid Post object (without relations) @@ -748,6 +749,7 @@ describe.each([ zip: '90210', type: 'RESIDENTIAL', }, + contacts: [], }; expect(userSchema.safeParse(validUser).success).toBe(true); expect( @@ -1680,4 +1682,137 @@ describe.each([ }); }); }); + + describe('custom type primitives', () => { + it('supports model validation using `this` attributes', () => { + const postSchema = factory.makeModelSchema('Post'); + const result = postSchema.safeParse({ ...validPost, tags: ['LOWERCASED', 'Other'] }); + expect(result.success).toBe(true); + expect(result.data?.tags).toMatchObject(['lowercased', 'other']); + }); + + it('supports model validation using `@@validate`', () => { + const userSchema = factory.makeModelSchema('User'); + let result = userSchema.safeParse({ ...validUser, score: 0 }); + expect(result.success).toBe(true); + + result = userSchema.safeParse({ ...validUser, score: 99.0 }); + expect(result.success).toBe(true); + + result = userSchema.safeParse({ ...validUser, score: 100.0 }); + expect(result.success).toBe(false); + + result = userSchema.safeParse({ ...validUser, score: -1.0 }); + expect(result.success).toBe(false); + }); + + it('supports field validation using `this` attributes', () => { + const postTagSchema = factory.makeTypeSchema('PostTag'); + const result = postTagSchema.safeParse('LOWERCASE'); + expect(result.success).toBe(true); + expect(result.data).toBe('lowercase'); + }); + + it('applies string `@length` on `this` field', () => { + const postTagSchema = factory.makeTypeSchema('PostTag'); + expect(postTagSchema.safeParse('').success).toBe(false); + expect(postTagSchema.safeParse('a').success).toBe(true); + expect(postTagSchema.safeParse('abcdefghijkl').success).toBe(true); + expect(postTagSchema.safeParse('abcdefghijklm').success).toBe(false); + }); + + it('supports `@@validate` attributes', () => { + const contactSchema = factory.makeTypeSchema('Contact'); + let result = contactSchema.safeParse('test@mail.com'); + expect(result.success).toBe(true); + expect(result.data).toBe('test@mail.com'); + + result = contactSchema.safeParse('+15555555555'); + expect(result.success).toBe(true); + expect(result.data).toBe('+15555555555'); + + result = contactSchema.safeParse('not-a-contact'); + expect(result.success).toBe(false); + }); + + it('supports multiple `@@validate` attributes', () => { + const contactSchema = factory.makeTypeSchema('Score'); + let result = contactSchema.safeParse(0); + expect(result.success).toBe(true); + expect(result.data).toBe(0); + + result = contactSchema.safeParse(99.0); + expect(result.success).toBe(true); + expect(result.data).toBe(99.0); + + result = contactSchema.safeParse(100.0); + expect(result.success).toBe(false); + + result = contactSchema.safeParse(-1.0); + expect(result.success).toBe(false); + }); + + describe('lists', () => { + it('applies field attributes to the list', () => { + const postSchema = factory.makeModelSchema('Post'); + let result = postSchema.safeParse({ ...validPost, tags: ['a', 'b'] }); + expect(result.success).toBe(true); + + result = postSchema.safeParse({ ...validPost, tags: ['a', 'b', 'c'] }); + expect(result.success).toBe(true); + + result = postSchema.safeParse({ ...validPost, tags: ['a'] }); + expect(result.success).toBe(false); + + result = postSchema.safeParse({ ...validPost, tags: ['a', 'b', 'c', 'd'] }); + expect(result.success).toBe(false); + }); + + it('applies both the list length and the element string length', () => { + const postSchema = factory.makeModelSchema('Post'); + let result = postSchema.safeParse({ ...validPost, tags: ['a', 'abcdefghijkl'] }); + expect(result.success).toBe(true); + + result = postSchema.safeParse({ ...validPost, tags: ['a', ''] }); + expect(result.success).toBe(false); + + result = postSchema.safeParse({ ...validPost, tags: ['a', 'abcdefghijklm'] }); + expect(result.success).toBe(false); + + result = postSchema.safeParse({ ...validPost, tags: ['a'] }); + expect(result.success).toBe(false); + + result = postSchema.safeParse({ ...validPost, tags: ['a', 'b', 'c', 'd'] }); + expect(result.success).toBe(false); + }); + + it('does not apply string lengths to lists', () => { + const holderSchema = factory.makeModelSchema('Holder'); + let result = holderSchema.safeParse({ + id: 0, + labels: [], + }); + expect(result.success).toBe(true); + + result = holderSchema.safeParse({ + id: 0, + labels: [''], + }); + expect(result.success).toBe(false); + + result = holderSchema.safeParse({ + id: 0, + labels: ['a'], + }); + expect(result.success).toBe(true); + }); + + it('applies typedef attributes to each item', () => { + const postSchema = factory.makeModelSchema('Post'); + const result = postSchema.safeParse({ ...validPost, tags: ['A', 'B'] }); + expect(result.success).toBe(true); + expect(result.data?.tags).toEqual(['a', 'b']); + }); + }); + }); }); diff --git a/packages/zod/test/schema/schema-lite.ts b/packages/zod/test/schema/schema-lite.ts index 29472e365..71f5b069e 100644 --- a/packages/zod/test/schema/schema-lite.ts +++ b/packages/zod/test/schema/schema-lite.ts @@ -49,13 +49,12 @@ export class SchemaType implements SchemaDef { }, age: { name: "age", - type: "Int", - attributes: [{ name: "@gt", args: [{ name: "value", value: ExpressionUtils.literal(0) }] }, { name: "@lte", args: [{ name: "value", value: ExpressionUtils.literal(150) }] }] as readonly AttributeApplication[] + type: "Age", + attributes: [{ name: "@lte", args: [{ name: "value", value: ExpressionUtils.literal(150) }] }] as readonly AttributeApplication[] }, score: { name: "score", - type: "Float", - attributes: [{ name: "@gte", args: [{ name: "value", value: ExpressionUtils.literal(0.0) }] }, { name: "@lt", args: [{ name: "value", value: ExpressionUtils.literal(100.0) }] }] as readonly AttributeApplication[] + type: "Score" }, bigNum: { name: "bigNum", @@ -118,6 +117,11 @@ export class SchemaType implements SchemaDef { type: "Post", array: true, relation: { opposite: "author" } + }, + contacts: { + name: "contacts", + type: "Contact", + array: true } }, attributes: [ @@ -149,8 +153,9 @@ export class SchemaType implements SchemaDef { }, tags: { name: "tags", - type: "String", - array: true + type: "PostTag", + array: true, + attributes: [{ name: "@length", args: [{ name: "min", value: ExpressionUtils.literal(2) }, { name: "max", value: ExpressionUtils.literal(3) }] }] as readonly AttributeApplication[] }, author: { name: "author", @@ -172,6 +177,27 @@ export class SchemaType implements SchemaDef { id: { type: "String" } } }, + Holder: { + name: "Holder", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }] as readonly AttributeApplication[], + default: ExpressionUtils.call("autoincrement") as FieldDefault + }, + labels: { + name: "labels", + type: "Label", + array: true + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "Int" } + } + }, Product: { name: "Product", fields: { @@ -347,6 +373,66 @@ export class SchemaType implements SchemaDef { { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.binary(ExpressionUtils.field("zip"), "==", ExpressionUtils._null()), "||", ExpressionUtils.binary(ExpressionUtils.call("length", [ExpressionUtils.field("zip")]), "==", ExpressionUtils.literal(5))) }, { name: "message", value: ExpressionUtils.literal("Zip code must be exactly 5 characters") }, { name: "path", value: ExpressionUtils.array("String", [ExpressionUtils.literal("zip")]) }] }, { name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("A mailing address") }] } ] as readonly AttributeApplication[] + }, + Label: { + name: "Label", + base: "String", + fields: { + this: { + name: "this", + type: "String", + attributes: [{ name: "@length", args: [{ name: "min", value: ExpressionUtils.literal(1) }, { name: "max", value: ExpressionUtils.literal(12) }] }] as readonly AttributeApplication[] + } + } + }, + Age: { + name: "Age", + base: "Int", + fields: { + this: { + name: "this", + type: "Int", + attributes: [{ name: "@gt", args: [{ name: "value", value: ExpressionUtils.literal(0) }] }] as readonly AttributeApplication[] + } + } + }, + Score: { + name: "Score", + base: "Float", + fields: { + this: { + name: "this", + type: "Float" + } + }, + attributes: [ + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils._this(), ">=", ExpressionUtils.literal(0.0)) }] }, + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils._this(), "<", ExpressionUtils.literal(100.0)) }] } + ] as readonly AttributeApplication[] + }, + Contact: { + name: "Contact", + base: "String", + fields: { + this: { + name: "this", + type: "String" + } + }, + attributes: [ + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.call("isPhone", [ExpressionUtils._this()]), "||", ExpressionUtils.call("isEmail", [ExpressionUtils._this()])) }] } + ] as readonly AttributeApplication[] + }, + PostTag: { + name: "PostTag", + base: "String", + fields: { + this: { + name: "this", + type: "String", + attributes: [{ name: "@lower" }, { name: "@length", args: [{ name: "min", value: ExpressionUtils.literal(1) }, { name: "max", value: ExpressionUtils.literal(12) }] }] as readonly AttributeApplication[] + } + } } } as const; enums = { diff --git a/packages/zod/test/schema/schema.ts b/packages/zod/test/schema/schema.ts index 275b25dcf..c4735be9d 100644 --- a/packages/zod/test/schema/schema.ts +++ b/packages/zod/test/schema/schema.ts @@ -49,13 +49,12 @@ export class SchemaType implements SchemaDef { }, age: { name: "age", - type: "Int", - attributes: [{ name: "@gt", args: [{ name: "value", value: ExpressionUtils.literal(0) }] }, { name: "@lte", args: [{ name: "value", value: ExpressionUtils.literal(150) }] }] as readonly AttributeApplication[] + type: "Age", + attributes: [{ name: "@lte", args: [{ name: "value", value: ExpressionUtils.literal(150) }] }] as readonly AttributeApplication[] }, score: { name: "score", - type: "Float", - attributes: [{ name: "@gte", args: [{ name: "value", value: ExpressionUtils.literal(0.0) }] }, { name: "@lt", args: [{ name: "value", value: ExpressionUtils.literal(100.0) }] }] as readonly AttributeApplication[] + type: "Score" }, bigNum: { name: "bigNum", @@ -119,6 +118,11 @@ export class SchemaType implements SchemaDef { type: "Post", array: true, relation: { opposite: "author" } + }, + contacts: { + name: "contacts", + type: "Contact", + array: true } }, attributes: [ @@ -150,8 +154,9 @@ export class SchemaType implements SchemaDef { }, tags: { name: "tags", - type: "String", - array: true + type: "PostTag", + array: true, + attributes: [{ name: "@length", args: [{ name: "min", value: ExpressionUtils.literal(2) }, { name: "max", value: ExpressionUtils.literal(3) }] }] as readonly AttributeApplication[] }, author: { name: "author", @@ -174,6 +179,27 @@ export class SchemaType implements SchemaDef { id: { type: "String" } } }, + Holder: { + name: "Holder", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }] as readonly AttributeApplication[], + default: ExpressionUtils.call("autoincrement") as FieldDefault + }, + labels: { + name: "labels", + type: "Label", + array: true + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "Int" } + } + }, Product: { name: "Product", fields: { @@ -353,6 +379,66 @@ export class SchemaType implements SchemaDef { { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.binary(ExpressionUtils.field("zip"), "==", ExpressionUtils._null()), "||", ExpressionUtils.binary(ExpressionUtils.call("length", [ExpressionUtils.field("zip")]), "==", ExpressionUtils.literal(5))) }, { name: "message", value: ExpressionUtils.literal("Zip code must be exactly 5 characters") }, { name: "path", value: ExpressionUtils.array("String", [ExpressionUtils.literal("zip")]) }] }, { name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("A mailing address") }] } ] as readonly AttributeApplication[] + }, + Label: { + name: "Label", + base: "String", + fields: { + this: { + name: "this", + type: "String", + attributes: [{ name: "@length", args: [{ name: "min", value: ExpressionUtils.literal(1) }, { name: "max", value: ExpressionUtils.literal(12) }] }] as readonly AttributeApplication[] + } + } + }, + Age: { + name: "Age", + base: "Int", + fields: { + this: { + name: "this", + type: "Int", + attributes: [{ name: "@gt", args: [{ name: "value", value: ExpressionUtils.literal(0) }] }] as readonly AttributeApplication[] + } + } + }, + Score: { + name: "Score", + base: "Float", + fields: { + this: { + name: "this", + type: "Float" + } + }, + attributes: [ + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils._this(), ">=", ExpressionUtils.literal(0.0)) }] }, + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils._this(), "<", ExpressionUtils.literal(100.0)) }] } + ] as readonly AttributeApplication[] + }, + Contact: { + name: "Contact", + base: "String", + fields: { + this: { + name: "this", + type: "String" + } + }, + attributes: [ + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.call("isPhone", [ExpressionUtils._this()]), "||", ExpressionUtils.call("isEmail", [ExpressionUtils._this()])) }] } + ] as readonly AttributeApplication[] + }, + PostTag: { + name: "PostTag", + base: "String", + fields: { + this: { + name: "this", + type: "String", + attributes: [{ name: "@lower" }, { name: "@length", args: [{ name: "min", value: ExpressionUtils.literal(1) }, { name: "max", value: ExpressionUtils.literal(12) }] }] as readonly AttributeApplication[] + } + } } } as const; enums = { diff --git a/packages/zod/test/schema/schema.zmodel b/packages/zod/test/schema/schema.zmodel index 3ceba9c35..88d2280f2 100644 --- a/packages/zod/test/schema/schema.zmodel +++ b/packages/zod/test/schema/schema.zmodel @@ -34,8 +34,8 @@ model User { username String @length(3, 50) website String? @url code String @startsWith("USR") - age Int @gt(0) @lte(150) - score Float @gte(0.0) @lt(100.0) + age Age @lte(150) + score Score bigNum BigInt @gte(0) balance Decimal @gt(0) active Boolean @@ -48,20 +48,26 @@ model User { status Status address Address? @json posts Post[] + contacts Contact[] @@validate(age >= 18, "Must be adult", ["age"]) @@meta("description", "A user of the system") } model Post { - id String @id @default(cuid()) + id String @id @default(cuid()) title String published Boolean - tags String[] - author User? @relation(fields: [authorId], references: [id]) + tags PostTag[] @length(2, 3) + author User? @relation(fields: [authorId], references: [id]) authorId String? } +model Holder { + id Int @id @default(autoincrement()) + labels Label[] +} + // --- Computed fields --- model Product { id String @id @default(cuid()) @@ -89,3 +95,29 @@ model Image extends Asset { format String width Int } + +// --- Primitives --- +type Label with String { + this String @length(1, 12) +} + +type Age with Int { + this Int @gt(0) +} + +type Score with Float { + this Float + + @@validate(this >= 0.0) + @@validate(this < 100.0) +} + +type Contact with String { + this String + + @@validate(isPhone(this) || isEmail(this)) +} + +type PostTag with String { + this String @lower @length(1, 12) +} diff --git a/tests/e2e/orm/client-api/custom-type-primitive.test.ts b/tests/e2e/orm/client-api/custom-type-primitive.test.ts new file mode 100644 index 000000000..67f745b7e --- /dev/null +++ b/tests/e2e/orm/client-api/custom-type-primitive.test.ts @@ -0,0 +1,172 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import type { ClientContract } from '@zenstackhq/orm'; +import { schema } from '../schemas/custom-type-primitive/schema'; +import { createTestClient } from '@zenstackhq/testtools'; + +describe('Custom type primitive tests', () => { + let client: ClientContract; + + beforeEach(async () => { + client = await createTestClient(schema, { + provider: 'postgresql', + }); + }); + + afterEach(async () => { + await client?.$disconnect(); + }); + + it('works with scalars', async () => { + await expect( + client.user.create({ + data: { + name: 'test', + }, + }), + ).resolves.toMatchObject({ + name: 'test', + }); + + await expect( + client.user.create({ + data: { + name: 't', + }, + }), + ).rejects.toThrow(/Too small/); + + await expect( + client.user.create({ + data: { + name: 'test', + age: 17, + }, + }), + ).rejects.toThrow(/Too small/); + }); + + it('works with arrays', async () => { + await expect( + client.user.create({ + data: { + name: 'test', + contacts: ['+15555555555'], + }, + }), + ).resolves.toMatchObject({ + name: 'test', + contacts: ['+15555555555'], + }); + + await expect( + client.user.create({ + data: { + name: 'test', + contacts: ['15555555555'], + }, + }), + ).rejects.toThrow(/Invalid E.164/); + + await expect( + client.user.create({ + data: { + name: 'test', + posts: { + create: { + tags: ['abc', '123'], + }, + }, + }, + + include: { + posts: true, + }, + }), + ).resolves.toMatchObject({ + name: 'test', + posts: [ + { + tags: ['abc', '123'], + }, + ], + }); + + await expect( + client.user.create({ + data: { + name: 'test', + posts: { + create: { + tags: [''], + }, + }, + }, + }), + ).rejects.toThrow(/Too small/); + + await expect( + client.user.create({ + data: { + name: 'test', + posts: { + create: { + tags: ['', ''], + }, + }, + }, + }), + ).rejects.toThrow(/Too small/); + + await expect( + client.user.create({ + data: { + name: 'test', + posts: { + create: { + tags: ['a'], + }, + }, + }, + }), + ).rejects.toThrow(/Too small/); + + await expect( + client.user.create({ + data: { + name: 'test', + posts: { + create: { + tags: [], + }, + }, + }, + }), + ).rejects.toThrow(/Too small/); + + await expect( + client.user.create({ + data: { + name: 'test', + posts: { + create: { + tags: ['abc', '123', 'xyz', 'def'], + }, + }, + }, + }), + ).rejects.toThrow(/Too big/); + + await expect( + client.user.create({ + data: { + name: 'test', + posts: { + create: { + tags: ['abcdefghijklm'], + }, + }, + }, + }), + ).rejects.toThrow(/Too big/); + }); +}); diff --git a/tests/e2e/orm/client-api/procedures.test.ts b/tests/e2e/orm/client-api/procedures.test.ts index a92e1e87a..cbace96fb 100644 --- a/tests/e2e/orm/client-api/procedures.test.ts +++ b/tests/e2e/orm/client-api/procedures.test.ts @@ -76,6 +76,27 @@ describe('Procedures tests', () => { }, }); }, + + getAge: async ({ client, args: { id } }) => { + const user = await client.$procs.getUser({ + args: { + id, + }, + }); + return user.age; + }, + + setAge: async ({ client, args: { id, age } }) => { + return await client.user.update({ + data: { + age, + }, + + where: { + id, + }, + }); + }, }, }); }); @@ -242,4 +263,36 @@ describe('Procedures tests', () => { }), ).rejects.toThrow(/Unrecognized key: "unknown"/); }); + + it('supports primitive type defs', async () => { + const user = await client.$procs.signUp({ args: { name: 'Alice' } }); + await expect( + client.$procs.setAge({ + args: { + id: user.id, + age: 18, + }, + }), + ).resolves.toMatchObject({ + id: user.id, + age: 18, + }); + + await expect( + client.$procs.getAge({ + args: { + id: user.id, + }, + }), + ).resolves.toBe(18); + + await expect( + client.$procs.setAge({ + args: { + id: user.id, + age: -1, + }, + }), + ).rejects.toThrow(/Validation error: Too small/); + }); }); diff --git a/tests/e2e/orm/schemas/custom-type-primitive/schema.ts b/tests/e2e/orm/schemas/custom-type-primitive/schema.ts new file mode 100644 index 000000000..92bdd74ce --- /dev/null +++ b/tests/e2e/orm/schemas/custom-type-primitive/schema.ts @@ -0,0 +1,137 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +/* eslint-disable */ + +import { type SchemaDef, type AttributeApplication, type FieldDefault, ExpressionUtils } from "@zenstackhq/schema"; +export class SchemaType implements SchemaDef { + provider = { + type: "postgresql" + } as const; + models = { + User: { + name: "User", + fields: { + id: { + name: "id", + type: "String", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("nanoid") }] }] as readonly AttributeApplication[], + default: ExpressionUtils.call("nanoid") as FieldDefault + }, + name: { + name: "name", + type: "UserName" + }, + contacts: { + name: "contacts", + type: "Contact", + array: true + }, + age: { + name: "age", + type: "Age", + optional: true, + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.literal(18) }] }] as readonly AttributeApplication[], + default: 18 as FieldDefault + }, + posts: { + name: "posts", + type: "Post", + array: true, + relation: { opposite: "author" } + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "String" } + } + }, + Post: { + name: "Post", + fields: { + id: { + name: "id", + type: "String", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("nanoid") }] }] as readonly AttributeApplication[], + default: ExpressionUtils.call("nanoid") as FieldDefault + }, + authorId: { + name: "authorId", + type: "String", + foreignKeyFor: [ + "author" + ] as readonly string[] + }, + author: { + name: "author", + type: "User", + attributes: [{ name: "@relation", args: [{ name: "fields", value: ExpressionUtils.array("String", [ExpressionUtils.field("authorId")]) }, { name: "references", value: ExpressionUtils.array("String", [ExpressionUtils.field("id")]) }] }] as readonly AttributeApplication[], + relation: { opposite: "posts", fields: ["authorId"], references: ["id"] } + }, + tags: { + name: "tags", + type: "PostTag", + array: true, + attributes: [{ name: "@length", args: [{ name: "min", value: ExpressionUtils.literal(2) }, { name: "max", value: ExpressionUtils.literal(3) }] }] as readonly AttributeApplication[] + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "String" } + } + } + } as const; + typeDefs = { + UserName: { + name: "UserName", + base: "String", + fields: { + this: { + name: "this", + type: "String", + attributes: [{ name: "@length", args: [{ name: "min", value: ExpressionUtils.literal(2) }, { name: "max", value: ExpressionUtils.literal(16) }] }] as readonly AttributeApplication[] + } + } + }, + Contact: { + name: "Contact", + base: "String", + fields: { + this: { + name: "this", + type: "String", + attributes: [{ name: "@phone" }] as readonly AttributeApplication[] + } + } + }, + Age: { + name: "Age", + base: "Int", + fields: { + this: { + name: "this", + type: "Int", + attributes: [{ name: "@gte", args: [{ name: "value", value: ExpressionUtils.literal(18) }] }] as readonly AttributeApplication[] + } + } + }, + PostTag: { + name: "PostTag", + base: "String", + fields: { + this: { + name: "this", + type: "String", + attributes: [{ name: "@length", args: [{ name: "min", value: ExpressionUtils.literal(1) }, { name: "max", value: ExpressionUtils.literal(12) }] }] as readonly AttributeApplication[] + } + } + } + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/orm/schemas/custom-type-primitive/schema.zmodel b/tests/e2e/orm/schemas/custom-type-primitive/schema.zmodel new file mode 100644 index 000000000..e08cf0305 --- /dev/null +++ b/tests/e2e/orm/schemas/custom-type-primitive/schema.zmodel @@ -0,0 +1,35 @@ +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model User { + id String @id @default(nanoid()) + name UserName + contacts Contact[] + age Age? @default(18) + posts Post[] +} + +model Post { + id String @id @default(nanoid()) + authorId String + author User @relation(fields: [authorId], references: [id]) + tags PostTag[] @length(2, 3) +} + +type UserName with String { + this String @length(2, 16) +} + +type Contact with String { + this String @phone +} + +type Age with Int { + this Int @gte(18) +} + +type PostTag with String { + this String @length(1, 12) +} diff --git a/tests/e2e/orm/schemas/procedures/schema.ts b/tests/e2e/orm/schemas/procedures/schema.ts index 9b84da04c..326f97ad8 100644 --- a/tests/e2e/orm/schemas/procedures/schema.ts +++ b/tests/e2e/orm/schemas/procedures/schema.ts @@ -38,6 +38,13 @@ export class SchemaType implements SchemaDef { type: "Profile", optional: true, attributes: [{ name: "@json" }] as readonly AttributeApplication[] + }, + age: { + name: "age", + type: "Age", + optional: true, + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.literal(18) }] }] as readonly AttributeApplication[], + default: 18 as FieldDefault } }, idFields: ["id"], @@ -84,6 +91,17 @@ export class SchemaType implements SchemaDef { { name: "@@strict" } ] as readonly AttributeApplication[], strict: true + }, + Age: { + name: "Age", + base: "Int", + fields: { + this: { + name: "this", + type: "Int", + attributes: [{ name: "@gte", args: [{ name: "value", value: ExpressionUtils.literal(0) }] }] as readonly AttributeApplication[] + } + } } } as const; enums = { @@ -127,6 +145,20 @@ export class SchemaType implements SchemaDef { params: {}, returnType: "Overview" }, + getAge: { + params: { + id: { name: "id", type: "Int" } + }, + returnType: "Age" + }, + setAge: { + params: { + id: { name: "id", type: "Int" }, + age: { name: "age", type: "Age" } + }, + returnType: "User", + mutation: true + }, createMultiple: { params: { names: { name: "names", array: true, type: "String" } diff --git a/tests/e2e/orm/schemas/procedures/schema.zmodel b/tests/e2e/orm/schemas/procedures/schema.zmodel index 66f9c6804..d90c2b7ca 100644 --- a/tests/e2e/orm/schemas/procedures/schema.zmodel +++ b/tests/e2e/orm/schemas/procedures/schema.zmodel @@ -10,9 +10,9 @@ enum Role { type Overview { userIds Int[] - total Int - roles Role[] - meta Json? + total Int + roles Role[] + meta Json? } type Profile { @@ -21,11 +21,16 @@ type Profile { @@strict } +type Age with Int { + this Int @gte(0) +} + model User { - id Int @id @default(autoincrement()) - name String @unique - role Role @default(USER) + id Int @id @default(autoincrement()) + name String @unique + role Role @default(USER) profile Profile? @json + age Age? @default(18) } procedure getUser(id: Int): User @@ -33,5 +38,7 @@ procedure listUsers(): User[] mutation procedure signUp(name: String, role: Role?): User mutation procedure setAdmin(userId: Int): Void procedure getOverview(): Overview +procedure getAge(id: Int): Age +mutation procedure setAge(id: Int, age: Age): User mutation procedure createMultiple(names: String[]): User[] mutation procedure updateProfile(userId: Int, profile: Profile): Void From 151524b17e743d60d9163a23aabb38baaeefd6b0 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Thu, 22 Oct 2026 11:40:16 -0700 Subject: [PATCH 09/18] chore(deps): bump dependencies flagged by Dependabot (#2879) Co-authored-by: Claude Opus 5.5 (1M context) --- package.json | 2 +- packages/auth-adapters/better-auth/README.md | 4 +- .../auth-adapters/better-auth/package.json | 6 +- .../better-auth/src/schema-generator.ts | 16 +- packages/cli/package.json | 2 +- packages/ide/vscode/package.json | 2 +- packages/orm/package.json | 4 +- packages/server/package.json | 2 +- pnpm-lock.yaml | 2261 +++++++++-------- pnpm-workspace.yaml | 2 +- samples/taskforge/package.json | 4 +- tests/e2e/package.json | 2 +- 12 files changed, 1282 insertions(+), 1025 deletions(-) diff --git a/package.json b/package.json index c1a28730e..919d7bbbe 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "overrides": { "cookie@<0.7.0": ">=0.7.0", "lodash@>=4.0.0 <=4.17.23": ">=4.18.0", - "@better-auth/core": "1.4.19" + "@better-auth/core": "1.7.12" }, "patchedDependencies": { "jsonschema@1.5.0": "patches/jsonschema@1.5.0.patch" diff --git a/packages/auth-adapters/better-auth/README.md b/packages/auth-adapters/better-auth/README.md index fc75d2a49..8af6f81a2 100644 --- a/packages/auth-adapters/better-auth/README.md +++ b/packages/auth-adapters/better-auth/README.md @@ -26,8 +26,8 @@ const auth = new BetterAuth({ ## Schema generation -You can use the `@better-auth/cli` to populate better-auth's data models into your ZModel schema. +You can use the `auth` CLI (formerly `@better-auth/cli`) to populate better-auth's data models into your ZModel schema. ```bash -npx @better-auth/cli generate +npx auth generate ``` diff --git a/packages/auth-adapters/better-auth/package.json b/packages/auth-adapters/better-auth/package.json index 7d5f27207..c89d8968b 100644 --- a/packages/auth-adapters/better-auth/package.json +++ b/packages/auth-adapters/better-auth/package.json @@ -65,15 +65,15 @@ "better-auth": "^1.3.0" }, "devDependencies": { - "@better-auth/cli": "1.4.19", - "@better-auth/core": "1.4.19", + "@better-auth/core": "1.7.12", + "auth": "1.7.12", "@types/tmp": "catalog:", "@zenstackhq/cli": "workspace:*", "@zenstackhq/eslint-config": "workspace:*", "@zenstackhq/tsdown-config": "workspace:*", "@zenstackhq/typescript-config": "workspace:*", "@zenstackhq/vitest-config": "workspace:*", - "better-auth": "1.4.19", + "better-auth": "1.7.12", "kysely": "catalog:", "tmp": "catalog:" }, diff --git a/packages/auth-adapters/better-auth/src/schema-generator.ts b/packages/auth-adapters/better-auth/src/schema-generator.ts index bea59baa8..e4c8bb391 100644 --- a/packages/auth-adapters/better-auth/src/schema-generator.ts +++ b/packages/auth-adapters/better-auth/src/schema-generator.ts @@ -97,15 +97,7 @@ async function updateSchema( const supportsArrays = getSupportsArrays(config); for (const [name, table] of Object.entries(tables)) { - const c = addOrUpdateModel( - name, - table, - zmodel, - tables, - toManyRelations, - supportsArrays, - !!options.advanced?.database?.useNumberId, - ); + const c = addOrUpdateModel(name, table, zmodel, tables, toManyRelations, supportsArrays, useNumberId(options)); changed = changed || c; } @@ -583,3 +575,9 @@ function createStringAttributeArg(value: string) { arg.value = expr; return arg; } + +function useNumberId(options: BetterAuthOptions) { + const dbOptions = options.advanced?.database as { useNumberId?: boolean; generateId?: unknown } | undefined; + // `useNumberId` was replaced by `generateId: 'serial'` in better-auth 1.7 + return !!dbOptions?.useNumberId || dbOptions?.generateId === 'serial'; +} diff --git a/packages/cli/package.json b/packages/cli/package.json index 7c48dda00..65b7e06cf 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -61,7 +61,7 @@ "@zenstackhq/plugin-policy": "workspace:*", "@zenstackhq/server": "workspace:*", "@hono/node-server": "^1.13.8", - "hono": "^4.11.10", + "hono": "^4.13.7", "chokidar": "^5.0.0", "colors": "1.4.0", "commander": "^8.3.0", diff --git a/packages/ide/vscode/package.json b/packages/ide/vscode/package.json index 41ce04d88..a0dbf9799 100644 --- a/packages/ide/vscode/package.json +++ b/packages/ide/vscode/package.json @@ -34,7 +34,7 @@ "@zenstackhq/language": "workspace:*", "langium": "catalog:", "mixpanel": "^0.18.0", - "uuid": "^11.1.0", + "uuid": "^11.1.1", "vscode-languageclient": "^9.0.1", "vscode-languageserver": "^9.0.1", "vscode-uri": "^3.1.0", diff --git a/packages/orm/package.json b/packages/orm/package.json index 71e437f5c..31c392fee 100644 --- a/packages/orm/package.json +++ b/packages/orm/package.json @@ -130,12 +130,12 @@ "decimal.js": "catalog:", "json-stable-stringify": "^1.3.0", "kysely": "catalog:", - "nanoid": "^5.0.9", + "nanoid": "^5.1.16", "postgres-array": "^3.0.4", "toposort": "^2.0.2", "ts-pattern": "catalog:", "ulid": "^3.0.0", - "uuid": "^11.0.5", + "uuid": "^11.1.1", "zod-validation-error": "catalog:" }, "peerDependencies": { diff --git a/packages/server/package.json b/packages/server/package.json index e4062f563..2a76af5bc 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -158,7 +158,7 @@ "fastify": "^5.6.1", "fastify-plugin": "^5.1.0", "h3": "^1.15.5", - "hono": "^4.11.10", + "hono": "^4.13.7", "next": "catalog:", "nuxt": "catalog:", "supertest": "^7.1.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5edb76121..8f9f69741 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,7 +53,7 @@ catalogs: version: 4.2.1 mysql2: specifier: ^3.16.1 - version: 3.16.1 + version: 3.24.6 next: specifier: 16.1.6 version: 16.1.6 @@ -72,15 +72,12 @@ catalogs: react-dom: specifier: 19.2.0 version: 19.2.0 - sql.js: - specifier: ^1.13.0 - version: 1.13.0 svelte: specifier: 5.53.5 version: 5.53.5 tmp: - specifier: ^0.2.4 - version: 0.2.5 + specifier: ^0.2.6 + version: 0.2.7 ts-pattern: specifier: ^5.7.1 version: 5.7.1 @@ -103,7 +100,7 @@ catalogs: overrides: cookie@<0.7.0: '>=0.7.0' lodash@>=4.0.0 <=4.17.23: '>=4.18.0' - '@better-auth/core': 1.4.19 + '@better-auth/core': 1.7.12 patchedDependencies: jsonschema@1.5.0: @@ -122,10 +119,10 @@ importers: version: 20.19.24 '@vitest/coverage-v8': specifier: ^4.0.16 - version: 4.0.16(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0)) + version: 4.0.16(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0)) eslint: specifier: ~9.29.0 - version: 9.29.0(jiti@2.6.1) + version: 9.29.0(jiti@2.7.0) glob: specifier: ^11.1.0 version: 11.1.0 @@ -161,10 +158,10 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.34.1 - version: 8.34.1(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) + version: 8.34.1(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) vitest: specifier: ^4.0.14 - version: 4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0) + version: 4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0) yaml: specifier: ^2.8.0 version: 2.8.0 @@ -184,12 +181,9 @@ importers: specifier: 'catalog:' version: 5.7.1 devDependencies: - '@better-auth/cli': - specifier: 1.4.19 - version: 1.4.19(@better-fetch/fetch@1.1.21)(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(@types/better-sqlite3@7.6.13)(@types/sql.js@1.4.9)(better-call@1.1.8(zod@4.3.6))(bun-types@1.3.3)(jose@6.2.3)(kysely@0.29.0)(magicast@0.5.1)(mysql2@3.16.1)(nanostores@1.3.0)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sql.js@1.13.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@25.5.2)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)) '@better-auth/core': - specifier: 1.4.19 - version: 1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) + specifier: 1.7.12 + version: 1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) '@types/tmp': specifier: 'catalog:' version: 0.2.6 @@ -208,21 +202,24 @@ importers: '@zenstackhq/vitest-config': specifier: workspace:* version: link:../../config/vitest-config + auth: + specifier: 1.7.12 + version: 1.7.12(@better-fetch/fetch@1.3.2)(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(better-call@1.4.4(zod@4.6.7))(giget@3.1.2)(jose@6.2.3)(kysely@0.29.0)(magicast@0.5.1)(mysql2@3.24.6(@types/node@25.5.2))(nanostores@1.3.0)(next@16.1.6(@babel/core@7.29.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@25.5.2)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)) better-auth: - specifier: 1.4.19 - version: 1.4.19(40c55c74539eb2c07288211f764cce6b) + specifier: 1.7.12 + version: 1.7.12(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(mysql2@3.24.6(@types/node@25.5.2))(next@16.1.6(@babel/core@7.29.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@25.5.2)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)) kysely: specifier: 'catalog:' version: 0.29.0 tmp: specifier: 'catalog:' - version: 0.2.5 + version: 0.2.7 packages/cli: dependencies: '@hono/node-server': specifier: ^1.13.8 - version: 1.19.17(hono@4.12.2) + version: 1.19.17(hono@4.13.13) '@zenstackhq/common-helpers': specifier: workspace:* version: link:../common-helpers @@ -266,8 +263,8 @@ importers: specifier: ^9.6.0 version: 9.6.0 hono: - specifier: ^4.11.10 - version: 4.12.2 + specifier: ^4.13.7 + version: 4.13.13 jiti: specifier: ^2.6.1 version: 2.6.1 @@ -279,7 +276,7 @@ importers: version: 0.18.1 mysql2: specifier: 'catalog:' - version: 3.16.1 + version: 3.24.6(@types/node@20.19.24) ora: specifier: ^5.4.1 version: 5.4.1 @@ -340,7 +337,7 @@ importers: version: link:../config/vitest-config tmp: specifier: 'catalog:' - version: 0.2.5 + version: 0.2.7 packages/clients/client-helpers: dependencies: @@ -559,8 +556,8 @@ importers: specifier: ^0.18.0 version: 0.18.1 uuid: - specifier: ^11.1.0 - version: 11.1.0 + specifier: ^11.1.1 + version: 11.1.1 vscode-languageclient: specifier: ^9.0.1 version: 9.0.1 @@ -637,7 +634,7 @@ importers: version: 4.2.1 tmp: specifier: 'catalog:' - version: 0.2.5 + version: 0.2.7 packages/orm: dependencies: @@ -670,10 +667,10 @@ importers: version: 0.29.0 mysql2: specifier: 'catalog:' - version: 3.16.1 + version: 3.24.6(@types/node@20.19.24) nanoid: - specifier: ^5.0.9 - version: 5.0.9 + specifier: ^5.1.16 + version: 5.1.16 pg: specifier: 'catalog:' version: 8.16.3 @@ -693,8 +690,8 @@ importers: specifier: ^3.0.0 version: 3.0.0 uuid: - specifier: ^11.0.5 - version: 11.0.5 + specifier: ^11.1.1 + version: 11.1.1 zod-validation-error: specifier: 'catalog:' version: 4.0.1(zod@4.1.12) @@ -939,14 +936,14 @@ importers: specifier: ^1.15.5 version: 1.15.5 hono: - specifier: ^4.11.10 - version: 4.12.2 + specifier: ^4.13.7 + version: 4.13.13 next: specifier: 'catalog:' version: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) nuxt: specifier: 'catalog:' - version: 4.3.1(175990d052329d8b0154ca336b6ac7a3) + version: 4.3.1(94603ee5b6c8044fb0adad8a8009d510) supertest: specifier: ^7.1.4 version: 7.1.4 @@ -985,7 +982,7 @@ importers: version: 0.29.0 mysql2: specifier: 'catalog:' - version: 3.16.1 + version: 3.24.6(@types/node@20.19.24) pg: specifier: 'catalog:' version: 8.16.3 @@ -994,7 +991,7 @@ importers: version: 6.19.0(magicast@0.3.5)(typescript@6.0.3) tmp: specifier: 'catalog:' - version: 0.2.5 + version: 0.2.7 ts-pattern: specifier: 'catalog:' version: 5.7.1 @@ -1108,10 +1105,10 @@ importers: version: link:../../packages/cli eslint: specifier: ^9 - version: 9.29.0(jiti@2.6.1) + version: 9.29.0(jiti@2.7.0) eslint-config-next: specifier: 16.0.1 - version: 16.0.1(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) + version: 16.0.1(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) tailwindcss: specifier: ^4 version: 4.1.16 @@ -1123,7 +1120,7 @@ importers: dependencies: '@tailwindcss/vite': specifier: ^4.1.18 - version: 4.1.18(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 4.1.18(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) '@tanstack/vue-query': specifier: 'catalog:' version: 5.90.2(vue@3.5.22(typescript@6.0.3)) @@ -1147,7 +1144,7 @@ importers: version: 2.0.8 nuxt: specifier: 'catalog:' - version: 4.3.1(dd35fc409f8d464b401077cb817b832f) + version: 4.3.1(6ecebe4c14ebf76ad94dfae71c880d6e) tailwindcss: specifier: ^4.1.18 version: 4.1.18 @@ -1231,16 +1228,16 @@ importers: devDependencies: '@sveltejs/adapter-auto': specifier: ^7.0.0 - version: 7.0.0(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))) + version: 7.0.0(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))) '@sveltejs/kit': specifier: 'catalog:' - version: 2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.1 - version: 6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) '@tailwindcss/vite': specifier: ^4.1.17 - version: 4.1.18(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 4.1.18(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) '@types/better-sqlite3': specifier: 'catalog:' version: 7.6.13 @@ -1270,7 +1267,7 @@ importers: version: 5.9.3 vite: specifier: ^7.2.6 - version: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + version: 7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) samples/taskforge: dependencies: @@ -1284,8 +1281,8 @@ importers: specifier: workspace:* version: link:../../packages/schema better-auth: - specifier: ^1.4.21 - version: 1.6.15(ea021998b94c175608aa591d0f2ab4df) + specifier: ^1.7.12 + version: 1.7.12(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(mysql2@3.24.6(@types/node@20.19.24))(next@16.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)) better-sqlite3: specifier: 'catalog:' version: 12.5.0 @@ -1293,9 +1290,6 @@ importers: specifier: ^14.0.2 version: 14.0.3 devDependencies: - '@better-auth/cli': - specifier: ^1.4.21 - version: 1.4.21(@better-fetch/fetch@1.1.21)(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(@types/better-sqlite3@7.6.13)(@types/sql.js@1.4.9)(better-call@1.1.8(zod@4.3.6))(bun-types@1.3.3)(jose@6.2.3)(kysely@0.29.0)(magicast@0.5.1)(mysql2@3.16.1)(nanostores@1.3.0)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sql.js@1.13.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)) '@types/better-sqlite3': specifier: 'catalog:' version: 7.6.13 @@ -1305,6 +1299,9 @@ importers: '@zenstackhq/cli': specifier: workspace:* version: link:../../packages/cli + auth: + specifier: ^1.7.12 + version: 1.7.12(@better-fetch/fetch@1.3.2)(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(better-call@1.4.4(zod@4.6.7))(giget@3.1.2)(jose@6.2.3)(kysely@0.29.0)(magicast@0.5.1)(mysql2@3.24.6(@types/node@20.19.24))(nanostores@1.3.0)(next@16.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)) tests/e2e: dependencies: @@ -1351,8 +1348,8 @@ importers: specifier: ^3.0.0 version: 3.0.0 uuid: - specifier: ^11.0.5 - version: 11.0.5 + specifier: ^11.1.1 + version: 11.1.1 zod: specifier: 'catalog:' version: 4.1.12 @@ -1486,8 +1483,8 @@ importers: packages: - '@acemir/cssom@0.9.31': - resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} + '@acemir/cssom@0.9.32': + resolution: {integrity: sha512-OijrAUu7rrJhSIoHNg6LMPS5e344nwUHA+1EFYYjrh64D+GmytUsfBQjci45TjT+rd5Spsp8B5xsZDT+TgyXXQ==} '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} @@ -1520,6 +1517,10 @@ packages: resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.28.5': resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} @@ -1528,6 +1529,10 @@ packages: resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + '@babel/core@7.28.5': resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} engines: {node: '>=6.9.0'} @@ -1536,6 +1541,10 @@ packages: resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} engines: {node: '>=6.9.0'} + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.28.5': resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} @@ -1548,6 +1557,10 @@ packages: resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + '@babel/generator@8.0.0-rc.3': resolution: {integrity: sha512-em37/13/nR320G4jab/nIIHZgc2Wz2y/D39lxnTyxB4/D/omPQncl/lSdlnJY1OhQcRGugTSIF2l/69o31C9dA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1564,6 +1577,10 @@ packages: resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + '@babel/helper-create-class-features-plugin@7.28.6': resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} engines: {node: '>=6.9.0'} @@ -1574,6 +1591,10 @@ packages: resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + '@babel/helper-member-expression-to-functions@7.28.5': resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} engines: {node: '>=6.9.0'} @@ -1586,6 +1607,10 @@ packages: resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-transforms@7.28.3': resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} @@ -1598,6 +1623,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.27.1': resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} engines: {node: '>=6.9.0'} @@ -1620,6 +1651,10 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@8.0.0-rc.3': resolution: {integrity: sha512-AmwWFx1m8G/a5cXkxLxTiWl+YEoWuoFLUCwqMlNuWO1tqAYITQAbCRPUkyBHv1VOFgfjVOqEj6L3u15J5ZCzTA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1628,6 +1663,10 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@8.0.0-rc.3': resolution: {integrity: sha512-8AWCJ2VJJyDFlGBep5GpaaQ9AAaE/FjAcrqI7jyssYhtL7WGV0DOKpJsQqM037xDbpRLHXsY8TwU7zDma7coOw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1636,6 +1675,10 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + '@babel/helpers@7.28.4': resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} @@ -1644,6 +1687,10 @@ packages: resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.29.10': + resolution: {integrity: sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.28.5': resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} engines: {node: '>=6.0.0'} @@ -1664,6 +1711,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.9': + resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/parser@8.0.0-rc.3': resolution: {integrity: sha512-B20dvP3MfNc/XS5KKCHy/oyWl5IA6Cn9YjXRdDlCjNmUFrjvLXMNUfQq/QUy9fnG2gYkKKcrto2YaF9B32ToOQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1741,6 +1793,10 @@ packages: resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.28.5': resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} engines: {node: '>=6.9.0'} @@ -1753,6 +1809,10 @@ packages: resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.29.10': + resolution: {integrity: sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==} + engines: {node: '>=6.9.0'} + '@babel/types@7.28.5': resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} @@ -1765,6 +1825,10 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + '@babel/types@8.0.0-rc.3': resolution: {integrity: sha512-mOm5ZrYmphGfqVWoH5YYMTITb3cDXsFgmvFlvkvWDMsR9X8RFnt7a0Wb6yNIdoFsiMO9WjYLq+U/FMtqIYAF8Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1773,65 +1837,61 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@better-auth/cli@1.4.19': - resolution: {integrity: sha512-kH1e+F8sPwcfEuhyNzFt3rdLo5SBTfw7a9m/hyMv1E7tk/yfsqOZRL0I5GR+Fkm0FJ7SoVA4LJREFV3S0Px8iA==} - hasBin: true - - '@better-auth/cli@1.4.21': - resolution: {integrity: sha512-bKEa8BupnZxNjLk9ZDntvgQGm5jogeE2wHdMbYifhet3GTyxgDi6pXoOK8+aqHYQGg1C3OALi9hVVWnrv7JJWQ==} - hasBin: true - - '@better-auth/core@1.4.19': - resolution: {integrity: sha512-uADLHG1jc5BnEJi7f6ijUN5DmPPRSj++7m/G19z3UqA3MVCo4Y4t1MMa4IIxLCqGDFv22drdfxescgW+HnIowA==} + '@better-auth/core@1.7.12': + resolution: {integrity: sha512-SUStBGHJ13KnSO6+7U/1DSlfuOtln4jtOrFZqzQtxZVHZIrAoMjwHNGitlF+s7hvKbuC3Z14xjnqfLLqIqGfxw==} peerDependencies: - '@better-auth/utils': 0.3.0 - '@better-fetch/fetch': 1.1.21 - better-call: 1.1.8 + '@better-auth/utils': ^0.4.2 || ^0.5.0 + '@better-fetch/fetch': 1.3.2 + '@opentelemetry/api': ^1.9.0 + better-call: 1.4.4 jose: ^6.1.0 - kysely: ^0.28.5 + kysely: ^0.28.5 || ^0.29.0 nanostores: ^1.0.1 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true - '@better-auth/drizzle-adapter@1.6.15': - resolution: {integrity: sha512-+ho2RozN6cZmCN+6XkZd/ec5iolVlefgInQ7znJ18qRPbgllHxdyu9tGrgKZyEsXSCyqAa6wi5Yb4hd3WZmTNQ==} + '@better-auth/drizzle-adapter@1.7.12': + resolution: {integrity: sha512-Qzoyuf05aK01Pc0k3EXv8Xj3XCT2m2dCWc5HoB7x+xX6N3Pm2eQsca+UIArsJcbuNEWWP3L51oIebnBoHJ5DTw==} peerDependencies: - '@better-auth/core': 1.4.19 - '@better-auth/utils': 0.4.1 - drizzle-orm: ^0.45.2 + '@better-auth/core': 1.7.12 + '@better-auth/utils': ^0.4.2 || ^0.5.0 + drizzle-orm: ^0.45.2 || >=1.0.0-rc.1 <2.0.0 peerDependenciesMeta: drizzle-orm: optional: true - '@better-auth/kysely-adapter@1.6.15': - resolution: {integrity: sha512-E29Sugm+DWRK4oQNBPrjPA4kBYiKy2bBtX1arIlkuyAB9A1BEb4Xn+8t7wlNIF5eFcxpkFZ3F80ceSpWDSYU0Q==} + '@better-auth/kysely-adapter@1.7.12': + resolution: {integrity: sha512-1PaFXktLv4n3DmrGbPaCj5SsvcT40NnWw7NLua5/J1+8/A051N5ZzqOwJifXEIQX5k6NdwvmymwDQxKytduEAQ==} peerDependencies: - '@better-auth/core': 1.4.19 - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.12 + '@better-auth/utils': ^0.4.2 || ^0.5.0 kysely: ^0.28.17 || ^0.29.0 peerDependenciesMeta: kysely: optional: true - '@better-auth/memory-adapter@1.6.15': - resolution: {integrity: sha512-BRE5Ft0Tn5thOPjCmyXHge7kOI/paLybaKDU+Hzg24DZMU6JvWooYWzvf52r1viDbxq5tz0G97iyaH4bouDyug==} + '@better-auth/memory-adapter@1.7.12': + resolution: {integrity: sha512-glaDwXzDptnddHqRuu7fj47FAssUcOzLNgdlTGJoEPrspkX69x2vnXwgAzNuApjZI21WLtPH/rQ6CN+DQGybPA==} peerDependencies: - '@better-auth/core': 1.4.19 - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.12 + '@better-auth/utils': ^0.4.2 || ^0.5.0 - '@better-auth/mongo-adapter@1.6.15': - resolution: {integrity: sha512-r0X9AtFhwDeOU1KMP9kZ5NeV0O3TSFzn8+uidaH+aicy2e1dNUsd9nPYrozEloSxdA2ZOnv5gD4jzH7HPeKkUQ==} + '@better-auth/mongo-adapter@1.7.12': + resolution: {integrity: sha512-HKV4sgYSw/Gdt9AuHlESMkiKL72SWdGweDy1Wsyxoh06BpG7aNu4aSJ7marNpMdKZteyRbxWWqQkA9ediA8qFw==} peerDependencies: - '@better-auth/core': 1.4.19 - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.12 + '@better-auth/utils': ^0.4.2 || ^0.5.0 mongodb: ^6.0.0 || ^7.0.0 peerDependenciesMeta: mongodb: optional: true - '@better-auth/prisma-adapter@1.6.15': - resolution: {integrity: sha512-pUOlIUnpMu2l8C2ytgu46+OoOr3LZ+aLpJMFRGIPdnWl1BF2co2YaoZPT3+ZBu1Lzm0FrBN1ZRCAzZbkFoeIAQ==} + '@better-auth/prisma-adapter@1.7.12': + resolution: {integrity: sha512-FZRX6ORbwhDmeYE1Tl0IPtUn8v3BlVFgbJxDnm/DCNHxuFRf+0t4HfddUgYeYehHFR8aSDsdYX44fEFWM9877w==} peerDependencies: - '@better-auth/core': 1.4.19 - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.12 + '@better-auth/utils': ^0.4.2 || ^0.5.0 '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 peerDependenciesMeta: @@ -1840,31 +1900,18 @@ packages: prisma: optional: true - '@better-auth/telemetry@1.4.19': - resolution: {integrity: sha512-ApGNS7olCTtDpKF8Ow3Z+jvFAirOj7c4RyFUpu8axklh3mH57ndpfUAUjhgA8UVoaaH/mnm/Tl884BlqiewLyw==} - peerDependencies: - '@better-auth/core': 1.4.19 - - '@better-auth/telemetry@1.4.21': - resolution: {integrity: sha512-LX+FGMZnhR2KQZ0idHH1+UwlXvkOl6P8w3Gne4TtjvUCt3QjG9FKIuP9JD3MAmEEkwGt0SoAPHPJEGTjUl3ydg==} + '@better-auth/telemetry@1.7.12': + resolution: {integrity: sha512-DaDkKNnI99vKGGbWq9KJg2uYeaOjsK9nIBw9T2m2KG/RkxpvQRjYkZ00++up2bZQkR5pB1nukGbplza3UJG/Xg==} peerDependencies: - '@better-auth/core': 1.4.19 + '@better-auth/core': 1.7.12 + '@better-auth/utils': ^0.4.2 || ^0.5.0 + '@better-fetch/fetch': 1.3.2 - '@better-auth/telemetry@1.6.15': - resolution: {integrity: sha512-eoFlUPVrVXLML9saHD11OSKKSRCAYETgRgBW4vQF3Y6pCgmThKD9oehYk3kkb/FsRgfqmsocmvAFqsiyjPIygg==} - peerDependencies: - '@better-auth/core': 1.4.19 - '@better-auth/utils': 0.4.1 - '@better-fetch/fetch': 1.1.21 - - '@better-auth/utils@0.3.0': - resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==} + '@better-auth/utils@0.5.0': + resolution: {integrity: sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA==} - '@better-auth/utils@0.4.1': - resolution: {integrity: sha512-SZBPRPF3z0nBvE5ygOkxae35wnnXPRShmqFo78S+qslLeFoPu/pMgnXAuNKFMMybac3tiLaVg1e3MQW5MC+1iA==} - - '@better-fetch/fetch@1.1.21': - resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} + '@better-fetch/fetch@1.3.2': + resolution: {integrity: sha512-Gs7n99b5tqUC6cQAPbV0uED3IraHB6xQbHLQ/C3l7ZFafHScOx9pQ+DYmP5blbLFShVWLqxNUlI9wi4xU/X+ow==} '@bomb.sh/tab@0.0.12': resolution: {integrity: sha512-dYRwg4MqfHR5/BcTy285XOGRhjQFmNpaJBZ0tl2oU+RY595MQ5ApTF6j3OvauPAooHL6cfoOZMySQrOQztT8RQ==} @@ -1884,83 +1931,73 @@ packages: '@borewit/text-codec@0.2.2': resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} - '@chevrotain/cst-dts-gen@10.5.0': - resolution: {integrity: sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==} - '@chevrotain/cst-dts-gen@12.0.0': resolution: {integrity: sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==} - '@chevrotain/gast@10.5.0': - resolution: {integrity: sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==} - '@chevrotain/gast@12.0.0': resolution: {integrity: sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==} '@chevrotain/regexp-to-ast@12.0.0': resolution: {integrity: sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==} - '@chevrotain/types@10.5.0': - resolution: {integrity: sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==} - '@chevrotain/types@12.0.0': resolution: {integrity: sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==} - '@chevrotain/utils@10.5.0': - resolution: {integrity: sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==} - '@chevrotain/utils@12.0.0': resolution: {integrity: sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==} - '@clack/core@0.5.0': - resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==} - '@clack/core@1.0.1': resolution: {integrity: sha512-WKeyK3NOBwDOzagPR5H08rFk9D/WuN705yEbuZvKqlkmoLM2woKtXb10OO2k1NoSU4SFG947i2/SCYh+2u5e4g==} - '@clack/prompts@0.11.0': - resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==} + '@clack/core@1.5.1': + resolution: {integrity: sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ==} + engines: {node: '>= 20.12.0'} '@clack/prompts@1.0.1': resolution: {integrity: sha512-/42G73JkuYdyWZ6m8d/CJtBrGl1Hegyc7Fy78m5Ob+jF85TOUmLR5XLce/U3LxYAw0kJ8CT5aI99RIvPHcGp/Q==} + '@clack/prompts@1.8.1': + resolution: {integrity: sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==} + engines: {node: '>= 20.12.0'} + '@cloudflare/kv-asset-handler@0.4.2': resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} engines: {node: '>=18.0.0'} - '@csstools/color-helpers@6.0.2': - resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} + '@csstools/color-helpers@6.1.2': + resolution: {integrity: sha512-grhRy3OKmniaAEKXMjua5z/EODX0MSqBGjunw8+j/3HQjOnahs2AGhvEOIYVUWcU6ScApbhLhVrQTX8XqrMrow==} engines: {node: '>=20.19.0'} - '@csstools/css-calc@3.1.1': - resolution: {integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==} + '@csstools/css-calc@3.4.3': + resolution: {integrity: sha512-iex20d8CHVkyvg6B7UKV7uHnI2Bqo9g+EFfT9E0y+GvTvhZ/DwONJ+9aKb1dlqm0ZiGsL5RXjp0fCoJYnkeDjA==} engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-parser-algorithms': ^4.0.0 - '@csstools/css-tokenizer': ^4.0.0 + '@csstools/css-parser-algorithms': ^4.0.2 + '@csstools/css-tokenizer': ^4.0.2 - '@csstools/css-color-parser@4.0.2': - resolution: {integrity: sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==} + '@csstools/css-color-parser@4.2.6': + resolution: {integrity: sha512-iiPQ3iRWwnJkeEn6RIu6SJPr7hYrLz6XZ9s/QZl+2/LI5KQVjpl2fdmDSZKuD4xP6GMmMPgHFFXg6k1Wkz0Trg==} engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-parser-algorithms': ^4.0.0 - '@csstools/css-tokenizer': ^4.0.0 + '@csstools/css-parser-algorithms': ^4.0.2 + '@csstools/css-tokenizer': ^4.0.2 - '@csstools/css-parser-algorithms@4.0.0': - resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + '@csstools/css-parser-algorithms@4.0.2': + resolution: {integrity: sha512-40cSKyMvK+tq4qz6Awrlye2WGuOKt3FwPgtGg6KTfbHOWNw+Rk1rzbAtZnZ6IBhsY491HLRnDXwoyBAijmmILA==} engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-tokenizer': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.2 - '@csstools/css-syntax-patches-for-csstree@1.1.2': - resolution: {integrity: sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA==} + '@csstools/css-syntax-patches-for-csstree@1.1.15': + resolution: {integrity: sha512-J0u7HkVl2nzSlhsiTOp4AmwcUQ3D+mGEEKfBy/7To5/y7F2OHwyLrXfrhR0SMgr4p5Lo+eaMVSeai24zUcBIxA==} peerDependencies: css-tree: ^3.2.1 peerDependenciesMeta: css-tree: optional: true - '@csstools/css-tokenizer@4.0.0': - resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + '@csstools/css-tokenizer@4.0.2': + resolution: {integrity: sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==} engines: {node: '>=20.19.0'} '@dxup/nuxt@0.3.2': @@ -2737,9 +2774,9 @@ packages: engines: {node: '>=18'} hasBin: true - '@mrleebo/prisma-ast@0.13.1': - resolution: {integrity: sha512-XyroGQXcHrZdvmrGJvsA9KNeOOgGMg1Vg9OlheUsBOSKznLMDl+YChxbkboRHvtFYJEMRYmlV3uoo/njCw05iw==} - engines: {node: '>=16'} + '@mrleebo/prisma-ast@0.16.0': + resolution: {integrity: sha512-a9ELYNIflEQP38tSu6gnUgSAWgXjuhMvC52868K5sWgyRmYsjiJMWTUmm8iy7hZT5EN2ZKVydMTFP2q3/+6ccg==} + engines: {node: '>=22.0.0'} '@mswjs/interceptors@0.39.8': resolution: {integrity: sha512-2+BzZbjRO7Ct61k8fMNHEtoKjeWI9pIlHFTqBwZ5icHpqszIgEZbjb1MW5Z0+bITTCTl3gk4PDBxs9tA/csXvA==} @@ -2815,10 +2852,6 @@ packages: cpu: [x64] os: [win32] - '@noble/ciphers@2.0.1': - resolution: {integrity: sha512-xHK3XHPUW8DTAobU+G0XT+/w+JLM7/8k1UFdB5xg/zTFPnFCobhftzw8wl4Lw2aq/Rvir5pxfZV5fEazmeCJ2g==} - engines: {node: '>= 20.19.0'} - '@noble/ciphers@2.2.0': resolution: {integrity: sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==} engines: {node: '>= 20.19.0'} @@ -2827,8 +2860,8 @@ packages: resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} engines: {node: ^14.21.3 || >=16} - '@noble/hashes@2.0.1': - resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} + '@noble/hashes@2.4.0': + resolution: {integrity: sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==} engines: {node: '>= 20.19.0'} '@nodelib/fs.scandir@2.1.5': @@ -3806,6 +3839,9 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@sveltejs/acorn-typescript@1.0.9': resolution: {integrity: sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==} peerDependencies: @@ -4836,6 +4872,11 @@ packages: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} + auth@1.7.12: + resolution: {integrity: sha512-A7kvpFgh3utL7+YtwcldA9mlkT4PGd+n9a+d8Hy0t2Thz9XFZXUIkl51kr9fbUCyfyovtn7x6B8JULdfxRmQvw==} + engines: {node: '>=22.12.0'} + hasBin: true + autoprefixer@10.4.27: resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==} engines: {node: ^10 || ^12 || >=14} @@ -4896,141 +4937,16 @@ packages: resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==} hasBin: true - better-auth@1.4.19: - resolution: {integrity: sha512-3RlZJcA0+NH25wYD85vpIGwW9oSTuEmLIaGbT8zg41w/Pa2hVWHKedjoUHHJtnzkBXzDb+CShkLnSw7IThDdqQ==} - peerDependencies: - '@lynx-js/react': '*' - '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 - '@sveltejs/kit': ^2.0.0 - '@tanstack/react-start': ^1.0.0 - '@tanstack/solid-start': ^1.0.0 - better-sqlite3: ^12.0.0 - drizzle-kit: '>=0.31.4' - drizzle-orm: '>=0.41.0' - mongodb: ^6.0.0 || ^7.0.0 - mysql2: ^3.0.0 - next: ^14.0.0 || ^15.0.0 || ^16.0.0 - pg: ^8.0.0 - prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - solid-js: ^1.0.0 - svelte: ^4.0.0 || ^5.0.0 - vitest: ^2.0.0 || ^3.0.0 || ^4.0.0 - vue: ^3.0.0 - peerDependenciesMeta: - '@lynx-js/react': - optional: true - '@prisma/client': - optional: true - '@sveltejs/kit': - optional: true - '@tanstack/react-start': - optional: true - '@tanstack/solid-start': - optional: true - better-sqlite3: - optional: true - drizzle-kit: - optional: true - drizzle-orm: - optional: true - mongodb: - optional: true - mysql2: - optional: true - next: - optional: true - pg: - optional: true - prisma: - optional: true - react: - optional: true - react-dom: - optional: true - solid-js: - optional: true - svelte: - optional: true - vitest: - optional: true - vue: - optional: true - - better-auth@1.4.21: - resolution: {integrity: sha512-qdrIZS7xnGF2HPBV5wYNPWTkPojhauOOjz1+MhLvwFy+zXpgLofQmWsI5I9DY+ef845NKt93XcgpyAc4RPPT9A==} - peerDependencies: - '@lynx-js/react': '*' - '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 - '@sveltejs/kit': ^2.0.0 - '@tanstack/react-start': ^1.0.0 - '@tanstack/solid-start': ^1.0.0 - better-sqlite3: ^12.0.0 - drizzle-kit: '>=0.31.4' - drizzle-orm: '>=0.41.0' - mongodb: ^6.0.0 || ^7.0.0 - mysql2: ^3.0.0 - next: ^14.0.0 || ^15.0.0 || ^16.0.0 - pg: ^8.0.0 - prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - solid-js: ^1.0.0 - svelte: ^4.0.0 || ^5.0.0 - vitest: ^2.0.0 || ^3.0.0 || ^4.0.0 - vue: ^3.0.0 - peerDependenciesMeta: - '@lynx-js/react': - optional: true - '@prisma/client': - optional: true - '@sveltejs/kit': - optional: true - '@tanstack/react-start': - optional: true - '@tanstack/solid-start': - optional: true - better-sqlite3: - optional: true - drizzle-kit: - optional: true - drizzle-orm: - optional: true - mongodb: - optional: true - mysql2: - optional: true - next: - optional: true - pg: - optional: true - prisma: - optional: true - react: - optional: true - react-dom: - optional: true - solid-js: - optional: true - svelte: - optional: true - vitest: - optional: true - vue: - optional: true - - better-auth@1.6.15: - resolution: {integrity: sha512-0nuQuEru3ZrLF+9xFUuN3llAmR+6gHLtLunoXaZxB9lXGjSmfBcc6SZUgYq4DfzugPnLvdnzYazsyprZFSFC4Q==} + better-auth@1.7.12: + resolution: {integrity: sha512-f1fEG0QdTslzbmFA+vOGFFW/XXCWkG4kBxLeH9n1vp1otTH9+ZzV6EJ6797GStWeiYYgS5Vnt9xqE12KwSyAAw==} peerDependencies: '@lynx-js/react': '*' '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 - '@sveltejs/kit': ^2.0.0 + '@sveltejs/kit': ^2.0.0 || ^3.0.0 '@tanstack/react-start': ^1.0.0 '@tanstack/solid-start': ^1.0.0 - better-sqlite3: ^12.0.0 - drizzle-kit: '>=0.31.4' - drizzle-orm: ^0.45.2 + drizzle-kit: '>=0.31.4 || >=1.0.0-beta.1' + drizzle-orm: ^0.45.2 || >=1.0.0-rc.1 <2.0.0 mongodb: ^6.0.0 || ^7.0.0 mysql2: ^3.0.0 next: ^14.0.0 || ^15.0.0 || ^16.0.0 @@ -5040,7 +4956,7 @@ packages: react-dom: ^18.0.0 || ^19.0.0 solid-js: ^1.0.0 svelte: ^4.0.0 || ^5.0.0 - vitest: ^2.0.0 || ^3.0.0 || ^4.0.0 + vitest: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 vue: ^3.0.0 peerDependenciesMeta: '@lynx-js/react': @@ -5053,8 +4969,6 @@ packages: optional: true '@tanstack/solid-start': optional: true - better-sqlite3: - optional: true drizzle-kit: optional: true drizzle-orm: @@ -5082,16 +4996,8 @@ packages: vue: optional: true - better-call@1.1.8: - resolution: {integrity: sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw==} - peerDependencies: - zod: ^4.0.0 - peerDependenciesMeta: - zod: - optional: true - - better-call@1.3.5: - resolution: {integrity: sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA==} + better-call@1.4.4: + resolution: {integrity: sha512-Gq4XdGFy4dmUxFEAX2nj0ycFcFbD9ZWh8YROouZoyXeF1M11TpYbplk0pDByTFF8Wz+MYa6W9052c9m7dD+mfw==} peerDependencies: zod: ^4.0.0 peerDependenciesMeta: @@ -5102,8 +5008,8 @@ packages: resolution: {integrity: sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg==} engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} - bidi-js@1.0.3: - resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + bidi-js@1.1.0: + resolution: {integrity: sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==} bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -5188,6 +5094,23 @@ packages: magicast: optional: true + c12@4.0.0-rc.2: + resolution: {integrity: sha512-Qfc/XXsG5yyQZRm2asHeemRRbK5zmo3D+enxiNrJZs6i6zbl9CMj7Wr0lvYGq84txPNAsUKJfe3ea2Rva8h/eA==} + peerDependencies: + dotenv: '*' + giget: '>=3.1.0' + jiti: '*' + magicast: '*' + peerDependenciesMeta: + dotenv: + optional: true + giget: + optional: true + jiti: + optional: true + magicast: + optional: true + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -5242,9 +5165,6 @@ packages: peerDependencies: chevrotain: ^12.0.0 - chevrotain@10.5.0: - resolution: {integrity: sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==} - chevrotain@12.0.0: resolution: {integrity: sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==} engines: {node: '>=22.0.0'} @@ -5329,14 +5249,14 @@ packages: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} - commander@14.0.3: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -5373,6 +5293,9 @@ packages: confbox@0.2.4: resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + confbox@0.3.1: + resolution: {integrity: sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==} + consola@3.4.2: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} @@ -5611,6 +5534,10 @@ packages: resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} engines: {node: '>=18'} + default-browser@5.5.1: + resolution: {integrity: sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==} + engines: {node: '>=18'} + defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -5706,6 +5633,10 @@ packages: resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + drizzle-orm@0.41.0: resolution: {integrity: sha512-7A4ZxhHk9gdlXmTdPj/lREtP+3u8KvZ4yEN6MYVxBzZGex5Wtdc+CWSbu7btgF6TB0N+MNPrvW7RKBbxJchs/Q==} peerDependencies: @@ -5866,14 +5797,14 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - entities@6.0.1: - resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} - engines: {node: '>=0.12'} - entities@7.0.1: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + entities@8.1.0: + resolution: {integrity: sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==} + engines: {node: '>=20.19.0'} + environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} @@ -6136,6 +6067,9 @@ packages: exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + exsolve@1.1.3: + resolution: {integrity: sha512-kuzL9zherLQoqDeQaS55AiiIg1kb4KYXaSusTtgZTPlvKP2gGCI82LNmBNh5RjDVUXIcmgx+/Mx7vPwRzBbhJA==} + fast-check@3.23.2: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} engines: {node: '>=8.0.0'} @@ -6175,9 +6109,18 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fastify-plugin@5.1.0: resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==} @@ -6196,8 +6139,8 @@ packages: picomatch: optional: true - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} figures@6.1.0: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} @@ -6337,6 +6280,9 @@ packages: get-tsconfig@4.13.7: resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} + get-tsconfig@4.14.3: + resolution: {integrity: sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==} + giget@2.0.0: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true @@ -6453,8 +6399,8 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - hono@4.12.2: - resolution: {integrity: sha512-gJnaDHXKDayjt8ue0n8Gs0A007yKXj4Xzb8+cNjZeYsSzzwKc0Lr+OZgYwVfB0pHfUs17EPoLvrOsEaJ9mj+Tg==} + hono@4.13.13: + resolution: {integrity: sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==} engines: {node: '>=16.9.0'} hookable@5.5.3: @@ -6524,6 +6470,10 @@ packages: resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -6650,6 +6600,10 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} @@ -6824,8 +6778,9 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - jose@6.1.2: - resolution: {integrity: sha512-MpcPtHLE5EmztuFIqB0vzHAWJPpmN1E6L4oo+kze56LIs3MyXIj9ZHMDxqOvkP38gBR7K1v3jqd4WU2+nrfONQ==} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} @@ -6929,10 +6884,6 @@ packages: peerDependencies: kysely: ^0.28.2 - kysely@0.28.16: - resolution: {integrity: sha512-3i5pmOiZvMDj00qhrIVbH0AnioVTx22DMP7Vn5At4yJO46iy+FM8Y/g61ltenLVSo3fiO8h8Q3QOFgf/gQ72ww==} - engines: {node: '>=20.0.0'} - kysely@0.29.0: resolution: {integrity: sha512-LrQfPUeTW7MXbMvT62moEMnpMTuj9TO3lqjCeLKjM975PJ4Alrl/43f2tlDX7xOsNptKgH4LSNGwIbXwEkLg4g==} engines: {node: '>=22.0.0'} @@ -7132,6 +7083,10 @@ packages: resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} engines: {node: 20 || >=22} + lru-cache@11.5.3: + resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -7139,6 +7094,10 @@ packages: resolution: {integrity: sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==} engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + lru.min@1.1.5: + resolution: {integrity: sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==} + engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -7307,9 +7266,11 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - mysql2@3.16.1: - resolution: {integrity: sha512-b75qsDB3ieYEzMsT1uRGsztM/sy6vWPY40uPZlVVl8eefAotFCoS7jaDB5DxDNtlW5kdVGd9jptSpkvujNxI2A==} + mysql2@3.24.6: + resolution: {integrity: sha512-enu73KhbAua2L3dIxjc6qg6ZJ7H3pt1BImZASWNWY5xSsmJ1ZuUPULTDGXgmJbI61vb+nJAG19IZclIq19UidQ==} engines: {node: '>= 8.0'} + peerDependencies: + '@types/node': '>= 8' named-placeholders@1.1.6: resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==} @@ -7320,20 +7281,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.0.9: - resolution: {integrity: sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==} - engines: {node: ^18 || >=20} - hasBin: true - - nanoid@5.1.6: - resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} + nanoid@5.1.16: + resolution: {integrity: sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==} engines: {node: ^18 || >=20} hasBin: true - nanostores@1.0.1: - resolution: {integrity: sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw==} - engines: {node: ^20.0.0 || >=22.0.0} - nanostores@1.3.0: resolution: {integrity: sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA==} engines: {node: ^20.0.0 || >=22.0.0} @@ -7552,6 +7504,10 @@ packages: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} + open@11.0.4: + resolution: {integrity: sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==} + engines: {node: '>=20'} + open@8.4.2: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} @@ -7617,8 +7573,8 @@ packages: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} - parse5@8.0.0: - resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} @@ -7763,6 +7719,9 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + pkg-types@2.4.0: + resolution: {integrity: sha512-07Fys7iVx0X6TcviMOgogM1Lk33ZyqOezMnU79S+kffWqwolG/5W9V8EgQSeYCIfFTYVgssRbnH630Oa2+soBA==} + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -7983,6 +7942,10 @@ packages: postgres-range@1.1.4: resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} + powershell-utils@0.2.1: + resolution: {integrity: sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==} + engines: {node: '>=20'} + prebuild-install@7.1.3: resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} engines: {node: '>=10'} @@ -8157,6 +8120,9 @@ packages: rc9@3.0.0: resolution: {integrity: sha512-MGOue0VqscKWQ104udASX/3GYDcKyPI4j4F8gu/jHHzglpmy9a/anZK3PNe8ug6aZFl+9GxLtdhe3kVZuMaQbA==} + rc9@3.1.0: + resolution: {integrity: sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -8218,9 +8184,6 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} - regexp-to-ast@0.5.0: - resolution: {integrity: sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==} - regexp-tree@0.1.27: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} hasBin: true @@ -8325,6 +8288,9 @@ packages: rou3@0.7.12: resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} + rou3@0.9.2: + resolution: {integrity: sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -8407,6 +8373,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + send@1.2.0: resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} engines: {node: '>= 18'} @@ -8438,6 +8409,9 @@ packages: set-cookie-parser@3.0.1: resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==} + set-cookie-parser@3.1.4: + resolution: {integrity: sha512-iEmdEew+Tn1BwMGMP8LzGBEPC7Mf1sySjglCeEPb/Vchm9bnGezePvacYcId+fGprQzIng+TBkzqfJ/qS7AsBg==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -8533,6 +8507,10 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -8564,6 +8542,10 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + sql-escaper@1.5.2: + resolution: {integrity: sha512-6CKD38c31SENivxOADeMNLdukOnUxUcflKtzVWzace7Riv1v7cAEym5Cx9Q7gYZ3ezIJI7ZpqecQq8cqNeSSFg==} + engines: {bun: '>=1.0.0', deno: '>=2.0.0', node: '>=12.0.0'} + sql.js@1.13.0: resolution: {integrity: sha512-RJbVP1HRDlUUXahJ7VMTcu9Rm1Nzw+EBpoPr94vnbD4LwR715F3CcxE2G2k45PewcaZ57pjetYa+LoSJLAASgA==} @@ -8678,8 +8660,8 @@ packages: strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} - strtok3@10.3.5: - resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} + strtok3@10.6.2: + resolution: {integrity: sha512-BkdixFFUt7r7LsX6aGNPVyYMLjW+M1USLlKPNbZsrZEmLFh5tGqhdfckKo6xgsZIJqdDD0CZGkeCJJwNWQIKQA==} engines: {node: '>=18'} structured-clone-es@1.0.0: @@ -8843,15 +8825,15 @@ packages: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} - tldts-core@7.0.27: - resolution: {integrity: sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==} + tldts-core@7.4.19: + resolution: {integrity: sha512-goNXV6Ho9TIOfpJZanTd6CKG5DyYgYAL+k+22T0SLh8l6+KHIfBUPFuePuSZv05UttIaaLmI9tb7TmGIdK5AkQ==} - tldts@7.0.27: - resolution: {integrity: sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==} + tldts@7.4.19: + resolution: {integrity: sha512-48dH6aSpQnpbzDUc4vIQZ1VjB8tkK+e8c1yx05/SZK+rkssgjEOlxDd/yU8/a4vGYQ9vRgJr9dwMq9TJ0b3X2Q==} hasBin: true - tmp@0.2.5: - resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} engines: {node: '>=14.14'} to-regex-range@5.0.1: @@ -8877,8 +8859,8 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} - tough-cookie@6.0.1: - resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} engines: {node: '>=16'} tr46@0.0.3: @@ -9042,8 +9024,8 @@ packages: ufo@1.6.3: resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} - uint8array-extras@1.5.0: - resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + uint8array-extras@1.6.0: + resolution: {integrity: sha512-8iAasVS4wUx0gPLjH8Xtz2PeDzTSiy8QiLGu2DT3X5GU+egFEQhpnbtkehbAwjvDcxIERmCYcysiODFmE3Ud0Q==} engines: {node: '>=18'} ulid@3.0.0: @@ -9235,12 +9217,8 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@11.0.5: - resolution: {integrity: sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==} - hasBin: true - - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + uuid@11.1.1: + resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} hasBin: true validate-npm-package-license@3.0.4: @@ -9611,13 +9589,29 @@ packages: utf-8-validate: optional: true - wsl-utils@0.1.0: - resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} - engines: {node: '>=18'} - - xml-name-validator@5.0.0: - resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} - engines: {node: '>=18'} + ws@8.22.0: + resolution: {integrity: sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + + wsl-utils@1.0.1: + resolution: {integrity: sha512-c16PKLIRxBYpR3jSWsdS7SFp0COm5SOQdiFjY818dYFHJbMGuLBszpF07LBWmOS1uzrHa9VGdynsEgoeKy0oOQ==} + engines: {node: '>=20'} + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} @@ -9664,8 +9658,8 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yocto-spinner@0.2.3: - resolution: {integrity: sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==} + yocto-spinner@1.2.2: + resolution: {integrity: sha512-DODGl1wJjA/s5pnJFKau9lIYHT81lnhob1i3e1TjxZRxEhWRKl74nTbWE6H5KlkViQQTo/Z29YFdxzTZAMY3ng==} engines: {node: '>=18.19'} yoctocolors@2.1.2: @@ -9697,9 +9691,12 @@ packages: zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zod@4.6.7: + resolution: {integrity: sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==} + snapshots: - '@acemir/cssom@0.9.31': + '@acemir/cssom@0.9.32': optional: true '@alloc/quick-lru@5.2.0': {} @@ -9711,20 +9708,20 @@ snapshots: '@asamuzakjp/css-color@4.1.2': dependencies: - '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-color-parser': 4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-tokenizer': 4.0.0 - lru-cache: 11.2.7 + '@csstools/css-calc': 3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-color-parser': 4.2.6(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 + lru-cache: 11.5.3 optional: true '@asamuzakjp/dom-selector@6.8.1': dependencies: '@asamuzakjp/nwsapi': 2.3.9 - bidi-js: 1.0.3 + bidi-js: 1.1.0 css-tree: 3.2.1 is-potential-custom-element-name: 1.0.1 - lru-cache: 11.2.7 + lru-cache: 11.5.3 optional: true '@asamuzakjp/nwsapi@2.3.9': @@ -9748,10 +9745,18 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.28.5': {} '@babel/compat-data@7.29.0': {} + '@babel/compat-data@7.29.7': {} + '@babel/core@7.28.5': dependencies: '@babel/code-frame': 7.29.0 @@ -9792,6 +9797,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.10 + '@babel/parser': 7.29.9 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.10 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/generator@7.28.5': dependencies: '@babel/parser': 7.29.2 @@ -9816,6 +9841,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/generator@8.0.0-rc.3': dependencies: '@babel/parser': 8.0.0-rc.3 @@ -9845,6 +9878,14 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -9858,8 +9899,23 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.6 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/helper-globals@7.28.0': {} + '@babel/helper-globals@7.29.7': {} + '@babel/helper-member-expression-to-functions@7.28.5': dependencies: '@babel/traverse': 7.28.5 @@ -9881,6 +9937,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.10 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -9899,6 +9962,24 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.10 + transitivePeerDependencies: + - supports-color + '@babel/helper-optimise-call-expression@7.27.1': dependencies: '@babel/types': 7.29.0 @@ -9914,6 +9995,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: '@babel/traverse': 7.28.5 @@ -9923,14 +10013,20 @@ snapshots: '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@7.29.7': {} + '@babel/helper-string-parser@8.0.0-rc.3': {} '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-identifier@8.0.0-rc.3': {} '@babel/helper-validator-option@7.27.1': {} + '@babel/helper-validator-option@7.29.7': {} + '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 @@ -9941,6 +10037,11 @@ snapshots: '@babel/template': 7.28.6 '@babel/types': 7.29.0 + '@babel/helpers@7.29.10': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + '@babel/parser@7.28.5': dependencies: '@babel/types': 7.28.5 @@ -9957,6 +10058,10 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@babel/parser@7.29.9': + dependencies: + '@babel/types': 7.29.8 + '@babel/parser@8.0.0-rc.3': dependencies: '@babel/types': 8.0.0-rc.3 @@ -9966,45 +10071,55 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.29.0)': + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.7) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.7) '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-plugin-utils': 7.28.6 @@ -10019,26 +10134,37 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/preset-react@7.28.5(@babel/core@7.29.0)': + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/preset-react@7.28.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.29.7) transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': + '@babel/preset-typescript@7.28.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.7) transitivePeerDependencies: - supports-color @@ -10056,6 +10182,12 @@ snapshots: '@babel/parser': 7.28.6 '@babel/types': 7.28.6 + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 + '@babel/traverse@7.28.5': dependencies: '@babel/code-frame': 7.29.0 @@ -10092,6 +10224,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.29.10': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -10107,6 +10251,11 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/types@8.0.0-rc.3': dependencies: '@babel/helper-string-parser': 8.0.0-rc.3 @@ -10114,251 +10263,58 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@better-auth/cli@1.4.19(@better-fetch/fetch@1.1.21)(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(@types/better-sqlite3@7.6.13)(@types/sql.js@1.4.9)(better-call@1.1.8(zod@4.3.6))(bun-types@1.3.3)(jose@6.2.3)(kysely@0.29.0)(magicast@0.5.1)(mysql2@3.16.1)(nanostores@1.3.0)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sql.js@1.13.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@25.5.2)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3))': + '@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0)': dependencies: - '@babel/core': 7.29.0 - '@babel/preset-react': 7.28.5(@babel/core@7.29.0) - '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) - '@better-auth/core': 1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) - '@better-auth/telemetry': 1.4.19(@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0)) - '@better-auth/utils': 0.3.0 - '@clack/prompts': 0.11.0 - '@mrleebo/prisma-ast': 0.13.1 - '@prisma/client': 5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)) - '@types/pg': 8.16.0 - better-auth: 1.4.19(40c55c74539eb2c07288211f764cce6b) - better-sqlite3: 12.5.0 - c12: 3.3.3(magicast@0.5.1) - chalk: 5.6.2 - commander: 12.1.0 - dotenv: 17.2.3 - drizzle-orm: 0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0) - open: 10.2.0 - pg: 8.16.3 - prettier: 3.8.1 - prompts: 2.4.2 - semver: 7.7.4 - yocto-spinner: 0.2.3 - zod: 4.3.6 - transitivePeerDependencies: - - '@aws-sdk/client-rds-data' - - '@better-fetch/fetch' - - '@cloudflare/workers-types' - - '@electric-sql/pglite' - - '@libsql/client' - - '@libsql/client-wasm' - - '@lynx-js/react' - - '@neondatabase/serverless' - - '@op-engineering/op-sqlite' - - '@opentelemetry/api' - - '@planetscale/database' - - '@sveltejs/kit' - - '@tanstack/react-start' - - '@tanstack/solid-start' - - '@tidbcloud/serverless' - - '@types/better-sqlite3' - - '@types/sql.js' - - '@vercel/postgres' - - '@xata.io/client' - - better-call - - bun-types - - drizzle-kit - - expo-sqlite - - gel - - jose - - knex - - kysely - - magicast - - mongodb - - mysql2 - - nanostores - - next - - pg-native - - postgres - - prisma - - react - - react-dom - - solid-js - - sql.js - - sqlite3 - - supports-color - - svelte - - vitest - - vue - - '@better-auth/cli@1.4.21(@better-fetch/fetch@1.1.21)(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(@types/better-sqlite3@7.6.13)(@types/sql.js@1.4.9)(better-call@1.1.8(zod@4.3.6))(bun-types@1.3.3)(jose@6.2.3)(kysely@0.29.0)(magicast@0.5.1)(mysql2@3.16.1)(nanostores@1.3.0)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sql.js@1.13.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3))': - dependencies: - '@babel/core': 7.29.0 - '@babel/preset-react': 7.28.5(@babel/core@7.29.0) - '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) - '@better-auth/core': 1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) - '@better-auth/telemetry': 1.4.21(@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0)) - '@better-auth/utils': 0.3.0 - '@clack/prompts': 0.11.0 - '@mrleebo/prisma-ast': 0.13.1 - '@prisma/client': 5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)) - '@types/pg': 8.16.0 - better-auth: 1.4.21(ea021998b94c175608aa591d0f2ab4df) - better-sqlite3: 12.5.0 - c12: 3.3.3(magicast@0.5.1) - chalk: 5.6.2 - commander: 12.1.0 - dotenv: 17.2.3 - drizzle-orm: 0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0) - open: 10.2.0 - pg: 8.16.3 - prettier: 3.8.1 - prompts: 2.4.2 - semver: 7.7.4 - yocto-spinner: 0.2.3 - zod: 4.3.6 - transitivePeerDependencies: - - '@aws-sdk/client-rds-data' - - '@better-fetch/fetch' - - '@cloudflare/workers-types' - - '@electric-sql/pglite' - - '@libsql/client' - - '@libsql/client-wasm' - - '@lynx-js/react' - - '@neondatabase/serverless' - - '@op-engineering/op-sqlite' - - '@opentelemetry/api' - - '@planetscale/database' - - '@sveltejs/kit' - - '@tanstack/react-start' - - '@tanstack/solid-start' - - '@tidbcloud/serverless' - - '@types/better-sqlite3' - - '@types/sql.js' - - '@vercel/postgres' - - '@xata.io/client' - - better-call - - bun-types - - drizzle-kit - - expo-sqlite - - gel - - jose - - knex - - kysely - - magicast - - mongodb - - mysql2 - - nanostores - - next - - pg-native - - postgres - - prisma - - react - - react-dom - - solid-js - - sql.js - - sqlite3 - - supports-color - - svelte - - vitest - - vue - - '@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.16)(nanostores@1.0.1)': - dependencies: - '@better-auth/utils': 0.3.0 - '@better-fetch/fetch': 1.1.21 - '@standard-schema/spec': 1.0.0 - better-call: 1.1.8(zod@4.3.6) - jose: 6.1.2 - kysely: 0.28.16 - nanostores: 1.0.1 - zod: 4.3.6 - - '@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.28.16)(nanostores@1.3.0)': - dependencies: - '@better-auth/utils': 0.3.0 - '@better-fetch/fetch': 1.1.21 - '@standard-schema/spec': 1.0.0 - better-call: 1.1.8(zod@4.3.6) - jose: 6.2.3 - kysely: 0.28.16 - nanostores: 1.3.0 - zod: 4.3.6 - - '@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0)': - dependencies: - '@better-auth/utils': 0.3.0 - '@better-fetch/fetch': 1.1.21 - '@standard-schema/spec': 1.0.0 - better-call: 1.1.8(zod@4.3.6) + '@better-auth/utils': 0.5.0 + '@better-fetch/fetch': 1.3.2 + '@standard-schema/spec': 1.1.0 + better-call: 1.4.4(zod@4.6.7) jose: 6.2.3 kysely: 0.29.0 nanostores: 1.3.0 - zod: 4.3.6 + zod: 4.6.7 - '@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0)': + '@better-auth/drizzle-adapter@1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0)': dependencies: - '@better-auth/utils': 0.4.1 - '@better-fetch/fetch': 1.1.21 - '@standard-schema/spec': 1.0.0 - better-call: 1.1.8(zod@4.3.6) - jose: 6.2.3 - kysely: 0.29.0 - nanostores: 1.3.0 - zod: 4.3.6 + '@better-auth/core': 1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) + '@better-auth/utils': 0.5.0 - '@better-auth/drizzle-adapter@1.6.15(@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))': + '@better-auth/kysely-adapter@1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0)(kysely@0.29.0)': dependencies: - '@better-auth/core': 1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) - '@better-auth/utils': 0.4.1 - optionalDependencies: - drizzle-orm: 0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0) - - '@better-auth/kysely-adapter@1.6.15(@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(kysely@0.29.0)': - dependencies: - '@better-auth/core': 1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) + '@better-auth/utils': 0.5.0 optionalDependencies: kysely: 0.29.0 - '@better-auth/memory-adapter@1.6.15(@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': + '@better-auth/memory-adapter@1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0)': dependencies: - '@better-auth/core': 1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) + '@better-auth/utils': 0.5.0 - '@better-auth/mongo-adapter@1.6.15(@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': + '@better-auth/mongo-adapter@1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0)': dependencies: - '@better-auth/core': 1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) + '@better-auth/utils': 0.5.0 - '@better-auth/prisma-adapter@1.6.15(@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))': + '@better-auth/prisma-adapter@1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0)(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))': dependencies: - '@better-auth/core': 1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) + '@better-auth/utils': 0.5.0 optionalDependencies: '@prisma/client': 5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)) prisma: 6.19.0(magicast@0.5.1)(typescript@5.9.3) - '@better-auth/telemetry@1.4.19(@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))': - dependencies: - '@better-auth/core': 1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) - '@better-auth/utils': 0.3.0 - '@better-fetch/fetch': 1.1.21 - - '@better-auth/telemetry@1.4.21(@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))': - dependencies: - '@better-auth/core': 1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) - '@better-auth/utils': 0.3.0 - '@better-fetch/fetch': 1.1.21 - - '@better-auth/telemetry@1.6.15(@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)': + '@better-auth/telemetry@1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)': dependencies: - '@better-auth/core': 1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) - '@better-auth/utils': 0.4.1 - '@better-fetch/fetch': 1.1.21 + '@better-auth/core': 1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) + '@better-auth/utils': 0.5.0 + '@better-fetch/fetch': 1.3.2 - '@better-auth/utils@0.3.0': {} - - '@better-auth/utils@0.4.1': + '@better-auth/utils@0.5.0': dependencies: - '@noble/hashes': 2.0.1 + '@noble/hashes': 2.4.0 - '@better-fetch/fetch@1.1.21': {} + '@better-fetch/fetch@1.3.2': {} '@bomb.sh/tab@0.0.12(cac@6.7.14)(citty@0.2.1)': optionalDependencies: @@ -10367,50 +10323,29 @@ snapshots: '@borewit/text-codec@0.2.2': {} - '@chevrotain/cst-dts-gen@10.5.0': - dependencies: - '@chevrotain/gast': 10.5.0 - '@chevrotain/types': 10.5.0 - lodash: 4.18.1 - '@chevrotain/cst-dts-gen@12.0.0': dependencies: '@chevrotain/gast': 12.0.0 '@chevrotain/types': 12.0.0 - '@chevrotain/gast@10.5.0': - dependencies: - '@chevrotain/types': 10.5.0 - lodash: 4.18.1 - '@chevrotain/gast@12.0.0': dependencies: '@chevrotain/types': 12.0.0 '@chevrotain/regexp-to-ast@12.0.0': {} - '@chevrotain/types@10.5.0': {} - '@chevrotain/types@12.0.0': {} - '@chevrotain/utils@10.5.0': {} - '@chevrotain/utils@12.0.0': {} - '@clack/core@0.5.0': - dependencies: - picocolors: 1.1.1 - sisteransi: 1.0.5 - '@clack/core@1.0.1': dependencies: picocolors: 1.1.1 sisteransi: 1.0.5 - '@clack/prompts@0.11.0': + '@clack/core@1.5.1': dependencies: - '@clack/core': 0.5.0 - picocolors: 1.1.1 + fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 '@clack/prompts@1.0.1': @@ -10419,36 +10354,43 @@ snapshots: picocolors: 1.1.1 sisteransi: 1.0.5 + '@clack/prompts@1.8.1': + dependencies: + '@clack/core': 1.5.1 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + '@cloudflare/kv-asset-handler@0.4.2': {} - '@csstools/color-helpers@6.0.2': + '@csstools/color-helpers@6.1.2': optional: true - '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-calc@3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': dependencies: - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-tokenizer': 4.0.0 + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 optional: true - '@csstools/css-color-parser@4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-color-parser@4.2.6(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': dependencies: - '@csstools/color-helpers': 6.0.2 - '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-tokenizer': 4.0.0 + '@csstools/color-helpers': 6.1.2 + '@csstools/css-calc': 3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 optional: true - '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2)': dependencies: - '@csstools/css-tokenizer': 4.0.0 + '@csstools/css-tokenizer': 4.0.2 optional: true - '@csstools/css-syntax-patches-for-csstree@1.1.2(css-tree@3.2.1)': + '@csstools/css-syntax-patches-for-csstree@1.1.15(css-tree@3.2.1)': optionalDependencies: css-tree: 3.2.1 optional: true - '@csstools/css-tokenizer@4.0.0': + '@csstools/css-tokenizer@4.0.2': optional: true '@dxup/nuxt@0.3.2(magicast@0.5.1)': @@ -10741,9 +10683,9 @@ snapshots: '@esbuild/win32-x64@0.28.0': optional: true - '@eslint-community/eslint-utils@4.7.0(eslint@9.29.0(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.7.0(eslint@9.29.0(jiti@2.7.0))': dependencies: - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} @@ -10812,9 +10754,9 @@ snapshots: '@fastify/forwarded': 3.0.1 ipaddr.js: 2.2.0 - '@hono/node-server@1.19.17(hono@4.12.2)': + '@hono/node-server@1.19.17(hono@4.13.13)': dependencies: - hono: 4.12.2 + hono: 4.13.13 '@humanfs/core@0.19.1': {} @@ -10980,9 +10922,9 @@ snapshots: - encoding - supports-color - '@mrleebo/prisma-ast@0.13.1': + '@mrleebo/prisma-ast@0.16.0': dependencies: - chevrotain: 10.5.0 + chevrotain: 12.0.0 lilconfig: 2.1.0 '@mswjs/interceptors@0.39.8': @@ -11045,13 +10987,11 @@ snapshots: '@next/swc-win32-x64-msvc@16.1.6': optional: true - '@noble/ciphers@2.0.1': {} - '@noble/ciphers@2.2.0': {} '@noble/hashes@1.7.1': {} - '@noble/hashes@2.0.1': {} + '@noble/hashes@2.4.0': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -11115,6 +11055,14 @@ snapshots: transitivePeerDependencies: - magicast + '@nuxt/devtools-kit@3.1.1(magicast@0.5.1)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': + dependencies: + '@nuxt/kit': 4.3.1(magicast@0.5.1) + execa: 8.0.1 + vite: 7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + transitivePeerDependencies: + - magicast + '@nuxt/devtools-wizard@3.1.1': dependencies: consola: 3.4.2 @@ -11167,12 +11115,12 @@ snapshots: - utf-8-validate - vue - '@nuxt/devtools@3.1.1(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3))': + '@nuxt/devtools@3.1.1(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3))': dependencies: - '@nuxt/devtools-kit': 3.1.1(magicast@0.5.1)(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + '@nuxt/devtools-kit': 3.1.1(magicast@0.5.1)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) '@nuxt/devtools-wizard': 3.1.1 '@nuxt/kit': 4.3.1(magicast@0.5.1) - '@vue/devtools-core': 8.0.5(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3)) + '@vue/devtools-core': 8.0.5(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3)) '@vue/devtools-kit': 8.0.5 birpc: 2.9.0 consola: 3.4.2 @@ -11197,9 +11145,9 @@ snapshots: sirv: 3.0.2 structured-clone-es: 1.0.0 tinyglobby: 0.2.15 - vite: 7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) - vite-plugin-inspect: 11.3.3(@nuxt/kit@4.3.1(magicast@0.5.1))(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) - vite-plugin-vue-tracer: 1.2.0(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3)) + vite: 7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite-plugin-inspect: 11.3.3(@nuxt/kit@4.3.1(magicast@0.5.1))(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + vite-plugin-vue-tracer: 1.2.0(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3)) which: 5.0.0 ws: 8.20.0 transitivePeerDependencies: @@ -11233,7 +11181,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/nitro-server@4.3.1(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(ioredis@5.9.3)(magicast@0.5.1)(mysql2@3.16.1)(nuxt@4.3.1(175990d052329d8b0154ca336b6ac7a3))(rolldown@1.0.0-rc.15)(typescript@5.9.3)': + '@nuxt/nitro-server@4.3.1(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(ioredis@5.9.3)(magicast@0.5.1)(mysql2@3.24.6(@types/node@25.5.2))(nuxt@4.3.1(94603ee5b6c8044fb0adad8a8009d510))(rolldown@1.0.0-rc.15)(typescript@5.9.3)': dependencies: '@nuxt/devalue': 2.0.2 '@nuxt/kit': 4.3.1(magicast@0.5.1) @@ -11250,8 +11198,8 @@ snapshots: impound: 1.0.0 klona: 2.0.6 mocked-exports: 0.1.1 - nitropack: 2.13.1(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1)(rolldown@1.0.0-rc.15) - nuxt: 4.3.1(175990d052329d8b0154ca336b6ac7a3) + nitropack: 2.13.1(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2))(rolldown@1.0.0-rc.15) + nuxt: 4.3.1(94603ee5b6c8044fb0adad8a8009d510) ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.0 @@ -11259,7 +11207,7 @@ snapshots: std-env: 3.10.0 ufo: 1.6.3 unctx: 2.5.0 - unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(ioredis@5.9.3) + unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)))(ioredis@5.9.3) vue: 3.5.29(typescript@5.9.3) vue-bundle-renderer: 2.2.0 vue-devtools-stub: 0.1.0 @@ -11298,7 +11246,7 @@ snapshots: - uploadthing - xml2js - '@nuxt/nitro-server@4.3.1(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.16.1))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(ioredis@5.9.3)(magicast@0.5.1)(mysql2@3.16.1)(nuxt@4.3.1(dd35fc409f8d464b401077cb817b832f))(rolldown@1.0.0-rc.15)(typescript@6.0.3)': + '@nuxt/nitro-server@4.3.1(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(ioredis@5.9.3)(magicast@0.5.1)(mysql2@3.24.6(@types/node@25.5.2))(nuxt@4.3.1(6ecebe4c14ebf76ad94dfae71c880d6e))(rolldown@1.0.0-rc.15)(typescript@6.0.3)': dependencies: '@nuxt/devalue': 2.0.2 '@nuxt/kit': 4.3.1(magicast@0.5.1) @@ -11315,8 +11263,8 @@ snapshots: impound: 1.0.0 klona: 2.0.6 mocked-exports: 0.1.1 - nitropack: 2.13.1(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.16.1)(rolldown@1.0.0-rc.15) - nuxt: 4.3.1(dd35fc409f8d464b401077cb817b832f) + nitropack: 2.13.1(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2))(rolldown@1.0.0-rc.15) + nuxt: 4.3.1(6ecebe4c14ebf76ad94dfae71c880d6e) ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.0 @@ -11324,7 +11272,7 @@ snapshots: std-env: 3.10.0 ufo: 1.6.3 unctx: 2.5.0 - unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.16.1))(ioredis@5.9.3) + unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)))(ioredis@5.9.3) vue: 3.5.29(typescript@6.0.3) vue-bundle-renderer: 2.2.0 vue-devtools-stub: 0.1.0 @@ -11380,12 +11328,12 @@ snapshots: rc9: 3.0.0 std-env: 3.10.0 - '@nuxt/vite-builder@4.3.1(@types/node@25.5.2)(eslint@9.29.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.3.1(175990d052329d8b0154ca336b6ac7a3))(optionator@0.9.4)(rolldown@1.0.0-rc.15)(rollup@4.59.0)(terser@5.44.0)(tsx@4.22.4)(typescript@5.9.3)(vue-tsc@3.2.5(typescript@5.9.3))(vue@3.5.29(typescript@5.9.3))(yaml@2.9.0)': + '@nuxt/vite-builder@4.3.1(@types/node@25.5.2)(eslint@9.29.0(jiti@2.7.0))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.3.1(6ecebe4c14ebf76ad94dfae71c880d6e))(optionator@0.9.4)(rolldown@1.0.0-rc.15)(rollup@4.59.0)(terser@5.44.0)(tsx@4.22.4)(typescript@6.0.3)(vue-tsc@3.2.5(typescript@6.0.3))(vue@3.5.29(typescript@6.0.3))(yaml@2.9.0)': dependencies: '@nuxt/kit': 4.3.1(magicast@0.5.1) '@rollup/plugin-replace': 6.0.3(rollup@4.59.0) - '@vitejs/plugin-vue': 6.0.4(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)) - '@vitejs/plugin-vue-jsx': 5.1.4(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)) + '@vitejs/plugin-vue': 6.0.4(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3)) + '@vitejs/plugin-vue-jsx': 5.1.4(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3)) autoprefixer: 10.4.27(postcss@8.5.6) consola: 3.4.2 cssnano: 7.1.2(postcss@8.5.6) @@ -11399,7 +11347,7 @@ snapshots: magic-string: 0.30.21 mlly: 1.8.0 mocked-exports: 0.1.1 - nuxt: 4.3.1(175990d052329d8b0154ca336b6ac7a3) + nuxt: 4.3.1(6ecebe4c14ebf76ad94dfae71c880d6e) pathe: 2.0.3 pkg-types: 2.3.0 postcss: 8.5.6 @@ -11410,8 +11358,8 @@ snapshots: unenv: 2.0.0-rc.24 vite: 7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) vite-node: 5.3.0(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) - vite-plugin-checker: 0.12.0(eslint@9.29.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue-tsc@3.2.5(typescript@5.9.3)) - vue: 3.5.29(typescript@5.9.3) + vite-plugin-checker: 0.12.0(eslint@9.29.0(jiti@2.7.0))(optionator@0.9.4)(typescript@6.0.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue-tsc@3.2.5(typescript@6.0.3)) + vue: 3.5.29(typescript@6.0.3) vue-bundle-renderer: 2.2.0 optionalDependencies: rolldown: 1.0.0-rc.15 @@ -11440,12 +11388,12 @@ snapshots: - vue-tsc - yaml - '@nuxt/vite-builder@4.3.1(@types/node@25.5.2)(eslint@9.29.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.3.1(dd35fc409f8d464b401077cb817b832f))(optionator@0.9.4)(rolldown@1.0.0-rc.15)(rollup@4.59.0)(terser@5.44.0)(tsx@4.22.4)(typescript@6.0.3)(vue-tsc@3.2.5(typescript@6.0.3))(vue@3.5.29(typescript@6.0.3))(yaml@2.9.0)': + '@nuxt/vite-builder@4.3.1(@types/node@25.5.2)(eslint@9.29.0(jiti@2.7.0))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.3.1(94603ee5b6c8044fb0adad8a8009d510))(optionator@0.9.4)(rolldown@1.0.0-rc.15)(rollup@4.59.0)(terser@5.44.0)(tsx@4.22.4)(typescript@5.9.3)(vue-tsc@3.2.5(typescript@5.9.3))(vue@3.5.29(typescript@5.9.3))(yaml@2.9.0)': dependencies: '@nuxt/kit': 4.3.1(magicast@0.5.1) '@rollup/plugin-replace': 6.0.3(rollup@4.59.0) - '@vitejs/plugin-vue': 6.0.4(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3)) - '@vitejs/plugin-vue-jsx': 5.1.4(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3)) + '@vitejs/plugin-vue': 6.0.4(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)) + '@vitejs/plugin-vue-jsx': 5.1.4(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)) autoprefixer: 10.4.27(postcss@8.5.6) consola: 3.4.2 cssnano: 7.1.2(postcss@8.5.6) @@ -11459,7 +11407,7 @@ snapshots: magic-string: 0.30.21 mlly: 1.8.0 mocked-exports: 0.1.1 - nuxt: 4.3.1(dd35fc409f8d464b401077cb817b832f) + nuxt: 4.3.1(94603ee5b6c8044fb0adad8a8009d510) pathe: 2.0.3 pkg-types: 2.3.0 postcss: 8.5.6 @@ -11470,8 +11418,8 @@ snapshots: unenv: 2.0.0-rc.24 vite: 7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) vite-node: 5.3.0(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) - vite-plugin-checker: 0.12.0(eslint@9.29.0(jiti@2.6.1))(optionator@0.9.4)(typescript@6.0.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue-tsc@3.2.5(typescript@6.0.3)) - vue: 3.5.29(typescript@6.0.3) + vite-plugin-checker: 0.12.0(eslint@9.29.0(jiti@2.7.0))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue-tsc@3.2.5(typescript@5.9.3)) + vue: 3.5.29(typescript@5.9.3) vue-bundle-renderer: 2.2.0 optionalDependencies: rolldown: 1.0.0-rc.15 @@ -11790,6 +11738,7 @@ snapshots: '@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))': optionalDependencies: prisma: 6.19.0(magicast@0.5.1)(typescript@5.9.3) + optional: true '@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))': optionalDependencies: @@ -12069,19 +12018,21 @@ snapshots: '@standard-schema/spec@1.0.0': {} + '@standard-schema/spec@1.1.0': {} + '@sveltejs/acorn-typescript@1.0.9(acorn@8.15.0)': dependencies: acorn: 8.15.0 - '@sveltejs/adapter-auto@7.0.0(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))': + '@sveltejs/adapter-auto@7.0.0(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))': dependencies: - '@sveltejs/kit': 2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/kit': 2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) - '@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': + '@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@standard-schema/spec': 1.0.0 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 1.1.1 @@ -12093,15 +12044,15 @@ snapshots: set-cookie-parser: 3.0.1 sirv: 3.0.2 svelte: 5.53.5 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) optionalDependencies: typescript: 5.9.3 - '@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': + '@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@standard-schema/spec': 1.0.0 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 1.1.1 @@ -12113,7 +12064,7 @@ snapshots: set-cookie-parser: 3.0.1 sirv: 3.0.2 svelte: 5.53.5 - vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) optionalDependencies: typescript: 5.9.3 optional: true @@ -12138,6 +12089,27 @@ snapshots: optionalDependencies: typescript: 5.9.3 + '@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': + dependencies: + '@standard-schema/spec': 1.0.0 + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + '@types/cookie': 0.6.0 + acorn: 8.15.0 + cookie: 1.1.1 + devalue: 5.6.3 + esm-env: 1.2.2 + kleur: 4.1.5 + magic-string: 0.30.21 + mrmime: 2.0.1 + set-cookie-parser: 3.0.1 + sirv: 3.0.2 + svelte: 5.53.5 + vite: 7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + optionalDependencies: + typescript: 5.9.3 + optional: true + '@sveltejs/package@2.5.7(svelte@5.53.5)(typescript@6.0.3)': dependencies: chokidar: 5.0.0 @@ -12149,21 +12121,21 @@ snapshots: transitivePeerDependencies: - typescript - '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) debug: 4.4.3 svelte: 5.53.5 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) debug: 4.4.3 svelte: 5.53.5 - vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) transitivePeerDependencies: - supports-color optional: true @@ -12177,27 +12149,37 @@ snapshots: transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + debug: 4.4.3 + svelte: 5.53.5 + vite: 7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + transitivePeerDependencies: + - supports-color + optional: true + + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) debug: 4.4.3 deepmerge: 4.3.1 magic-string: 0.30.21 svelte: 5.53.5 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) - vitefu: 1.1.1(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + vite: 7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vitefu: 1.1.1(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) debug: 4.4.3 deepmerge: 4.3.1 magic-string: 0.30.21 svelte: 5.53.5 - vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) - vitefu: 1.1.1(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + vite: 7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vitefu: 1.1.1(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) transitivePeerDependencies: - supports-color optional: true @@ -12214,6 +12196,19 @@ snapshots: transitivePeerDependencies: - supports-color + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + debug: 4.4.3 + deepmerge: 4.3.1 + magic-string: 0.30.21 + svelte: 5.53.5 + vite: 7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vitefu: 1.1.1(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + transitivePeerDependencies: + - supports-color + optional: true + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -12348,19 +12343,19 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.16 - '@tailwindcss/vite@4.1.18(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': + '@tailwindcss/vite@4.1.18(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@tailwindcss/node': 4.1.18 '@tailwindcss/oxide': 4.1.18 tailwindcss: 4.1.18 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) - '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': + '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@tailwindcss/node': 4.1.18 '@tailwindcss/oxide': 4.1.18 tailwindcss: 4.1.18 - vite: 7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) '@tanstack/match-sorter-utils@8.19.4': dependencies: @@ -12412,7 +12407,7 @@ snapshots: '@tokenizer/inflate@0.2.7': dependencies: debug: 4.4.3 - fflate: 0.8.2 + fflate: 0.8.3 token-types: 6.1.2 transitivePeerDependencies: - supports-color @@ -12572,15 +12567,15 @@ snapshots: dependencies: '@types/node': 25.5.2 - '@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.34.1 - '@typescript-eslint/type-utils': 8.34.1(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/utils': 8.34.1(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.34.1(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.34.1(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.34.1 - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -12589,15 +12584,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/parser': 8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.46.2 - '@typescript-eslint/type-utils': 8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/utils': 8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.46.2 - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -12606,26 +12601,26 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.34.1 '@typescript-eslint/types': 8.34.1 '@typescript-eslint/typescript-estree': 8.34.1(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.34.1 debug: 4.4.1 - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.46.2 '@typescript-eslint/types': 8.46.2 '@typescript-eslint/typescript-estree': 8.46.2(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.46.2 debug: 4.4.3 - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -12666,24 +12661,24 @@ snapshots: dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.34.1(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.34.1(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/typescript-estree': 8.34.1(typescript@6.0.3) - '@typescript-eslint/utils': 8.34.1(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/utils': 8.34.1(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) ts-api-utils: 2.1.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.46.2 '@typescript-eslint/typescript-estree': 8.46.2(typescript@6.0.3) - '@typescript-eslint/utils': 8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/utils': 8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) ts-api-utils: 2.1.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -12725,24 +12720,24 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.34.1 '@typescript-eslint/types': 8.34.1 '@typescript-eslint/typescript-estree': 8.34.1(typescript@6.0.3) - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/utils@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.46.2 '@typescript-eslint/types': 8.46.2 '@typescript-eslint/typescript-estree': 8.46.2(typescript@6.0.3) - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -12859,14 +12854,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue-jsx@5.1.4(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3))': + '@vitejs/plugin-vue-jsx@5.1.4(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) '@rolldown/pluginutils': 1.0.0-rc.5 '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.0) - vite: 7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) vue: 3.5.29(typescript@6.0.3) transitivePeerDependencies: - supports-color @@ -12877,13 +12872,13 @@ snapshots: vite: 7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) vue: 3.5.29(typescript@5.9.3) - '@vitejs/plugin-vue@6.0.4(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3))': + '@vitejs/plugin-vue@6.0.4(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.2 - vite: 7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) vue: 3.5.29(typescript@6.0.3) - '@vitest/coverage-v8@4.0.16(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0))': + '@vitest/coverage-v8@4.0.16(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.0.16 @@ -12896,7 +12891,7 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0) + vitest: 4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0) transitivePeerDependencies: - supports-color @@ -12909,30 +12904,30 @@ snapshots: chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.14(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0))': + '@vitest/mocker@4.0.14(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0))': dependencies: '@vitest/spy': 4.0.14 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0) + vite: 7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0) - '@vitest/mocker@4.0.14(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': + '@vitest/mocker@4.0.14(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.0.14 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) optional: true - '@vitest/mocker@4.0.14(vite@7.3.0(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': + '@vitest/mocker@4.0.14(vite@7.3.0(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.0.14 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.0(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.0(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) optional: true '@vitest/pretty-format@4.0.14': @@ -13124,21 +13119,21 @@ snapshots: '@vue/devtools-kit': 8.0.5 '@vue/devtools-shared': 8.0.5 mitt: 3.0.1 - nanoid: 5.1.6 + nanoid: 5.1.16 pathe: 2.0.3 vite-hot-client: 2.1.0(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) vue: 3.5.29(typescript@5.9.3) transitivePeerDependencies: - vite - '@vue/devtools-core@8.0.5(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3))': + '@vue/devtools-core@8.0.5(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3))': dependencies: '@vue/devtools-kit': 8.0.5 '@vue/devtools-shared': 8.0.5 mitt: 3.0.1 - nanoid: 5.1.6 + nanoid: 5.1.16 pathe: 2.0.3 - vite-hot-client: 2.1.0(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + vite-hot-client: 2.1.0(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) vue: 3.5.29(typescript@6.0.3) transitivePeerDependencies: - vite @@ -13441,6 +13436,110 @@ snapshots: atomic-sleep@1.0.0: {} + auth@1.7.12(@better-fetch/fetch@1.3.2)(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(better-call@1.4.4(zod@4.6.7))(giget@3.1.2)(jose@6.2.3)(kysely@0.29.0)(magicast@0.5.1)(mysql2@3.24.6(@types/node@20.19.24))(nanostores@1.3.0)(next@16.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)): + dependencies: + '@babel/core': 7.29.7 + '@babel/preset-react': 7.28.5(@babel/core@7.29.7) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.7) + '@better-auth/core': 1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) + '@better-auth/telemetry': 1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2) + '@better-auth/utils': 0.5.0 + '@clack/prompts': 1.8.1 + '@mrleebo/prisma-ast': 0.16.0 + better-auth: 1.7.12(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(mysql2@3.24.6(@types/node@20.19.24))(next@16.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)) + c12: 4.0.0-rc.2(dotenv@17.4.2)(giget@3.1.2)(jiti@2.7.0)(magicast@0.5.1) + chalk: 5.6.2 + commander: 15.0.0 + dotenv: 17.4.2 + get-tsconfig: 4.14.3 + jiti: 2.7.0 + open: 11.0.4 + prettier: 3.8.1 + prompts: 2.4.2 + semver: 7.8.5 + yocto-spinner: 1.2.2 + zod: 4.6.7 + transitivePeerDependencies: + - '@better-fetch/fetch' + - '@lynx-js/react' + - '@opentelemetry/api' + - '@prisma/client' + - '@sveltejs/kit' + - '@tanstack/react-start' + - '@tanstack/solid-start' + - better-call + - drizzle-kit + - drizzle-orm + - giget + - jose + - kysely + - magicast + - mongodb + - mysql2 + - nanostores + - next + - pg + - prisma + - react + - react-dom + - solid-js + - supports-color + - svelte + - vitest + - vue + + auth@1.7.12(@better-fetch/fetch@1.3.2)(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(better-call@1.4.4(zod@4.6.7))(giget@3.1.2)(jose@6.2.3)(kysely@0.29.0)(magicast@0.5.1)(mysql2@3.24.6(@types/node@25.5.2))(nanostores@1.3.0)(next@16.1.6(@babel/core@7.29.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@25.5.2)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)): + dependencies: + '@babel/core': 7.29.7 + '@babel/preset-react': 7.28.5(@babel/core@7.29.7) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.7) + '@better-auth/core': 1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) + '@better-auth/telemetry': 1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2) + '@better-auth/utils': 0.5.0 + '@clack/prompts': 1.8.1 + '@mrleebo/prisma-ast': 0.16.0 + better-auth: 1.7.12(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(mysql2@3.24.6(@types/node@25.5.2))(next@16.1.6(@babel/core@7.29.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@25.5.2)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)) + c12: 4.0.0-rc.2(dotenv@17.4.2)(giget@3.1.2)(jiti@2.7.0)(magicast@0.5.1) + chalk: 5.6.2 + commander: 15.0.0 + dotenv: 17.4.2 + get-tsconfig: 4.14.3 + jiti: 2.7.0 + open: 11.0.4 + prettier: 3.8.1 + prompts: 2.4.2 + semver: 7.8.5 + yocto-spinner: 1.2.2 + zod: 4.6.7 + transitivePeerDependencies: + - '@better-fetch/fetch' + - '@lynx-js/react' + - '@opentelemetry/api' + - '@prisma/client' + - '@sveltejs/kit' + - '@tanstack/react-start' + - '@tanstack/solid-start' + - better-call + - drizzle-kit + - drizzle-orm + - giget + - jose + - kysely + - magicast + - mongodb + - mysql2 + - nanostores + - next + - pg + - prisma + - react + - react-dom + - solid-js + - supports-color + - svelte + - vitest + - vue + autoprefixer@10.4.27(postcss@8.5.6): dependencies: browserslist: 4.28.1 @@ -13479,122 +13578,89 @@ snapshots: baseline-browser-mapping@2.9.11: {} - better-auth@1.4.19(40c55c74539eb2c07288211f764cce6b): - dependencies: - '@better-auth/core': 1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.16)(nanostores@1.0.1) - '@better-auth/telemetry': 1.4.19(@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0)) - '@better-auth/utils': 0.3.0 - '@better-fetch/fetch': 1.1.21 - '@noble/ciphers': 2.0.1 - '@noble/hashes': 2.0.1 - better-call: 1.1.8(zod@4.3.6) - defu: 6.1.4 - jose: 6.1.2 - kysely: 0.28.16 - nanostores: 1.0.1 - zod: 4.3.6 - optionalDependencies: - '@prisma/client': 5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)) - '@sveltejs/kit': 2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) - better-sqlite3: 12.5.0 - drizzle-orm: 0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0) - mysql2: 3.16.1 - next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - pg: 8.16.3 - prisma: 6.19.0(magicast@0.5.1)(typescript@5.9.3) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - svelte: 5.53.5 - vitest: 4.0.14(@edge-runtime/vm@5.0.0)(@types/node@25.5.2)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) - vue: 3.5.29(typescript@5.9.3) - - better-auth@1.4.21(ea021998b94c175608aa591d0f2ab4df): - dependencies: - '@better-auth/core': 1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.28.16)(nanostores@1.3.0) - '@better-auth/telemetry': 1.4.21(@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0)) - '@better-auth/utils': 0.3.0 - '@better-fetch/fetch': 1.1.21 + better-auth@1.7.12(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(mysql2@3.24.6(@types/node@20.19.24))(next@16.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)): + dependencies: + '@better-auth/core': 1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) + '@better-auth/drizzle-adapter': 1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0) + '@better-auth/kysely-adapter': 1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0)(kysely@0.29.0) + '@better-auth/memory-adapter': 1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0) + '@better-auth/mongo-adapter': 1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0) + '@better-auth/prisma-adapter': 1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0)(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)) + '@better-auth/telemetry': 1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2) + '@better-auth/utils': 0.5.0 + '@better-fetch/fetch': 1.3.2 '@noble/ciphers': 2.2.0 - '@noble/hashes': 2.0.1 - better-call: 1.1.8(zod@4.3.6) + '@noble/hashes': 2.4.0 + better-call: 1.4.4(zod@4.6.7) defu: 6.1.7 jose: 6.2.3 - kysely: 0.28.16 + kysely: 0.29.0 nanostores: 1.3.0 - zod: 4.3.6 + zod: 4.6.7 optionalDependencies: '@prisma/client': 5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)) - '@sveltejs/kit': 2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) - better-sqlite3: 12.5.0 - drizzle-orm: 0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0) - mysql2: 3.16.1 - next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@sveltejs/kit': 2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + mysql2: 3.24.6(@types/node@20.19.24) + next: 16.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) pg: 8.16.3 prisma: 6.19.0(magicast@0.5.1)(typescript@5.9.3) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) svelte: 5.53.5 - vitest: 4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vitest: 4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) vue: 3.5.29(typescript@5.9.3) + transitivePeerDependencies: + - '@opentelemetry/api' - better-auth@1.6.15(ea021998b94c175608aa591d0f2ab4df): - dependencies: - '@better-auth/core': 1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) - '@better-auth/drizzle-adapter': 1.6.15(@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0)) - '@better-auth/kysely-adapter': 1.6.15(@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(kysely@0.29.0) - '@better-auth/memory-adapter': 1.6.15(@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.4.1) - '@better-auth/mongo-adapter': 1.6.15(@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.4.1) - '@better-auth/prisma-adapter': 1.6.15(@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)) - '@better-auth/telemetry': 1.6.15(@better-auth/core@1.4.19(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.3.6))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21) - '@better-auth/utils': 0.4.1 - '@better-fetch/fetch': 1.1.21 + better-auth@1.7.12(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(mysql2@3.24.6(@types/node@25.5.2))(next@16.1.6(@babel/core@7.29.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@25.5.2)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)): + dependencies: + '@better-auth/core': 1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0) + '@better-auth/drizzle-adapter': 1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0) + '@better-auth/kysely-adapter': 1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0)(kysely@0.29.0) + '@better-auth/memory-adapter': 1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0) + '@better-auth/mongo-adapter': 1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0) + '@better-auth/prisma-adapter': 1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0)(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)) + '@better-auth/telemetry': 1.7.12(@better-auth/core@1.7.12(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-call@1.4.4(zod@4.6.7))(jose@6.2.3)(kysely@0.29.0)(nanostores@1.3.0))(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2) + '@better-auth/utils': 0.5.0 + '@better-fetch/fetch': 1.3.2 '@noble/ciphers': 2.2.0 - '@noble/hashes': 2.0.1 - better-call: 1.3.5(zod@4.3.6) + '@noble/hashes': 2.4.0 + better-call: 1.4.4(zod@4.6.7) defu: 6.1.7 jose: 6.2.3 kysely: 0.29.0 nanostores: 1.3.0 - zod: 4.3.6 + zod: 4.6.7 optionalDependencies: '@prisma/client': 5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)) - '@sveltejs/kit': 2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) - better-sqlite3: 12.5.0 - drizzle-orm: 0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0) - mysql2: 3.16.1 - next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@sveltejs/kit': 2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + mysql2: 3.24.6(@types/node@25.5.2) + next: 16.1.6(@babel/core@7.29.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) pg: 8.16.3 prisma: 6.19.0(magicast@0.5.1)(typescript@5.9.3) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) svelte: 5.53.5 - vitest: 4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vitest: 4.0.14(@edge-runtime/vm@5.0.0)(@types/node@25.5.2)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) vue: 3.5.29(typescript@5.9.3) + transitivePeerDependencies: + - '@opentelemetry/api' - better-call@1.1.8(zod@4.3.6): - dependencies: - '@better-auth/utils': 0.3.0 - '@better-fetch/fetch': 1.1.21 - rou3: 0.7.12 - set-cookie-parser: 2.7.2 - optionalDependencies: - zod: 4.3.6 - - better-call@1.3.5(zod@4.3.6): + better-call@1.4.4(zod@4.6.7): dependencies: - '@better-auth/utils': 0.4.1 - '@better-fetch/fetch': 1.1.21 - rou3: 0.7.12 - set-cookie-parser: 3.0.1 + '@better-auth/utils': 0.5.0 + '@better-fetch/fetch': 1.3.2 + rou3: 0.9.2 + set-cookie-parser: 3.1.4 optionalDependencies: - zod: 4.3.6 + zod: 4.6.7 better-sqlite3@12.5.0: dependencies: bindings: 1.5.0 prebuild-install: 7.1.3 - bidi-js@1.0.3: + bidi-js@1.1.0: dependencies: require-from-string: 2.0.2 optional: true @@ -13738,6 +13804,20 @@ snapshots: optionalDependencies: magicast: 0.5.1 + c12@4.0.0-rc.2(dotenv@17.4.2)(giget@3.1.2)(jiti@2.7.0)(magicast@0.5.1): + dependencies: + confbox: 0.3.1 + defu: 6.1.7 + exsolve: 1.1.3 + pathe: 2.0.3 + pkg-types: 2.4.0 + rc9: 3.1.0 + optionalDependencies: + dotenv: 17.4.2 + giget: 3.1.2 + jiti: 2.7.0 + magicast: 0.5.1 + cac@6.7.14: {} cac@7.0.0: {} @@ -13792,15 +13872,6 @@ snapshots: chevrotain: 12.0.0 lodash-es: 4.18.1 - chevrotain@10.5.0: - dependencies: - '@chevrotain/cst-dts-gen': 10.5.0 - '@chevrotain/gast': 10.5.0 - '@chevrotain/types': 10.5.0 - '@chevrotain/utils': 10.5.0 - lodash: 4.18.1 - regexp-to-ast: 0.5.0 - chevrotain@12.0.0: dependencies: '@chevrotain/cst-dts-gen': 12.0.0 @@ -13875,10 +13946,10 @@ snapshots: commander@11.1.0: {} - commander@12.1.0: {} - commander@14.0.3: {} + commander@15.0.0: {} + commander@2.20.3: {} commander@8.3.0: {} @@ -13907,6 +13978,8 @@ snapshots: confbox@0.2.4: {} + confbox@0.3.1: {} + consola@3.4.2: {} content-disposition@1.0.0: @@ -13996,7 +14069,7 @@ snapshots: css-tree@3.2.1: dependencies: mdn-data: 2.27.1 - source-map-js: 1.2.1 + source-map-js: 1.2.2 optional: true css-what@6.2.2: {} @@ -14054,9 +14127,9 @@ snapshots: cssstyle@5.3.7: dependencies: '@asamuzakjp/css-color': 4.1.2 - '@csstools/css-syntax-patches-for-csstree': 1.1.2(css-tree@3.2.1) + '@csstools/css-syntax-patches-for-csstree': 1.1.15(css-tree@3.2.1) css-tree: 3.2.1 - lru-cache: 11.2.7 + lru-cache: 11.5.3 optional: true csstype@3.2.3: {} @@ -14089,17 +14162,17 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1): + db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)): optionalDependencies: better-sqlite3: 12.5.0 - drizzle-orm: 0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0) - mysql2: 3.16.1 + drizzle-orm: 0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0) + mysql2: 3.24.6(@types/node@25.5.2) - db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.16.1): + db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)): optionalDependencies: better-sqlite3: 12.5.0 - drizzle-orm: 0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0) - mysql2: 3.16.1 + drizzle-orm: 0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0) + mysql2: 3.24.6(@types/node@25.5.2) debug@3.2.7: dependencies: @@ -14136,6 +14209,11 @@ snapshots: bundle-name: 4.1.0 default-browser-id: 5.0.0 + default-browser@5.5.1: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.0 + defaults@1.0.4: dependencies: clone: 1.0.4 @@ -14215,7 +14293,9 @@ snapshots: dotenv@17.2.3: {} - drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0): + dotenv@17.4.2: {} + + drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0): optionalDependencies: '@prisma/client': 5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)) '@types/better-sqlite3': 7.6.13 @@ -14224,12 +14304,13 @@ snapshots: better-sqlite3: 12.5.0 bun-types: 1.3.3 kysely: 0.29.0 - mysql2: 3.16.1 + mysql2: 3.24.6(@types/node@25.5.2) pg: 8.16.3 prisma: 6.19.0(magicast@0.5.1)(typescript@5.9.3) sql.js: 1.13.0 + optional: true - drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0): + drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0): optionalDependencies: '@prisma/client': 5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)) '@types/better-sqlite3': 7.6.13 @@ -14238,7 +14319,7 @@ snapshots: better-sqlite3: 12.5.0 bun-types: 1.3.3 kysely: 0.29.0 - mysql2: 3.16.1 + mysql2: 3.24.6(@types/node@25.5.2) pg: 8.16.3 prisma: 6.19.0(magicast@0.5.1)(typescript@6.0.3) sql.js: 1.13.0 @@ -14298,11 +14379,11 @@ snapshots: entities@4.5.0: {} - entities@6.0.1: - optional: true - entities@7.0.1: {} + entities@8.1.0: + optional: true + environment@1.1.0: {} error-ex@1.3.4: @@ -14515,18 +14596,18 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-next@16.0.1(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3): + eslint-config-next@16.0.1(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3): dependencies: '@next/eslint-plugin-next': 16.0.1 - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.29.0(jiti@2.6.1)) - eslint-plugin-react: 7.37.5(eslint@9.29.0(jiti@2.6.1)) - eslint-plugin-react-hooks: 7.0.1(eslint@9.29.0(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.29.0(jiti@2.7.0)))(eslint@9.29.0(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.29.0(jiti@2.7.0)))(eslint@9.29.0(jiti@2.7.0)))(eslint@9.29.0(jiti@2.7.0)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.29.0(jiti@2.7.0)) + eslint-plugin-react: 7.37.5(eslint@9.29.0(jiti@2.7.0)) + eslint-plugin-react-hooks: 7.0.1(eslint@9.29.0(jiti@2.7.0)) globals: 16.4.0 - typescript-eslint: 8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) + typescript-eslint: 8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) optionalDependencies: typescript: 6.0.3 transitivePeerDependencies: @@ -14543,33 +14624,33 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.29.0(jiti@2.7.0)))(eslint@9.29.0(jiti@2.7.0)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) get-tsconfig: 4.10.1 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.29.0(jiti@2.7.0)))(eslint@9.29.0(jiti@2.7.0)))(eslint@9.29.0(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.29.0(jiti@2.7.0)))(eslint@9.29.0(jiti@2.7.0)))(eslint@9.29.0(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) - eslint: 9.29.0(jiti@2.6.1) + '@typescript-eslint/parser': 8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 9.29.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.29.0(jiti@2.7.0)))(eslint@9.29.0(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.29.0(jiti@2.7.0)))(eslint@9.29.0(jiti@2.7.0)))(eslint@9.29.0(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -14578,9 +14659,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.29.0(jiti@2.7.0)))(eslint@9.29.0(jiti@2.7.0)))(eslint@9.29.0(jiti@2.7.0)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -14592,13 +14673,13 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/parser': 8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.29.0(jiti@2.6.1)): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.29.0(jiti@2.7.0)): dependencies: aria-query: 5.3.2 array-includes: 3.1.9 @@ -14608,7 +14689,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -14617,18 +14698,18 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@7.0.1(eslint@9.29.0(jiti@2.6.1)): + eslint-plugin-react-hooks@7.0.1(eslint@9.29.0(jiti@2.7.0)): dependencies: '@babel/core': 7.28.5 '@babel/parser': 7.28.5 - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) hermes-parser: 0.25.1 zod: 4.3.6 zod-validation-error: 4.0.1(zod@4.3.6) transitivePeerDependencies: - supports-color - eslint-plugin-react@7.37.5(eslint@9.29.0(jiti@2.6.1)): + eslint-plugin-react@7.37.5(eslint@9.29.0(jiti@2.7.0)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -14636,7 +14717,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.2.1 - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -14659,9 +14740,9 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.29.0(jiti@2.6.1): + eslint@9.29.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.20.1 '@eslint/config-helpers': 0.2.3 @@ -14697,7 +14778,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.6.1 + jiti: 2.7.0 transitivePeerDependencies: - supports-color @@ -14814,6 +14895,8 @@ snapshots: exsolve@1.0.8: {} + exsolve@1.1.3: {} + fast-check@3.23.2: dependencies: pure-rand: 6.1.0 @@ -14861,8 +14944,18 @@ snapshots: fast-safe-stringify@2.1.1: {} + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + fast-uri@3.1.0: {} + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + fastify-plugin@5.1.0: {} fastify@5.7.4: @@ -14895,7 +14988,7 @@ snapshots: optionalDependencies: picomatch: 4.0.4 - fflate@0.8.2: {} + fflate@0.8.3: {} figures@6.1.0: dependencies: @@ -14908,9 +15001,9 @@ snapshots: file-type@21.0.0: dependencies: '@tokenizer/inflate': 0.2.7 - strtok3: 10.3.5 + strtok3: 10.6.2 token-types: 6.1.2 - uint8array-extras: 1.5.0 + uint8array-extras: 1.6.0 transitivePeerDependencies: - supports-color @@ -15057,6 +15150,10 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-tsconfig@4.14.3: + dependencies: + resolve-pkg-maps: 1.0.0 + giget@2.0.0: dependencies: citty: 0.1.6 @@ -15190,7 +15287,7 @@ snapshots: dependencies: hermes-estree: 0.25.1 - hono@4.12.2: {} + hono@4.13.13: {} hookable@5.5.3: {} @@ -15260,6 +15357,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -15387,6 +15488,8 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-in-ssh@1.0.0: {} + is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 @@ -15545,7 +15648,7 @@ snapshots: jiti@2.6.1: {} - jose@6.1.2: {} + jiti@2.7.0: {} jose@6.2.3: {} @@ -15559,7 +15662,7 @@ snapshots: jsdom@27.1.0: dependencies: - '@acemir/cssom': 0.9.31 + '@acemir/cssom': 0.9.32 '@asamuzakjp/dom-selector': 6.8.1 cssstyle: 5.3.7 data-urls: 6.0.1 @@ -15568,16 +15671,16 @@ snapshots: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - parse5: 8.0.0 + parse5: 8.0.1 saxes: 6.0.0 symbol-tree: 3.2.4 - tough-cookie: 6.0.1 + tough-cookie: 6.0.2 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 15.1.0 - ws: 8.20.0 + ws: 8.22.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil @@ -15653,8 +15756,6 @@ snapshots: bun-types: 1.3.3 kysely: 0.29.0 - kysely@0.28.16: {} - kysely@0.29.0: {} langium-cli@4.2.1: @@ -15850,12 +15951,17 @@ snapshots: lru-cache@11.2.7: {} + lru-cache@11.5.3: + optional: true + lru-cache@5.1.1: dependencies: yallist: 3.1.1 lru.min@1.1.3: {} + lru.min@1.1.5: {} + lz-string@1.5.0: {} magic-regexp@0.10.0: @@ -15878,9 +15984,9 @@ snapshots: magicast@0.3.5: dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - source-map-js: 1.2.1 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 + source-map-js: 1.2.2 optional: true magicast@0.5.1: @@ -16003,29 +16109,36 @@ snapshots: muggle-string@0.4.1: {} - mysql2@3.16.1: + mysql2@3.24.6(@types/node@20.19.24): dependencies: + '@types/node': 20.19.24 aws-ssl-profiles: 1.1.2 - denque: 2.1.0 generate-function: 2.3.1 - iconv-lite: 0.7.0 + iconv-lite: 0.7.3 + long: 5.3.2 + lru.min: 1.1.5 + named-placeholders: 1.1.6 + sql-escaper: 1.5.2 + + mysql2@3.24.6(@types/node@25.5.2): + dependencies: + '@types/node': 25.5.2 + aws-ssl-profiles: 1.1.2 + generate-function: 2.3.1 + iconv-lite: 0.7.3 long: 5.3.2 - lru.min: 1.1.3 + lru.min: 1.1.5 named-placeholders: 1.1.6 - seq-queue: 0.0.5 - sqlstring: 2.3.3 + sql-escaper: 1.5.2 + optional: true named-placeholders@1.1.6: dependencies: - lru.min: 1.1.3 + lru.min: 1.1.5 nanoid@3.3.11: {} - nanoid@5.0.9: {} - - nanoid@5.1.6: {} - - nanostores@1.0.1: {} + nanoid@5.1.16: {} nanostores@1.3.0: {} @@ -16087,9 +16200,59 @@ snapshots: - '@babel/core' - babel-plugin-macros + next@16.1.6(@babel/core@7.29.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + '@next/env': 16.1.6 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.9.11 + caniuse-lite: 1.0.30001774 + postcss: 8.4.31 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.0) + optionalDependencies: + '@next/swc-darwin-arm64': 16.1.6 + '@next/swc-darwin-x64': 16.1.6 + '@next/swc-linux-arm64-gnu': 16.1.6 + '@next/swc-linux-arm64-musl': 16.1.6 + '@next/swc-linux-x64-gnu': 16.1.6 + '@next/swc-linux-x64-musl': 16.1.6 + '@next/swc-win32-arm64-msvc': 16.1.6 + '@next/swc-win32-x64-msvc': 16.1.6 + sharp: 0.34.4 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + optional: true + + next@16.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + '@next/env': 16.1.6 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.9.11 + caniuse-lite: 1.0.30001774 + postcss: 8.4.31 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.0) + optionalDependencies: + '@next/swc-darwin-arm64': 16.1.6 + '@next/swc-darwin-x64': 16.1.6 + '@next/swc-linux-arm64-gnu': 16.1.6 + '@next/swc-linux-arm64-musl': 16.1.6 + '@next/swc-linux-x64-gnu': 16.1.6 + '@next/swc-linux-x64-musl': 16.1.6 + '@next/swc-win32-arm64-msvc': 16.1.6 + '@next/swc-win32-x64-msvc': 16.1.6 + sharp: 0.34.4 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + optional: true + nice-try@1.0.5: {} - nitropack@2.13.1(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1)(rolldown@1.0.0-rc.15): + nitropack@2.13.1(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2))(rolldown@1.0.0-rc.15): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.59.0) @@ -16110,7 +16273,7 @@ snapshots: cookie-es: 2.0.0 croner: 9.1.0 crossws: 0.3.5 - db0: 0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1) + db0: 0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)) defu: 6.1.7 destr: 2.0.5 dot-prop: 10.1.0 @@ -16156,7 +16319,7 @@ snapshots: unenv: 2.0.0-rc.24 unimport: 5.6.0 unplugin-utils: 0.3.1 - unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(ioredis@5.9.3) + unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)))(ioredis@5.9.3) untyped: 2.0.0 unwasm: 0.5.3 youch: 4.1.0-beta.13 @@ -16191,7 +16354,7 @@ snapshots: - supports-color - uploadthing - nitropack@2.13.1(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.16.1)(rolldown@1.0.0-rc.15): + nitropack@2.13.1(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2))(rolldown@1.0.0-rc.15): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.59.0) @@ -16212,7 +16375,7 @@ snapshots: cookie-es: 2.0.0 croner: 9.1.0 crossws: 0.3.5 - db0: 0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.16.1) + db0: 0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)) defu: 6.1.7 destr: 2.0.5 dot-prop: 10.1.0 @@ -16258,7 +16421,7 @@ snapshots: unenv: 2.0.0-rc.24 unimport: 5.6.0 unplugin-utils: 0.3.1 - unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.16.1))(ioredis@5.9.3) + unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)))(ioredis@5.9.3) untyped: 2.0.0 unwasm: 0.5.3 youch: 4.1.0-beta.13 @@ -16357,17 +16520,17 @@ snapshots: dependencies: boolbase: 1.0.0 - nuxt@4.3.1(175990d052329d8b0154ca336b6ac7a3): + nuxt@4.3.1(6ecebe4c14ebf76ad94dfae71c880d6e): dependencies: '@dxup/nuxt': 0.3.2(magicast@0.5.1) '@nuxt/cli': 3.33.1(@nuxt/schema@4.3.1)(cac@6.7.14)(magicast@0.5.1) - '@nuxt/devtools': 3.1.1(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)) + '@nuxt/devtools': 3.1.1(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3)) '@nuxt/kit': 4.3.1(magicast@0.5.1) - '@nuxt/nitro-server': 4.3.1(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(ioredis@5.9.3)(magicast@0.5.1)(mysql2@3.16.1)(nuxt@4.3.1(175990d052329d8b0154ca336b6ac7a3))(rolldown@1.0.0-rc.15)(typescript@5.9.3) + '@nuxt/nitro-server': 4.3.1(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(ioredis@5.9.3)(magicast@0.5.1)(mysql2@3.24.6(@types/node@25.5.2))(nuxt@4.3.1(6ecebe4c14ebf76ad94dfae71c880d6e))(rolldown@1.0.0-rc.15)(typescript@6.0.3) '@nuxt/schema': 4.3.1 '@nuxt/telemetry': 2.7.0(@nuxt/kit@4.3.1(magicast@0.5.1)) - '@nuxt/vite-builder': 4.3.1(@types/node@25.5.2)(eslint@9.29.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.3.1(175990d052329d8b0154ca336b6ac7a3))(optionator@0.9.4)(rolldown@1.0.0-rc.15)(rollup@4.59.0)(terser@5.44.0)(tsx@4.22.4)(typescript@5.9.3)(vue-tsc@3.2.5(typescript@5.9.3))(vue@3.5.29(typescript@5.9.3))(yaml@2.9.0) - '@unhead/vue': 2.1.7(vue@3.5.29(typescript@5.9.3)) + '@nuxt/vite-builder': 4.3.1(@types/node@25.5.2)(eslint@9.29.0(jiti@2.7.0))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.3.1(6ecebe4c14ebf76ad94dfae71c880d6e))(optionator@0.9.4)(rolldown@1.0.0-rc.15)(rollup@4.59.0)(terser@5.44.0)(tsx@4.22.4)(typescript@6.0.3)(vue-tsc@3.2.5(typescript@6.0.3))(vue@3.5.29(typescript@6.0.3))(yaml@2.9.0) + '@unhead/vue': 2.1.7(vue@3.5.29(typescript@6.0.3)) '@vue/shared': 3.5.29 c12: 3.3.3(magicast@0.5.1) chokidar: 5.0.0 @@ -16412,10 +16575,10 @@ snapshots: unctx: 2.5.0 unimport: 5.6.0 unplugin: 3.0.0 - unplugin-vue-router: 0.19.2(@vue/compiler-sfc@3.5.29)(vue-router@4.6.4(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)) + unplugin-vue-router: 0.19.2(@vue/compiler-sfc@3.5.29)(vue-router@4.6.4(vue@3.5.22(typescript@6.0.3)))(vue@3.5.29(typescript@6.0.3)) untyped: 2.0.0 - vue: 3.5.29(typescript@5.9.3) - vue-router: 4.6.4(vue@3.5.29(typescript@5.9.3)) + vue: 3.5.29(typescript@6.0.3) + vue-router: 4.6.4(vue@3.5.29(typescript@6.0.3)) optionalDependencies: '@parcel/watcher': 2.5.1 '@types/node': 25.5.2 @@ -16480,17 +16643,17 @@ snapshots: - xml2js - yaml - nuxt@4.3.1(dd35fc409f8d464b401077cb817b832f): + nuxt@4.3.1(94603ee5b6c8044fb0adad8a8009d510): dependencies: '@dxup/nuxt': 0.3.2(magicast@0.5.1) '@nuxt/cli': 3.33.1(@nuxt/schema@4.3.1)(cac@6.7.14)(magicast@0.5.1) - '@nuxt/devtools': 3.1.1(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3)) + '@nuxt/devtools': 3.1.1(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)) '@nuxt/kit': 4.3.1(magicast@0.5.1) - '@nuxt/nitro-server': 4.3.1(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.16.1))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(ioredis@5.9.3)(magicast@0.5.1)(mysql2@3.16.1)(nuxt@4.3.1(dd35fc409f8d464b401077cb817b832f))(rolldown@1.0.0-rc.15)(typescript@6.0.3) + '@nuxt/nitro-server': 4.3.1(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(ioredis@5.9.3)(magicast@0.5.1)(mysql2@3.24.6(@types/node@25.5.2))(nuxt@4.3.1(94603ee5b6c8044fb0adad8a8009d510))(rolldown@1.0.0-rc.15)(typescript@5.9.3) '@nuxt/schema': 4.3.1 '@nuxt/telemetry': 2.7.0(@nuxt/kit@4.3.1(magicast@0.5.1)) - '@nuxt/vite-builder': 4.3.1(@types/node@25.5.2)(eslint@9.29.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.3.1(dd35fc409f8d464b401077cb817b832f))(optionator@0.9.4)(rolldown@1.0.0-rc.15)(rollup@4.59.0)(terser@5.44.0)(tsx@4.22.4)(typescript@6.0.3)(vue-tsc@3.2.5(typescript@6.0.3))(vue@3.5.29(typescript@6.0.3))(yaml@2.9.0) - '@unhead/vue': 2.1.7(vue@3.5.29(typescript@6.0.3)) + '@nuxt/vite-builder': 4.3.1(@types/node@25.5.2)(eslint@9.29.0(jiti@2.7.0))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.3.1(94603ee5b6c8044fb0adad8a8009d510))(optionator@0.9.4)(rolldown@1.0.0-rc.15)(rollup@4.59.0)(terser@5.44.0)(tsx@4.22.4)(typescript@5.9.3)(vue-tsc@3.2.5(typescript@5.9.3))(vue@3.5.29(typescript@5.9.3))(yaml@2.9.0) + '@unhead/vue': 2.1.7(vue@3.5.29(typescript@5.9.3)) '@vue/shared': 3.5.29 c12: 3.3.3(magicast@0.5.1) chokidar: 5.0.0 @@ -16535,10 +16698,10 @@ snapshots: unctx: 2.5.0 unimport: 5.6.0 unplugin: 3.0.0 - unplugin-vue-router: 0.19.2(@vue/compiler-sfc@3.5.29)(vue-router@4.6.4(vue@3.5.22(typescript@6.0.3)))(vue@3.5.29(typescript@6.0.3)) + unplugin-vue-router: 0.19.2(@vue/compiler-sfc@3.5.29)(vue-router@4.6.4(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)) untyped: 2.0.0 - vue: 3.5.29(typescript@6.0.3) - vue-router: 4.6.4(vue@3.5.29(typescript@6.0.3)) + vue: 3.5.29(typescript@5.9.3) + vue-router: 4.6.4(vue@3.5.29(typescript@5.9.3)) optionalDependencies: '@parcel/watcher': 2.5.1 '@types/node': 25.5.2 @@ -16700,6 +16863,15 @@ snapshots: is-inside-container: 1.0.0 wsl-utils: 0.1.0 + open@11.0.4: + dependencies: + default-browser: 5.5.1 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.2.1 + wsl-utils: 1.0.1 + open@8.4.2: dependencies: define-lazy-prop: 2.0.0 @@ -16836,9 +17008,9 @@ snapshots: parse-ms@4.0.0: {} - parse5@8.0.0: + parse5@8.0.1: dependencies: - entities: 6.0.1 + entities: 8.1.0 optional: true parseurl@1.3.3: {} @@ -16977,6 +17149,12 @@ snapshots: exsolve: 1.0.7 pathe: 2.0.3 + pkg-types@2.4.0: + dependencies: + confbox: 0.3.1 + exsolve: 1.1.3 + pathe: 2.0.3 + pluralize@8.0.0: {} possible-typed-array-names@1.1.0: {} @@ -17171,6 +17349,8 @@ snapshots: postgres-range@1.1.4: {} + powershell-utils@0.2.1: {} + prebuild-install@7.1.3: dependencies: detect-libc: 2.1.2 @@ -17315,6 +17495,11 @@ snapshots: defu: 6.1.7 destr: 2.0.5 + rc9@3.1.0: + dependencies: + defu: 6.1.7 + destr: 2.0.5 + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -17390,8 +17575,6 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 - regexp-to-ast@0.5.0: {} - regexp-tree@0.1.27: {} regexp.prototype.flags@1.5.4: @@ -17526,6 +17709,8 @@ snapshots: rou3@0.7.12: {} + rou3@0.9.2: {} + router@2.2.0: dependencies: debug: 4.4.1 @@ -17600,6 +17785,8 @@ snapshots: semver@7.7.4: {} + semver@7.8.5: {} + send@1.2.0: dependencies: debug: 4.4.1 @@ -17650,6 +17837,8 @@ snapshots: set-cookie-parser@3.0.1: {} + set-cookie-parser@3.1.4: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -17786,6 +17975,9 @@ snapshots: source-map-js@1.2.1: {} + source-map-js@1.2.2: + optional: true + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -17813,6 +18005,8 @@ snapshots: split2@4.2.0: {} + sql-escaper@1.5.2: {} + sql.js@1.13.0: {} sqlstring@2.3.3: {} @@ -17946,7 +18140,7 @@ snapshots: dependencies: js-tokens: 9.0.1 - strtok3@10.3.5: + strtok3@10.6.2: dependencies: '@tokenizer/token': 0.3.0 @@ -17966,6 +18160,14 @@ snapshots: optionalDependencies: '@babel/core': 7.29.0 + styled-jsx@5.1.6(@babel/core@7.29.7)(react@19.2.0): + dependencies: + client-only: 0.0.1 + react: 19.2.0 + optionalDependencies: + '@babel/core': 7.29.7 + optional: true + stylehacks@7.0.7(postcss@8.5.6): dependencies: browserslist: 4.28.1 @@ -18153,15 +18355,15 @@ snapshots: tinyrainbow@3.0.3: {} - tldts-core@7.0.27: + tldts-core@7.4.19: optional: true - tldts@7.0.27: + tldts@7.4.19: dependencies: - tldts-core: 7.0.27 + tldts-core: 7.4.19 optional: true - tmp@0.2.5: {} + tmp@0.2.7: {} to-regex-range@5.0.1: dependencies: @@ -18181,9 +18383,9 @@ snapshots: totalist@3.0.1: {} - tough-cookie@6.0.1: + tough-cookie@6.0.2: dependencies: - tldts: 7.0.27 + tldts: 7.4.19 optional: true tr46@0.0.3: {} @@ -18325,23 +18527,23 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.34.1(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3): + typescript-eslint@8.34.1(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/utils': 8.34.1(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) - eslint: 9.29.0(jiti@2.6.1) + '@typescript-eslint/eslint-plugin': 8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.34.1(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 9.29.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - typescript-eslint@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3): + typescript-eslint@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3))(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/parser': 8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/typescript-estree': 8.46.2(typescript@6.0.3) - '@typescript-eslint/utils': 8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@6.0.3) - eslint: 9.29.0(jiti@2.6.1) + '@typescript-eslint/utils': 8.46.2(eslint@9.29.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 9.29.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -18354,7 +18556,7 @@ snapshots: ufo@1.6.3: {} - uint8array-extras@1.5.0: {} + uint8array-extras@1.6.0: {} ulid@3.0.0: {} @@ -18519,7 +18721,7 @@ snapshots: dependencies: rolldown: 1.0.0-rc.15 - unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(ioredis@5.9.3): + unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)))(ioredis@5.9.3): dependencies: anymatch: 3.1.3 chokidar: 5.0.0 @@ -18530,10 +18732,10 @@ snapshots: ofetch: 1.5.1 ufo: 1.6.3 optionalDependencies: - db0: 0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1) + db0: 0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)) ioredis: 5.9.3 - unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.16.1))(ioredis@5.9.3): + unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)))(ioredis@5.9.3): dependencies: anymatch: 3.1.3 chokidar: 5.0.0 @@ -18544,7 +18746,7 @@ snapshots: ofetch: 1.5.1 ufo: 1.6.3 optionalDependencies: - db0: 0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.16.1) + db0: 0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.29.0)(mysql2@3.24.6(@types/node@25.5.2))(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@6.0.3))(sql.js@1.13.0))(mysql2@3.24.6(@types/node@25.5.2)) ioredis: 5.9.3 untun@0.1.3: @@ -18592,9 +18794,7 @@ snapshots: util-deprecate@1.0.2: {} - uuid@11.0.5: {} - - uuid@11.1.0: {} + uuid@11.1.1: {} validate-npm-package-license@3.0.4: dependencies: @@ -18609,10 +18809,20 @@ snapshots: vite: 7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) vite-hot-client: 2.1.0(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + vite-dev-rpc@1.1.0(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)): + dependencies: + birpc: 2.9.0 + vite: 7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite-hot-client: 2.1.0(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + vite-hot-client@2.1.0(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)): dependencies: vite: 7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite-hot-client@2.1.0(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)): + dependencies: + vite: 7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite-node@5.3.0(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0): dependencies: cac: 6.7.14 @@ -18633,7 +18843,7 @@ snapshots: - tsx - yaml - vite-plugin-checker@0.12.0(eslint@9.29.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue-tsc@3.2.5(typescript@5.9.3)): + vite-plugin-checker@0.12.0(eslint@9.29.0(jiti@2.7.0))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue-tsc@3.2.5(typescript@5.9.3)): dependencies: '@babel/code-frame': 7.28.6 chokidar: 4.0.3 @@ -18645,12 +18855,12 @@ snapshots: vite: 7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) vscode-uri: 3.1.0 optionalDependencies: - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) optionator: 0.9.4 typescript: 5.9.3 vue-tsc: 3.2.5(typescript@5.9.3) - vite-plugin-checker@0.12.0(eslint@9.29.0(jiti@2.6.1))(optionator@0.9.4)(typescript@6.0.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue-tsc@3.2.5(typescript@6.0.3)): + vite-plugin-checker@0.12.0(eslint@9.29.0(jiti@2.7.0))(optionator@0.9.4)(typescript@6.0.3)(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue-tsc@3.2.5(typescript@6.0.3)): dependencies: '@babel/code-frame': 7.28.6 chokidar: 4.0.3 @@ -18659,10 +18869,10 @@ snapshots: picomatch: 4.0.3 tiny-invariant: 1.3.3 tinyglobby: 0.2.15 - vite: 7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) vscode-uri: 3.1.0 optionalDependencies: - eslint: 9.29.0(jiti@2.6.1) + eslint: 9.29.0(jiti@2.7.0) optionator: 0.9.4 typescript: 6.0.3 vue-tsc: 3.2.5(typescript@6.0.3) @@ -18684,6 +18894,23 @@ snapshots: transitivePeerDependencies: - supports-color + vite-plugin-inspect@11.3.3(@nuxt/kit@4.3.1(magicast@0.5.1))(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)): + dependencies: + ansis: 4.2.0 + debug: 4.4.3 + error-stack-parser-es: 1.0.5 + ohash: 2.0.11 + open: 10.2.0 + perfect-debounce: 2.1.0 + sirv: 3.0.2 + unplugin-utils: 0.3.1 + vite: 7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite-dev-rpc: 1.1.0(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + optionalDependencies: + '@nuxt/kit': 4.3.1(magicast@0.5.1) + transitivePeerDependencies: + - supports-color + vite-plugin-vue-tracer@1.2.0(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@5.9.3)): dependencies: estree-walker: 3.0.3 @@ -18694,17 +18921,17 @@ snapshots: vite: 7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) vue: 3.5.29(typescript@5.9.3) - vite-plugin-vue-tracer@1.2.0(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3)): + vite-plugin-vue-tracer@1.2.0(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.29(typescript@6.0.3)): dependencies: estree-walker: 3.0.3 exsolve: 1.0.8 magic-string: 0.30.21 pathe: 2.0.3 source-map-js: 1.2.1 - vite: 7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) vue: 3.5.29(typescript@6.0.3) - vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0): + vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -18715,13 +18942,13 @@ snapshots: optionalDependencies: '@types/node': 20.19.24 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 2.7.0 lightningcss: 1.30.2 terser: 5.44.0 tsx: 4.22.4 yaml: 2.8.0 - vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0): + vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -18732,13 +18959,13 @@ snapshots: optionalDependencies: '@types/node': 20.19.24 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 2.7.0 lightningcss: 1.30.2 terser: 5.44.0 tsx: 4.22.4 yaml: 2.9.0 - vite@7.3.0(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0): + vite@7.3.0(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -18749,14 +18976,14 @@ snapshots: optionalDependencies: '@types/node': 25.5.2 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 2.7.0 lightningcss: 1.30.2 terser: 5.44.0 tsx: 4.22.4 yaml: 2.9.0 optional: true - vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0): + vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0): dependencies: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) @@ -18767,7 +18994,7 @@ snapshots: optionalDependencies: '@types/node': 20.19.24 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 2.7.0 lightningcss: 1.30.2 terser: 5.44.0 tsx: 4.22.4 @@ -18791,23 +19018,45 @@ snapshots: tsx: 4.22.4 yaml: 2.9.0 - vitefu@1.1.1(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)): + vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0): + dependencies: + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.59.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 25.5.2 + fsevents: 2.3.3 + jiti: 2.7.0 + lightningcss: 1.30.2 + terser: 5.44.0 + tsx: 4.22.4 + yaml: 2.9.0 + + vitefu@1.1.1(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)): optionalDependencies: - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) - vitefu@1.1.1(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)): + vitefu@1.1.1(vite@7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)): optionalDependencies: - vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.1(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) optional: true vitefu@1.1.1(vite@7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)): optionalDependencies: vite: 7.3.1(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) - vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0): + vitefu@1.1.1(vite@7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)): + optionalDependencies: + vite: 7.3.1(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + optional: true + + vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0): dependencies: '@vitest/expect': 4.0.14 - '@vitest/mocker': 4.0.14(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0)) + '@vitest/mocker': 4.0.14(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0)) '@vitest/pretty-format': 4.0.14 '@vitest/runner': 4.0.14 '@vitest/snapshot': 4.0.14 @@ -18824,7 +19073,7 @@ snapshots: tinyexec: 0.3.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0) + vite: 7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: '@edge-runtime/vm': 5.0.0 @@ -18844,10 +19093,10 @@ snapshots: - tsx - yaml - vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0): + vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0): dependencies: '@vitest/expect': 4.0.14 - '@vitest/mocker': 4.0.14(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + '@vitest/mocker': 4.0.14(vite@7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) '@vitest/pretty-format': 4.0.14 '@vitest/runner': 4.0.14 '@vitest/snapshot': 4.0.14 @@ -18864,7 +19113,7 @@ snapshots: tinyexec: 0.3.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.0(@types/node@20.19.24)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@edge-runtime/vm': 5.0.0 @@ -18885,10 +19134,10 @@ snapshots: - yaml optional: true - vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@25.5.2)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0): + vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@25.5.2)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0): dependencies: '@vitest/expect': 4.0.14 - '@vitest/mocker': 4.0.14(vite@7.3.0(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) + '@vitest/mocker': 4.0.14(vite@7.3.0(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0)) '@vitest/pretty-format': 4.0.14 '@vitest/runner': 4.0.14 '@vitest/snapshot': 4.0.14 @@ -18905,7 +19154,7 @@ snapshots: tinyexec: 0.3.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.0(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 7.3.0(@types/node@25.5.2)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@edge-runtime/vm': 5.0.0 @@ -19133,10 +19382,18 @@ snapshots: ws@8.20.0: {} + ws@8.22.0: + optional: true + wsl-utils@0.1.0: dependencies: is-wsl: 3.1.0 + wsl-utils@1.0.1: + dependencies: + is-wsl: 3.1.0 + powershell-utils: 0.2.1 + xml-name-validator@5.0.0: optional: true @@ -19172,7 +19429,7 @@ snapshots: yocto-queue@0.1.0: {} - yocto-spinner@0.2.3: + yocto-spinner@1.2.2: dependencies: yoctocolors: 2.1.2 @@ -19210,3 +19467,5 @@ snapshots: zod@4.1.12: {} zod@4.3.6: {} + + zod@4.6.7: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f15d2e997..ea4cf9234 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -28,7 +28,7 @@ catalog: react-dom: 19.2.0 sql.js: ^1.13.0 svelte: 5.53.5 - tmp: ^0.2.4 + tmp: ^0.2.6 ts-pattern: ^5.7.1 typescript: ^6.0.3 vue: 3.5.22 diff --git a/samples/taskforge/package.json b/samples/taskforge/package.json index 7287b2122..2d3df1f84 100644 --- a/samples/taskforge/package.json +++ b/samples/taskforge/package.json @@ -18,12 +18,12 @@ "@zenstackhq/better-auth": "workspace:*", "@zenstackhq/orm": "workspace:*", "@zenstackhq/schema": "workspace:*", - "better-auth": "^1.4.21", + "better-auth": "^1.7.12", "better-sqlite3": "catalog:", "commander": "^14.0.2" }, "devDependencies": { - "@better-auth/cli": "^1.4.21", + "auth": "^1.7.12", "@types/better-sqlite3": "catalog:", "@types/node": "catalog:", "@zenstackhq/cli": "workspace:*" diff --git a/tests/e2e/package.json b/tests/e2e/package.json index a1688bdce..77f701656 100644 --- a/tests/e2e/package.json +++ b/tests/e2e/package.json @@ -25,7 +25,7 @@ "decimal.js": "catalog:", "kysely": "catalog:", "ulid": "^3.0.0", - "uuid": "^11.0.5", + "uuid": "^11.1.1", "cuid": "^3.0.0", "zod": "catalog:", "ts-pattern": "catalog:" From db24ecfbe3409b6b55d998484bf6c4f5ff5e6826 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Thu, 22 Oct 2026 13:02:30 -0700 Subject: [PATCH 10/18] fix(language): remove `this` grammar ambiguity in expressions (#2885) Co-authored-by: Claude Opus 5.5 (1M context) --- packages/language/src/generated/ast.ts | 12 +- packages/language/src/generated/grammar.ts | 140 ++++++++++++--------- packages/language/src/zmodel.langium | 9 +- 3 files changed, 96 insertions(+), 65 deletions(-) diff --git a/packages/language/src/generated/ast.ts b/packages/language/src/generated/ast.ts index 5676b8889..cfe800a62 100644 --- a/packages/language/src/generated/ast.ts +++ b/packages/language/src/generated/ast.ts @@ -354,7 +354,7 @@ export interface DataField extends langium.AstNode { readonly $type: 'DataField'; attributes: Array; comments: Array; - name: RegularIDWithTypeNames; + name: DataFieldName; params: Array; type: DataFieldType; } @@ -389,6 +389,12 @@ export function isDataFieldAttribute(item: unknown): item is DataFieldAttribute return reflection.isInstance(item, DataFieldAttribute.$type); } +export type DataFieldName = 'this' | RegularIDWithTypeNames; + +export function isDataFieldName(item: unknown): item is DataFieldName { + return isRegularIDWithTypeNames(item) || item === 'this'; +} + export interface DataFieldParam extends langium.AstNode { readonly $container: DataField; readonly $type: 'DataFieldParam'; @@ -914,10 +920,10 @@ export function isReferenceTarget(item: unknown): item is ReferenceTarget { return reflection.isInstance(item, ReferenceTarget.$type); } -export type RegularID = 'abstract' | 'attribute' | 'datasource' | 'enum' | 'import' | 'in' | 'model' | 'plugin' | 'this' | 'type' | 'view' | string; +export type RegularID = 'abstract' | 'attribute' | 'datasource' | 'enum' | 'import' | 'in' | 'model' | 'plugin' | 'type' | 'view' | string; export function isRegularID(item: unknown): item is RegularID { - return item === 'model' || item === 'enum' || item === 'attribute' || item === 'datasource' || item === 'plugin' || item === 'abstract' || item === 'in' || item === 'view' || item === 'import' || item === 'type' || item === 'this' || (typeof item === 'string' && (/[_a-zA-Z][\w_]*/.test(item))); + return item === 'model' || item === 'enum' || item === 'attribute' || item === 'datasource' || item === 'plugin' || item === 'abstract' || item === 'in' || item === 'view' || item === 'import' || item === 'type' || (typeof item === 'string' && (/[_a-zA-Z][\w_]*/.test(item))); } export type RegularIDWithTypeNames = 'Any' | 'BigInt' | 'Boolean' | 'Bytes' | 'DateTime' | 'Decimal' | 'Float' | 'Int' | 'Json' | 'Null' | 'Object' | 'String' | 'Unsupported' | 'Void' | RegularID; diff --git a/packages/language/src/generated/grammar.ts b/packages/language/src/generated/grammar.ts index 14d288069..4c173e145 100644 --- a/packages/language/src/generated/grammar.ts +++ b/packages/language/src/generated/grammar.ts @@ -67,7 +67,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@71" + "$ref": "#/rules@72" }, "arguments": [] } @@ -141,7 +141,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] }, @@ -167,7 +167,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [], "cardinality": "*" @@ -224,7 +224,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [], "cardinality": "*" @@ -281,7 +281,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [], "cardinality": "*" @@ -329,7 +329,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [], "cardinality": "*" @@ -386,7 +386,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [], "cardinality": "*" @@ -468,7 +468,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@72" + "$ref": "#/rules@73" }, "arguments": [] } @@ -487,7 +487,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@71" + "$ref": "#/rules@72" }, "arguments": [] } @@ -506,7 +506,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@65" + "$ref": "#/rules@66" }, "arguments": [] } @@ -621,7 +621,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@70" + "$ref": "#/rules@71" }, "arguments": [] } @@ -713,7 +713,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@70" + "$ref": "#/rules@71" }, "arguments": [] } @@ -907,7 +907,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@54" + "$ref": "#/rules@55" }, "arguments": [] }, @@ -1001,7 +1001,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@70" + "$ref": "#/rules@71" }, "arguments": [] } @@ -1116,7 +1116,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@71" + "$ref": "#/rules@72" }, "arguments": [] } @@ -1841,7 +1841,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [] }, @@ -1983,7 +1983,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@60" }, "arguments": [] } @@ -2035,7 +2035,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@64" + "$ref": "#/rules@65" }, "arguments": [] } @@ -2113,7 +2113,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [] }, @@ -2198,7 +2198,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [] }, @@ -2219,7 +2219,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [], "cardinality": "*" @@ -2284,7 +2284,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@64" + "$ref": "#/rules@65" }, "arguments": [] } @@ -2371,7 +2371,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [] }, @@ -2427,7 +2427,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@60" }, "arguments": [] } @@ -2494,7 +2494,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [] }, @@ -2542,7 +2542,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@60" }, "arguments": [] } @@ -2573,7 +2573,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [] }, @@ -2586,7 +2586,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@54" + "$ref": "#/rules@55" }, "arguments": [] } @@ -2598,7 +2598,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [] }, @@ -2619,7 +2619,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [], "cardinality": "*" @@ -2732,7 +2732,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@60" + "$ref": "#/rules@61" }, "arguments": [] }, @@ -2753,7 +2753,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [], "cardinality": "*" @@ -2818,7 +2818,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@63" + "$ref": "#/rules@64" }, "arguments": [] } @@ -2879,7 +2879,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [], "cardinality": "*" @@ -2937,7 +2937,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [], "cardinality": "*" @@ -3039,7 +3039,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@60" + "$ref": "#/rules@61" }, "arguments": [] }, @@ -3061,7 +3061,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@70" + "$ref": "#/rules@71" }, "arguments": [] }, @@ -3104,6 +3104,26 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "Keyword", "value": "type" + } + ] + }, + "entry": false, + "fragment": false, + "parameters": [] + }, + { + "$type": "ParserRule", + "name": "DataFieldName", + "dataType": "string", + "definition": { + "$type": "Alternatives", + "elements": [ + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@55" + }, + "arguments": [] }, { "$type": "Keyword", @@ -3204,7 +3224,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [] }, @@ -3224,21 +3244,21 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@67" + "$ref": "#/rules@68" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@68" + "$ref": "#/rules@69" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@69" + "$ref": "#/rules@70" }, "arguments": [] } @@ -3259,7 +3279,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@57" }, "arguments": [] } @@ -3278,7 +3298,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@57" }, "arguments": [] } @@ -3300,7 +3320,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@60" + "$ref": "#/rules@61" }, "arguments": [] }, @@ -3325,7 +3345,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [] }, @@ -3364,7 +3384,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@57" + "$ref": "#/rules@58" }, "arguments": [] } @@ -3376,7 +3396,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@60" + "$ref": "#/rules@61" }, "arguments": [] }, @@ -3407,7 +3427,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@63" + "$ref": "#/rules@64" }, "arguments": [] }, @@ -3496,12 +3516,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@69" + "$ref": "#/rules@70" }, "arguments": [] }, @@ -3519,7 +3539,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@61" + "$ref": "#/rules@62" }, "arguments": [], "cardinality": "?" @@ -3546,7 +3566,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@73" + "$ref": "#/rules@74" }, "arguments": [], "cardinality": "*" @@ -3558,12 +3578,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@68" + "$ref": "#/rules@69" }, "arguments": [] }, @@ -3581,7 +3601,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@61" + "$ref": "#/rules@62" }, "arguments": [], "cardinality": "?" @@ -3612,12 +3632,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@67" + "$ref": "#/rules@68" }, "arguments": [] }, @@ -3635,7 +3655,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@61" + "$ref": "#/rules@62" }, "arguments": [], "cardinality": "?" @@ -3667,7 +3687,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@62" + "$ref": "#/rules@63" }, "arguments": [] } @@ -3686,7 +3706,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@62" + "$ref": "#/rules@63" }, "arguments": [] } diff --git a/packages/language/src/zmodel.langium b/packages/language/src/zmodel.langium index c2845aac1..2ef9d23e7 100644 --- a/packages/language/src/zmodel.langium +++ b/packages/language/src/zmodel.langium @@ -188,7 +188,7 @@ fragment ExtendsClause: DataField: (comments+=TRIPLE_SLASH_COMMENT)* - name=RegularIDWithTypeNames ('(' params+=DataFieldParam (',' params+=DataFieldParam)* ')')? type=DataFieldType (attributes+=DataFieldAttribute)*; + name=DataFieldName ('(' params+=DataFieldParam (',' params+=DataFieldParam)* ')')? type=DataFieldType (attributes+=DataFieldAttribute)*; // Parameters of a parameterized field (only meaningful on `@computed` fields). // Shares the shape of FunctionParam/ProcedureParam. @@ -242,7 +242,12 @@ Procedure: // https://github.com/langium/langium/discussions/1012 RegularID returns string: // include keywords that we'd like to work as ID in most places - ID | 'model' | 'enum' | 'attribute' | 'datasource' | 'plugin' | 'abstract' | 'in' | 'view' | 'import' | 'type' | 'this'; + ID | 'model' | 'enum' | 'attribute' | 'datasource' | 'plugin' | 'abstract' | 'in' | 'view' | 'import' | 'type'; + +// `this` is only allowed as a field name (for primitive type defs' `this` field); keeping it out of +// `RegularID` avoids ambiguity with `ThisExpr` in expressions +DataFieldName returns string: + RegularIDWithTypeNames | 'this'; RegularIDWithTypeNames returns string: RegularID | 'String' | 'Boolean' | 'Int' | 'BigInt' | 'Float' | 'Decimal' | 'DateTime' | 'Json' | 'Bytes' | 'Null' | 'Object' | 'Any' | 'Void' | 'Unsupported'; From 675fe20d72226a84f55b6f325eb5c208ef9ea82b Mon Sep 17 00:00:00 2001 From: Natansal-weme Date: Thu, 22 Oct 2026 23:05:37 +0300 Subject: [PATCH 11/18] fix(orm): pass the model alias to `$expr` filters (#2864) Co-authored-by: Cursor --- packages/orm/src/client/crud-types.ts | 19 +++- .../src/client/crud/dialects/base-dialect.ts | 2 +- tests/e2e/orm/client-api/find.test.ts | 26 +++++ tests/regression/test/issue-2863.test.ts | 97 +++++++++++++++++++ 4 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 tests/regression/test/issue-2863.test.ts diff --git a/packages/orm/src/client/crud-types.ts b/packages/orm/src/client/crud-types.ts index b4b137e4c..3d6bef971 100644 --- a/packages/orm/src/client/crud-types.ts +++ b/packages/orm/src/client/crud-types.ts @@ -364,6 +364,20 @@ export type BatchResult = { count: number }; //#region Common structures +/** + * Context object passed to `$expr` filters. + */ +export type ExprFilterContext> = { + /** + * The alias that can be used to refer to the filtered model. It's the model name for top-level + * filters, but relation filters select the model under a generated alias. + * + * Typed as the model name to match the expression builder's scope, so qualified references + * like `` eb.ref(`${modelAlias}.field`) `` type-check. + */ + modelAlias: Model; +}; + export type WhereInput< Schema extends SchemaDef, Model extends GetModels, @@ -381,7 +395,10 @@ export type WhereInput< { args: ComputedFieldArgs } & FieldFilter : FieldFilter; } & { - $expr?: (eb: ExpressionBuilder, Model>) => OperandExpression; + $expr?: ( + eb: ExpressionBuilder, Model>, + context: ExprFilterContext, + ) => OperandExpression; } & { AND?: OrArray>; OR?: WhereInput[]; diff --git a/packages/orm/src/client/crud/dialects/base-dialect.ts b/packages/orm/src/client/crud/dialects/base-dialect.ts index 2fa4b4d37..0b5226624 100644 --- a/packages/orm/src/client/crud/dialects/base-dialect.ts +++ b/packages/orm/src/client/crud/dialects/base-dialect.ts @@ -296,7 +296,7 @@ export abstract class BaseCrudDialect { // call expression builder and combine the results if ('$expr' in _where && typeof _where['$expr'] === 'function') { - result = this.and(result, _where['$expr'](this.eb)); + result = this.and(result, _where['$expr'](this.eb, { modelAlias })); } return result; diff --git a/tests/e2e/orm/client-api/find.test.ts b/tests/e2e/orm/client-api/find.test.ts index 69d9b1c0d..1e4bc9c54 100644 --- a/tests/e2e/orm/client-api/find.test.ts +++ b/tests/e2e/orm/client-api/find.test.ts @@ -1277,4 +1277,30 @@ describe('Client find tests ', () => { }), ).resolves.toHaveLength(0); }); + + it('supports qualified references with the $expr model alias', async () => { + const user1 = await createUser(client, 'yiming@zenstack.dev'); + const user2 = await createUser(client, 'yiming@gmail.com'); + await createPosts(client, user1.id); + await createPosts(client, user2.id); + + await expect( + client.user.findMany({ + where: { + $expr: (eb, { modelAlias }) => eb(eb.ref(`${modelAlias}.email`), 'like', '%@zenstack.dev'), + }, + }), + ).resolves.toHaveLength(1); + + // relation filters select the related model under a generated alias + const posts = await client.post.findMany({ + where: { + author: { + $expr: (eb, { modelAlias }) => eb(eb.ref(`${modelAlias}.email`), 'like', '%@zenstack.dev'), + }, + }, + }); + expect(posts.length).toBeGreaterThan(0); + expect(posts.every((p) => p.authorId === user1.id)).toBe(true); + }); }); diff --git a/tests/regression/test/issue-2863.test.ts b/tests/regression/test/issue-2863.test.ts new file mode 100644 index 000000000..d1197336d --- /dev/null +++ b/tests/regression/test/issue-2863.test.ts @@ -0,0 +1,97 @@ +import { createTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +describe('Regression for issue #2863', () => { + const schema = ` +model User { + id Int @id @default(autoincrement()) + email String @unique + posts Post[] +} + +model Post { + id Int @id @default(autoincrement()) + title String + authorId Int + author User @relation(fields: [authorId], references: [id]) +} +`; + + async function setup() { + const db = await createTestClient(schema); + await db.user.create({ + data: { email: 'u1@zenstack.dev', posts: { create: [{ title: 'p1' }] } }, + }); + await db.user.create({ + data: { email: 'u2@example.com', posts: { create: [{ title: 'p2' }, { title: 'p3' }] } }, + }); + return db; + } + + it('passes the model alias to top-level $expr', async () => { + const db = await setup(); + let alias: string | undefined; + + const users = await db.user.findMany({ + where: { + $expr: (eb: any, { modelAlias }: { modelAlias: string }) => { + alias = modelAlias; + return eb(eb.ref(`${modelAlias}.email`), 'like', '%@zenstack.dev'); + }, + }, + }); + + expect(alias).toBe('User'); + expect(users.map((u: any) => u.email)).toEqual(['u1@zenstack.dev']); + }); + + it('passes the generated alias to $expr inside a to-one relation filter', async () => { + const db = await setup(); + let alias: string | undefined; + + const posts = await db.post.findMany({ + where: { + author: { + $expr: (eb: any, { modelAlias }: { modelAlias: string }) => { + alias = modelAlias; + return eb(eb.ref(`${modelAlias}.email`), 'like', '%@zenstack.dev'); + }, + }, + }, + }); + + expect(alias).not.toBe('User'); + expect(posts.map((p: any) => p.title)).toEqual(['p1']); + }); + + it('passes the generated alias to $expr inside a to-many relation filter', async () => { + const db = await setup(); + + const users = await db.user.findMany({ + where: { + posts: { + some: { + $expr: (eb: any, { modelAlias }: { modelAlias: string }) => + eb(eb.ref(`${modelAlias}.title`), '=', 'p3'), + }, + }, + }, + }); + + expect(users.map((u: any) => u.email)).toEqual(['u2@example.com']); + }); + + it('keeps supporting unqualified references inside relation filters', async () => { + const db = await setup(); + + const posts = await db.post.findMany({ + where: { + author: { + $expr: (eb: any) => eb('email', 'like', '%@zenstack.dev'), + }, + }, + }); + + expect(posts.map((p: any) => p.title)).toEqual(['p1']); + }); +}); From ee7198272551b186d8020a74fe92b3b0e354b9fd Mon Sep 17 00:00:00 2001 From: Michael <51033404+theluckystrike@users.noreply.github.com> Date: Fri, 23 Oct 2026 07:59:35 +0700 Subject: [PATCH 12/18] fix(policy): compare `before()` enum fields as text on PostgreSQL (#2869) Co-authored-by: Claude Opus 5.5 Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com> --- .../policy/src/expression-transformer.ts | 23 ++++--- tests/regression/test/issue-2727.test.ts | 60 +++++++++++++++++++ 2 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 tests/regression/test/issue-2727.test.ts diff --git a/packages/plugins/policy/src/expression-transformer.ts b/packages/plugins/policy/src/expression-transformer.ts index ea5d0f1ee..847e7de46 100644 --- a/packages/plugins/policy/src/expression-transformer.ts +++ b/packages/plugins/policy/src/expression-transformer.ts @@ -342,14 +342,13 @@ export class ExpressionTransformer { const rightFieldDef = this.getFieldDefFromFieldRef(normalizedRight, context); // Map ZModel operator to SQL operator string const sqlOp = op === '==' ? '=' : op; + // `before()` enums are text in the VALUES table and pg can't compare text to a native enum + const asText = + this.isBeforeEnumField(normalizedLeft, context) || this.isBeforeEnumField(normalizedRight, context); + const operand = (node: OperationNode) => + asText ? this.dialect.castText(new ExpressionWrapper(node)) : new ExpressionWrapper(node); return this.dialect - .buildComparison( - new ExpressionWrapper(left), - leftFieldDef, - sqlOp, - new ExpressionWrapper(right), - rightFieldDef, - ) + .buildComparison(operand(left), leftFieldDef, sqlOp, operand(right), rightFieldDef) .toOperationNode(); } } @@ -1215,6 +1214,16 @@ export class ExpressionTransformer { return !!fieldDef?.relation; } + private isBeforeEnumField(expr: Expression, context: ExpressionTransformerContext) { + if (!ExpressionUtils.isMember(expr) || !isBeforeInvocation(expr.receiver)) { + return false; + } + // `before()` always refers to the model being updated (`thisType`), even inside + // collection predicates where `modelOrType` is the related model + const fieldDef = QueryUtils.getField(this.schema, context.thisType, expr.members[0]!); + return !!fieldDef && QueryUtils.isEnum(this.schema, fieldDef.type); + } + private getFieldDefFromFieldRef(expr: Expression, context: ExpressionTransformerContext): FieldDef | undefined { return this.resolveFieldRef(expr, context)?.fieldDef; } diff --git a/tests/regression/test/issue-2727.test.ts b/tests/regression/test/issue-2727.test.ts new file mode 100644 index 000000000..668517fa0 --- /dev/null +++ b/tests/regression/test/issue-2727.test.ts @@ -0,0 +1,60 @@ +import { createPolicyTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +describe('Regression for issue #2727', () => { + it('compares before() enum fields in post-update rules on PostgreSQL native enums', async () => { + const db = await createPolicyTestClient( + ` +enum State { + DRAFT + IN_PROGRESS + DONE +} + +model Post { + id Int @id + state State @default(DRAFT) + @@allow('all', true) + @@deny('post-update', before().state != state && before().state == DRAFT && state != IN_PROGRESS) +} + `, + { provider: 'postgresql', usePrismaPush: true }, + ); + + await db.post.create({ data: { id: 1 } }); + await expect(db.post.update({ where: { id: 1 }, data: { state: 'DONE' } })).toBeRejectedByPolicy(); + await db.post.update({ where: { id: 1 }, data: { state: 'IN_PROGRESS' } }); + }); + + it('compares before() enum fields inside collection predicates on PostgreSQL native enums', async () => { + const db = await createPolicyTestClient( + ` +enum State { + DRAFT + IN_PROGRESS + DONE +} + +model Post { + id Int @id + state State @default(DRAFT) + comments Comment[] + @@allow('all', true) + @@deny('post-update', comments?[before().state != this.state] && state == DONE) +} + +model Comment { + id Int @id + post Post @relation(fields: [postId], references: [id]) + postId Int + @@allow('all', true) +} + `, + { provider: 'postgresql', usePrismaPush: true }, + ); + + await db.post.create({ data: { id: 1, comments: { create: { id: 1 } } } }); + await expect(db.post.update({ where: { id: 1 }, data: { state: 'DONE' } })).toBeRejectedByPolicy(); + await db.post.update({ where: { id: 1 }, data: { state: 'IN_PROGRESS' } }); + }); +}); From 862d1c2bb7f70cc18816298081e9d5b439ca5f7e Mon Sep 17 00:00:00 2001 From: Erik Dakoda Date: Mon, 26 Oct 2026 00:43:10 -0400 Subject: [PATCH 13/18] fix(orm): alias delegate joins in relation subqueries so they don't shadow outer tables (#2877) Co-authored-by: Claude Opus 5.5 --- .../src/client/crud/dialects/base-dialect.ts | 67 +++++-- .../orm/src/client/crud/operations/base.ts | 2 +- packages/plugins/soft-delete/src/plugin.ts | 7 +- .../soft-delete/test/soft-delete.test.ts | 39 ++++ tests/regression/test/issue-2870.test.ts | 175 ++++++++++++++++++ 5 files changed, 273 insertions(+), 17 deletions(-) create mode 100644 tests/regression/test/issue-2870.test.ts diff --git a/packages/orm/src/client/crud/dialects/base-dialect.ts b/packages/orm/src/client/crud/dialects/base-dialect.ts index 0b5226624..0160c9586 100644 --- a/packages/orm/src/client/crud/dialects/base-dialect.ts +++ b/packages/orm/src/client/crud/dialects/base-dialect.ts @@ -282,7 +282,7 @@ export abstract class BaseCrudDialect { const fieldRef = this.fieldRef( fieldDef.originModel ?? model, key, - fieldDef.originModel ?? modelAlias, + this.delegateFieldAlias(model, modelAlias, fieldDef), true, computedArgs, ); @@ -328,10 +328,10 @@ export abstract class BaseCrudDialect { const _order = negateOrderBy ? (order === 'asc' ? 'desc' : 'asc') : order; const op = j === i ? (_order === 'asc' ? '>=' : '<=') : '='; // Fields inherited from a delegate base live on the base table, which is - // joined with its model name as alias. See buildSelectModel/buildDelegateJoin. + // joined under `delegateJoinAlias`. See buildSelectModel/buildDelegateJoin. const fieldDef = requireField(this.schema, model, field); - const outerAlias = fieldDef.originModel ?? modelAlias; - const subSelectAlias = fieldDef.originModel ?? subQueryAlias; + const outerAlias = this.delegateFieldAlias(model, modelAlias, fieldDef); + const subSelectAlias = this.delegateFieldAlias(model, subQueryAlias, fieldDef); andFilters.push( this.eb( this.eb.ref(`${outerAlias}.${field}`), @@ -431,7 +431,7 @@ export abstract class BaseCrudDialect { this.schema, model, // if field is from a base, use the base model to join - fieldDef.originModel ?? modelAlias, + this.delegateFieldAlias(model, modelAlias, fieldDef), field, joinAlias, ); @@ -1184,7 +1184,13 @@ export abstract class BaseCrudDialect { const buildFieldRef = (model: string, field: string, modelAlias: string, computedArgs?: unknown) => { const fieldDef = requireField(this.schema, model, field); return fieldDef.originModel - ? this.fieldRef(fieldDef.originModel, field, fieldDef.originModel, true, computedArgs) + ? this.fieldRef( + fieldDef.originModel, + field, + this.delegateJoinAlias(model, modelAlias, fieldDef.originModel), + true, + computedArgs, + ) : this.fieldRef(model, field, modelAlias, true, computedArgs); }; @@ -1283,12 +1289,25 @@ export abstract class BaseCrudDialect { // order by to-one relation const joinAlias = tmpAlias(`${modelAlias}$ob$${index}`); - const joined = query.leftJoin(`${relationModel} as ${joinAlias}`, (join) => { - const joinPairs = buildJoinPairs(this.schema, model, modelAlias, field, joinAlias); + let joined = query.leftJoin(`${relationModel} as ${joinAlias}`, (join) => { + const joinPairs = buildJoinPairs( + this.schema, + model, + // if field is from a base, use the base model to join + this.delegateFieldAlias(model, modelAlias, fieldDef), + field, + joinAlias, + ); return join.on((eb) => this.and(...joinPairs.map(([left, right]) => eb(this.eb.ref(left), '=', this.eb.ref(right)))), ); }); + // join delegate bases so that the ordering can reference inherited fields + let joinBase = requireModel(this.schema, relationModel).baseModel; + while (joinBase) { + joined = this.buildDelegateJoin(relationModel, joinAlias, joinBase, joined); + joinBase = requireModel(this.schema, joinBase).baseModel; + } return this.buildOrderBy(joined, relationModel, joinAlias, value, negated, take); } @@ -1475,6 +1494,7 @@ export abstract class BaseCrudDialect { const descendants = getDelegateDescendantModels(this.schema, model); for (const subModel of descendants) { result = this.buildDelegateJoin(model, modelAlias, subModel.name, result); + const subModelAlias = this.delegateJoinAlias(model, modelAlias, subModel.name); result = result.select(() => { const jsonObject: Record> = {}; for (const fieldDef of getModelFields(this.schema, subModel.name, { computed: true })) { @@ -1485,7 +1505,7 @@ export abstract class BaseCrudDialect { if (fieldDef.computed && fieldDef.params) { continue; } - jsonObject[fieldDef.name] = this.fieldRef(subModel.name, fieldDef.name, subModel.name); + jsonObject[fieldDef.name] = this.fieldRef(subModel.name, fieldDef.name, subModelAlias); } return this.buildJsonObject(jsonObject).as(`${DELEGATE_JOINED_FIELD_PREFIX}${subModel.name}`); }); @@ -1546,10 +1566,9 @@ export abstract class BaseCrudDialect { ): SelectQueryBuilder { const fieldDef = requireField(this.schema, model, field); - // if field is defined on a delegate base, the base model is joined with its - // model name from outer query, so we should use it directly as the alias + // if field is defined on a delegate base, it's selected from the joined base table const fieldModel = fieldDef.originModel ?? model; - const alias = fieldDef.originModel ?? modelAlias; + const alias = this.delegateFieldAlias(model, modelAlias, fieldDef); // `computedArgs` is set for a parameterized computed field selected via `select`/`include` return query.select(() => this.fieldRef(fieldModel, field, alias, true, computedArgs).as(field)); @@ -1558,11 +1577,13 @@ export abstract class BaseCrudDialect { buildDelegateJoin( thisModel: string, thisModelAlias: string, - otherModelAlias: string, + otherModel: string, query: SelectQueryBuilder, ) { const idFields = requireIdFields(this.schema, thisModel); - query = query.leftJoin(otherModelAlias, (qb) => { + const otherModelAlias = this.delegateJoinAlias(thisModel, thisModelAlias, otherModel); + const table = otherModelAlias === otherModel ? otherModel : `${otherModel} as ${otherModelAlias}`; + query = query.leftJoin(table, (qb) => { for (const idField of idFields) { qb = qb.onRef(`${thisModelAlias}.${idField}`, '=', `${otherModelAlias}.${idField}`); } @@ -1571,6 +1592,24 @@ export abstract class BaseCrudDialect { return query; } + /** + * Gets the alias under which a delegate base or sub model is joined to a model selected as `modelAlias`. + * A model selected under its own name (a top-level query) has them joined under their own model names. + * A model selected under another alias (a relation subquery) has them joined under aliases derived from + * it, so they never shadow a table of an enclosing query in the same delegate hierarchy. + */ + delegateJoinAlias(model: string, modelAlias: string, joinedModel: string) { + return modelAlias === model ? joinedModel : tmpAlias(`${modelAlias}$${joinedModel}`); + } + + /** + * Gets the alias of the table holding a field of a model selected as `modelAlias`: the joined base + * table for a field inherited from a delegate base, otherwise the model's own table. + */ + delegateFieldAlias(model: string, modelAlias: string, fieldDef: FieldDef) { + return fieldDef.originModel ? this.delegateJoinAlias(model, modelAlias, fieldDef.originModel) : modelAlias; + } + buildCountJson(model: string, eb: ExpressionBuilder, parentAlias: string, payload: any) { const modelDef = requireModel(this.schema, model); const toManyRelations = Object.entries(modelDef.fields).filter(([, field]) => field.relation && field.array); diff --git a/packages/orm/src/client/crud/operations/base.ts b/packages/orm/src/client/crud/operations/base.ts index f68f6139b..1de82f313 100644 --- a/packages/orm/src/client/crud/operations/base.ts +++ b/packages/orm/src/client/crud/operations/base.ts @@ -258,7 +258,7 @@ export abstract class BaseOperationHandler { result, fieldDef.originModel, field, - fieldDef.originModel, + this.dialect.delegateJoinAlias(model, parentAlias, fieldDef.originModel), payload, ); } else { diff --git a/packages/plugins/soft-delete/src/plugin.ts b/packages/plugins/soft-delete/src/plugin.ts index 5ba8347b4..bb26452c4 100644 --- a/packages/plugins/soft-delete/src/plugin.ts +++ b/packages/plugins/soft-delete/src/plugin.ts @@ -207,7 +207,7 @@ class SoftDeleteHandler extends OperationNodeTransform // Resolve the single `@deletedAt` column to filter when reading a table. `@@@onceInModel` allows // at most one marker per inheritance hierarchy, so it's either declared on the model itself or // inherited from exactly one delegate base — where the column physically lives on the base's - // table (LEFT-joined under its own model name, see buildDelegateJoin), so the filter must key off + // table (LEFT-joined under `delegateJoinAlias`, see buildDelegateJoin), so the filter must key off // that base rather than the queried table. private getSoftDeleteTarget(info: TableInfo): { table: string; column: string } | undefined { const own = this.getDeletedAtField(info.model); @@ -218,7 +218,10 @@ class SoftDeleteHandler extends OperationNodeTransform while (base) { const marker = this.getDeletedAtField(base); if (marker) { - return { table: base, column: marker.name }; + return { + table: this.dialect.delegateJoinAlias(info.model, info.alias ?? info.model, base), + column: marker.name, + }; } base = QueryUtils.getModel(this.client.$schema, base)?.baseModel; } diff --git a/packages/plugins/soft-delete/test/soft-delete.test.ts b/packages/plugins/soft-delete/test/soft-delete.test.ts index d4a7eac28..2b655b81b 100644 --- a/packages/plugins/soft-delete/test/soft-delete.test.ts +++ b/packages/plugins/soft-delete/test/soft-delete.test.ts @@ -431,4 +431,43 @@ model Article extends Content { const row = await raw.article.findUniqueOrThrow({ where: { id: a.id } }); expect(row.title).toBe('a'); }); + + it('hides soft-deleted delegate rows reached through a relation', async () => { + // In a relation subquery the delegate base is joined under an alias derived from the + // subquery's alias, so the read filter must reference the base through that alias. + const delegateSchema = ` +model User { + id Int @id @default(autoincrement()) + articles Article[] +} + +model Content { + id Int @id @default(autoincrement()) + contentType String + deletedAt DateTime? @deletedAt + @@delegate(contentType) +} + +model Article extends Content { + title String + author User @relation(fields: [authorId], references: [id]) + authorId Int +} +`; + const raw = await createSoftDeleteTestClient(delegateSchema, { usePrismaPush: true }); + const db = raw.$use(new SoftDeletePlugin()); + + const user = await db.user.create({ data: {} }); + const kept = await db.article.create({ data: { title: 'kept', authorId: user.id } }); + const removed = await db.article.create({ data: { title: 'removed', authorId: user.id } }); + await db.article.delete({ where: { id: removed.id } }); + + await expect(db.user.findUnique({ where: { id: user.id }, include: { articles: true } })).resolves.toEqual( + expect.objectContaining({ articles: [expect.objectContaining({ id: kept.id })] }), + ); + await expect( + db.user.findUnique({ where: { id: user.id }, include: { _count: { select: { articles: true } } } }), + ).resolves.toMatchObject({ _count: { articles: 1 } }); + await expect(db.user.count({ where: { articles: { some: { title: 'removed' } } } })).resolves.toBe(0); + }); }); diff --git a/tests/regression/test/issue-2870.test.ts b/tests/regression/test/issue-2870.test.ts new file mode 100644 index 000000000..1f3374357 --- /dev/null +++ b/tests/regression/test/issue-2870.test.ts @@ -0,0 +1,175 @@ +import { createPolicyTestClient, createTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +// https://github.com/zenstackhq/zenstack/issues/2870 +describe('Regression for issue #2870', () => { + const schema = ` +model Item { + id String @id @default(cuid()) + itemKind String + rank Int @default(0) + notesAsSource Note[] @relation("NoteSource") + pinnedTaskId String? + pinnedTask Task? @relation("Pinned", fields: [pinnedTaskId], references: [id]) + tags Tag[] @relation("ItemTags") + + @@delegate(itemKind) +} + +model Task extends Item { + pinnedBy Item[] @relation("Pinned") +} + +model Tag extends Item { + taggedItems Item[] @relation("ItemTags") +} + +model Note extends Item { + sourceId String? + source Item? @relation("NoteSource", fields: [sourceId], references: [id]) + comments Comment[] +} + +model Comment { + id String @id @default(cuid()) + noteId String? + note Note? @relation(fields: [noteId], references: [id]) +} +`; + + // explicit ids so that ordering by id differs from ordering by relation count + async function setup() { + const db = await createTestClient(schema, { usePrismaPush: true }); + const task = await db.task.create({ data: { id: 'z-task' } }); + const note = await db.note.create({ data: { id: 'a-note', sourceId: task.id, pinnedTaskId: task.id } }); + const tag = await db.tag.create({ data: { id: 'm-tag', taggedItems: { connect: { id: note.id } } } }); + return { db, task, note, tag }; + } + + const ids = (rows: { id: string }[]) => rows.map((r) => r.id).sort(); + + it('filters a base model by a to-many relation into its own hierarchy', async () => { + const { db, task, note, tag } = await setup(); + + expect(ids(await db.item.findMany({ where: { notesAsSource: { some: {} } } }))).toEqual([task.id]); + expect(ids(await db.item.findMany({ where: { notesAsSource: { none: {} } } }))).toEqual( + [note.id, tag.id].sort(), + ); + expect(ids(await db.item.findMany({ where: { notesAsSource: { every: { id: { not: note.id } } } } }))).toEqual( + [note.id, tag.id].sort(), + ); + await expect(db.item.count({ where: { notesAsSource: { some: {} } } })).resolves.toBe(1); + expect(ids(await db.item.findMany({ where: { tags: { some: {} } } }))).toEqual([note.id]); + }); + + it('filters by a to-one relation into the same hierarchy', async () => { + const { db, note } = await setup(); + + expect(ids(await db.item.findMany({ where: { pinnedTask: { is: {} } } }))).toEqual([note.id]); + expect(ids(await db.note.findMany({ where: { pinnedTask: { is: {} } } }))).toEqual([note.id]); + expect(ids(await db.note.findMany({ where: { source: { is: { itemKind: 'Task' } } } }))).toEqual([note.id]); + }); + + it('counts a base model relation into its own hierarchy', async () => { + const { db, task, note } = await setup(); + + await expect( + db.item.findUnique({ where: { id: task.id }, include: { _count: { select: { notesAsSource: true } } } }), + ).resolves.toMatchObject({ _count: { notesAsSource: 1 } }); + await expect( + db.item.findUnique({ + where: { id: task.id }, + select: { _count: { select: { notesAsSource: { where: { id: note.id } } } } }, + }), + ).resolves.toEqual({ _count: { notesAsSource: 1 } }); + await expect( + db.item.findUnique({ where: { id: note.id }, include: { _count: { select: { tags: true } } } }), + ).resolves.toMatchObject({ _count: { tags: 1 } }); + await expect( + db.item.findFirst({ orderBy: [{ notesAsSource: { _count: 'desc' } }, { id: 'asc' }] }), + ).resolves.toMatchObject({ id: task.id }); + }); + + it('orders by an inherited field of a to-one relation', async () => { + const db = await createTestClient(schema, { usePrismaPush: true }); + const highTask = await db.task.create({ data: { rank: 2 } }); + const lowTask = await db.task.create({ data: { rank: 1 } }); + // each note's own rank is the opposite of its pinned task's rank + const first = await db.note.create({ data: { rank: 2, pinnedTaskId: lowTask.id } }); + const second = await db.note.create({ data: { rank: 1, pinnedTaskId: highTask.id } }); + + // outer model in the same hierarchy as the related model's base + await expect(db.note.findMany({ orderBy: { pinnedTask: { rank: 'asc' } } })).resolves.toEqual([ + expect.objectContaining({ id: first.id }), + expect.objectContaining({ id: second.id }), + ]); + + // outer model outside the hierarchy + const c1 = await db.comment.create({ data: { noteId: second.id } }); + const c2 = await db.comment.create({ data: { noteId: first.id } }); + await expect(db.comment.findMany({ orderBy: { note: { rank: 'desc' } } })).resolves.toEqual([ + expect.objectContaining({ id: c2.id }), + expect.objectContaining({ id: c1.id }), + ]); + }); + + it('includes relations into the same hierarchy', async () => { + const { db, task, note, tag } = await setup(); + + const taskWithNotes = await db.item.findUnique({ where: { id: task.id }, include: { notesAsSource: true } }); + expect(ids(taskWithNotes!.notesAsSource)).toEqual([note.id]); + + await expect(db.note.findUnique({ where: { id: note.id }, include: { source: true } })).resolves.toMatchObject({ + source: { id: task.id }, + }); + await expect( + db.item.findUnique({ where: { id: note.id }, include: { pinnedTask: true } }), + ).resolves.toMatchObject({ pinnedTask: { id: task.id } }); + + const noteWithTags = await db.item.findUnique({ where: { id: note.id }, include: { tags: true } }); + expect(ids(noteWithTags!.tags)).toEqual([tag.id]); + }); + + it('gives the same result through the sub model', async () => { + const { db, task } = await setup(); + + expect(ids(await db.task.findMany({ where: { notesAsSource: { some: {} } } }))).toEqual([task.id]); + expect(ids(await db.task.findMany({ where: { pinnedBy: { some: {} } } }))).toEqual([task.id]); + await expect( + db.task.findFirst({ include: { _count: { select: { notesAsSource: true } } } }), + ).resolves.toMatchObject({ _count: { notesAsSource: 1 } }); + }); + + it('includes relations into the same hierarchy with access policies', async () => { + const db = await createPolicyTestClient(` +model Item { + id String @id @default(cuid()) + itemKind String + notesAsSource Note[] @relation("NoteSource") + + @@delegate(itemKind) + @@allow('create', true) + @@allow('read', notesAsSource?[true] || itemKind == 'Note') +} + +model Task extends Item {} + +model Note extends Item { + sourceId String? + source Item? @relation("NoteSource", fields: [sourceId], references: [id]) +} +`); + const raw = db.$unuseAll(); + const task = await raw.task.create({ data: {} }); + const hidden = await raw.task.create({ data: {} }); + const note = await raw.note.create({ data: { sourceId: task.id } }); + + expect(ids(await db.item.findMany())).toEqual([task.id, note.id].sort()); + await expect(db.task.findUnique({ where: { id: hidden.id } })).resolves.toBeNull(); + await expect(db.note.findFirst({ include: { source: true } })).resolves.toMatchObject({ + source: { id: task.id }, + }); + const taskWithNotes = await db.item.findUnique({ where: { id: task.id }, include: { notesAsSource: true } }); + expect(ids(taskWithNotes!.notesAsSource)).toEqual([note.id]); + }); +}); From 710aeb043f04c8789ff93dabe0e69d13f900604f Mon Sep 17 00:00:00 2001 From: sanny-io <3054653+sanny-io@users.noreply.github.com> Date: Mon, 26 Oct 2026 17:14:28 -0700 Subject: [PATCH 14/18] fix(orm): primitive custom type outputs (#2892) Co-authored-by: Claude Opus 5.5 --- .../orm/src/client/crud/dialects/mysql.ts | 18 +- .../src/client/crud/dialects/postgresql.ts | 6 +- .../custom-type-primitive-output.test.ts | 110 ++++++++++ .../client-api/custom-type-primitive.test.ts | 16 ++ .../custom-type-primitive-output/input.ts | 60 ++++++ .../custom-type-primitive-output/models.ts | 20 ++ .../custom-type-primitive-output/schema.ts | 201 ++++++++++++++++++ .../schema.zmodel | 61 ++++++ .../schemas/custom-type-primitive/schema.ts | 15 ++ .../custom-type-primitive/schema.zmodel | 5 + 10 files changed, 509 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/orm/client-api/custom-type-primitive-output.test.ts create mode 100644 tests/e2e/orm/schemas/custom-type-primitive-output/input.ts create mode 100644 tests/e2e/orm/schemas/custom-type-primitive-output/models.ts create mode 100644 tests/e2e/orm/schemas/custom-type-primitive-output/schema.ts create mode 100644 tests/e2e/orm/schemas/custom-type-primitive-output/schema.zmodel diff --git a/packages/orm/src/client/crud/dialects/mysql.ts b/packages/orm/src/client/crud/dialects/mysql.ts index 075f41e17..3b06c4589 100644 --- a/packages/orm/src/client/crud/dialects/mysql.ts +++ b/packages/orm/src/client/crud/dialects/mysql.ts @@ -120,11 +120,15 @@ export class MySqlCrudDialect extends LateralJoinDiale } } - override transformOutput(value: unknown, type: BuiltinType, array: boolean) { + override transformOutput(value: unknown, type: BuiltinType, array: boolean): unknown { if (value === null || value === undefined) { return value; } + if (isPrimitiveTypeDef(this.schema, type)) { + return this.transformOutput(value, this.schema.typeDefs![type]!.base!, array); + } + switch (type) { case 'Boolean': return this.transformOutputBoolean(value); @@ -185,7 +189,17 @@ export class MySqlCrudDialect extends LateralJoinDiale } private transformOutputBytes(value: unknown) { - return Buffer.isBuffer(value) ? Uint8Array.from(value) : value; + if (Buffer.isBuffer(value)) { + return Uint8Array.from(value); + } + // MySQL encodes binary values embedded in JSON as `base64:type:` + if (typeof value === 'string') { + const match = /^base64:type\d+:(.*)$/s.exec(value); + if (match) { + return Uint8Array.from(Buffer.from(match[1]!, 'base64')); + } + } + return value; } // #endregion diff --git a/packages/orm/src/client/crud/dialects/postgresql.ts b/packages/orm/src/client/crud/dialects/postgresql.ts index fb6780af0..fa66f6036 100644 --- a/packages/orm/src/client/crud/dialects/postgresql.ts +++ b/packages/orm/src/client/crud/dialects/postgresql.ts @@ -245,11 +245,15 @@ export class PostgresCrudDialect extends LateralJoinDi } } - override transformOutput(value: unknown, type: BuiltinType, array: boolean) { + override transformOutput(value: unknown, type: BuiltinType, array: boolean): unknown { if (value === null || value === undefined) { return value; } + if (isPrimitiveTypeDef(this.schema, type)) { + return this.transformOutput(value, this.schema.typeDefs![type]!.base!, array); + } + switch (type) { case 'DateTime': return this.transformOutputDate(value); diff --git a/tests/e2e/orm/client-api/custom-type-primitive-output.test.ts b/tests/e2e/orm/client-api/custom-type-primitive-output.test.ts new file mode 100644 index 000000000..10ac5ded0 --- /dev/null +++ b/tests/e2e/orm/client-api/custom-type-primitive-output.test.ts @@ -0,0 +1,110 @@ +import type { ClientContract } from '@zenstackhq/orm'; +import { createTestClient } from '@zenstackhq/testtools'; +import Decimal from 'decimal.js'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { schema } from '../schemas/custom-type-primitive-output/schema'; + +const seenAt = new Date('2026-01-02T03:04:05.000Z'); + +const itemData = { + label: 'hello', + active: true, + quantity: 3, + ratio: 1.5, + views: 12345678901234n, + price: new Decimal('19.99'), + seenAt, + payload: new Uint8Array([1, 2, 3]), + meta: { a: 1, tags: ['x'] }, +}; + +// asserts that each primitive type def field is returned in its base type's runtime representation +function expectConverted(item: Record, { withPayload = true } = {}) { + expect(item['label']).toBe('hello'); + expect(item['active']).toBe(true); + expect(item['quantity']).toBe(3); + expect(item['ratio']).toBe(1.5); + expect(item['views']).toBe(12345678901234n); + expect(Decimal.isDecimal(item['price'])).toBe(true); + expect((item['price'] as Decimal).toString()).toBe('19.99'); + expect(item['seenAt']).toBeInstanceOf(Date); + expect((item['seenAt'] as Date).toISOString()).toBe(seenAt.toISOString()); + if (withPayload) { + expect(item['payload']).toBeInstanceOf(Uint8Array); + expect(Buffer.isBuffer(item['payload'])).toBe(false); + expect(Array.from(item['payload'] as Uint8Array)).toEqual([1, 2, 3]); + } + expect(item['meta']).toEqual({ a: 1, tags: ['x'] }); + expect(item['note']).toBeNull(); +} + +describe('Custom type primitive output conversion tests', () => { + let client: ClientContract; + let ownerId: number; + + beforeEach(async () => { + client = await createTestClient(schema); + const owner = await client.owner.create({ data: {} }); + ownerId = owner.id; + }); + + afterEach(async () => { + await client?.$disconnect(); + }); + + it('converts fields returned from create', async () => { + const item = await client.item.create({ data: { ...itemData, ownerId } }); + expectConverted(item); + }); + + it('converts fields on direct reads', async () => { + const { id } = await client.item.create({ data: { ...itemData, ownerId } }); + + expectConverted(await client.item.findUniqueOrThrow({ where: { id } })); + + const [item] = await client.item.findMany(); + expectConverted(item!); + + const selected = await client.item.findFirstOrThrow({ select: { views: true, price: true, active: true } }); + expect(selected.views).toBe(12345678901234n); + expect(Decimal.isDecimal(selected.price)).toBe(true); + expect(selected.active).toBe(true); + }); + + it('converts fields of nested relations', async () => { + await client.item.create({ data: { ...itemData, ownerId } }); + + // SQLite can't put BLOB values into the JSON it builds for nested relations (same for plain `Bytes`) + const withPayload = client.$schema.provider.type !== 'sqlite'; + const omit = { payload: !withPayload }; + + const owner = await client.owner.findUniqueOrThrow({ where: { id: ownerId }, include: { items: { omit } } }); + expectConverted(owner.items[0]!, { withPayload }); + + const selected = await client.owner.findUniqueOrThrow({ + where: { id: ownerId }, + select: { + items: { select: { views: true, price: true, seenAt: true, payload: withPayload, active: true } }, + }, + }); + expect(selected.items[0]!.views).toBe(12345678901234n); + expect(Decimal.isDecimal(selected.items[0]!.price)).toBe(true); + expect(selected.items[0]!.seenAt).toBeInstanceOf(Date); + expect(selected.items[0]!.active).toBe(true); + if (withPayload) { + expect(Buffer.isBuffer(selected.items[0]!.payload)).toBe(false); + expect(Array.from(selected.items[0]!.payload as Uint8Array)).toEqual([1, 2, 3]); + } + + const item = await client.item.findFirstOrThrow({ include: { owner: { include: { items: { omit } } } } }); + expectConverted(item); + expectConverted(item.owner.items[0]!, { withPayload }); + }); + + it('converts fields returned from update', async () => { + const { id } = await client.item.create({ data: { ...itemData, ownerId } }); + const updated = await client.item.update({ where: { id }, data: { quantity: 4 } }); + expectConverted({ ...updated, quantity: 3 }); + expect(updated.quantity).toBe(4); + }); +}); diff --git a/tests/e2e/orm/client-api/custom-type-primitive.test.ts b/tests/e2e/orm/client-api/custom-type-primitive.test.ts index 67f745b7e..0b2f6743e 100644 --- a/tests/e2e/orm/client-api/custom-type-primitive.test.ts +++ b/tests/e2e/orm/client-api/custom-type-primitive.test.ts @@ -169,4 +169,20 @@ describe('Custom type primitive tests', () => { }), ).rejects.toThrow(/Too big/); }); + + it('converts array items to the base type', async () => { + const user = await client.user.create({ + data: { name: 'test', scores: [1n, 12345678901234n] }, + }); + expect(user.scores).toEqual([1n, 12345678901234n]); + + const found = await client.user.findUniqueOrThrow({ where: { id: user.id } }); + expect(found.scores).toEqual([1n, 12345678901234n]); + + const post = await client.post.create({ + data: { tags: ['abc', '123'], author: { connect: { id: user.id } } }, + include: { author: true }, + }); + expect(post.author.scores).toEqual([1n, 12345678901234n]); + }); }); diff --git a/tests/e2e/orm/schemas/custom-type-primitive-output/input.ts b/tests/e2e/orm/schemas/custom-type-primitive-output/input.ts new file mode 100644 index 000000000..70f0b1f1a --- /dev/null +++ b/tests/e2e/orm/schemas/custom-type-primitive-output/input.ts @@ -0,0 +1,60 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +/* eslint-disable */ + +import { type SchemaType as $Schema } from "./schema"; +import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, ExistsArgs as $ExistsArgs, CreateArgs as $CreateArgs, CreateManyArgs as $CreateManyArgs, CreateManyAndReturnArgs as $CreateManyAndReturnArgs, UpdateArgs as $UpdateArgs, UpdateManyArgs as $UpdateManyArgs, UpdateManyAndReturnArgs as $UpdateManyAndReturnArgs, UpsertArgs as $UpsertArgs, DeleteArgs as $DeleteArgs, DeleteManyArgs as $DeleteManyArgs, CountArgs as $CountArgs, AggregateArgs as $AggregateArgs, GroupByArgs as $GroupByArgs, WhereInput as $WhereInput, SelectInput as $SelectInput, IncludeInput as $IncludeInput, OmitInput as $OmitInput, UncheckedCreateInput as $UncheckedCreateInput, CheckedCreateInput as $CheckedCreateInput, UncheckedUpdateInput as $UncheckedUpdateInput, CheckedUpdateInput as $CheckedUpdateInput, QueryOptions as $QueryOptions } from "@zenstackhq/orm"; +import type { SimplifiedPlainResult as $Result, SelectIncludeOmit as $SelectIncludeOmit } from "@zenstackhq/orm"; +export type OwnerFindManyArgs = $FindManyArgs<$Schema, "Owner">; +export type OwnerFindUniqueArgs = $FindUniqueArgs<$Schema, "Owner">; +export type OwnerFindFirstArgs = $FindFirstArgs<$Schema, "Owner">; +export type OwnerExistsArgs = $ExistsArgs<$Schema, "Owner">; +export type OwnerCreateArgs = $CreateArgs<$Schema, "Owner">; +export type OwnerCreateManyArgs = $CreateManyArgs<$Schema, "Owner">; +export type OwnerCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Owner">; +export type OwnerUpdateArgs = $UpdateArgs<$Schema, "Owner">; +export type OwnerUpdateManyArgs = $UpdateManyArgs<$Schema, "Owner">; +export type OwnerUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs<$Schema, "Owner">; +export type OwnerUpsertArgs = $UpsertArgs<$Schema, "Owner">; +export type OwnerDeleteArgs = $DeleteArgs<$Schema, "Owner">; +export type OwnerDeleteManyArgs = $DeleteManyArgs<$Schema, "Owner">; +export type OwnerCountArgs = $CountArgs<$Schema, "Owner">; +export type OwnerAggregateArgs = $AggregateArgs<$Schema, "Owner">; +export type OwnerGroupByArgs = $GroupByArgs<$Schema, "Owner">; +export type OwnerWhereInput = $WhereInput<$Schema, "Owner">; +export type OwnerSelect = $SelectInput<$Schema, "Owner">; +export type OwnerInclude = $IncludeInput<$Schema, "Owner">; +export type OwnerOmit = $OmitInput<$Schema, "Owner">; +export type OwnerUncheckedCreateInput = $UncheckedCreateInput<$Schema, "Owner">; +export type OwnerCheckedCreateInput = $CheckedCreateInput<$Schema, "Owner">; +export type OwnerUncheckedUpdateInput = $UncheckedUpdateInput<$Schema, "Owner">; +export type OwnerCheckedUpdateInput = $CheckedUpdateInput<$Schema, "Owner">; +export type OwnerGetPayload, Options extends $QueryOptions<$Schema> = $QueryOptions<$Schema>> = $Result<$Schema, "Owner", Args, Options>; +export type ItemFindManyArgs = $FindManyArgs<$Schema, "Item">; +export type ItemFindUniqueArgs = $FindUniqueArgs<$Schema, "Item">; +export type ItemFindFirstArgs = $FindFirstArgs<$Schema, "Item">; +export type ItemExistsArgs = $ExistsArgs<$Schema, "Item">; +export type ItemCreateArgs = $CreateArgs<$Schema, "Item">; +export type ItemCreateManyArgs = $CreateManyArgs<$Schema, "Item">; +export type ItemCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Item">; +export type ItemUpdateArgs = $UpdateArgs<$Schema, "Item">; +export type ItemUpdateManyArgs = $UpdateManyArgs<$Schema, "Item">; +export type ItemUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs<$Schema, "Item">; +export type ItemUpsertArgs = $UpsertArgs<$Schema, "Item">; +export type ItemDeleteArgs = $DeleteArgs<$Schema, "Item">; +export type ItemDeleteManyArgs = $DeleteManyArgs<$Schema, "Item">; +export type ItemCountArgs = $CountArgs<$Schema, "Item">; +export type ItemAggregateArgs = $AggregateArgs<$Schema, "Item">; +export type ItemGroupByArgs = $GroupByArgs<$Schema, "Item">; +export type ItemWhereInput = $WhereInput<$Schema, "Item">; +export type ItemSelect = $SelectInput<$Schema, "Item">; +export type ItemInclude = $IncludeInput<$Schema, "Item">; +export type ItemOmit = $OmitInput<$Schema, "Item">; +export type ItemUncheckedCreateInput = $UncheckedCreateInput<$Schema, "Item">; +export type ItemCheckedCreateInput = $CheckedCreateInput<$Schema, "Item">; +export type ItemUncheckedUpdateInput = $UncheckedUpdateInput<$Schema, "Item">; +export type ItemCheckedUpdateInput = $CheckedUpdateInput<$Schema, "Item">; +export type ItemGetPayload, Options extends $QueryOptions<$Schema> = $QueryOptions<$Schema>> = $Result<$Schema, "Item", Args, Options>; diff --git a/tests/e2e/orm/schemas/custom-type-primitive-output/models.ts b/tests/e2e/orm/schemas/custom-type-primitive-output/models.ts new file mode 100644 index 000000000..dbd86e316 --- /dev/null +++ b/tests/e2e/orm/schemas/custom-type-primitive-output/models.ts @@ -0,0 +1,20 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +/* eslint-disable */ + +import { type SchemaType as $Schema } from "./schema"; +import type { ModelResult as $ModelResult, TypeDefResult as $TypeDefResult } from "@zenstackhq/orm"; +export type Owner = $ModelResult<$Schema, "Owner">; +export type Item = $ModelResult<$Schema, "Item">; +export type Label = $TypeDefResult<$Schema, "Label">; +export type Flag = $TypeDefResult<$Schema, "Flag">; +export type Quantity = $TypeDefResult<$Schema, "Quantity">; +export type Ratio = $TypeDefResult<$Schema, "Ratio">; +export type ViewCount = $TypeDefResult<$Schema, "ViewCount">; +export type Price = $TypeDefResult<$Schema, "Price">; +export type Timestamp = $TypeDefResult<$Schema, "Timestamp">; +export type Blob = $TypeDefResult<$Schema, "Blob">; +export type Meta = $TypeDefResult<$Schema, "Meta">; diff --git a/tests/e2e/orm/schemas/custom-type-primitive-output/schema.ts b/tests/e2e/orm/schemas/custom-type-primitive-output/schema.ts new file mode 100644 index 000000000..a07d53a2d --- /dev/null +++ b/tests/e2e/orm/schemas/custom-type-primitive-output/schema.ts @@ -0,0 +1,201 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +/* eslint-disable */ + +import { type SchemaDef, type AttributeApplication, type FieldDefault, ExpressionUtils } from "@zenstackhq/schema"; +export class SchemaType implements SchemaDef { + provider = { + type: "sqlite" + } as const; + models = { + Owner: { + name: "Owner", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }] as readonly AttributeApplication[], + default: ExpressionUtils.call("autoincrement") as FieldDefault + }, + items: { + name: "items", + type: "Item", + array: true, + relation: { opposite: "owner" } + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "Int" } + } + }, + Item: { + name: "Item", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }] as readonly AttributeApplication[], + default: ExpressionUtils.call("autoincrement") as FieldDefault + }, + ownerId: { + name: "ownerId", + type: "Int", + foreignKeyFor: [ + "owner" + ] as readonly string[] + }, + owner: { + name: "owner", + type: "Owner", + attributes: [{ name: "@relation", args: [{ name: "fields", value: ExpressionUtils.array("Int", [ExpressionUtils.field("ownerId")]) }, { name: "references", value: ExpressionUtils.array("Int", [ExpressionUtils.field("id")]) }] }] as readonly AttributeApplication[], + relation: { opposite: "items", fields: ["ownerId"], references: ["id"] } + }, + label: { + name: "label", + type: "Label" + }, + active: { + name: "active", + type: "Flag" + }, + quantity: { + name: "quantity", + type: "Quantity" + }, + ratio: { + name: "ratio", + type: "Ratio" + }, + views: { + name: "views", + type: "ViewCount" + }, + price: { + name: "price", + type: "Price" + }, + seenAt: { + name: "seenAt", + type: "Timestamp" + }, + payload: { + name: "payload", + type: "Blob" + }, + meta: { + name: "meta", + type: "Meta" + }, + note: { + name: "note", + type: "Label", + optional: true + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "Int" } + } + } + } as const; + typeDefs = { + Label: { + name: "Label", + base: "String", + fields: { + this: { + name: "this", + type: "String" + } + } + }, + Flag: { + name: "Flag", + base: "Boolean", + fields: { + this: { + name: "this", + type: "Boolean" + } + } + }, + Quantity: { + name: "Quantity", + base: "Int", + fields: { + this: { + name: "this", + type: "Int" + } + } + }, + Ratio: { + name: "Ratio", + base: "Float", + fields: { + this: { + name: "this", + type: "Float" + } + } + }, + ViewCount: { + name: "ViewCount", + base: "BigInt", + fields: { + this: { + name: "this", + type: "BigInt" + } + } + }, + Price: { + name: "Price", + base: "Decimal", + fields: { + this: { + name: "this", + type: "Decimal" + } + } + }, + Timestamp: { + name: "Timestamp", + base: "DateTime", + fields: { + this: { + name: "this", + type: "DateTime" + } + } + }, + Blob: { + name: "Blob", + base: "Bytes", + fields: { + this: { + name: "this", + type: "Bytes" + } + } + }, + Meta: { + name: "Meta", + base: "Json", + fields: { + this: { + name: "this", + type: "Json" + } + } + } + } as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/orm/schemas/custom-type-primitive-output/schema.zmodel b/tests/e2e/orm/schemas/custom-type-primitive-output/schema.zmodel new file mode 100644 index 000000000..ef2603562 --- /dev/null +++ b/tests/e2e/orm/schemas/custom-type-primitive-output/schema.zmodel @@ -0,0 +1,61 @@ +datasource db { + provider = "sqlite" + url = "file:./dev.db" +} + +model Owner { + id Int @id @default(autoincrement()) + items Item[] +} + +model Item { + id Int @id @default(autoincrement()) + ownerId Int + owner Owner @relation(fields: [ownerId], references: [id]) + label Label + active Flag + quantity Quantity + ratio Ratio + views ViewCount + price Price + seenAt Timestamp + payload Blob + meta Meta + note Label? +} + +type Label with String { + this String +} + +type Flag with Boolean { + this Boolean +} + +type Quantity with Int { + this Int +} + +type Ratio with Float { + this Float +} + +type ViewCount with BigInt { + this BigInt +} + +type Price with Decimal { + this Decimal +} + +type Timestamp with DateTime { + this DateTime +} + +type Blob with Bytes { + this Bytes +} + +type Meta with Json { + this Json +} diff --git a/tests/e2e/orm/schemas/custom-type-primitive/schema.ts b/tests/e2e/orm/schemas/custom-type-primitive/schema.ts index 92bdd74ce..89e832899 100644 --- a/tests/e2e/orm/schemas/custom-type-primitive/schema.ts +++ b/tests/e2e/orm/schemas/custom-type-primitive/schema.ts @@ -37,6 +37,11 @@ export class SchemaType implements SchemaDef { attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.literal(18) }] }] as readonly AttributeApplication[], default: 18 as FieldDefault }, + scores: { + name: "scores", + type: "Score", + array: true + }, posts: { name: "posts", type: "Post", @@ -129,6 +134,16 @@ export class SchemaType implements SchemaDef { attributes: [{ name: "@length", args: [{ name: "min", value: ExpressionUtils.literal(1) }, { name: "max", value: ExpressionUtils.literal(12) }] }] as readonly AttributeApplication[] } } + }, + Score: { + name: "Score", + base: "BigInt", + fields: { + this: { + name: "this", + type: "BigInt" + } + } } } as const; authType = "User" as const; diff --git a/tests/e2e/orm/schemas/custom-type-primitive/schema.zmodel b/tests/e2e/orm/schemas/custom-type-primitive/schema.zmodel index e08cf0305..fc185a366 100644 --- a/tests/e2e/orm/schemas/custom-type-primitive/schema.zmodel +++ b/tests/e2e/orm/schemas/custom-type-primitive/schema.zmodel @@ -8,6 +8,7 @@ model User { name UserName contacts Contact[] age Age? @default(18) + scores Score[] posts Post[] } @@ -33,3 +34,7 @@ type Age with Int { type PostTag with String { this String @length(1, 12) } + +type Score with BigInt { + this BigInt +} From 99dceb5fc65eec378f660bb8eb4aab2244560005 Mon Sep 17 00:00:00 2001 From: sanny-io <3054653+sanny-io@users.noreply.github.com> Date: Mon, 26 Oct 2026 17:23:51 -0700 Subject: [PATCH 15/18] fix(language): transitive field reference scoping (#2803) --- packages/language/src/zmodel-scope.ts | 51 +++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/packages/language/src/zmodel-scope.ts b/packages/language/src/zmodel-scope.ts index 8dd3c4dab..655dc8b04 100644 --- a/packages/language/src/zmodel-scope.ts +++ b/packages/language/src/zmodel-scope.ts @@ -17,11 +17,15 @@ import { } from 'langium'; import { match } from 'ts-pattern'; import { + AttributeArg, BinaryExpr, + DataModel, Expression, MemberAccessExpr, + isAttributeArg, isCollectionPredicateBinding, isDataField, + isDataFieldAttribute, isDataModel, isEnumField, isInvocationExpr, @@ -132,6 +136,13 @@ export class ZModelScopeProvider extends DefaultScopeProvider { if (containerCollectionPredicate) { return this.getCollectionPredicateScope(context, containerCollectionPredicate); } + + // @relation(fields: [], references: []) + // `references` should include the target model's fields instead of the current model's + const targetModel = this.getTransitiveFieldReferenceTargetModel(context); + if (targetModel) { + return this.getTransitiveFieldReferenceScope(targetModel); + } } return super.getScope(context); @@ -264,6 +275,46 @@ export class ZModelScopeProvider extends DefaultScopeProvider { .otherwise(() => EMPTY_SCOPE); } + private getTransitiveFieldReferenceScope(dm: DataModel) { + return this.createScope( + getAllFields(dm).map((field) => this.descriptions.createDescription(field, field.name)), + ); + } + + private getTransitiveFieldReferenceTargetModel(context: ReferenceInfo) { + let current: AstNode | undefined = context.container; + while (current) { + if (isAttributeArg(current)) { + const attr = current.$container; + if (!isDataFieldAttribute(attr)) { + return undefined; + } + + const decl = attr.decl.ref; + if (!decl) { + return undefined; + } + + const param = current.name + ? decl.params.find((p) => p.name === (current as AttributeArg).name) + : decl.params[attr.args.indexOf(current)]; + if (param?.type?.type !== 'TransitiveFieldReference') { + return undefined; + } + + const field = attr.$container; + if (!isDataField(field)) { + return undefined; + } + + const targetType = field.type?.reference?.ref; + return isDataModel(targetType) ? targetType : undefined; + } + current = current.$container; + } + return undefined; + } + private createScopeForContainingModel(node: AstNode, globalScope: Scope) { const model = AstUtils.getContainerOfType(node, isDataModel); if (model) { From fb9861445d2336a04ebb6d3bd1256b85b61b9196 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 17:24:32 -0700 Subject: [PATCH 16/18] [CI] Bump version 3.10.0 (#2865) Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com> --- package.json | 2 +- packages/auth-adapters/better-auth/package.json | 2 +- packages/cli/package.json | 2 +- packages/clients/client-helpers/package.json | 2 +- packages/clients/fetch-client/package.json | 2 +- packages/clients/tanstack-query/package.json | 2 +- packages/common-helpers/package.json | 2 +- packages/config/eslint-config/package.json | 2 +- packages/config/tsdown-config/package.json | 2 +- packages/config/typescript-config/package.json | 2 +- packages/config/vitest-config/package.json | 2 +- packages/create-zenstack/package.json | 2 +- packages/ide/vscode/package.json | 2 +- packages/language/package.json | 2 +- packages/orm/package.json | 2 +- packages/plugins/policy/package.json | 2 +- packages/plugins/soft-delete/package.json | 2 +- packages/schema/package.json | 2 +- packages/sdk/package.json | 2 +- packages/server/package.json | 2 +- packages/testtools/package.json | 2 +- packages/zod/package.json | 2 +- samples/orm/package.json | 2 +- samples/taskforge/package.json | 2 +- tests/e2e/package.json | 2 +- tests/regression/package.json | 2 +- tests/runtimes/bun/package.json | 2 +- tests/runtimes/edge-runtime/package.json | 2 +- 28 files changed, 28 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 919d7bbbe..aa054663a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "zenstack-v3", "displayName": "ZenStack", "description": "ZenStack", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/auth-adapters/better-auth/package.json b/packages/auth-adapters/better-auth/package.json index c89d8968b..8179a10a1 100644 --- a/packages/auth-adapters/better-auth/package.json +++ b/packages/auth-adapters/better-auth/package.json @@ -2,7 +2,7 @@ "name": "@zenstackhq/better-auth", "displayName": "ZenStack Better Auth Adapter", "description": "ZenStack Better Auth Adapter. This adapter is modified from better-auth's Prisma adapter.", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/cli/package.json b/packages/cli/package.json index 65b7e06cf..d9c31c161 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -2,7 +2,7 @@ "name": "@zenstackhq/cli", "displayName": "ZenStack CLI", "description": "FullStack database toolkit with built-in access control and automatic API generation.", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/clients/client-helpers/package.json b/packages/clients/client-helpers/package.json index 61dffda90..812e177a5 100644 --- a/packages/clients/client-helpers/package.json +++ b/packages/clients/client-helpers/package.json @@ -2,7 +2,7 @@ "name": "@zenstackhq/client-helpers", "displayName": "ZenStack Client Helpers", "description": "Helpers for implementing clients that consume ZenStack's CRUD service", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/clients/fetch-client/package.json b/packages/clients/fetch-client/package.json index b0a22a57b..cf0618f32 100644 --- a/packages/clients/fetch-client/package.json +++ b/packages/clients/fetch-client/package.json @@ -2,7 +2,7 @@ "name": "@zenstackhq/fetch-client", "displayName": "ZenStack Fetch Client", "description": "Simple fetch-based client for consuming ZenStack's RPC-style CRUD API", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/clients/tanstack-query/package.json b/packages/clients/tanstack-query/package.json index e90fde2de..3d0aceef6 100644 --- a/packages/clients/tanstack-query/package.json +++ b/packages/clients/tanstack-query/package.json @@ -2,7 +2,7 @@ "name": "@zenstackhq/tanstack-query", "displayName": "ZenStack TanStack Query Integration", "description": "TanStack Query Client for consuming ZenStack v3's CRUD service", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/common-helpers/package.json b/packages/common-helpers/package.json index 7160b5cdf..af75d3ba7 100644 --- a/packages/common-helpers/package.json +++ b/packages/common-helpers/package.json @@ -2,7 +2,7 @@ "name": "@zenstackhq/common-helpers", "displayName": "ZenStack Common Helpers", "description": "ZenStack Common Helpers", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/config/eslint-config/package.json b/packages/config/eslint-config/package.json index 302cf90da..7d01e5284 100644 --- a/packages/config/eslint-config/package.json +++ b/packages/config/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/eslint-config", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "private": true, "license": "MIT" diff --git a/packages/config/tsdown-config/package.json b/packages/config/tsdown-config/package.json index 5e582f10b..1f6166638 100644 --- a/packages/config/tsdown-config/package.json +++ b/packages/config/tsdown-config/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/tsdown-config", - "version": "3.9.7", + "version": "3.10.0", "private": true, "type": "module", "license": "MIT", diff --git a/packages/config/typescript-config/package.json b/packages/config/typescript-config/package.json index 784687951..4c79af62c 100644 --- a/packages/config/typescript-config/package.json +++ b/packages/config/typescript-config/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/typescript-config", - "version": "3.9.7", + "version": "3.10.0", "private": true, "license": "MIT" } diff --git a/packages/config/vitest-config/package.json b/packages/config/vitest-config/package.json index 44957b247..f572f3a18 100644 --- a/packages/config/vitest-config/package.json +++ b/packages/config/vitest-config/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/vitest-config", "type": "module", - "version": "3.9.7", + "version": "3.10.0", "private": true, "license": "MIT", "exports": { diff --git a/packages/create-zenstack/package.json b/packages/create-zenstack/package.json index ccdf5d364..5994776bb 100644 --- a/packages/create-zenstack/package.json +++ b/packages/create-zenstack/package.json @@ -2,7 +2,7 @@ "name": "create-zenstack", "displayName": "Create ZenStack", "description": "Create a new ZenStack project", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/ide/vscode/package.json b/packages/ide/vscode/package.json index a0dbf9799..041ff8b81 100644 --- a/packages/ide/vscode/package.json +++ b/packages/ide/vscode/package.json @@ -1,7 +1,7 @@ { "name": "zenstack-v3", "publisher": "zenstack", - "version": "3.9.7", + "version": "3.10.0", "displayName": "ZenStack V3 Language Tools", "description": "VSCode extension for ZenStack (v3) ZModel language", "private": true, diff --git a/packages/language/package.json b/packages/language/package.json index 648e1880b..9efef6ce4 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -2,7 +2,7 @@ "name": "@zenstackhq/language", "displayName": "ZenStack Language Tooling", "description": "ZenStack ZModel language specification", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/orm/package.json b/packages/orm/package.json index 31c392fee..b1e37b86a 100644 --- a/packages/orm/package.json +++ b/packages/orm/package.json @@ -2,7 +2,7 @@ "name": "@zenstackhq/orm", "displayName": "ZenStack ORM", "description": "ZenStack ORM", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/plugins/policy/package.json b/packages/plugins/policy/package.json index 8d858498b..ceda16676 100644 --- a/packages/plugins/policy/package.json +++ b/packages/plugins/policy/package.json @@ -2,7 +2,7 @@ "name": "@zenstackhq/plugin-policy", "displayName": "ZenStack Access Policy Plugin", "description": "ZenStack plugin that enforces access control policies defined in the schema", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/plugins/soft-delete/package.json b/packages/plugins/soft-delete/package.json index 5c13ff2e2..ab44006ba 100644 --- a/packages/plugins/soft-delete/package.json +++ b/packages/plugins/soft-delete/package.json @@ -2,7 +2,7 @@ "name": "@zenstackhq/plugin-soft-delete", "displayName": "ZenStack Soft Delete Plugin", "description": "ZenStack plugin that implements soft-delete by intercepting Kysely queries", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/schema/package.json b/packages/schema/package.json index 2e4ba6cfa..78bf17c81 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -2,7 +2,7 @@ "name": "@zenstackhq/schema", "displayName": "ZenStack Schema Object Model", "description": "TypeScript representation of ZModel schema", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 7d85ec3f8..7016a08fc 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -2,7 +2,7 @@ "name": "@zenstackhq/sdk", "displayName": "ZenStack SDK", "description": "Utilities for building ZenStack plugins", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/server/package.json b/packages/server/package.json index 2a76af5bc..89f8c49f9 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -2,7 +2,7 @@ "name": "@zenstackhq/server", "displayName": "ZenStack Automatic CRUD Server", "description": "ZenStack automatic CRUD API handlers and server adapters for popular frameworks", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/testtools/package.json b/packages/testtools/package.json index 2b98013fb..c7d87ea64 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -2,7 +2,7 @@ "name": "@zenstackhq/testtools", "displayName": "ZenStack Test Tools", "description": "ZenStack Test Tools", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/packages/zod/package.json b/packages/zod/package.json index fa7fc5d86..c1e177818 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -2,7 +2,7 @@ "name": "@zenstackhq/zod", "displayName": "ZenStack Zod Integration", "description": "Automatically deriving Zod schemas from ZModel schemas", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "author": { "name": "ZenStack Team", diff --git a/samples/orm/package.json b/samples/orm/package.json index 949a7195c..db56ebcbc 100644 --- a/samples/orm/package.json +++ b/samples/orm/package.json @@ -1,6 +1,6 @@ { "name": "sample-orm", - "version": "3.9.7", + "version": "3.10.0", "description": "", "main": "index.js", "private": true, diff --git a/samples/taskforge/package.json b/samples/taskforge/package.json index 2d3df1f84..870946d10 100644 --- a/samples/taskforge/package.json +++ b/samples/taskforge/package.json @@ -1,6 +1,6 @@ { "name": "taskforge", - "version": "3.9.7", + "version": "3.10.0", "type": "module", "private": true, "description": "A CLI for a team collaboration / project-tracking platform, built on ZenStack v3 (ORM) and better-auth.", diff --git a/tests/e2e/package.json b/tests/e2e/package.json index 77f701656..6b909d08a 100644 --- a/tests/e2e/package.json +++ b/tests/e2e/package.json @@ -1,6 +1,6 @@ { "name": "e2e", - "version": "3.9.7", + "version": "3.10.0", "private": true, "type": "module", "scripts": { diff --git a/tests/regression/package.json b/tests/regression/package.json index 34cc1fae2..b74c51293 100644 --- a/tests/regression/package.json +++ b/tests/regression/package.json @@ -1,6 +1,6 @@ { "name": "regression", - "version": "3.9.7", + "version": "3.10.0", "private": true, "type": "module", "scripts": { diff --git a/tests/runtimes/bun/package.json b/tests/runtimes/bun/package.json index 3fdfd00e0..7d42f831c 100644 --- a/tests/runtimes/bun/package.json +++ b/tests/runtimes/bun/package.json @@ -1,6 +1,6 @@ { "name": "bun-e2e", - "version": "3.9.7", + "version": "3.10.0", "private": true, "type": "module", "scripts": { diff --git a/tests/runtimes/edge-runtime/package.json b/tests/runtimes/edge-runtime/package.json index 5e33787c0..41ca231ff 100644 --- a/tests/runtimes/edge-runtime/package.json +++ b/tests/runtimes/edge-runtime/package.json @@ -1,6 +1,6 @@ { "name": "edge-runtime-e2e", - "version": "3.9.7", + "version": "3.10.0", "private": true, "type": "module", "scripts": { From 87e87e4783f24bd20e9c4ab9a62043b9a6c26604 Mon Sep 17 00:00:00 2001 From: Erik Dakoda Date: Mon, 26 Oct 2026 21:52:13 -0400 Subject: [PATCH 17/18] fix(orm): set @updatedAt fields in updateMany (#2875) --- .../orm/src/client/crud/operations/base.ts | 123 ++++++++++++++---- tests/e2e/orm/client-api/delegate.test.ts | 71 ++++++++++ tests/e2e/orm/client-api/update-many.test.ts | 72 ++++++++++ 3 files changed, 243 insertions(+), 23 deletions(-) diff --git a/packages/orm/src/client/crud/operations/base.ts b/packages/orm/src/client/crud/operations/base.ts index 1de82f313..9e198f1d0 100644 --- a/packages/orm/src/client/crud/operations/base.ts +++ b/packages/orm/src/client/crud/operations/base.ts @@ -1081,28 +1081,11 @@ export abstract class BaseOperationHandler { const origWhere = combinedWhere; const modelDef = this.requireModel(model); - let finalData = data; // fill in automatically updated fields - const autoUpdatedFields: string[] = []; - for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) { - if (fieldDef.updatedAt && finalData[fieldName] === undefined) { - const ignoredFields = new Set(typeof fieldDef.updatedAt === 'boolean' ? [] : fieldDef.updatedAt.ignore); - const hasNonIgnoredFields = Object.keys(data).some( - (field) => - (isScalarField(this.schema, modelDef.name, field) || - isForeignKeyField(this.schema, modelDef.name, field)) && - !ignoredFields.has(field), - ); - if (hasNonIgnoredFields) { - if (finalData === data) { - finalData = clone(data); - } - finalData[fieldName] = this.dialect.transformInput(new Date(), 'DateTime', false, fieldDef); - autoUpdatedFields.push(fieldName); - } - } - } + const filled = this.fillUpdatedAtFields(modelDef, data); + let finalData = filled.data; + const autoUpdatedFields = filled.autoUpdatedFields; // lazily load the entity to be updated let thisEntity: any; @@ -1504,14 +1487,30 @@ export abstract class BaseOperationHandler { combinedWhere = Object.keys(combinedWhere).length > 0 ? { AND: [parentWhere, combinedWhere] } : parentWhere; } + // fill in automatically updated fields + const { data: finalData } = this.fillUpdatedAtFields(modelDef, data); + + // NOTE: besides naming the model the filter is built against, `filterModel` also tells + // entry calls apart from recursive ones: it's `undefined` only when called from an + // operation handler or a nested relation update, while the recursive calls made by + // `updateManyByIds` and `processBaseModelUpdateMany` always pass it. Only entry calls + // need the id-resolution step below; the recursive ones already filter by ids or + // are walking up the delegate hierarchy. (Could be refactored into an explicit flag.) + if (modelDef.baseModel && filterModel === undefined) { + // the base model's table is written before this model's table, so a filter that + // reads a field changed by that write (an `@updatedAt` field, for example) would + // no longer match; resolve the matching rows first and update them by id + return this.updateManyByIds(kysely, model, combinedWhere, finalData, returnData, fieldsToReturn); + } + filterModel ??= model; let updateFields: any = {}; - for (const field in data) { + for (const field in finalData) { if (isRelationField(this.schema, model, field)) { continue; } - updateFields[field] = this.processScalarFieldUpdateData(model, field, data); + updateFields[field] = this.processScalarFieldUpdateData(model, field, finalData); } let resultFromBaseModel: any = undefined; @@ -1653,6 +1652,84 @@ export abstract class BaseOperationHandler { } } + /** + * Runs `updateMany` on a delegate sub model by id. Reads the ids of the rows that match + * `where`, then updates them in batches with an id filter. Callers run this inside a + * transaction (`needsNestedUpdate` is true for every model with a base model). + */ + private async updateManyByIds( + kysely: AnyKysely, + model: string, + where: any, + data: any, + returnData: ReturnData, + fieldsToReturn?: readonly string[], + ): Promise { + const idFields = requireIdFields(this.schema, model); + const rows = await this.read(kysely, model, { + where, + select: Object.fromEntries(idFields.map((f) => [f, true])), + } as any); + + // keep each statement within the database limits on parameters + const batchSize = idFields.length === 1 ? 1000 : 100; + let count = 0; + const returned: unknown[] = []; + for (let i = 0; i < rows.length; i += batchSize) { + const batch = rows.slice(i, i + batchSize); + const idWhere = + idFields.length === 1 + ? { [idFields[0]!]: { in: batch.map((row) => row[idFields[0]!]) } } + : { OR: batch.map((row) => Object.fromEntries(idFields.map((f) => [f, row[f]]))) }; + const result: any = await this.updateMany( + kysely, + model, + idWhere, + data, + undefined, + returnData, + model, + undefined, + fieldsToReturn, + ); + if (returnData) { + returned.push(...result); + } else { + count += result.count; + } + } + return (returnData ? returned : { count }) as Result; + } + + /** + * Sets `@updatedAt` fields to the current time when `data` changes a scalar or foreign key + * field that is not in the field's `ignore` list. A value given explicitly in `data` is kept. + * Returns the original `data` object when nothing is filled in, otherwise a clone. + */ + private fillUpdatedAtFields(modelDef: ModelDef, data: any) { + let result = data; + const autoUpdatedFields: string[] = []; + for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) { + if (fieldDef.updatedAt && result[fieldName] === undefined) { + const ignoredFields = new Set(typeof fieldDef.updatedAt === 'boolean' ? [] : fieldDef.updatedAt.ignore); + const hasNonIgnoredFields = Object.keys(data).some( + (field) => + (isScalarField(this.schema, modelDef.name, field) || + isForeignKeyField(this.schema, modelDef.name, field)) && + !ignoredFields.has(field), + ); + if (hasNonIgnoredFields) { + if (result === data) { + result = clone(data); + } + result[fieldName] = this.dialect.transformInput(new Date(), 'DateTime', false, fieldDef); + autoUpdatedFields.push(fieldName); + } + } + } + return { data: result, autoUpdatedFields }; + } + private async processBaseModelUpdateMany( kysely: AnyKysely, model: string, @@ -1807,7 +1884,7 @@ export abstract class BaseOperationHandler { item.data, item.limit, false, - fieldModel, + undefined, fromRelationContext, ); } diff --git a/tests/e2e/orm/client-api/delegate.test.ts b/tests/e2e/orm/client-api/delegate.test.ts index af205c40e..5133ddb3e 100644 --- a/tests/e2e/orm/client-api/delegate.test.ts +++ b/tests/e2e/orm/client-api/delegate.test.ts @@ -813,6 +813,77 @@ describe('Delegate model tests ', () => { ).rejects.toThrow('Updating with a limit is not supported for polymorphic models'); }); + it('sets base model updatedAt with updateMany', async () => { + const OLD = new Date('2020-01-01T00:00:00.000Z'); + await client.asset.update({ where: { id: 1 }, data: { updatedAt: OLD } }); + + // only sub model fields change + await expect(client.ratedVideo.updateMany({ where: { id: 1 }, data: { rating: 3 } })).resolves.toEqual({ + count: 1, + }); + + const video = await client.ratedVideo.findUniqueOrThrow({ where: { id: 1 } }); + expect(video.rating).toBe(3); + expect(video.updatedAt.getTime()).toBeGreaterThan(OLD.getTime()); + }); + + it('updateMany on a sub model matches rows before the base model is written', async () => { + const OLD = new Date('2020-01-01T00:00:00.000Z'); + await client.asset.update({ where: { id: 1 }, data: { updatedAt: OLD } }); + + // the filter reads the base model's updatedAt, which the base write changes + await expect( + client.ratedVideo.updateMany({ + where: { updatedAt: { lt: new Date('2021-01-01T00:00:00.000Z') } }, + data: { rating: 3 }, + }), + ).resolves.toEqual({ count: 1 }); + await expect(client.ratedVideo.findUniqueOrThrow({ where: { id: 1 } })).resolves.toMatchObject({ + rating: 3, + }); + + // the filter reads a base model field that the update changes + await expect( + client.ratedVideo.updateMany({ + where: { duration: 100 }, + data: { duration: 200, rating: 4 }, + }), + ).resolves.toEqual({ count: 1 }); + await expect(client.ratedVideo.findUniqueOrThrow({ where: { id: 1 } })).resolves.toMatchObject({ + duration: 200, + rating: 4, + }); + }); + + it('nested updateMany on a sub model matches rows before the base model is written', async () => { + const OLD = new Date('2020-01-01T00:00:00.000Z'); + await client.ratedVideo.create({ + data: { id: 2, duration: 100, url: 'v2', rating: 5 }, + }); + await client.asset.updateMany({ data: { updatedAt: OLD } }); + + await client.user.update({ + where: { id: 1 }, + data: { + ratedVideos: { + updateMany: { + where: { updatedAt: { lt: new Date('2021-01-01T00:00:00.000Z') } }, + data: { rating: 3 }, + }, + }, + }, + }); + + const video1 = await client.ratedVideo.findUniqueOrThrow({ where: { id: 1 } }); + expect(video1.rating).toBe(3); + expect(video1.updatedAt.getTime()).toBeGreaterThan(OLD.getTime()); + + // not related to the user, so not updated + const video2 = await client.ratedVideo.findUniqueOrThrow({ where: { id: 2 } }); + expect(video2.rating).toBe(5); + expect(video2.updatedAt.getTime()).toEqual(OLD.getTime()); + }); + it('works with updateManyAndReturn', async () => { if (client.$schema.provider.type === ('mysql' as any)) { return; diff --git a/tests/e2e/orm/client-api/update-many.test.ts b/tests/e2e/orm/client-api/update-many.test.ts index f47fe1a76..ebb15dbc9 100644 --- a/tests/e2e/orm/client-api/update-many.test.ts +++ b/tests/e2e/orm/client-api/update-many.test.ts @@ -127,4 +127,76 @@ describe('Client updateMany tests', () => { // @ts-expect-error expect(r1[0]!.email).toBeUndefined(); }); + + describe('@updatedAt', () => { + const OLD = new Date('2020-01-01T00:00:00.000Z'); + + async function createUserWithOldUpdatedAt(id: string) { + await client.user.create({ data: { id, email: `${id}@test.com`, name: id } }); + // an explicit value is kept by `update` + await client.user.update({ where: { id }, data: { updatedAt: OLD } }); + } + + async function updatedAtOf(id: string) { + const user = await client.user.findUniqueOrThrow({ where: { id } }); + return user.updatedAt.getTime(); + } + + it('sets updatedAt on the matched rows only', async () => { + await createUserWithOldUpdatedAt('1'); + await createUserWithOldUpdatedAt('2'); + + await expect(client.user.updateMany({ where: { id: '1' }, data: { name: 'Foo' } })).resolves.toMatchObject({ + count: 1, + }); + + expect(await updatedAtOf('1')).toBeGreaterThan(OLD.getTime()); + expect(await updatedAtOf('2')).toEqual(OLD.getTime()); + }); + + it('keeps an explicit updatedAt', async () => { + await createUserWithOldUpdatedAt('1'); + const explicit = new Date('2021-06-01T00:00:00.000Z'); + + await client.user.updateMany({ data: { name: 'Foo', updatedAt: explicit } }); + + expect(await updatedAtOf('1')).toEqual(explicit.getTime()); + }); + + it('does not set updatedAt if only ignored fields are present', async () => { + await createUserWithOldUpdatedAt('1'); + + await client.user.updateMany({ data: { createdAt: new Date() } }); + + expect(await updatedAtOf('1')).toEqual(OLD.getTime()); + }); + + it('sets updatedAt with updateManyAndReturn', async () => { + if (client.$schema.provider.type === ('mysql' as any)) { + // skip for mysql as it does not support returning + return; + } + await createUserWithOldUpdatedAt('1'); + + const [user] = await client.user.updateManyAndReturn({ data: { name: 'Foo' } }); + + expect(user!.updatedAt.getTime()).toBeGreaterThan(OLD.getTime()); + expect(await updatedAtOf('1')).toEqual(user!.updatedAt.getTime()); + }); + + it('sets updatedAt with nested updateMany', async () => { + await createUserWithOldUpdatedAt('1'); + const post = await client.post.create({ data: { title: 'Post1', authorId: '1' } }); + await client.post.update({ where: { id: post.id }, data: { updatedAt: OLD } }); + + await client.user.update({ + where: { id: '1' }, + data: { posts: { updateMany: { where: {}, data: { title: 'Post2' } } } }, + }); + + const updatedPost = await client.post.findUniqueOrThrow({ where: { id: post.id } }); + expect(updatedPost.title).toBe('Post2'); + expect(updatedPost.updatedAt.getTime()).toBeGreaterThan(OLD.getTime()); + }); + }); }); From a36809a3356c3bd0066d8a807928316201ae23a0 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Tue, 27 Oct 2026 10:06:25 -0700 Subject: [PATCH 18/18] fix(orm): resolve rows before simulating delegate cascades from regular models (#2878) Co-authored-by: Claude Fable 5.1 --- .../orm/src/client/crud/operations/base.ts | 28 ++++--------------- .../delegate-cascade-delete.test.ts | 24 ++++++++++++---- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/packages/orm/src/client/crud/operations/base.ts b/packages/orm/src/client/crud/operations/base.ts index 9e198f1d0..b44ca4265 100644 --- a/packages/orm/src/client/crud/operations/base.ts +++ b/packages/orm/src/client/crud/operations/base.ts @@ -2379,30 +2379,12 @@ export abstract class BaseOperationHandler { return this.executeDelete(kysely, model, where, limit, filterModel, fieldsToReturn); } - if (!modelDef.isDelegate) { - if (limit !== undefined) { - throw createNotSupportedError('Deleting with a limit is not supported for polymorphic models'); - } - for (const { fieldDef } of cascadeRelations) { - await this.delete( - kysely, - fieldDef.type, - { [fieldDef.relation!.opposite!]: where }, - undefined, - undefined, - undefined, - visited, - ); - } - return this.executeDelete(kysely, model, where, limit, filterModel, fieldsToReturn); - } - - // for a delegate model, resolve the rows being deleted first: `where` can filter on a sub model or on - // related rows, which the cascades below may delete - const discriminator = getDiscriminatorField(this.schema, model)!; + // resolve the rows being deleted first: `where` can filter on a sub model or on related rows, which + // the cascades below may delete + const discriminator = getDiscriminatorField(this.schema, model); const deletedRows = await this.read(kysely, filterModel, { where, - select: { ...this.makeIdSelect(filterModel), [discriminator]: true }, + select: { ...this.makeIdSelect(filterModel), ...(discriminator ? { [discriminator]: true } : {}) }, ...(limit !== undefined ? { take: limit } : {}), }); @@ -2413,7 +2395,7 @@ export abstract class BaseOperationHandler { const parentRows = discriminatorValue === undefined ? deletedRows - : deletedRows.filter((row) => row[discriminator] === discriminatorValue); + : deletedRows.filter((row) => row[discriminator!] === discriminatorValue); for (const batch of this.batchRows(model, parentRows)) { const childModel = fieldDef.type; const childRows = await this.read(kysely, childModel, { diff --git a/tests/e2e/orm/client-api/delegate-cascade-delete.test.ts b/tests/e2e/orm/client-api/delegate-cascade-delete.test.ts index 2c50f3a41..ecebe0cdc 100644 --- a/tests/e2e/orm/client-api/delegate-cascade-delete.test.ts +++ b/tests/e2e/orm/client-api/delegate-cascade-delete.test.ts @@ -116,6 +116,18 @@ model Thread { }); expect(await itemIds()).toEqual([kept.id]); }); + + it('deletes non-delegate rows filtered by the related rows it cascades to', async () => { + db = await createTestClient(schema, { usePrismaPush: true }); + const thread = await db.thread.create({ data: {} }); + await db.note.create({ data: { threadId: thread.id } }); + const kept = await db.thread.create({ data: {} }); + await expect(db.thread.deleteMany({ where: { notes: { some: {} } } })).resolves.toEqual({ + count: 1, + }); + expect(await itemIds()).toEqual([]); + expect(await db.thread.findMany()).toEqual([{ id: kept.id }]); + }); }); describe('with a sub model relation', () => { @@ -249,14 +261,16 @@ model Thread { } `; - it('deletes rows that can be deleted but not read', async () => { + it('skips rows that can be deleted but not read', async () => { + // the rows to delete are resolved with a read before the cascade runs, so rows hidden by the read + // policy are left alone together with their related rows db = await createPolicyTestClient(schema, { usePrismaPush: true }); const raw = db.$unuseAll(); await raw.thread.create({ data: { id: 'visible', notes: { create: {} } } }); - await raw.thread.create({ data: { id: 'hidden', hidden: true, notes: { create: {} } } }); - await expect(db.thread.deleteMany({})).resolves.toEqual({ count: 2 }); - expect(await raw.thread.count()).toBe(0); - expect(await itemIds()).toEqual([]); + await raw.thread.create({ data: { id: 'hidden', hidden: true, notes: { create: { id: 'hiddenNote' } } } }); + await expect(db.thread.deleteMany({})).resolves.toEqual({ count: 1 }); + expect(await raw.thread.findMany()).toEqual([expect.objectContaining({ id: 'hidden' })]); + expect(await itemIds()).toEqual(['hiddenNote']); }); }); });