@nanokajs/core 1.9.0 → 1.12.2
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 +46 -0
- package/README.md +47 -5
- package/dist/{chunk-SHPR4NTZ.js → chunk-UBDSGKTX.js} +14 -4
- package/dist/chunk-UBDSGKTX.js.map +1 -0
- package/dist/cli.js +20 -4
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +3 -3
- package/dist/config.js +1 -1
- package/dist/{factories-CiYoD7dl.d.ts → factories-D7estT7E.d.ts} +9 -1
- package/dist/index.d.ts +25 -10
- package/dist/index.js +77 -33
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
- package/dist/chunk-SHPR4NTZ.js.map +0 -1
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,
|
|
4
|
-
export {
|
|
3
|
+
import { F as Field, I as InferFieldType, b as FieldPolicy, R as RelationDef } from './factories-D7estT7E.js';
|
|
4
|
+
export { a as FieldModifiers, c as IsRelationField, d as RelationFieldBuilder, e as RelationKind, f as RelationTargetLike, t } from './factories-D7estT7E.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';
|
|
@@ -114,12 +114,13 @@ type WithResult<Fields extends Record<string, Field<any, any, any>>, With> = Row
|
|
|
114
114
|
/**
|
|
115
115
|
* Options for findMany query.
|
|
116
116
|
* `limit` is required (no default).
|
|
117
|
-
* `offset` defaults to 0 if omitted.
|
|
117
|
+
* `offset` defaults to 0 if omitted. Runtime-capped at 100_000 to prevent read amplification — use cursor pagination for deeper paging.
|
|
118
118
|
* `orderBy` is optional for column ordering.
|
|
119
119
|
* `where` accepts either an equality object or a Drizzle SQL expression.
|
|
120
120
|
*/
|
|
121
121
|
interface FindManyOptions<Fields extends Record<string, Field<any, any, any>>, With extends WithOptions<Fields> | undefined = undefined> {
|
|
122
122
|
readonly limit: number;
|
|
123
|
+
/** default: 0; runtime cap: 100000 */
|
|
123
124
|
readonly offset?: number;
|
|
124
125
|
readonly orderBy?: OrderBy<Fields>;
|
|
125
126
|
readonly where?: Where<Fields> | SQL;
|
|
@@ -149,15 +150,26 @@ type OrderBy<Fields extends Record<string, Field<any, any, any>>> = (NonRelation
|
|
|
149
150
|
readonly direction?: 'asc' | 'desc';
|
|
150
151
|
}>;
|
|
151
152
|
/**
|
|
152
|
-
*
|
|
153
|
+
* A single where value: either an equality value or an `{ in: [...] }` operator for IN matching.
|
|
154
|
+
* Note: json fields whose stored value happens to be `{ in: [...] }` in shape are still treated
|
|
155
|
+
* as equality at runtime — the IN operator is applied to non-json fields only.
|
|
156
|
+
*/
|
|
157
|
+
type WhereValue<T> = T | {
|
|
158
|
+
readonly in: readonly T[];
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* Where clause object: each key is a field name, each value is the desired equality, or an
|
|
162
|
+
* `{ in: [...] }` operator for IN matching.
|
|
163
|
+
* json フィールドの値が偶然 `{ in: [...] }` 形でも型上は演算子と区別できないが、ランタイムでは
|
|
164
|
+
* json フィールドは常に等値比較される(IN 演算子は非 json フィールドのみ)。
|
|
153
165
|
*/
|
|
154
166
|
type Where<Fields extends Record<string, Field<any, any, any>>> = {
|
|
155
|
-
readonly [K in NonRelationKeys<Fields>]?: InferFieldType<Fields[K]
|
|
167
|
+
readonly [K in NonRelationKeys<Fields>]?: WhereValue<InferFieldType<Fields[K]>>;
|
|
156
168
|
};
|
|
157
169
|
/**
|
|
158
|
-
* Identifier or Where clause: can be a primary key value or a
|
|
170
|
+
* Identifier or Where clause: can be a primary key value, a Where object, or a Drizzle SQL expression.
|
|
159
171
|
*/
|
|
160
|
-
type IdOrWhere<Fields extends Record<string, Field<any, any, any>>> = string | number | Where<Fields
|
|
172
|
+
type IdOrWhere<Fields extends Record<string, Field<any, any, any>>> = string | number | Where<Fields> | SQL;
|
|
161
173
|
/**
|
|
162
174
|
* Row type: full record type from fields, excluding relation fields.
|
|
163
175
|
*/
|
|
@@ -219,7 +231,7 @@ type ServerOnlyKeys<Fields extends Record<string, Field<any, any, any>>> = {
|
|
|
219
231
|
/**
|
|
220
232
|
* @internal
|
|
221
233
|
* Determines if a field is required for create input.
|
|
222
|
-
* A field is optional if it has a default, is optional, or is readOnly.
|
|
234
|
+
* A field is optional if it has a default, has defaultNow, is optional, or is readOnly.
|
|
223
235
|
*
|
|
224
236
|
* Uses direct shape matching (`Field<any, { hasDefault: true }, any>`) to avoid
|
|
225
237
|
* the TypeScript infer-widening issue where `infer M` resolves to `FieldModifiers`
|
|
@@ -228,6 +240,8 @@ type ServerOnlyKeys<Fields extends Record<string, Field<any, any, any>>> = {
|
|
|
228
240
|
*/
|
|
229
241
|
type IsRequired<F extends Field<any, any, any>> = F extends Field<any, {
|
|
230
242
|
hasDefault: true;
|
|
243
|
+
}, any> ? false : F extends Field<any, {
|
|
244
|
+
defaultNow: true;
|
|
231
245
|
}, any> ? false : F extends Field<any, {
|
|
232
246
|
optional: true;
|
|
233
247
|
}, any> ? false : F extends Field<any, {
|
|
@@ -238,7 +252,7 @@ type IsRequired<F extends Field<any, any, any>> = F extends Field<any, {
|
|
|
238
252
|
* - `readOnly` fields are optional (library internals can pass them; API callers
|
|
239
253
|
* are excluded via inputSchema — but CreateInput itself keeps them as optional
|
|
240
254
|
* so handlers can supply generated values like `id` or `createdAt`)
|
|
241
|
-
* - fields with `default` are optional
|
|
255
|
+
* - fields with `default` or `defaultNow` are optional (DB or runtime fills the value)
|
|
242
256
|
* - fields marked `optional` are optional
|
|
243
257
|
* - all other fields are required
|
|
244
258
|
* - `serverOnly` fields are completely excluded (cannot be passed even internally)
|
|
@@ -475,7 +489,7 @@ interface Model<Fields extends Record<string, Field<any, any, any>>> {
|
|
|
475
489
|
/**
|
|
476
490
|
* Fetches multiple rows with pagination and optional ordering.
|
|
477
491
|
* `limit` is required (no default) to prevent accidental unbounded queries.
|
|
478
|
-
* Offset defaults to 0 if omitted.
|
|
492
|
+
* Offset defaults to 0 if omitted. Runtime-capped at offset 100_000 — use cursor pagination for deeper paging.
|
|
479
493
|
*
|
|
480
494
|
* @example
|
|
481
495
|
* const users = await User.findMany(adapter, { limit: 20 })
|
|
@@ -604,6 +618,7 @@ interface NanokaModel<Fields extends Record<string, Field<any, any, any>>> {
|
|
|
604
618
|
/**
|
|
605
619
|
* Fetches multiple rows with pagination and optional ordering.
|
|
606
620
|
* `limit` is required (no default) to prevent accidental unbounded queries.
|
|
621
|
+
* Runtime-capped at offset 100_000 — use cursor pagination for deeper paging.
|
|
607
622
|
*/
|
|
608
623
|
findMany<With extends WithOptions<Fields>>(options: FindManyOptions<Fields, With> & {
|
|
609
624
|
readonly with: With;
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { t } from './chunk-
|
|
1
|
+
export { t } from './chunk-UBDSGKTX.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
|
|
5
|
+
import { SQL, asc, desc, inArray, eq, and } 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';
|
|
@@ -355,6 +355,7 @@ function buildTable(tableName, fields) {
|
|
|
355
355
|
}
|
|
356
356
|
|
|
357
357
|
// src/model/crud.ts
|
|
358
|
+
var MAX_OFFSET = 1e5;
|
|
358
359
|
function guardLimit(limit) {
|
|
359
360
|
if (!Number.isInteger(limit)) {
|
|
360
361
|
throw new HTTPException(400, { message: "limit must be an integer" });
|
|
@@ -399,6 +400,9 @@ function findPrimaryKey(fields) {
|
|
|
399
400
|
}
|
|
400
401
|
return [pkKey, pkField];
|
|
401
402
|
}
|
|
403
|
+
function isInOperator(value, field) {
|
|
404
|
+
return field?.kind !== "json" && typeof value === "object" && value !== null && !Array.isArray(value) && Object.hasOwn(value, "in") && Array.isArray(value.in);
|
|
405
|
+
}
|
|
402
406
|
function buildWhereClause(table, fields, where) {
|
|
403
407
|
const conditions = [];
|
|
404
408
|
for (const [key, value] of Object.entries(where)) {
|
|
@@ -409,7 +413,12 @@ function buildWhereClause(table, fields, where) {
|
|
|
409
413
|
throw new HTTPException(400, { message: "invalid column in table" });
|
|
410
414
|
}
|
|
411
415
|
const col = table[key];
|
|
412
|
-
|
|
416
|
+
const field = fields[key];
|
|
417
|
+
if (isInOperator(value, field)) {
|
|
418
|
+
conditions.push(inArray(col, value.in));
|
|
419
|
+
} else {
|
|
420
|
+
conditions.push(eq(col, value));
|
|
421
|
+
}
|
|
413
422
|
}
|
|
414
423
|
if (conditions.length === 0) {
|
|
415
424
|
return null;
|
|
@@ -523,6 +532,11 @@ async function loadRelations(adapter, _parentTable, parentFields, parentRows, wi
|
|
|
523
532
|
async function findManyImpl(adapter, table, fields, options) {
|
|
524
533
|
const limit = guardLimit(options.limit);
|
|
525
534
|
const offset = options.offset !== void 0 ? guardOffset(options.offset) : 0;
|
|
535
|
+
if (offset > MAX_OFFSET) {
|
|
536
|
+
throw new HTTPException(400, {
|
|
537
|
+
message: `offset must be <= ${MAX_OFFSET}; use cursor pagination for deeper paging`
|
|
538
|
+
});
|
|
539
|
+
}
|
|
526
540
|
let query = adapter.drizzle.select().from(table).limit(limit).offset(offset);
|
|
527
541
|
if (options.where !== void 0) {
|
|
528
542
|
if (options.where instanceof SQL) {
|
|
@@ -610,17 +624,22 @@ async function findAllImpl(adapter, table, fields, options) {
|
|
|
610
624
|
return rows;
|
|
611
625
|
}
|
|
612
626
|
async function findOneImpl(adapter, table, fields, idOrWhere, options) {
|
|
613
|
-
let
|
|
614
|
-
if (
|
|
615
|
-
|
|
616
|
-
if (!pk) {
|
|
617
|
-
throw new HTTPException(500, { message: "model has no primary key" });
|
|
618
|
-
}
|
|
619
|
-
where = { [pk[0]]: idOrWhere };
|
|
627
|
+
let whereClause;
|
|
628
|
+
if (idOrWhere instanceof SQL) {
|
|
629
|
+
whereClause = idOrWhere;
|
|
620
630
|
} else {
|
|
621
|
-
where
|
|
631
|
+
let where;
|
|
632
|
+
if (typeof idOrWhere === "string" || typeof idOrWhere === "number") {
|
|
633
|
+
const pk = findPrimaryKey(fields);
|
|
634
|
+
if (!pk) {
|
|
635
|
+
throw new HTTPException(500, { message: "model has no primary key" });
|
|
636
|
+
}
|
|
637
|
+
where = { [pk[0]]: idOrWhere };
|
|
638
|
+
} else {
|
|
639
|
+
where = idOrWhere;
|
|
640
|
+
}
|
|
641
|
+
whereClause = buildWhereClause(table, fields, where);
|
|
622
642
|
}
|
|
623
|
-
const whereClause = buildWhereClause(table, fields, where);
|
|
624
643
|
if (!whereClause) {
|
|
625
644
|
throw new HTTPException(400, { message: "where clause must not be empty" });
|
|
626
645
|
}
|
|
@@ -638,39 +657,51 @@ async function findOneImpl(adapter, table, fields, idOrWhere, options) {
|
|
|
638
657
|
return rows[0];
|
|
639
658
|
}
|
|
640
659
|
async function createImpl(adapter, table, data) {
|
|
641
|
-
const
|
|
660
|
+
const builder = adapter.drizzle.insert(table).values(data).returning();
|
|
661
|
+
const rows = await builder;
|
|
642
662
|
return rows[0];
|
|
643
663
|
}
|
|
644
664
|
async function updateImpl(adapter, table, fields, idOrWhere, data) {
|
|
645
|
-
let
|
|
646
|
-
if (
|
|
647
|
-
|
|
648
|
-
if (!pk) {
|
|
649
|
-
throw new HTTPException(500, { message: "model has no primary key" });
|
|
650
|
-
}
|
|
651
|
-
where = { [pk[0]]: idOrWhere };
|
|
665
|
+
let whereClause;
|
|
666
|
+
if (idOrWhere instanceof SQL) {
|
|
667
|
+
whereClause = idOrWhere;
|
|
652
668
|
} else {
|
|
653
|
-
where
|
|
669
|
+
let where;
|
|
670
|
+
if (typeof idOrWhere === "string" || typeof idOrWhere === "number") {
|
|
671
|
+
const pk = findPrimaryKey(fields);
|
|
672
|
+
if (!pk) {
|
|
673
|
+
throw new HTTPException(500, { message: "model has no primary key" });
|
|
674
|
+
}
|
|
675
|
+
where = { [pk[0]]: idOrWhere };
|
|
676
|
+
} else {
|
|
677
|
+
where = idOrWhere;
|
|
678
|
+
}
|
|
679
|
+
whereClause = buildWhereClause(table, fields, where);
|
|
654
680
|
}
|
|
655
|
-
const whereClause = buildWhereClause(table, fields, where);
|
|
656
681
|
if (!whereClause) {
|
|
657
682
|
throw new HTTPException(400, { message: "where clause must not be empty" });
|
|
658
683
|
}
|
|
659
|
-
const
|
|
684
|
+
const builder = adapter.drizzle.update(table).set(data).where(whereClause).returning();
|
|
685
|
+
const rows = await builder;
|
|
660
686
|
return rows.length > 0 ? rows[0] : null;
|
|
661
687
|
}
|
|
662
688
|
async function deleteImpl(adapter, table, fields, idOrWhere) {
|
|
663
|
-
let
|
|
664
|
-
if (
|
|
665
|
-
|
|
666
|
-
if (!pk) {
|
|
667
|
-
throw new HTTPException(500, { message: "model has no primary key" });
|
|
668
|
-
}
|
|
669
|
-
where = { [pk[0]]: idOrWhere };
|
|
689
|
+
let whereClause;
|
|
690
|
+
if (idOrWhere instanceof SQL) {
|
|
691
|
+
whereClause = idOrWhere;
|
|
670
692
|
} else {
|
|
671
|
-
where
|
|
693
|
+
let where;
|
|
694
|
+
if (typeof idOrWhere === "string" || typeof idOrWhere === "number") {
|
|
695
|
+
const pk = findPrimaryKey(fields);
|
|
696
|
+
if (!pk) {
|
|
697
|
+
throw new HTTPException(500, { message: "model has no primary key" });
|
|
698
|
+
}
|
|
699
|
+
where = { [pk[0]]: idOrWhere };
|
|
700
|
+
} else {
|
|
701
|
+
where = idOrWhere;
|
|
702
|
+
}
|
|
703
|
+
whereClause = buildWhereClause(table, fields, where);
|
|
672
704
|
}
|
|
673
|
-
const whereClause = buildWhereClause(table, fields, where);
|
|
674
705
|
if (!whereClause) {
|
|
675
706
|
throw new HTTPException(400, { message: "where clause must not be empty" });
|
|
676
707
|
}
|
|
@@ -708,7 +739,20 @@ function defineModel(tableName, fields) {
|
|
|
708
739
|
accessor
|
|
709
740
|
);
|
|
710
741
|
const baseSchema = buildBaseObject(fields);
|
|
711
|
-
|
|
742
|
+
let schema = applySchemaOptions(baseSchema, merged);
|
|
743
|
+
if (usage === "create") {
|
|
744
|
+
const shape = schema.shape;
|
|
745
|
+
const patch = {};
|
|
746
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
747
|
+
if (field.modifiers.defaultNow === true && field.modifiers.policy !== "readOnly" && key in shape) {
|
|
748
|
+
patch[key] = shape[key].optional();
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (Object.keys(patch).length > 0) {
|
|
752
|
+
schema = schema.extend(patch);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return schema;
|
|
712
756
|
},
|
|
713
757
|
outputSchema(opts) {
|
|
714
758
|
const merged = derivePolicyOptions(fields, "output", opts, accessor);
|