@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.
- package/LICENSE +21 -0
- package/README.md +408 -0
- package/dist/chunk-F5OBRXXY.js +571 -0
- package/dist/chunk-F5OBRXXY.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +181 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -0
- package/dist/factories-CDsk1Hdt.d.ts +402 -0
- package/dist/index.d.ts +366 -0
- package/dist/index.js +268 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -0
package/dist/index.d.ts
ADDED
|
@@ -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
|