@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/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, 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';
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
- * Where clause object: each key is a field name, each value is the desired equality.
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 Where object.
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-SHPR4NTZ.js';
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, inArray } from 'drizzle-orm';
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
- conditions.push(eq(col, value));
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 where = null;
614
- if (typeof idOrWhere === "string" || typeof idOrWhere === "number") {
615
- const pk = findPrimaryKey(fields);
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 = idOrWhere;
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 rows = await adapter.drizzle.insert(table).values(data).returning();
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 where = null;
646
- if (typeof idOrWhere === "string" || typeof idOrWhere === "number") {
647
- const pk = findPrimaryKey(fields);
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 = idOrWhere;
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 rows = await adapter.drizzle.update(table).set(data).where(whereClause).returning();
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 where = null;
664
- if (typeof idOrWhere === "string" || typeof idOrWhere === "number") {
665
- const pk = findPrimaryKey(fields);
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 = idOrWhere;
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
- return applySchemaOptions(baseSchema, merged);
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);