@nanokajs/core 1.6.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +11 -11
- package/dist/{chunk-GZC7BVP2.js → chunk-SHPR4NTZ.js} +29 -3
- package/dist/{chunk-GZC7BVP2.js.map → chunk-SHPR4NTZ.js.map} +1 -1
- package/dist/cli.js +8 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +1 -1
- package/dist/{factories-Cmvx0495.d.ts → factories-CiYoD7dl.d.ts} +41 -2
- package/dist/index.d.ts +75 -28
- package/dist/index.js +187 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -13,7 +13,29 @@ interface FieldModifiers {
|
|
|
13
13
|
max?: number;
|
|
14
14
|
policy?: FieldPolicy;
|
|
15
15
|
}
|
|
16
|
-
type FieldKind = 'string' | 'uuid' | 'number' | 'integer' | 'boolean' | 'timestamp' | 'json';
|
|
16
|
+
type FieldKind = 'string' | 'uuid' | 'number' | 'integer' | 'boolean' | 'timestamp' | 'json' | 'relation';
|
|
17
|
+
type RelationKind = 'hasMany' | 'belongsTo';
|
|
18
|
+
interface RelationTargetLike {
|
|
19
|
+
readonly fields: Record<string, {
|
|
20
|
+
kind: FieldKind;
|
|
21
|
+
zodBase: unknown;
|
|
22
|
+
drizzleColumn(name: string): unknown;
|
|
23
|
+
}>;
|
|
24
|
+
readonly tableName: string;
|
|
25
|
+
}
|
|
26
|
+
interface RelationDef<Target extends RelationTargetLike = RelationTargetLike, FK extends string = string> {
|
|
27
|
+
readonly kind: 'relation';
|
|
28
|
+
readonly relationKind: RelationKind;
|
|
29
|
+
readonly target: Target | (() => Target);
|
|
30
|
+
readonly foreignKey: FK;
|
|
31
|
+
readonly tsType: never;
|
|
32
|
+
readonly zodBase: z.ZodNever;
|
|
33
|
+
readonly modifiers: {};
|
|
34
|
+
drizzleColumn(name: string): never;
|
|
35
|
+
}
|
|
36
|
+
type IsRelationField<F> = F extends {
|
|
37
|
+
kind: 'relation';
|
|
38
|
+
} ? true : false;
|
|
17
39
|
/**
|
|
18
40
|
* Represents a typed database field with Zod validation and Drizzle column mapping.
|
|
19
41
|
*
|
|
@@ -431,6 +453,17 @@ declare class JsonFieldBuilder<T = unknown, TS = T, Mods extends FieldModifiers
|
|
|
431
453
|
policy: 'readOnly';
|
|
432
454
|
}>;
|
|
433
455
|
}
|
|
456
|
+
declare class RelationFieldBuilder<Target extends RelationTargetLike, FK extends string, Kind extends RelationKind> {
|
|
457
|
+
readonly kind: "relation";
|
|
458
|
+
readonly relationKind: Kind;
|
|
459
|
+
readonly target: Target | (() => Target);
|
|
460
|
+
readonly foreignKey: FK;
|
|
461
|
+
readonly tsType: never;
|
|
462
|
+
readonly modifiers: {};
|
|
463
|
+
constructor(relationKind: Kind, target: Target | (() => Target), foreignKey: FK);
|
|
464
|
+
get zodBase(): z.ZodNever;
|
|
465
|
+
drizzleColumn(name: string): never;
|
|
466
|
+
}
|
|
434
467
|
declare function jsonField<S extends z.ZodTypeAny>(schema: S): JsonFieldBuilder<z.infer<S>, z.infer<S>, {}>;
|
|
435
468
|
declare function jsonField<T = unknown>(): JsonFieldBuilder<T, T, {}>;
|
|
436
469
|
/**
|
|
@@ -464,6 +497,12 @@ declare const t: {
|
|
|
464
497
|
* Drizzle 型が欲しい場合は生成後に手動で書き換えるか `as` を使うこと。
|
|
465
498
|
*/
|
|
466
499
|
json: typeof jsonField;
|
|
500
|
+
hasMany<Target extends RelationTargetLike, FK extends string>(target: Target | (() => Target), options: {
|
|
501
|
+
foreignKey: FK;
|
|
502
|
+
}): RelationFieldBuilder<Target, FK, "hasMany">;
|
|
503
|
+
belongsTo<Target extends RelationTargetLike, FK extends string>(target: Target | (() => Target), options: {
|
|
504
|
+
foreignKey: FK;
|
|
505
|
+
}): RelationFieldBuilder<Target, FK, "belongsTo">;
|
|
467
506
|
};
|
|
468
507
|
|
|
469
|
-
export { type Field as F, type InferFieldType as I, type FieldPolicy as a, type FieldModifiers as b, t };
|
|
508
|
+
export { type Field as F, type InferFieldType as I, type RelationDef as R, type FieldPolicy as a, type FieldModifiers as b, type IsRelationField as c, RelationFieldBuilder as d, type RelationKind as e, type RelationTargetLike as f, t };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { D1Database } from '@cloudflare/workers-types';
|
|
2
2
|
import { A as Adapter } from './types-D2QDVyks.js';
|
|
3
|
-
import { F as Field, I as InferFieldType, a as FieldPolicy } from './factories-
|
|
4
|
-
export { b as FieldModifiers, t } from './factories-
|
|
3
|
+
import { F as Field, I as InferFieldType, a as FieldPolicy, R as RelationDef } from './factories-CiYoD7dl.js';
|
|
4
|
+
export { b as FieldModifiers, c as IsRelationField, d as RelationFieldBuilder, e as RelationKind, f as RelationTargetLike, t } from './factories-CiYoD7dl.js';
|
|
5
5
|
import { Hook } from '@hono/zod-validator';
|
|
6
6
|
import { SQL } from 'drizzle-orm';
|
|
7
7
|
import { SQLiteTableWithColumns, AnySQLiteColumn } from 'drizzle-orm/sqlite-core';
|
|
@@ -81,6 +81,35 @@ interface OpenAPIDocument {
|
|
|
81
81
|
readonly paths: Record<string, Record<string, unknown>>;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* @internal
|
|
86
|
+
* Extracts keys of non-relation fields from a Fields record.
|
|
87
|
+
* Relation fields (kind: 'relation') are excluded from DB column types, RowType, etc.
|
|
88
|
+
*/
|
|
89
|
+
type NonRelationKeys<Fields extends Record<string, Field<any, any, any>>> = {
|
|
90
|
+
[K in keyof Fields]: Fields[K] extends {
|
|
91
|
+
kind: 'relation';
|
|
92
|
+
} ? never : K;
|
|
93
|
+
}[keyof Fields];
|
|
94
|
+
/**
|
|
95
|
+
* @internal
|
|
96
|
+
* Extracts keys of relation fields from a Fields record.
|
|
97
|
+
*/
|
|
98
|
+
type RelationKeys<Fields extends Record<string, Field<any, any, any>>> = {
|
|
99
|
+
[K in keyof Fields]: Fields[K] extends {
|
|
100
|
+
kind: 'relation';
|
|
101
|
+
} ? K : never;
|
|
102
|
+
}[keyof Fields];
|
|
103
|
+
type RelationTargetFields<Target> = Target extends () => infer Resolved ? RelationTargetFields<Resolved> : Target extends {
|
|
104
|
+
fields: infer TargetFields;
|
|
105
|
+
} ? TargetFields : never;
|
|
106
|
+
type RelationResult<F> = F extends RelationDef<infer Target, string> ? RelationTargetFields<Target> extends Record<string, Field<any, any, any>> ? F['relationKind'] extends 'hasMany' ? RowType<RelationTargetFields<Target>>[] : RowType<RelationTargetFields<Target>> | null : never : never;
|
|
107
|
+
type WithOptions<Fields extends Record<string, Field<any, any, any>>> = {
|
|
108
|
+
readonly [K in RelationKeys<Fields>]?: true;
|
|
109
|
+
};
|
|
110
|
+
type WithResult<Fields extends Record<string, Field<any, any, any>>, With> = RowType<Fields> & {
|
|
111
|
+
readonly [K in keyof With]: RelationResult<Fields[K & keyof Fields]>;
|
|
112
|
+
};
|
|
84
113
|
/**
|
|
85
114
|
* Options for findMany query.
|
|
86
115
|
* `limit` is required (no default).
|
|
@@ -88,11 +117,15 @@ interface OpenAPIDocument {
|
|
|
88
117
|
* `orderBy` is optional for column ordering.
|
|
89
118
|
* `where` accepts either an equality object or a Drizzle SQL expression.
|
|
90
119
|
*/
|
|
91
|
-
interface FindManyOptions<Fields extends Record<string, Field<any, any, any
|
|
120
|
+
interface FindManyOptions<Fields extends Record<string, Field<any, any, any>>, With extends WithOptions<Fields> | undefined = undefined> {
|
|
92
121
|
readonly limit: number;
|
|
93
122
|
readonly offset?: number;
|
|
94
123
|
readonly orderBy?: OrderBy<Fields>;
|
|
95
124
|
readonly where?: Where<Fields> | SQL;
|
|
125
|
+
readonly with?: With;
|
|
126
|
+
}
|
|
127
|
+
interface FindOneOptions<Fields extends Record<string, Field<any, any, any>>, With extends WithOptions<Fields> | undefined = undefined> {
|
|
128
|
+
readonly with?: With;
|
|
96
129
|
}
|
|
97
130
|
/**
|
|
98
131
|
* Options for findAll query.
|
|
@@ -107,38 +140,38 @@ interface FindAllOptions<Fields extends Record<string, Field<any, any, any>>> {
|
|
|
107
140
|
/**
|
|
108
141
|
* Order-by specification: single field, or field with direction, or array of multiple.
|
|
109
142
|
*/
|
|
110
|
-
type OrderBy<Fields extends Record<string, Field<any, any, any>>> = (
|
|
111
|
-
readonly column:
|
|
143
|
+
type OrderBy<Fields extends Record<string, Field<any, any, any>>> = (NonRelationKeys<Fields> & string) | {
|
|
144
|
+
readonly column: NonRelationKeys<Fields> & string;
|
|
112
145
|
readonly direction?: 'asc' | 'desc';
|
|
113
|
-
} | ReadonlyArray<(
|
|
114
|
-
readonly column:
|
|
146
|
+
} | ReadonlyArray<(NonRelationKeys<Fields> & string) | {
|
|
147
|
+
readonly column: NonRelationKeys<Fields> & string;
|
|
115
148
|
readonly direction?: 'asc' | 'desc';
|
|
116
149
|
}>;
|
|
117
150
|
/**
|
|
118
151
|
* Where clause object: each key is a field name, each value is the desired equality.
|
|
119
152
|
*/
|
|
120
153
|
type Where<Fields extends Record<string, Field<any, any, any>>> = {
|
|
121
|
-
readonly [K in
|
|
154
|
+
readonly [K in NonRelationKeys<Fields>]?: InferFieldType<Fields[K]>;
|
|
122
155
|
};
|
|
123
156
|
/**
|
|
124
157
|
* Identifier or Where clause: can be a primary key value or a Where object.
|
|
125
158
|
*/
|
|
126
159
|
type IdOrWhere<Fields extends Record<string, Field<any, any, any>>> = string | number | Where<Fields>;
|
|
127
160
|
/**
|
|
128
|
-
* Row type: full record type from fields.
|
|
161
|
+
* Row type: full record type from fields, excluding relation fields.
|
|
129
162
|
*/
|
|
130
163
|
type RowType<Fields extends Record<string, Field<any, any, any>>> = {
|
|
131
|
-
[K in
|
|
164
|
+
[K in NonRelationKeys<Fields>]: InferFieldType<Fields[K]>;
|
|
132
165
|
};
|
|
133
166
|
/**
|
|
134
|
-
* Drizzle table type with columns typed to match the model's Fields.
|
|
167
|
+
* Drizzle table type with columns typed to match the model's Fields (excluding relation fields).
|
|
135
168
|
* Allows type-safe column access via Model.table.fieldName.
|
|
136
169
|
*/
|
|
137
170
|
type ModelTable<Fields extends Record<string, Field<any, any, any>>> = SQLiteTableWithColumns<{
|
|
138
171
|
name: string;
|
|
139
172
|
schema: undefined;
|
|
140
173
|
columns: {
|
|
141
|
-
[K in
|
|
174
|
+
[K in NonRelationKeys<Fields>]: AnySQLiteColumn;
|
|
142
175
|
};
|
|
143
176
|
dialect: 'sqlite';
|
|
144
177
|
}>;
|
|
@@ -209,11 +242,12 @@ type IsRequired<F extends Field<any, any, any>> = F extends Field<any, {
|
|
|
209
242
|
* - all other fields are required
|
|
210
243
|
* - `serverOnly` fields are completely excluded (cannot be passed even internally)
|
|
211
244
|
* - `writeOnly` fields are included as required (they are input fields by design)
|
|
245
|
+
* - relation fields are completely excluded (no DB column)
|
|
212
246
|
*/
|
|
213
247
|
type CreateInput<Fields extends Record<string, Field<any, any, any>>> = {
|
|
214
|
-
[K in
|
|
248
|
+
[K in NonRelationKeys<Fields> as IsRequired<Fields[K]> extends true ? K extends ServerOnlyKeys<Fields> ? never : K : never]: InferFieldType<Fields[K]>;
|
|
215
249
|
} & {
|
|
216
|
-
[K in
|
|
250
|
+
[K in NonRelationKeys<Fields> as IsRequired<Fields[K]> extends true ? never : K extends ServerOnlyKeys<Fields> ? never : K]?: InferFieldType<Fields[K]>;
|
|
217
251
|
};
|
|
218
252
|
type FieldAccessor<K extends string> = {
|
|
219
253
|
readonly [Key in K]: Key;
|
|
@@ -226,11 +260,12 @@ interface SchemaOptions<K extends string = string> {
|
|
|
226
260
|
/**
|
|
227
261
|
* @internal
|
|
228
262
|
* Extracts a Zod shape from fields for the Apply type.
|
|
229
|
-
*
|
|
263
|
+
* Only non-relation fields are included (relation fields have zodBase: ZodNever
|
|
264
|
+
* and are skipped at runtime in buildBaseObject).
|
|
230
265
|
* Indexed access ensures concrete Zod types are preserved (not `any`).
|
|
231
266
|
*/
|
|
232
267
|
type FieldsToZodShape<Fields extends Record<string, Field<any, any, any>>> = {
|
|
233
|
-
[K in
|
|
268
|
+
[K in NonRelationKeys<Fields>]: Fields[K]['zodBase'];
|
|
234
269
|
};
|
|
235
270
|
/**
|
|
236
271
|
* @internal
|
|
@@ -352,7 +387,7 @@ type ValidatorInput<Target extends keyof ValidationTargets, Schema extends z.Zod
|
|
|
352
387
|
* Uses RowType<Fields> directly for input/output type computation
|
|
353
388
|
* to ensure Zod v3/v4 compatibility (z.input<ZodTypeAny> returns unknown in Zod v4).
|
|
354
389
|
*/
|
|
355
|
-
type ModelValidatorReturn<Fields extends Record<string, Field<any, any, any>>, Target extends keyof ValidationTargets, Opts extends SchemaOptions<
|
|
390
|
+
type ModelValidatorReturn<Fields extends Record<string, Field<any, any, any>>, Target extends keyof ValidationTargets, Opts extends SchemaOptions<NonRelationKeys<Fields> & string> | undefined> = MiddlewareHandler<any, string, ValidatorInput<Target, Apply<FieldsToZodShape<Fields>, Opts>, ApplyToRecord<RowType<Fields>, Opts>, ApplyToRecord<RowType<Fields>, Opts>>>;
|
|
356
391
|
|
|
357
392
|
/**
|
|
358
393
|
* Represents a database model with type-safe schema derivation and CRUD operations.
|
|
@@ -369,7 +404,7 @@ interface Model<Fields extends Record<string, Field<any, any, any>>> {
|
|
|
369
404
|
* const CreateSchema = User.schema({ omit: ['passwordHash'] })
|
|
370
405
|
* const UpdateSchema = User.schema({ partial: true, pick: ['name', 'email'] })
|
|
371
406
|
*/
|
|
372
|
-
schema<Opts extends SchemaOptions<
|
|
407
|
+
schema<Opts extends SchemaOptions<NonRelationKeys<Fields> & string> | undefined>(opts?: Opts): Apply<FieldsToZodShape<Fields>, Opts>;
|
|
373
408
|
/**
|
|
374
409
|
* Returns a Hono middleware validator derived from this model's schema.
|
|
375
410
|
* Integrates with @hono/zod-validator to validate request inputs.
|
|
@@ -393,7 +428,7 @@ interface Model<Fields extends Record<string, Field<any, any, any>>> {
|
|
|
393
428
|
*/
|
|
394
429
|
validator<Target extends keyof ValidationTargets, E extends Env = Env, P extends string = string>(target: Target, preset: 'create', hook?: Hook<Omit<RowType<Fields>, PolicyOmitKeys<Fields, 'input-create'> & string>, E, P, Target>): MiddlewareHandler<E, P, ValidatorInput<Target, z.ZodObject<z.ZodRawShape>, Omit<RowType<Fields>, PolicyOmitKeys<Fields, 'input-create'> & string>, Omit<RowType<Fields>, PolicyOmitKeys<Fields, 'input-create'> & string>>>;
|
|
395
430
|
validator<Target extends keyof ValidationTargets, E extends Env = Env, P extends string = string>(target: Target, preset: 'update', hook?: Hook<Partial<Omit<RowType<Fields>, PolicyOmitKeys<Fields, 'input-update'> & string>>, E, P, Target>): MiddlewareHandler<E, P, ValidatorInput<Target, z.ZodObject<z.ZodRawShape>, Partial<Omit<RowType<Fields>, PolicyOmitKeys<Fields, 'input-update'> & string>>, Partial<Omit<RowType<Fields>, PolicyOmitKeys<Fields, 'input-update'> & string>>>>;
|
|
396
|
-
validator<Target extends keyof ValidationTargets, Opts extends SchemaOptions<
|
|
431
|
+
validator<Target extends keyof ValidationTargets, Opts extends SchemaOptions<NonRelationKeys<Fields> & string> | undefined, E extends Env = Env, P extends string = string>(target: Target, opts?: Opts, hook?: Hook<z.output<Apply<FieldsToZodShape<Fields>, Opts>>, E, P, Target>): ModelValidatorReturn<Fields, Target, Opts>;
|
|
397
432
|
/**
|
|
398
433
|
* Returns a Zod schema for API input, with policy-based field exclusions applied.
|
|
399
434
|
* - 'create': serverOnly and readOnly fields are excluded
|
|
@@ -401,13 +436,13 @@ interface Model<Fields extends Record<string, Field<any, any, any>>> {
|
|
|
401
436
|
*
|
|
402
437
|
* The return type precisely reflects policy-derived omissions using PolicyOmitKeys.
|
|
403
438
|
*/
|
|
404
|
-
inputSchema<Opts extends SchemaOptions<
|
|
405
|
-
inputSchema<Opts extends SchemaOptions<
|
|
439
|
+
inputSchema<Opts extends SchemaOptions<NonRelationKeys<Fields> & string> | undefined = undefined>(usage: 'create', opts?: Opts): z.ZodObject<ApplyShape<Omit<FieldsToZodShape<Fields>, PolicyOmitKeys<Fields, 'input-create'> & string>, Opts>>;
|
|
440
|
+
inputSchema<Opts extends SchemaOptions<NonRelationKeys<Fields> & string> | undefined = undefined>(usage: 'update', opts?: Opts): z.ZodObject<ApplyShape<Omit<FieldsToZodShape<Fields>, PolicyOmitKeys<Fields, 'input-update'> & string>, Opts extends undefined ? {
|
|
406
441
|
partial: true;
|
|
407
442
|
} : Opts & {
|
|
408
443
|
partial: true;
|
|
409
444
|
}>>;
|
|
410
|
-
inputSchema(usage: 'create' | 'update', opts?: SchemaOptions<
|
|
445
|
+
inputSchema(usage: 'create' | 'update', opts?: SchemaOptions<NonRelationKeys<Fields> & string>): z.ZodObject<z.ZodRawShape>;
|
|
411
446
|
/**
|
|
412
447
|
* Returns a Zod schema for API output, with policy-based field exclusions applied.
|
|
413
448
|
* - serverOnly and writeOnly fields are excluded
|
|
@@ -415,7 +450,7 @@ interface Model<Fields extends Record<string, Field<any, any, any>>> {
|
|
|
415
450
|
*
|
|
416
451
|
* The return type precisely reflects policy-derived omissions using PolicyOmitKeys.
|
|
417
452
|
*/
|
|
418
|
-
outputSchema<Opts extends SchemaOptions<
|
|
453
|
+
outputSchema<Opts extends SchemaOptions<NonRelationKeys<Fields> & string> | undefined = undefined>(opts?: Opts): z.ZodObject<ApplyShape<Omit<FieldsToZodShape<Fields>, PolicyOmitKeys<Fields, 'output'> & string>, Opts>>;
|
|
419
454
|
/**
|
|
420
455
|
* Parses a DB row through outputSchema and returns the safe response object.
|
|
421
456
|
* Equivalent to `User.outputSchema().parse(row)`.
|
|
@@ -443,6 +478,9 @@ interface Model<Fields extends Record<string, Field<any, any, any>>> {
|
|
|
443
478
|
* const page2 = await User.findMany(adapter, { limit: 20, offset: 20 })
|
|
444
479
|
* const sorted = await User.findMany(adapter, { limit: 10, orderBy: 'name' })
|
|
445
480
|
*/
|
|
481
|
+
findMany<With extends WithOptions<Fields>>(adapter: Adapter, options: FindManyOptions<Fields, With> & {
|
|
482
|
+
readonly with: With;
|
|
483
|
+
}): Promise<WithResult<Fields, With>[]>;
|
|
446
484
|
findMany(adapter: Adapter, options: FindManyOptions<Fields>): Promise<RowType<Fields>[]>;
|
|
447
485
|
/**
|
|
448
486
|
* Fetches all rows without a LIMIT clause.
|
|
@@ -461,6 +499,9 @@ interface Model<Fields extends Record<string, Field<any, any, any>>> {
|
|
|
461
499
|
* const user = await User.findOne(adapter, id)
|
|
462
500
|
* const byEmail = await User.findOne(adapter, { email: 'john@example.com' })
|
|
463
501
|
*/
|
|
502
|
+
findOne<With extends WithOptions<Fields>>(adapter: Adapter, idOrWhere: IdOrWhere<Fields>, options: FindOneOptions<Fields, With> & {
|
|
503
|
+
readonly with: With;
|
|
504
|
+
}): Promise<WithResult<Fields, With> | null>;
|
|
464
505
|
findOne(adapter: Adapter, idOrWhere: IdOrWhere<Fields>): Promise<RowType<Fields> | null>;
|
|
465
506
|
/**
|
|
466
507
|
* Creates a new row and returns the created record.
|
|
@@ -529,7 +570,7 @@ interface NanokaModel<Fields extends Record<string, Field<any, any, any>>> {
|
|
|
529
570
|
* Returns a Zod schema derived from this model's fields.
|
|
530
571
|
* Supports pick, omit, and partial transformations.
|
|
531
572
|
*/
|
|
532
|
-
schema<Opts extends SchemaOptions<
|
|
573
|
+
schema<Opts extends SchemaOptions<NonRelationKeys<Fields> & string> | undefined = undefined>(opts?: Opts): Apply<FieldsToZodShape<Fields>, Opts>;
|
|
533
574
|
/**
|
|
534
575
|
* Returns a Hono middleware validator for this model.
|
|
535
576
|
* The optional `hook` argument is passed through to @hono/zod-validator.
|
|
@@ -537,15 +578,15 @@ interface NanokaModel<Fields extends Record<string, Field<any, any, any>>> {
|
|
|
537
578
|
*/
|
|
538
579
|
validator<Target extends keyof hono.ValidationTargets, E extends Env$1 = Env$1, P extends string = string>(target: Target, preset: 'create', hook?: Hook<Omit<RowType<Fields>, PolicyOmitKeys<Fields, 'input-create'> & string>, E, P, Target>): MiddlewareHandler<E, P, ValidatorInput<Target, z.ZodObject<z.ZodRawShape>, Omit<RowType<Fields>, PolicyOmitKeys<Fields, 'input-create'> & string>, Omit<RowType<Fields>, PolicyOmitKeys<Fields, 'input-create'> & string>>>;
|
|
539
580
|
validator<Target extends keyof hono.ValidationTargets, E extends Env$1 = Env$1, P extends string = string>(target: Target, preset: 'update', hook?: Hook<Partial<Omit<RowType<Fields>, PolicyOmitKeys<Fields, 'input-update'> & string>>, E, P, Target>): MiddlewareHandler<E, P, ValidatorInput<Target, z.ZodObject<z.ZodRawShape>, Partial<Omit<RowType<Fields>, PolicyOmitKeys<Fields, 'input-update'> & string>>, Partial<Omit<RowType<Fields>, PolicyOmitKeys<Fields, 'input-update'> & string>>>>;
|
|
540
|
-
validator<Target extends keyof hono.ValidationTargets, Opts extends SchemaOptions<
|
|
581
|
+
validator<Target extends keyof hono.ValidationTargets, Opts extends SchemaOptions<NonRelationKeys<Fields> & string> | undefined = undefined, E extends Env$1 = Env$1, P extends string = string>(target: Target, opts?: Opts, hook?: Hook<z.output<Apply<FieldsToZodShape<Fields>, Opts>>, E, P, Target>): ModelValidatorReturn<Fields, Target, Opts>;
|
|
541
582
|
/**
|
|
542
583
|
* Returns a Zod schema for API input, with policy-based field exclusions applied.
|
|
543
584
|
*/
|
|
544
|
-
inputSchema(usage: 'create' | 'update', opts?: SchemaOptions<
|
|
585
|
+
inputSchema(usage: 'create' | 'update', opts?: SchemaOptions<NonRelationKeys<Fields> & string>): z.ZodObject<z.ZodRawShape>;
|
|
545
586
|
/**
|
|
546
587
|
* Returns a Zod schema for API output, with policy-based field exclusions applied.
|
|
547
588
|
*/
|
|
548
|
-
outputSchema(opts?: SchemaOptions<
|
|
589
|
+
outputSchema(opts?: SchemaOptions<NonRelationKeys<Fields> & string>): z.ZodObject<z.ZodRawShape>;
|
|
549
590
|
/**
|
|
550
591
|
* Parses a DB row through outputSchema and returns the safe response object.
|
|
551
592
|
*/
|
|
@@ -557,6 +598,9 @@ interface NanokaModel<Fields extends Record<string, Field<any, any, any>>> {
|
|
|
557
598
|
* Fetches multiple rows with pagination and optional ordering.
|
|
558
599
|
* `limit` is required (no default) to prevent accidental unbounded queries.
|
|
559
600
|
*/
|
|
601
|
+
findMany<With extends WithOptions<Fields>>(options: FindManyOptions<Fields, With> & {
|
|
602
|
+
readonly with: With;
|
|
603
|
+
}): Promise<WithResult<Fields, With>[]>;
|
|
560
604
|
findMany(options: FindManyOptions<Fields>): Promise<RowType<Fields>[]>;
|
|
561
605
|
/**
|
|
562
606
|
* Fetches all rows without a LIMIT clause.
|
|
@@ -567,6 +611,9 @@ interface NanokaModel<Fields extends Record<string, Field<any, any, any>>> {
|
|
|
567
611
|
* Fetches a single row by primary key or where clause.
|
|
568
612
|
* Returns null if not found.
|
|
569
613
|
*/
|
|
614
|
+
findOne<With extends WithOptions<Fields>>(idOrWhere: IdOrWhere<Fields>, options: FindOneOptions<Fields, With> & {
|
|
615
|
+
readonly with: With;
|
|
616
|
+
}): Promise<WithResult<Fields, With> | null>;
|
|
570
617
|
findOne(idOrWhere: IdOrWhere<Fields>): Promise<RowType<Fields> | null>;
|
|
571
618
|
/**
|
|
572
619
|
* Creates a new row and returns the created record.
|
|
@@ -655,4 +702,4 @@ interface Nanoka<E extends Env$1 = BlankEnv> extends Hono<E> {
|
|
|
655
702
|
*/
|
|
656
703
|
declare function nanoka<E extends Env$1 = BlankEnv>(adapter: Adapter): Nanoka<E>;
|
|
657
704
|
|
|
658
|
-
export { Adapter, type CreateInput, Field, FieldPolicy, type FindAllOptions, type FindManyOptions, type HttpMethod, type IdOrWhere, InferFieldType, type Model, type Nanoka, type NanokaModel, type OpenAPIDocument, type OpenAPIModelComponent, type OpenAPIParamSpec, type OpenAPIRequestBodySpec, type OpenAPIResponseSpec, type OpenAPIRouteMetadata, type OpenAPISchemaObject, type OpenAPISpecOptions, type OpenAPIUsage, type OrderBy, type RouteOpenAPIMetadata, type RouteOpenAPIOption, type RowType, type SchemaOptions, type Where, d1Adapter, defineModel, nanoka, swaggerUI };
|
|
705
|
+
export { Adapter, type CreateInput, Field, FieldPolicy, type FindAllOptions, type FindManyOptions, type FindOneOptions, type HttpMethod, type IdOrWhere, InferFieldType, type Model, type Nanoka, type NanokaModel, type OpenAPIDocument, type OpenAPIModelComponent, type OpenAPIParamSpec, type OpenAPIRequestBodySpec, type OpenAPIResponseSpec, type OpenAPIRouteMetadata, type OpenAPISchemaObject, type OpenAPISpecOptions, type OpenAPIUsage, type OrderBy, RelationDef, type RelationKeys, type RouteOpenAPIMetadata, type RouteOpenAPIOption, type RowType, type SchemaOptions, type Where, type WithOptions, type WithResult, d1Adapter, defineModel, nanoka, swaggerUI };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { t } from './chunk-
|
|
1
|
+
export { t } from './chunk-SHPR4NTZ.js';
|
|
2
2
|
import { drizzle } from 'drizzle-orm/d1';
|
|
3
3
|
import { zValidator } from '@hono/zod-validator';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { SQL, asc, desc, eq, and } from 'drizzle-orm';
|
|
5
|
+
import { SQL, asc, desc, eq, and, inArray } from 'drizzle-orm';
|
|
6
6
|
import { HTTPException } from 'hono/http-exception';
|
|
7
7
|
import { sqliteTable } from 'drizzle-orm/sqlite-core';
|
|
8
8
|
import { Hono } from 'hono';
|
|
@@ -23,7 +23,10 @@ function resolveSchemaOptionKeys(opt, accessor) {
|
|
|
23
23
|
}
|
|
24
24
|
function buildFieldAccessor(fields) {
|
|
25
25
|
const acc = {};
|
|
26
|
-
for (const k of Object.
|
|
26
|
+
for (const [k, field] of Object.entries(fields)) {
|
|
27
|
+
if (field.kind === "relation") continue;
|
|
28
|
+
acc[k] = k;
|
|
29
|
+
}
|
|
27
30
|
return Object.freeze(acc);
|
|
28
31
|
}
|
|
29
32
|
|
|
@@ -37,6 +40,13 @@ function computeForcedOmit(fields) {
|
|
|
37
40
|
}
|
|
38
41
|
return omit;
|
|
39
42
|
}
|
|
43
|
+
function computeRelationOmit(fields) {
|
|
44
|
+
const keys = /* @__PURE__ */ new Set();
|
|
45
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
46
|
+
if (field.kind === "relation") keys.add(key);
|
|
47
|
+
}
|
|
48
|
+
return keys;
|
|
49
|
+
}
|
|
40
50
|
function computeOptionalOmit(fields, target) {
|
|
41
51
|
const omit = [];
|
|
42
52
|
for (const [key, field] of Object.entries(fields)) {
|
|
@@ -51,7 +61,9 @@ function computeOptionalOmit(fields, target) {
|
|
|
51
61
|
return omit;
|
|
52
62
|
}
|
|
53
63
|
function derivePolicyOptions(fields, usage, userOpts, accessor) {
|
|
54
|
-
const
|
|
64
|
+
const forcedBase = computeForcedOmit(fields);
|
|
65
|
+
const relationOmit = computeRelationOmit(fields);
|
|
66
|
+
const forced = [.../* @__PURE__ */ new Set([...forcedBase, ...relationOmit])];
|
|
55
67
|
const optional = computeOptionalOmit(fields, usage);
|
|
56
68
|
const acc = accessor ?? {};
|
|
57
69
|
const resolvedPick = resolveSchemaOptionKeys(userOpts?.pick, acc);
|
|
@@ -79,6 +91,7 @@ function derivePolicyOptions(fields, usage, userOpts, accessor) {
|
|
|
79
91
|
function buildBaseObject(fields) {
|
|
80
92
|
const shape = {};
|
|
81
93
|
for (const [key, field] of Object.entries(fields)) {
|
|
94
|
+
if (field.kind === "relation") continue;
|
|
82
95
|
shape[key] = field.zodBase;
|
|
83
96
|
}
|
|
84
97
|
return z.object(shape);
|
|
@@ -129,6 +142,7 @@ function toOpenAPISchema(fields, usage, opts) {
|
|
|
129
142
|
for (const [key, zodSchema] of Object.entries(shape)) {
|
|
130
143
|
const field = fields[key];
|
|
131
144
|
if (field === void 0) continue;
|
|
145
|
+
if (field.kind === "relation") continue;
|
|
132
146
|
properties[key] = fieldToOpenAPISchema(field, opts?.strict);
|
|
133
147
|
if (field.modifiers.policy === "writeOnly" && (usage === "create" || usage === "update")) {
|
|
134
148
|
properties[key].writeOnly = true;
|
|
@@ -316,6 +330,16 @@ function getZodCheckDef(check) {
|
|
|
316
330
|
}
|
|
317
331
|
return check;
|
|
318
332
|
}
|
|
333
|
+
function buildTable(tableName, fields) {
|
|
334
|
+
const columns = {};
|
|
335
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
336
|
+
if (field.kind === "relation") continue;
|
|
337
|
+
columns[key] = field.drizzleColumn(key);
|
|
338
|
+
}
|
|
339
|
+
return sqliteTable(tableName, columns);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/model/crud.ts
|
|
319
343
|
function guardLimit(limit) {
|
|
320
344
|
if (!Number.isInteger(limit)) {
|
|
321
345
|
throw new HTTPException(400, { message: "limit must be an integer" });
|
|
@@ -380,6 +404,143 @@ function buildWhereClause(table, fields, where) {
|
|
|
380
404
|
}
|
|
381
405
|
return and(...conditions);
|
|
382
406
|
}
|
|
407
|
+
function resolveRelationTarget(field) {
|
|
408
|
+
return typeof field.target === "function" ? field.target() : field.target;
|
|
409
|
+
}
|
|
410
|
+
function getTargetTable(target) {
|
|
411
|
+
if (Object.hasOwn(target, "table")) {
|
|
412
|
+
return target.table;
|
|
413
|
+
}
|
|
414
|
+
return buildTable(target.tableName, target.fields);
|
|
415
|
+
}
|
|
416
|
+
function validateWithOptions(fields, withOptions) {
|
|
417
|
+
if (withOptions === null || typeof withOptions !== "object" || Array.isArray(withOptions)) {
|
|
418
|
+
throw new HTTPException(400, { message: "invalid with option" });
|
|
419
|
+
}
|
|
420
|
+
for (const [key, value] of Object.entries(withOptions)) {
|
|
421
|
+
if (value !== true) {
|
|
422
|
+
throw new HTTPException(400, { message: "nested with is not supported" });
|
|
423
|
+
}
|
|
424
|
+
if (!Object.hasOwn(fields, key) || fields[key]?.kind !== "relation") {
|
|
425
|
+
throw new HTTPException(400, { message: "invalid relation in with option" });
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
var relationCycleCache = /* @__PURE__ */ new WeakMap();
|
|
430
|
+
function assertNoRelationCycle(fields, rootName = "root") {
|
|
431
|
+
const cached = relationCycleCache.get(fields);
|
|
432
|
+
if (cached !== void 0) {
|
|
433
|
+
if (cached !== null) throw cached;
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
437
|
+
const visited = /* @__PURE__ */ new Set();
|
|
438
|
+
const visit = (currentFields, path) => {
|
|
439
|
+
if (visiting.has(currentFields)) {
|
|
440
|
+
return new HTTPException(500, { message: "relation cycle detected" });
|
|
441
|
+
}
|
|
442
|
+
if (visited.has(currentFields)) {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
visiting.add(currentFields);
|
|
446
|
+
for (const field of Object.values(currentFields)) {
|
|
447
|
+
if (field.kind !== "relation") continue;
|
|
448
|
+
const target = resolveRelationTarget(field);
|
|
449
|
+
const targetFields = target.fields;
|
|
450
|
+
const error2 = visit(targetFields, [...path, target.tableName]);
|
|
451
|
+
if (error2 !== null) {
|
|
452
|
+
return error2;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
visiting.delete(currentFields);
|
|
456
|
+
visited.add(currentFields);
|
|
457
|
+
return null;
|
|
458
|
+
};
|
|
459
|
+
const error = visit(fields, [rootName]);
|
|
460
|
+
relationCycleCache.set(fields, error);
|
|
461
|
+
if (error !== null) {
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
async function loadRelations(adapter, _parentTable, parentFields, parentRows, withOptions) {
|
|
466
|
+
if (parentRows.length === 0) {
|
|
467
|
+
return [];
|
|
468
|
+
}
|
|
469
|
+
const rows = parentRows.map((row) => ({ ...row }));
|
|
470
|
+
for (const relationKey of Object.keys(withOptions)) {
|
|
471
|
+
const field = parentFields[relationKey];
|
|
472
|
+
if (field?.kind !== "relation") {
|
|
473
|
+
throw new HTTPException(400, { message: "invalid relation in with option" });
|
|
474
|
+
}
|
|
475
|
+
const relation = field;
|
|
476
|
+
const target = resolveRelationTarget(relation);
|
|
477
|
+
const targetFields = target.fields;
|
|
478
|
+
const targetTable = getTargetTable(target);
|
|
479
|
+
if (relation.relationKind === "hasMany") {
|
|
480
|
+
const parentPk = findPrimaryKey(parentFields);
|
|
481
|
+
if (!parentPk) {
|
|
482
|
+
throw new HTTPException(500, { message: "model has no primary key" });
|
|
483
|
+
}
|
|
484
|
+
const parentPkKey = parentPk[0];
|
|
485
|
+
const foreignKey = relation.foreignKey;
|
|
486
|
+
if (!Object.hasOwn(targetFields, foreignKey) || !Object.hasOwn(targetTable, foreignKey)) {
|
|
487
|
+
throw new HTTPException(500, { message: "invalid relation foreign key" });
|
|
488
|
+
}
|
|
489
|
+
const parentIds = Array.from(
|
|
490
|
+
new Set(parentRows.map((row) => row[parentPkKey]))
|
|
491
|
+
);
|
|
492
|
+
const targetRows = parentIds.length === 0 ? [] : await adapter.drizzle.select().from(targetTable).where(
|
|
493
|
+
inArray(
|
|
494
|
+
targetTable[foreignKey],
|
|
495
|
+
parentIds
|
|
496
|
+
)
|
|
497
|
+
);
|
|
498
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
499
|
+
for (const targetRow of targetRows) {
|
|
500
|
+
const value = targetRow[foreignKey];
|
|
501
|
+
const group = grouped.get(value);
|
|
502
|
+
if (group === void 0) {
|
|
503
|
+
grouped.set(value, [targetRow]);
|
|
504
|
+
} else {
|
|
505
|
+
group.push(targetRow);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
for (const row of rows) {
|
|
509
|
+
row[relationKey] = grouped.get(row[parentPkKey]) ?? [];
|
|
510
|
+
}
|
|
511
|
+
} else {
|
|
512
|
+
const foreignKey = relation.foreignKey;
|
|
513
|
+
if (!Object.hasOwn(parentFields, foreignKey) || !Object.hasOwn(_parentTable, foreignKey)) {
|
|
514
|
+
throw new HTTPException(500, { message: "invalid relation foreign key" });
|
|
515
|
+
}
|
|
516
|
+
const targetPk = findPrimaryKey(targetFields);
|
|
517
|
+
if (!targetPk) {
|
|
518
|
+
throw new HTTPException(500, { message: "model has no primary key" });
|
|
519
|
+
}
|
|
520
|
+
const targetPkKey = targetPk[0];
|
|
521
|
+
if (!Object.hasOwn(targetTable, targetPkKey)) {
|
|
522
|
+
throw new HTTPException(500, { message: "invalid relation primary key" });
|
|
523
|
+
}
|
|
524
|
+
const parentFkValues = Array.from(
|
|
525
|
+
new Set(parentRows.map((row) => row[foreignKey]))
|
|
526
|
+
);
|
|
527
|
+
const targetRows = parentFkValues.length === 0 ? [] : await adapter.drizzle.select().from(targetTable).where(
|
|
528
|
+
inArray(
|
|
529
|
+
targetTable[targetPkKey],
|
|
530
|
+
parentFkValues
|
|
531
|
+
)
|
|
532
|
+
);
|
|
533
|
+
const byPk = /* @__PURE__ */ new Map();
|
|
534
|
+
for (const targetRow of targetRows) {
|
|
535
|
+
byPk.set(targetRow[targetPkKey], targetRow);
|
|
536
|
+
}
|
|
537
|
+
for (const row of rows) {
|
|
538
|
+
row[relationKey] = byPk.get(row[foreignKey]) ?? null;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return rows;
|
|
543
|
+
}
|
|
383
544
|
async function findManyImpl(adapter, table, fields, options) {
|
|
384
545
|
const limit = guardLimit(options.limit);
|
|
385
546
|
const offset = options.offset !== void 0 ? guardOffset(options.offset) : 0;
|
|
@@ -421,6 +582,11 @@ async function findManyImpl(adapter, table, fields, options) {
|
|
|
421
582
|
}
|
|
422
583
|
}
|
|
423
584
|
const rows = await query;
|
|
585
|
+
if (options.with !== void 0) {
|
|
586
|
+
validateWithOptions(fields, options.with);
|
|
587
|
+
assertNoRelationCycle(fields);
|
|
588
|
+
return loadRelations(adapter, table, fields, rows, options.with);
|
|
589
|
+
}
|
|
424
590
|
return rows;
|
|
425
591
|
}
|
|
426
592
|
async function findAllImpl(adapter, table, fields, options) {
|
|
@@ -465,7 +631,7 @@ async function findAllImpl(adapter, table, fields, options) {
|
|
|
465
631
|
const rows = await query;
|
|
466
632
|
return rows;
|
|
467
633
|
}
|
|
468
|
-
async function findOneImpl(adapter, table, fields, idOrWhere) {
|
|
634
|
+
async function findOneImpl(adapter, table, fields, idOrWhere, options) {
|
|
469
635
|
let where = null;
|
|
470
636
|
if (typeof idOrWhere === "string" || typeof idOrWhere === "number") {
|
|
471
637
|
const pk = findPrimaryKey(fields);
|
|
@@ -480,8 +646,19 @@ async function findOneImpl(adapter, table, fields, idOrWhere) {
|
|
|
480
646
|
if (!whereClause) {
|
|
481
647
|
throw new HTTPException(400, { message: "where clause must not be empty" });
|
|
482
648
|
}
|
|
649
|
+
if (options?.with !== void 0) {
|
|
650
|
+
validateWithOptions(fields, options.with);
|
|
651
|
+
assertNoRelationCycle(fields);
|
|
652
|
+
}
|
|
483
653
|
const rows = await adapter.drizzle.select().from(table).where(whereClause).limit(1);
|
|
484
|
-
|
|
654
|
+
if (rows.length === 0) {
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
if (options?.with !== void 0) {
|
|
658
|
+
const withRows = await loadRelations(adapter, table, fields, [rows[0]], options.with);
|
|
659
|
+
return withRows[0] ?? null;
|
|
660
|
+
}
|
|
661
|
+
return rows[0];
|
|
485
662
|
}
|
|
486
663
|
async function createImpl(adapter, table, data) {
|
|
487
664
|
const rows = await adapter.drizzle.insert(table).values(data).returning();
|
|
@@ -523,13 +700,6 @@ async function deleteImpl(adapter, table, fields, idOrWhere) {
|
|
|
523
700
|
const result = await adapter.drizzle.delete(table).where(whereClause).returning();
|
|
524
701
|
return { deleted: result.length };
|
|
525
702
|
}
|
|
526
|
-
function buildTable(tableName, fields) {
|
|
527
|
-
const columns = {};
|
|
528
|
-
for (const [key, field] of Object.entries(fields)) {
|
|
529
|
-
columns[key] = field.drizzleColumn(key);
|
|
530
|
-
}
|
|
531
|
-
return sqliteTable(tableName, columns);
|
|
532
|
-
}
|
|
533
703
|
|
|
534
704
|
// src/model/define.ts
|
|
535
705
|
function defineModel(tableName, fields) {
|
|
@@ -581,15 +751,11 @@ function defineModel(tableName, fields) {
|
|
|
581
751
|
toOpenAPISchema(usage) {
|
|
582
752
|
return toOpenAPISchema(fields, usage);
|
|
583
753
|
},
|
|
584
|
-
findMany(adapter, options)
|
|
585
|
-
return findManyImpl(adapter, table, fields, options);
|
|
586
|
-
},
|
|
754
|
+
findMany: ((adapter, options) => findManyImpl(adapter, table, fields, options)),
|
|
587
755
|
findAll(adapter, options) {
|
|
588
756
|
return findAllImpl(adapter, table, fields, options);
|
|
589
757
|
},
|
|
590
|
-
findOne(adapter, idOrWhere)
|
|
591
|
-
return findOneImpl(adapter, table, fields, idOrWhere);
|
|
592
|
-
},
|
|
758
|
+
findOne: ((adapter, idOrWhere, options) => findOneImpl(adapter, table, fields, idOrWhere, options)),
|
|
593
759
|
create(adapter, data) {
|
|
594
760
|
return createImpl(adapter, table, data);
|
|
595
761
|
},
|
|
@@ -716,9 +882,9 @@ function nanoka(adapter) {
|
|
|
716
882
|
toResponseMany: (rows) => inner.toResponseMany(rows),
|
|
717
883
|
toOpenAPIComponent: () => inner.toOpenAPIComponent(),
|
|
718
884
|
toOpenAPISchema: (usage) => inner.toOpenAPISchema(usage),
|
|
719
|
-
findMany: (options) => inner.findMany(adapter, options),
|
|
885
|
+
findMany: ((options) => inner.findMany(adapter, options)),
|
|
720
886
|
findAll: (options) => inner.findAll(adapter, options),
|
|
721
|
-
findOne: (idOrWhere) => inner.findOne(adapter, idOrWhere),
|
|
887
|
+
findOne: ((idOrWhere, options) => inner.findOne(adapter, idOrWhere, options)),
|
|
722
888
|
create: (data) => inner.create(adapter, data),
|
|
723
889
|
update: (idOrWhere, data) => inner.update(adapter, idOrWhere, data),
|
|
724
890
|
delete: (idOrWhere) => inner.delete(adapter, idOrWhere)
|