@nanokajs/core 0.1.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.
@@ -0,0 +1,366 @@
1
+ import { D1Database } from '@cloudflare/workers-types';
2
+ import { BatchItem, BatchResponse } from 'drizzle-orm/batch';
3
+ import { BaseSQLiteDatabase, SQLiteTable } from 'drizzle-orm/sqlite-core';
4
+ import { F as Field, I as InferFieldType } from './factories-CDsk1Hdt.js';
5
+ export { a as FieldModifiers, t } from './factories-CDsk1Hdt.js';
6
+ import { Hook } from '@hono/zod-validator';
7
+ import * as hono from 'hono';
8
+ import { ValidationTargets, Env, MiddlewareHandler, Hono } from 'hono';
9
+ import { z } from 'zod';
10
+
11
+ /**
12
+ * Generic database adapter abstraction for SQLite-compatible databases.
13
+ *
14
+ * Permits implementations over D1, Turso/libSQL, and other SQLite-compatible backends
15
+ * without requiring changes to core query path or router.
16
+ *
17
+ * The `drizzle` property deliberately exposes the raw Drizzle database instance,
18
+ * adhering to rule #4 (escape hatch always open).
19
+ *
20
+ * The `TResultKind` generic is intentionally constrained to `any` to avoid
21
+ * coupling adapter implementations to Drizzle's internal result kind type.
22
+ */
23
+ interface Adapter<TSchema extends Record<string, unknown> = Record<string, never>> {
24
+ readonly drizzle: BaseSQLiteDatabase<'async', any, TSchema>;
25
+ /**
26
+ * Executes multiple Drizzle queries in a single batch.
27
+ * Returns results in the same order as the input.
28
+ *
29
+ * **重要**: 実装は `this` 非依存であること(`nanoka()` 内で thin wrapper として再公開されるため、
30
+ * `this` バインディングが失われた状態で呼び出される可能性がある)。
31
+ */
32
+ batch<U extends BatchItem<'sqlite'>, T extends readonly [U, ...U[]]>(queries: T): Promise<BatchResponse<T>>;
33
+ }
34
+
35
+ /**
36
+ * D1-specific adapter factory. Wraps Cloudflare D1 binding in the generic
37
+ * Adapter abstraction, enabling the core query path and router to remain
38
+ * implementation-agnostic.
39
+ */
40
+ declare function d1Adapter<TSchema extends Record<string, unknown> = Record<string, never>>(d1: D1Database): Adapter<TSchema>;
41
+
42
+ /**
43
+ * Options for findMany query.
44
+ * `limit` is required (no default).
45
+ * `offset` defaults to 0 if omitted.
46
+ * `orderBy` is optional for column ordering.
47
+ */
48
+ interface FindManyOptions<Fields extends Record<string, Field<any, any, any>>> {
49
+ readonly limit: number;
50
+ readonly offset?: number;
51
+ readonly orderBy?: OrderBy<Fields>;
52
+ }
53
+ /**
54
+ * Order-by specification: single field, or field with direction, or array of multiple.
55
+ */
56
+ type OrderBy<Fields extends Record<string, Field<any, any, any>>> = (keyof Fields & string) | {
57
+ readonly column: keyof Fields & string;
58
+ readonly direction?: 'asc' | 'desc';
59
+ } | ReadonlyArray<(keyof Fields & string) | {
60
+ readonly column: keyof Fields & string;
61
+ readonly direction?: 'asc' | 'desc';
62
+ }>;
63
+ /**
64
+ * Where clause object: each key is a field name, each value is the desired equality.
65
+ */
66
+ type Where<Fields extends Record<string, Field<any, any, any>>> = {
67
+ readonly [K in keyof Fields]?: InferFieldType<Fields[K]>;
68
+ };
69
+ /**
70
+ * Identifier or Where clause: can be a primary key value or a Where object.
71
+ */
72
+ type IdOrWhere<Fields extends Record<string, Field<any, any, any>>> = string | number | Where<Fields>;
73
+ /**
74
+ * Row type: full record type from fields.
75
+ */
76
+ type RowType<Fields extends Record<string, Field<any, any, any>>> = {
77
+ [K in keyof Fields]: InferFieldType<Fields[K]>;
78
+ };
79
+ /**
80
+ * Create input: partial row (most fields optional for insert).
81
+ * Phase 1 では `Partial<RowType>` のままとし、必須/任意フィールドの型区別は Phase 2 で扱う。
82
+ */
83
+ type CreateInput<Fields extends Record<string, Field<any, any, any>>> = Partial<RowType<Fields>>;
84
+ interface SchemaOptions<K extends string = string> {
85
+ readonly pick?: readonly K[];
86
+ readonly omit?: readonly K[];
87
+ readonly partial?: boolean;
88
+ }
89
+ /**
90
+ * @internal
91
+ * Extracts a Zod shape from fields for the Apply type.
92
+ * Each field's zodBase becomes a property in the shape.
93
+ * Indexed access ensures concrete Zod types are preserved (not `any`).
94
+ */
95
+ type FieldsToZodShape<Fields extends Record<string, Field<any, any, any>>> = {
96
+ [K in keyof Fields]: Fields[K]['zodBase'];
97
+ };
98
+ /**
99
+ * @internal
100
+ * Utility to narrow array literal types to union of strings.
101
+ */
102
+ type ArrayKeys<A> = A extends readonly (infer U)[] ? U : never;
103
+ /**
104
+ * @internal
105
+ * Apply pick transformation to a shape.
106
+ */
107
+ type ApplyPickToShape<Shape extends z.ZodRawShape, PickKeys> = PickKeys extends readonly string[] ? Pick<Shape, ArrayKeys<PickKeys> & keyof Shape> : Shape;
108
+ /**
109
+ * @internal
110
+ * Apply omit transformation to a shape.
111
+ */
112
+ type ApplyOmitToShape<Shape extends z.ZodRawShape, OmitKeys> = OmitKeys extends readonly string[] ? Omit<Shape, ArrayKeys<OmitKeys> & keyof Shape> : Shape;
113
+ /**
114
+ * @internal
115
+ * Apply partial transformation to a shape.
116
+ */
117
+ type ApplyPartialToShape<Shape extends z.ZodRawShape> = {
118
+ [K in keyof Shape]: z.ZodOptional<Shape[K]>;
119
+ };
120
+ /**
121
+ * @internal
122
+ * Apply pick/omit/partial transformations to a Zod raw shape.
123
+ * Order: pick → omit → partial
124
+ */
125
+ type ApplyShape<Shape extends z.ZodRawShape, Opts extends SchemaOptions | undefined> = Opts extends undefined ? Shape : Opts extends {
126
+ pick: readonly string[];
127
+ } ? Opts extends {
128
+ omit: readonly string[];
129
+ } ? Opts extends {
130
+ partial: true;
131
+ } ? ApplyPartialToShape<ApplyOmitToShape<ApplyPickToShape<Shape, Opts['pick']>, Opts['omit']>> : ApplyOmitToShape<ApplyPickToShape<Shape, Opts['pick']>, Opts['omit']> : Opts extends {
132
+ partial: true;
133
+ } ? ApplyPartialToShape<ApplyPickToShape<Shape, Opts['pick']>> : ApplyPickToShape<Shape, Opts['pick']> : Opts extends {
134
+ omit: readonly string[];
135
+ } ? Opts extends {
136
+ partial: true;
137
+ } ? ApplyPartialToShape<ApplyOmitToShape<Shape, Opts['omit']>> : ApplyOmitToShape<Shape, Opts['omit']> : Opts extends {
138
+ partial: true;
139
+ } ? ApplyPartialToShape<Shape> : Shape;
140
+ /**
141
+ * @internal
142
+ * Returns the correct Zod object type after applying pick/omit/partial transformations.
143
+ */
144
+ type Apply<Shape extends z.ZodRawShape, Opts extends SchemaOptions | undefined> = z.ZodObject<ApplyShape<Shape, Opts>>;
145
+ /**
146
+ * @internal
147
+ * Mirrors @hono/zod-validator's middleware Input type.
148
+ * Aligned with @hono/zod-validator@0.4.x; revisit on version bump.
149
+ */
150
+ type HasUndefined<T> = undefined extends T ? true : false;
151
+ type ValidatorInput<Target extends keyof ValidationTargets, Schema extends z.ZodType<any, z.ZodTypeDef, any>, In = z.input<Schema>, Out = z.output<Schema>> = {
152
+ in: HasUndefined<In> extends true ? {
153
+ [K in Target]?: (In extends ValidationTargets[K] ? In : {
154
+ [K2 in keyof In]?: ValidationTargets[K][K2] | undefined;
155
+ }) | undefined;
156
+ } : {
157
+ [K in Target]: In extends ValidationTargets[K] ? In : {
158
+ [K2 in keyof In]: ValidationTargets[K][K2];
159
+ };
160
+ };
161
+ out: {
162
+ [K in Target]: Out;
163
+ };
164
+ };
165
+ /**
166
+ * @internal
167
+ * Type-safe Hono middleware handler return for Model.validator().
168
+ * Provides proper type narrowing for c.req.valid(target).
169
+ */
170
+ type ModelValidatorReturn<Fields extends Record<string, Field<any, any, any>>, Target extends keyof ValidationTargets, Opts extends SchemaOptions<keyof Fields & string> | undefined> = MiddlewareHandler<any, string, ValidatorInput<Target, Apply<FieldsToZodShape<Fields>, Opts>>>;
171
+
172
+ /**
173
+ * Represents a database model with type-safe schema derivation and CRUD operations.
174
+ */
175
+ interface Model<Fields extends Record<string, Field<any, any, any>>> {
176
+ readonly fields: Fields;
177
+ readonly tableName: string;
178
+ readonly table: SQLiteTable;
179
+ /**
180
+ * Returns a Zod schema derived from this model's fields.
181
+ * Supports pick, omit, and partial transformations.
182
+ *
183
+ * @example
184
+ * const CreateSchema = User.schema({ omit: ['passwordHash'] })
185
+ * const UpdateSchema = User.schema({ partial: true, pick: ['name', 'email'] })
186
+ */
187
+ schema<Opts extends SchemaOptions<keyof Fields & string> | undefined = undefined>(opts?: Opts): Apply<FieldsToZodShape<Fields>, Opts>;
188
+ /**
189
+ * Returns a Hono middleware validator derived from this model's schema.
190
+ * Integrates with @hono/zod-validator to validate request inputs.
191
+ * The optional `hook` argument is passed through to @hono/zod-validator.
192
+ *
193
+ * @example
194
+ * app.post('/users', User.validator('json', { omit: ['passwordHash'] }), c => {
195
+ * const body = c.req.valid('json')
196
+ * })
197
+ * // With hook for hardened error responses:
198
+ * app.post('/users', User.validator('json', opts, (result, c) => {
199
+ * if (!result.success) return c.json({ error: 'Invalid request' }, 400)
200
+ * }), handler)
201
+ */
202
+ validator<Target extends keyof ValidationTargets, Opts extends SchemaOptions<keyof Fields & string> | undefined = 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>;
203
+ /**
204
+ * Fetches multiple rows with pagination and optional ordering.
205
+ * `limit` is required (no default) to prevent accidental unbounded queries.
206
+ * Offset defaults to 0 if omitted.
207
+ *
208
+ * @example
209
+ * const users = await User.findMany(adapter, { limit: 20 })
210
+ * const page2 = await User.findMany(adapter, { limit: 20, offset: 20 })
211
+ * const sorted = await User.findMany(adapter, { limit: 10, orderBy: 'name' })
212
+ */
213
+ findMany(adapter: Adapter, options: FindManyOptions<Fields>): Promise<RowType<Fields>[]>;
214
+ /**
215
+ * Fetches a single row by primary key or where clause.
216
+ * Returns null if not found.
217
+ *
218
+ * @example
219
+ * const user = await User.findOne(adapter, id)
220
+ * const byEmail = await User.findOne(adapter, { email: 'john@example.com' })
221
+ */
222
+ findOne(adapter: Adapter, idOrWhere: IdOrWhere<Fields>): Promise<RowType<Fields> | null>;
223
+ /**
224
+ * Creates a new row and returns the created record.
225
+ *
226
+ * @example
227
+ * const user = await User.create(adapter, { name: 'John', email: 'john@example.com' })
228
+ */
229
+ create(adapter: Adapter, data: CreateInput<Fields>): Promise<RowType<Fields>>;
230
+ /**
231
+ * Updates rows matching the given id or where clause.
232
+ * Returns the updated row(s), or null if no rows matched.
233
+ *
234
+ * @example
235
+ * const updated = await User.update(adapter, id, { name: 'Jane' })
236
+ * const byEmail = await User.update(adapter, { email: '...' }, { name: 'Jane' })
237
+ */
238
+ update(adapter: Adapter, idOrWhere: IdOrWhere<Fields>, data: Partial<RowType<Fields>>): Promise<RowType<Fields> | null>;
239
+ /**
240
+ * Deletes rows matching the given id or where clause.
241
+ * Returns the number of deleted rows.
242
+ *
243
+ * @example
244
+ * const result = await User.delete(adapter, id)
245
+ * console.log(result.deleted) // number
246
+ */
247
+ delete(adapter: Adapter, idOrWhere: IdOrWhere<Fields>): Promise<{
248
+ readonly deleted: number;
249
+ }>;
250
+ }
251
+
252
+ /**
253
+ * Creates a Model instance from a tableName and fields definition.
254
+ * The model supports deriving Zod schemas, Hono validators, and CRUD operations.
255
+ *
256
+ * @example
257
+ * const User = defineModel('users', {
258
+ * id: t.uuid().primary(),
259
+ * name: t.string(),
260
+ * email: t.string().email(),
261
+ * passwordHash: t.string(),
262
+ * })
263
+ *
264
+ * const CreateSchema = User.schema({ omit: ['passwordHash'] })
265
+ * const users = await User.findMany(adapter, { limit: 20 })
266
+ */
267
+ declare function defineModel<Fields extends Record<string, Field<any, any, any>>>(tableName: string, fields: Fields): Model<Fields>;
268
+
269
+ /**
270
+ * A model registered in a Nanoka application.
271
+ * Wraps Model<Fields> with adapter binding, so CRUD methods do not require
272
+ * passing the adapter each time.
273
+ *
274
+ * @template Fields - The field definitions of this model
275
+ */
276
+ interface NanokaModel<Fields extends Record<string, Field<any, any, any>>> {
277
+ readonly fields: Fields;
278
+ readonly tableName: string;
279
+ readonly table: SQLiteTable;
280
+ /**
281
+ * Returns a Zod schema derived from this model's fields.
282
+ * Supports pick, omit, and partial transformations.
283
+ */
284
+ schema<Opts extends SchemaOptions<keyof Fields & string> | undefined = undefined>(opts?: Opts): Apply<FieldsToZodShape<Fields>, Opts>;
285
+ /**
286
+ * Returns a Hono middleware validator for this model.
287
+ * The optional `hook` argument is passed through to @hono/zod-validator.
288
+ */
289
+ validator<Target extends keyof hono.ValidationTargets, Opts extends SchemaOptions<keyof Fields & string> | undefined = 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>;
290
+ /**
291
+ * Fetches multiple rows with pagination and optional ordering.
292
+ * `limit` is required (no default) to prevent accidental unbounded queries.
293
+ */
294
+ findMany(options: FindManyOptions<Fields>): Promise<RowType<Fields>[]>;
295
+ /**
296
+ * Fetches a single row by primary key or where clause.
297
+ * Returns null if not found.
298
+ */
299
+ findOne(idOrWhere: IdOrWhere<Fields>): Promise<RowType<Fields> | null>;
300
+ /**
301
+ * Creates a new row and returns the created record.
302
+ */
303
+ create(data: CreateInput<Fields>): Promise<RowType<Fields>>;
304
+ /**
305
+ * Updates rows matching the given id or where clause.
306
+ * Returns the updated row, or null if no rows matched.
307
+ */
308
+ update(idOrWhere: IdOrWhere<Fields>, data: Partial<RowType<Fields>>): Promise<RowType<Fields> | null>;
309
+ /**
310
+ * Deletes rows matching the given id or where clause.
311
+ * Returns the number of deleted rows.
312
+ */
313
+ delete(idOrWhere: IdOrWhere<Fields>): Promise<{
314
+ readonly deleted: number;
315
+ }>;
316
+ }
317
+ /**
318
+ * Nanoka application: a Hono instance extended with model registration,
319
+ * adapter binding, and escape hatches for raw Drizzle and batch operations.
320
+ */
321
+ interface Nanoka extends Hono {
322
+ /**
323
+ * Raw Drizzle database instance (escape hatch per rule #4).
324
+ * Exposes the full Drizzle API for advanced queries.
325
+ */
326
+ readonly db: Adapter['drizzle'];
327
+ /**
328
+ * Batch execution API (transactions = D1 batch, per rule #7).
329
+ * Executes multiple queries in a single D1 batch request.
330
+ * No bespoke transaction abstraction is provided; batch is a first-class API.
331
+ */
332
+ batch: Adapter['batch'];
333
+ /**
334
+ * Registers a new model in this application with adapter binding.
335
+ * Returns a NanokaModel<Fields> with CRUD methods pre-bound to the adapter.
336
+ *
337
+ * @example
338
+ * const app = nanoka(d1Adapter(env.DB))
339
+ * const User = app.model('users', { id, name, email, passwordHash })
340
+ * const users = await User.findMany({ limit: 20 })
341
+ */
342
+ model<Fields extends Record<string, Field<any, any, any>>>(name: string, fields: Fields): NanokaModel<Fields>;
343
+ }
344
+
345
+ /**
346
+ * Creates a Nanoka application instance with the given database adapter.
347
+ *
348
+ * @param adapter - The database adapter (e.g., d1Adapter(env.DB))
349
+ * @returns A Nanoka application (Hono extended with model(), db, and batch)
350
+ *
351
+ * @example
352
+ * export default {
353
+ * fetch(req: Request, env: Env, ctx: ExecutionContext) {
354
+ * const app = nanoka(d1Adapter(env.DB))
355
+ * const User = app.model('users', { id, name, email, passwordHash })
356
+ * app.get('/users', async (c) => {
357
+ * const users = await User.findMany({ limit: 20 })
358
+ * return c.json(users)
359
+ * })
360
+ * return app.fetch(req, env, ctx)
361
+ * }
362
+ * }
363
+ */
364
+ declare function nanoka(adapter: Adapter): Nanoka;
365
+
366
+ export { type Adapter, type CreateInput, Field, type FindManyOptions, type IdOrWhere, InferFieldType, type Model, type Nanoka, type NanokaModel, type OrderBy, type RowType, type SchemaOptions, type Where, d1Adapter, defineModel, nanoka };
package/dist/index.js ADDED
@@ -0,0 +1,268 @@
1
+ export { t } from './chunk-F5OBRXXY.js';
2
+ import { drizzle } from 'drizzle-orm/d1';
3
+ import { zValidator } from '@hono/zod-validator';
4
+ import { asc, desc, eq, and } from 'drizzle-orm';
5
+ import { HTTPException } from 'hono/http-exception';
6
+ import { z } from 'zod';
7
+ import { sqliteTable } from 'drizzle-orm/sqlite-core';
8
+ import { Hono } from 'hono';
9
+
10
+ function d1Adapter(d1) {
11
+ const db = drizzle(d1);
12
+ return {
13
+ drizzle: db,
14
+ batch: (queries) => db.batch(queries)
15
+ };
16
+ }
17
+ var MAX_LIMIT = 100;
18
+ function guardLimit(limit) {
19
+ if (!Number.isInteger(limit)) {
20
+ throw new HTTPException(400, { message: "limit must be an integer" });
21
+ }
22
+ const n = limit;
23
+ if (n < 0) {
24
+ throw new HTTPException(400, { message: "limit must be >= 0" });
25
+ }
26
+ if (n > MAX_LIMIT) {
27
+ throw new HTTPException(400, { message: `limit must be <= ${MAX_LIMIT}` });
28
+ }
29
+ return n;
30
+ }
31
+ function guardOffset(offset) {
32
+ if (!Number.isInteger(offset)) {
33
+ throw new HTTPException(400, { message: "offset must be an integer" });
34
+ }
35
+ const n = offset;
36
+ if (n < 0) {
37
+ throw new HTTPException(400, { message: "offset must be >= 0" });
38
+ }
39
+ return n;
40
+ }
41
+ function findPrimaryKey(fields) {
42
+ let pkCount = 0;
43
+ let pkKey = null;
44
+ let pkField = null;
45
+ for (const [key, field] of Object.entries(fields)) {
46
+ if (field.modifiers.primary === true) {
47
+ pkCount++;
48
+ if (pkCount === 1) {
49
+ pkKey = key;
50
+ pkField = field;
51
+ }
52
+ }
53
+ }
54
+ if (pkCount === 0) {
55
+ return null;
56
+ }
57
+ if (pkCount > 1) {
58
+ throw new HTTPException(500, { message: "model has multiple primary keys" });
59
+ }
60
+ return [pkKey, pkField];
61
+ }
62
+ function buildWhereClause(table, fields, where) {
63
+ const conditions = [];
64
+ for (const [key, value] of Object.entries(where)) {
65
+ if (!Object.hasOwn(fields, key)) {
66
+ throw new HTTPException(400, { message: "invalid field in where clause" });
67
+ }
68
+ if (!Object.hasOwn(table, key)) {
69
+ throw new HTTPException(400, { message: "invalid column in table" });
70
+ }
71
+ const col = table[key];
72
+ conditions.push(eq(col, value));
73
+ }
74
+ if (conditions.length === 0) {
75
+ return null;
76
+ }
77
+ if (conditions.length === 1) {
78
+ return conditions[0];
79
+ }
80
+ return and(...conditions);
81
+ }
82
+ async function findManyImpl(adapter, table, fields, options) {
83
+ const limit = guardLimit(options.limit);
84
+ const offset = options.offset !== void 0 ? guardOffset(options.offset) : 0;
85
+ let query = adapter.drizzle.select().from(table).limit(limit).offset(offset);
86
+ if (options.orderBy !== void 0) {
87
+ const orderByList = Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy];
88
+ for (const orderByItem of orderByList) {
89
+ let column;
90
+ let direction = "asc";
91
+ if (typeof orderByItem === "string") {
92
+ column = orderByItem;
93
+ } else if (typeof orderByItem === "object" && orderByItem !== null && "column" in orderByItem) {
94
+ column = orderByItem.column;
95
+ if (orderByItem.direction) {
96
+ direction = orderByItem.direction;
97
+ }
98
+ } else {
99
+ throw new HTTPException(400, { message: "invalid orderBy format" });
100
+ }
101
+ if (!Object.hasOwn(table, column)) {
102
+ throw new HTTPException(400, { message: "invalid field in orderBy" });
103
+ }
104
+ const col = table[column];
105
+ query = query.orderBy(direction === "asc" ? asc(col) : desc(col));
106
+ }
107
+ }
108
+ const rows = await query;
109
+ return rows;
110
+ }
111
+ async function findOneImpl(adapter, table, fields, idOrWhere) {
112
+ let where = null;
113
+ if (typeof idOrWhere === "string" || typeof idOrWhere === "number") {
114
+ const pk = findPrimaryKey(fields);
115
+ if (!pk) {
116
+ throw new HTTPException(500, { message: "model has no primary key" });
117
+ }
118
+ where = { [pk[0]]: idOrWhere };
119
+ } else {
120
+ where = idOrWhere;
121
+ }
122
+ const whereClause = buildWhereClause(table, fields, where);
123
+ if (!whereClause) {
124
+ throw new HTTPException(400, { message: "where clause must not be empty" });
125
+ }
126
+ const rows = await adapter.drizzle.select().from(table).where(whereClause).limit(1);
127
+ return rows.length > 0 ? rows[0] : null;
128
+ }
129
+ async function createImpl(adapter, table, data) {
130
+ const rows = await adapter.drizzle.insert(table).values(data).returning();
131
+ return rows[0];
132
+ }
133
+ async function updateImpl(adapter, table, fields, idOrWhere, data) {
134
+ let where = null;
135
+ if (typeof idOrWhere === "string" || typeof idOrWhere === "number") {
136
+ const pk = findPrimaryKey(fields);
137
+ if (!pk) {
138
+ throw new HTTPException(500, { message: "model has no primary key" });
139
+ }
140
+ where = { [pk[0]]: idOrWhere };
141
+ } else {
142
+ where = idOrWhere;
143
+ }
144
+ const whereClause = buildWhereClause(table, fields, where);
145
+ if (!whereClause) {
146
+ throw new HTTPException(400, { message: "where clause must not be empty" });
147
+ }
148
+ const rows = await adapter.drizzle.update(table).set(data).where(whereClause).returning();
149
+ return rows.length > 0 ? rows[0] : null;
150
+ }
151
+ async function deleteImpl(adapter, table, fields, idOrWhere) {
152
+ let where = null;
153
+ if (typeof idOrWhere === "string" || typeof idOrWhere === "number") {
154
+ const pk = findPrimaryKey(fields);
155
+ if (!pk) {
156
+ throw new HTTPException(500, { message: "model has no primary key" });
157
+ }
158
+ where = { [pk[0]]: idOrWhere };
159
+ } else {
160
+ where = idOrWhere;
161
+ }
162
+ const whereClause = buildWhereClause(table, fields, where);
163
+ if (!whereClause) {
164
+ throw new HTTPException(400, { message: "where clause must not be empty" });
165
+ }
166
+ const result = await adapter.drizzle.delete(table).where(whereClause).returning();
167
+ return { deleted: result.length };
168
+ }
169
+ function buildBaseObject(fields) {
170
+ const shape = {};
171
+ for (const [key, field] of Object.entries(fields)) {
172
+ shape[key] = field.zodBase;
173
+ }
174
+ return z.object(shape);
175
+ }
176
+ function applySchemaOptions(baseSchema, opts) {
177
+ if (!opts) {
178
+ return baseSchema;
179
+ }
180
+ let schema = baseSchema;
181
+ if (opts.pick !== void 0) {
182
+ const pickArray = Array.from(opts.pick);
183
+ schema = schema.pick(
184
+ // biome-ignore lint/suspicious/noExplicitAny: Zod API
185
+ Object.fromEntries(pickArray.map((k) => [k, true]))
186
+ );
187
+ }
188
+ if (opts.omit !== void 0) {
189
+ const omitArray = Array.from(opts.omit);
190
+ schema = schema.omit(
191
+ // biome-ignore lint/suspicious/noExplicitAny: Zod API
192
+ Object.fromEntries(omitArray.map((k) => [k, true]))
193
+ );
194
+ }
195
+ if (opts.partial === true) {
196
+ schema = schema.partial();
197
+ }
198
+ return schema;
199
+ }
200
+ function buildTable(tableName, fields) {
201
+ const columns = {};
202
+ for (const [key, field] of Object.entries(fields)) {
203
+ columns[key] = field.drizzleColumn(key);
204
+ }
205
+ return sqliteTable(tableName, columns);
206
+ }
207
+
208
+ // src/model/define.ts
209
+ function defineModel(tableName, fields) {
210
+ const table = buildTable(tableName, fields);
211
+ return {
212
+ fields,
213
+ tableName,
214
+ table,
215
+ schema(opts) {
216
+ const baseSchema = buildBaseObject(fields);
217
+ const schema = applySchemaOptions(baseSchema, opts);
218
+ return schema;
219
+ },
220
+ validator(target, opts, hook) {
221
+ const schema = this.schema(opts);
222
+ return zValidator(target, schema, hook);
223
+ },
224
+ findMany(adapter, options) {
225
+ return findManyImpl(adapter, table, fields, options);
226
+ },
227
+ findOne(adapter, idOrWhere) {
228
+ return findOneImpl(adapter, table, fields, idOrWhere);
229
+ },
230
+ create(adapter, data) {
231
+ return createImpl(adapter, table, data);
232
+ },
233
+ update(adapter, idOrWhere, data) {
234
+ return updateImpl(adapter, table, fields, idOrWhere, data);
235
+ },
236
+ delete(adapter, idOrWhere) {
237
+ return deleteImpl(adapter, table, fields, idOrWhere);
238
+ }
239
+ };
240
+ }
241
+ function nanoka(adapter) {
242
+ const app = new Hono();
243
+ Object.defineProperty(app, "db", {
244
+ get: () => adapter.drizzle,
245
+ enumerable: true
246
+ });
247
+ app.batch = ((queries) => adapter.batch(queries));
248
+ app.model = function model(name, fields) {
249
+ const inner = defineModel(name, fields);
250
+ return {
251
+ fields: inner.fields,
252
+ tableName: inner.tableName,
253
+ table: inner.table,
254
+ schema: (opts) => inner.schema(opts),
255
+ validator: (target, opts, hook) => inner.validator(target, opts, hook),
256
+ findMany: (options) => inner.findMany(adapter, options),
257
+ findOne: (idOrWhere) => inner.findOne(adapter, idOrWhere),
258
+ create: (data) => inner.create(adapter, data),
259
+ update: (idOrWhere, data) => inner.update(adapter, idOrWhere, data),
260
+ delete: (idOrWhere) => inner.delete(adapter, idOrWhere)
261
+ };
262
+ };
263
+ return app;
264
+ }
265
+
266
+ export { d1Adapter, defineModel, nanoka };
267
+ //# sourceMappingURL=index.js.map
268
+ //# sourceMappingURL=index.js.map