@nanokajs/core 0.1.3 → 0.1.4

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/README.md CHANGED
@@ -19,6 +19,8 @@ Targets **Cloudflare Workers + D1 (SQLite)** as first-class. Hono-compatible rou
19
19
 
20
20
  Phase 1 (MVP). Experimental. Expect breaking changes until v1.0.
21
21
 
22
+ The v1.0 line will be cut when Nanoka's core API-boundary model is stable: field policies (`serverOnly` / `writeOnly` / `readOnly`), purpose-specific schemas (`inputSchema` / `outputSchema`), validator presets, typed JSON fields, Zod 4 support, and OpenAPI component generation. Relations, route-level OpenAPI, scaffolding, and editor tooling are not v1.0 blockers.
23
+
22
24
  ## Install
23
25
 
24
26
  Nanoka assumes a Hono + Cloudflare Workers project. The standard starting point is `pnpm create hono@latest` (Cloudflare Workers template), which scaffolds `wrangler` and `hono` but **not** TypeScript, `@cloudflare/workers-types`, or `drizzle-kit`. From a fresh scaffold, add Nanoka and the missing pieces:
@@ -154,12 +156,13 @@ export default {
154
156
  User.validator('json', { omit: ['id', 'passwordHash', 'createdAt'] }),
155
157
  async (c) => {
156
158
  const body = c.req.valid('json')
157
- const user = await User.create({
159
+ const created = await User.create({
158
160
  ...body,
159
161
  id: crypto.randomUUID(),
160
162
  passwordHash: 'hashed_value_here', // use bcrypt, argon2, etc. in production
161
163
  createdAt: new Date(),
162
164
  })
165
+ const user = User.schema({ omit: ['passwordHash'] }).parse(created)
163
166
  return c.json(user, 201)
164
167
  }
165
168
  )
@@ -172,7 +175,7 @@ export default {
172
175
  ### Full working example
173
176
 
174
177
  Complete CRUD with curl tests, deployment steps, and troubleshooting:
175
- [examples/basic/README.md](../../examples/basic/README.md)
178
+ [examples/basic/README.md](https://github.com/nanokajs/nanoka/blob/main/examples/basic/README.md)
176
179
 
177
180
  ## How it fits with drizzle-kit and wrangler
178
181
 
@@ -381,11 +384,14 @@ Note: this handler will not be called for validation failures originating from `
381
384
 
382
385
  Nanoka Phase 1 is intentionally minimal. These features are Phase 2 or later:
383
386
 
384
- - **Relations** (`hasMany()`, `belongsTo()`, lazy loading) — Phase 2. Use raw Drizzle for joins.
385
- - **Field accessor API** (`User.schema({ pick: f => [f.name, f.email] })`, `User.where(f => eq(f.email, x))`) — Phase 2. Phase 1 uses string arrays and object-form `where`. When Phase 2 lands, the `f` object will be `as const`, with zero runtime cost.
387
+ - **Field policies** (`serverOnly()`, `writeOnly()`, `readOnly()`) — Phase 2. These will reduce repeated `omit` boilerplate for API input/output boundaries while keeping response shaping explicit.
388
+ - **Purpose-specific schemas** (`inputSchema('create')`, `inputSchema('update')`, `outputSchema()`) — Phase 2. Phase 1 uses `schema({ pick, omit, partial })` directly.
389
+ - **Typed JSON fields** (`t.json(zodSchema)`) — Phase 2. Phase 1 `t.json()` uses `z.unknown()`.
390
+ - **Field accessor API** (`User.schema({ pick: f => [f.name, f.email] })`) — Phase 2. Phase 1 uses string arrays. When Phase 2 lands, the `f` object will be `as const`, with zero runtime cost.
386
391
  - **Zod 4 support** — Phase 2. Zod 4 changed `ZodType<Output, Def, Input>` (v3) to `ZodType<Output, Input, Internals>` (v4); Nanoka's field type derivation depends on the v3 generic order, so installing Zod 4 collapses `InferFieldType` to `never`. Pin `zod@^3.23.0` for now.
387
- - **OpenAPI generation** — Phase 2 or 3.
388
- - **Turso / libSQL adapters** — Phase 2. Nanoka is designed adapter-first; additional adapters will follow.
392
+ - **OpenAPI component generation** — Phase 2 seed. Route-level OpenAPI, route auto-discovery, and Swagger UI are Phase 3.
393
+ - **Relations** (`hasMany()`, `belongsTo()`, lazy loading) — Phase 2 later or Phase 3. Use raw Drizzle for joins.
394
+ - **Turso / libSQL adapters** — Phase 2 later or Phase 3. Nanoka is designed adapter-first; additional adapters will follow.
389
395
  - **CLI scaffolder** (`create-nanoka-app`) — Phase 3.
390
396
  - **Auth, full-stack React, complex query DSL** — explicitly out of scope at every phase.
391
397
 
@@ -439,7 +445,7 @@ Before running `pnpm publish`:
439
445
  - `tsconfig.json`, `vitest.config.ts`, `tsup.config.ts`, `scripts/`
440
446
  - `node_modules/`
441
447
 
442
- Note: `../../examples/basic` is a relative link in this README. It resolves correctly on GitHub but not on the npm registry. The link is kept for GitHub usability.
448
+ Note: External links use absolute GitHub URLs (https://github.com/nanokajs/nanoka/...) so they resolve on both GitHub and the npm registry page.
443
449
 
444
450
  2. **Verify publish access**
445
451
  ```bash
package/dist/cli.js CHANGED
@@ -106,9 +106,7 @@ function generateDrizzleSchema(models) {
106
106
  const tableContent = fieldsStr ? `{
107
107
  ${fieldsStr}
108
108
  }` : "{}";
109
- lines.push(
110
- `export const ${model.name} = sqliteTable('${model.name}', ${tableContent})`
111
- );
109
+ lines.push(`export const ${model.name} = sqliteTable('${model.name}', ${tableContent})`);
112
110
  lines.push("");
113
111
  }
114
112
  while (lines.length > 0 && lines[lines.length - 1] === "") {
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/codegen/render-field.ts","../src/codegen/generate.ts","../src/cli/load-config.ts","../src/cli/index.ts"],"names":["fs"],"mappings":"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","file":"cli.js","sourcesContent":["import type { Field } from '../field'\n\nexport function renderField(fieldName: string, field: Field<any, any, any>): string {\n const lines: string[] = []\n\n const kind = field.kind\n let colType: string\n\n if (kind === 'string' || kind === 'uuid') {\n colType = `text('${fieldName}')`\n } else if (kind === 'number') {\n colType = `real('${fieldName}')`\n } else if (kind === 'integer') {\n colType = `integer('${fieldName}')`\n } else if (kind === 'boolean') {\n colType = `integer('${fieldName}', { mode: 'boolean' })`\n } else if (kind === 'timestamp') {\n colType = `integer('${fieldName}', { mode: 'timestamp_ms' })`\n } else if (kind === 'json') {\n colType = `text('${fieldName}', { mode: 'json' }).$type<unknown>()`\n } else {\n throw new Error(`Unknown field kind: ${kind}`)\n }\n\n let column = colType\n\n if (field.modifiers.primary) {\n column += '.primaryKey()'\n }\n\n if (!field.modifiers.optional) {\n column += '.notNull()'\n }\n\n if (field.modifiers.unique) {\n column += '.unique()'\n }\n\n if (field.modifiers.hasDefault) {\n const defaultValue = field.modifiers.defaultValue\n if (typeof defaultValue === 'function') {\n console.warn(\n `[nanoka] Warning: field \"${fieldName}\" has a function default. Default clause will be omitted. ` +\n `Use Drizzle's $defaultFn() or .onUpdateCurrent() in your custom schema if needed.`,\n )\n } else {\n const serializedValue = JSON.stringify(defaultValue)\n column += `.default(${serializedValue})`\n }\n }\n\n lines.push(` ${fieldName}: ${column},`)\n return lines.join('\\n')\n}\n","import { renderField } from './render-field'\nimport type { ModelDef } from './types'\n\nconst USED_DRIZZLE_IMPORTS = {\n text: false,\n integer: false,\n real: false,\n sqliteTable: false,\n}\n\nfunction detectImports(\n models: readonly ModelDef[],\n): Record<keyof typeof USED_DRIZZLE_IMPORTS, boolean> {\n const imports = { ...USED_DRIZZLE_IMPORTS }\n\n for (const model of models) {\n for (const field of Object.values(model.fields)) {\n const kind = field.kind\n\n if (kind === 'string' || kind === 'uuid' || kind === 'json') {\n imports.text = true\n } else if (kind === 'number') {\n imports.real = true\n } else if (kind === 'integer' || kind === 'boolean' || kind === 'timestamp') {\n imports.integer = true\n }\n }\n\n if (Object.values(model.fields).length > 0) {\n imports.sqliteTable = true\n }\n }\n\n return imports\n}\n\nfunction buildImportStatement(imports: Record<keyof typeof USED_DRIZZLE_IMPORTS, boolean>): string {\n const used: string[] = []\n\n if (imports.integer) used.push('integer')\n if (imports.real) used.push('real')\n if (imports.sqliteTable) used.push('sqliteTable')\n if (imports.text) used.push('text')\n\n if (used.length === 0) {\n return \"import { sqliteTable } from 'drizzle-orm/sqlite-core'\"\n }\n\n return `import { ${used.join(', ')} } from 'drizzle-orm/sqlite-core'`\n}\n\nexport function generateDrizzleSchema(models: readonly ModelDef[]): string {\n const imports = detectImports(models)\n const importStatement = buildImportStatement(imports)\n\n const lines: string[] = [\n '// AUTO-GENERATED by `nanoka generate`. DO NOT EDIT BY HAND.',\n '// To regenerate: `npx nanoka generate`',\n importStatement,\n '',\n ]\n\n for (const model of models) {\n const fieldLines: string[] = []\n\n for (const [fieldName, field] of Object.entries(model.fields)) {\n fieldLines.push(renderField(fieldName, field))\n }\n\n const fieldsStr = fieldLines.length > 0 ? fieldLines.join('\\n') : ''\n const tableContent = fieldsStr ? `{\\n${fieldsStr}\\n}` : '{}'\n\n lines.push(\n `export const ${model.name} = sqliteTable('${model.name}', ${tableContent})`,\n )\n lines.push('')\n }\n\n // Remove trailing empty line and add single newline at EOF\n while (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop()\n }\n\n return lines.join('\\n') + '\\n'\n}\n","import { createJiti } from 'jiti'\nimport type { NanokaConfig } from '../codegen/types'\n\nexport async function loadConfig(configPath: string): Promise<NanokaConfig> {\n const jiti = createJiti(import.meta.url, { interopDefault: true })\n const mod = await jiti.import<{ default: NanokaConfig }>(configPath)\n return mod.default\n}\n","import { promises as fs, realpathSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { generateDrizzleSchema } from '../codegen/generate'\nimport { loadConfig } from './load-config'\n\nasync function main(args: string[]): Promise<number> {\n try {\n if (args.length === 0 || args[0] === 'help' || args[0] === '--help' || args[0] === '-h') {\n console.log('Usage: nanoka generate [--config <path>] [--output <path>]')\n console.log('')\n console.log('Options:')\n console.log(' --config <path> Path to nanoka.config.ts (default: ./nanoka.config.ts)')\n console.log(' --output <path> Output file path (overrides config.output)')\n return 0\n }\n\n if (args[0] !== 'generate') {\n console.error(`Error: unknown command '${args[0]}'`)\n console.error('Usage: nanoka generate [--config <path>] [--output <path>]')\n return 1\n }\n\n let configPath: string = './nanoka.config.ts'\n let outputPath: string | undefined\n\n let i = 1\n while (i < args.length) {\n if (args[i] === '--config' && i + 1 < args.length) {\n configPath = args[i + 1]!\n i += 2\n } else if (args[i] === '--output' && i + 1 < args.length) {\n outputPath = args[i + 1]!\n i += 2\n } else {\n console.error(`Error: unknown option '${args[i]}'`)\n return 1\n }\n }\n\n const absoluteConfigPath = resolve(process.cwd(), configPath)\n const config = await loadConfig(absoluteConfigPath)\n\n const finalOutputPath = outputPath ?? config.output ?? './drizzle/schema.ts'\n const absoluteOutputPath = resolve(process.cwd(), finalOutputPath)\n\n const schema = generateDrizzleSchema(config.models)\n\n await fs.mkdir(dirname(absoluteOutputPath), { recursive: true })\n await fs.writeFile(absoluteOutputPath, schema, 'utf-8')\n\n console.log(`✓ Generated ${absoluteOutputPath}`)\n return 0\n } catch (error) {\n if (error instanceof Error) {\n console.error(`Error: ${error.message}`)\n } else {\n console.error('Unknown error:', error)\n }\n return 1\n }\n}\n\n// Entry-point detection: compare realpath-resolved argv[1] (which may be a\n// symlink under pnpm/npm bin shims) with this module's URL. A naive\n// `file://${argv[1]}` comparison fails whenever the package is installed via\n// pnpm because node_modules entries are symlinks into the .pnpm store while\n// import.meta.url is always the resolved path.\nfunction isCliEntrypoint(): boolean {\n const argv1 = process.argv[1]\n if (!argv1) return false\n try {\n return pathToFileURL(realpathSync(argv1)).href === import.meta.url\n } catch {\n return false\n }\n}\n\nif (isCliEntrypoint()) {\n main(process.argv.slice(2)).then((code) => {\n process.exit(code)\n })\n}\n\nexport { main }\n"]}
1
+ {"version":3,"sources":["../src/codegen/render-field.ts","../src/codegen/generate.ts","../src/cli/load-config.ts","../src/cli/index.ts"],"names":["fs"],"mappings":"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","file":"cli.js","sourcesContent":["import type { Field } from '../field'\n\nexport function renderField(fieldName: string, field: Field<any, any, any>): string {\n const lines: string[] = []\n\n const kind = field.kind\n let colType: string\n\n if (kind === 'string' || kind === 'uuid') {\n colType = `text('${fieldName}')`\n } else if (kind === 'number') {\n colType = `real('${fieldName}')`\n } else if (kind === 'integer') {\n colType = `integer('${fieldName}')`\n } else if (kind === 'boolean') {\n colType = `integer('${fieldName}', { mode: 'boolean' })`\n } else if (kind === 'timestamp') {\n colType = `integer('${fieldName}', { mode: 'timestamp_ms' })`\n } else if (kind === 'json') {\n colType = `text('${fieldName}', { mode: 'json' }).$type<unknown>()`\n } else {\n throw new Error(`Unknown field kind: ${kind}`)\n }\n\n let column = colType\n\n if (field.modifiers.primary) {\n column += '.primaryKey()'\n }\n\n if (!field.modifiers.optional) {\n column += '.notNull()'\n }\n\n if (field.modifiers.unique) {\n column += '.unique()'\n }\n\n if (field.modifiers.hasDefault) {\n const defaultValue = field.modifiers.defaultValue\n if (typeof defaultValue === 'function') {\n console.warn(\n `[nanoka] Warning: field \"${fieldName}\" has a function default. Default clause will be omitted. ` +\n `Use Drizzle's $defaultFn() or .onUpdateCurrent() in your custom schema if needed.`,\n )\n } else {\n const serializedValue = JSON.stringify(defaultValue)\n column += `.default(${serializedValue})`\n }\n }\n\n lines.push(` ${fieldName}: ${column},`)\n return lines.join('\\n')\n}\n","import { renderField } from './render-field'\nimport type { ModelDef } from './types'\n\nconst USED_DRIZZLE_IMPORTS = {\n text: false,\n integer: false,\n real: false,\n sqliteTable: false,\n}\n\nfunction detectImports(\n models: readonly ModelDef[],\n): Record<keyof typeof USED_DRIZZLE_IMPORTS, boolean> {\n const imports = { ...USED_DRIZZLE_IMPORTS }\n\n for (const model of models) {\n for (const field of Object.values(model.fields)) {\n const kind = field.kind\n\n if (kind === 'string' || kind === 'uuid' || kind === 'json') {\n imports.text = true\n } else if (kind === 'number') {\n imports.real = true\n } else if (kind === 'integer' || kind === 'boolean' || kind === 'timestamp') {\n imports.integer = true\n }\n }\n\n if (Object.values(model.fields).length > 0) {\n imports.sqliteTable = true\n }\n }\n\n return imports\n}\n\nfunction buildImportStatement(imports: Record<keyof typeof USED_DRIZZLE_IMPORTS, boolean>): string {\n const used: string[] = []\n\n if (imports.integer) used.push('integer')\n if (imports.real) used.push('real')\n if (imports.sqliteTable) used.push('sqliteTable')\n if (imports.text) used.push('text')\n\n if (used.length === 0) {\n return \"import { sqliteTable } from 'drizzle-orm/sqlite-core'\"\n }\n\n return `import { ${used.join(', ')} } from 'drizzle-orm/sqlite-core'`\n}\n\nexport function generateDrizzleSchema(models: readonly ModelDef[]): string {\n const imports = detectImports(models)\n const importStatement = buildImportStatement(imports)\n\n const lines: string[] = [\n '// AUTO-GENERATED by `nanoka generate`. DO NOT EDIT BY HAND.',\n '// To regenerate: `npx nanoka generate`',\n importStatement,\n '',\n ]\n\n for (const model of models) {\n const fieldLines: string[] = []\n\n for (const [fieldName, field] of Object.entries(model.fields)) {\n fieldLines.push(renderField(fieldName, field))\n }\n\n const fieldsStr = fieldLines.length > 0 ? fieldLines.join('\\n') : ''\n const tableContent = fieldsStr ? `{\\n${fieldsStr}\\n}` : '{}'\n\n lines.push(`export const ${model.name} = sqliteTable('${model.name}', ${tableContent})`)\n lines.push('')\n }\n\n // Remove trailing empty line and add single newline at EOF\n while (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop()\n }\n\n return lines.join('\\n') + '\\n'\n}\n","import { createJiti } from 'jiti'\nimport type { NanokaConfig } from '../codegen/types'\n\nexport async function loadConfig(configPath: string): Promise<NanokaConfig> {\n const jiti = createJiti(import.meta.url, { interopDefault: true })\n const mod = await jiti.import<{ default: NanokaConfig }>(configPath)\n return mod.default\n}\n","import { promises as fs, realpathSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { generateDrizzleSchema } from '../codegen/generate'\nimport { loadConfig } from './load-config'\n\nasync function main(args: string[]): Promise<number> {\n try {\n if (args.length === 0 || args[0] === 'help' || args[0] === '--help' || args[0] === '-h') {\n console.log('Usage: nanoka generate [--config <path>] [--output <path>]')\n console.log('')\n console.log('Options:')\n console.log(' --config <path> Path to nanoka.config.ts (default: ./nanoka.config.ts)')\n console.log(' --output <path> Output file path (overrides config.output)')\n return 0\n }\n\n if (args[0] !== 'generate') {\n console.error(`Error: unknown command '${args[0]}'`)\n console.error('Usage: nanoka generate [--config <path>] [--output <path>]')\n return 1\n }\n\n let configPath: string = './nanoka.config.ts'\n let outputPath: string | undefined\n\n let i = 1\n while (i < args.length) {\n if (args[i] === '--config' && i + 1 < args.length) {\n configPath = args[i + 1]!\n i += 2\n } else if (args[i] === '--output' && i + 1 < args.length) {\n outputPath = args[i + 1]!\n i += 2\n } else {\n console.error(`Error: unknown option '${args[i]}'`)\n return 1\n }\n }\n\n const absoluteConfigPath = resolve(process.cwd(), configPath)\n const config = await loadConfig(absoluteConfigPath)\n\n const finalOutputPath = outputPath ?? config.output ?? './drizzle/schema.ts'\n const absoluteOutputPath = resolve(process.cwd(), finalOutputPath)\n\n const schema = generateDrizzleSchema(config.models)\n\n await fs.mkdir(dirname(absoluteOutputPath), { recursive: true })\n await fs.writeFile(absoluteOutputPath, schema, 'utf-8')\n\n console.log(`✓ Generated ${absoluteOutputPath}`)\n return 0\n } catch (error) {\n if (error instanceof Error) {\n console.error(`Error: ${error.message}`)\n } else {\n console.error('Unknown error:', error)\n }\n return 1\n }\n}\n\n// Entry-point detection: compare realpath-resolved argv[1] (which may be a\n// symlink under pnpm/npm bin shims) with this module's URL. A naive\n// `file://${argv[1]}` comparison fails whenever the package is installed via\n// pnpm because node_modules entries are symlinks into the .pnpm store while\n// import.meta.url is always the resolved path.\nfunction isCliEntrypoint(): boolean {\n const argv1 = process.argv[1]\n if (!argv1) return false\n try {\n return pathToFileURL(realpathSync(argv1)).href === import.meta.url\n } catch {\n return false\n }\n}\n\nif (isCliEntrypoint()) {\n main(process.argv.slice(2)).then((code) => {\n process.exit(code)\n })\n}\n\nexport { main }\n"]}
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ import { Hook } from '@hono/zod-validator';
7
7
  import * as hono from 'hono';
8
8
  import { ValidationTargets, Env, MiddlewareHandler, Hono } from 'hono';
9
9
  import { z } from 'zod';
10
+ import { Env as Env$1, BlankEnv } from 'hono/types';
10
11
 
11
12
  /**
12
13
  * Generic database adapter abstraction for SQLite-compatible databases.
@@ -286,7 +287,7 @@ interface NanokaModel<Fields extends Record<string, Field<any, any, any>>> {
286
287
  * Returns a Hono middleware validator for this model.
287
288
  * The optional `hook` argument is passed through to @hono/zod-validator.
288
289
  */
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
+ validator<Target extends keyof hono.ValidationTargets, Opts extends SchemaOptions<keyof 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>;
290
291
  /**
291
292
  * Fetches multiple rows with pagination and optional ordering.
292
293
  * `limit` is required (no default) to prevent accidental unbounded queries.
@@ -318,7 +319,7 @@ interface NanokaModel<Fields extends Record<string, Field<any, any, any>>> {
318
319
  * Nanoka application: a Hono instance extended with model registration,
319
320
  * adapter binding, and escape hatches for raw Drizzle and batch operations.
320
321
  */
321
- interface Nanoka extends Hono {
322
+ interface Nanoka<E extends Env$1 = BlankEnv> extends Hono<E> {
322
323
  /**
323
324
  * Raw Drizzle database instance (escape hatch per rule #4).
324
325
  * Exposes the full Drizzle API for advanced queries.
@@ -361,6 +362,6 @@ interface Nanoka extends Hono {
361
362
  * }
362
363
  * }
363
364
  */
364
- declare function nanoka(adapter: Adapter): Nanoka;
365
+ declare function nanoka<E extends Env$1 = BlankEnv>(adapter: Adapter): Nanoka<E>;
365
366
 
366
367
  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 CHANGED
@@ -79,7 +79,7 @@ function buildWhereClause(table, fields, where) {
79
79
  }
80
80
  return and(...conditions);
81
81
  }
82
- async function findManyImpl(adapter, table, fields, options) {
82
+ async function findManyImpl(adapter, table, _fields, options) {
83
83
  const limit = guardLimit(options.limit);
84
84
  const offset = options.offset !== void 0 ? guardOffset(options.offset) : 0;
85
85
  let query = adapter.drizzle.select().from(table).limit(limit).offset(offset);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapter/d1.ts","../src/model/crud.ts","../src/model/schema.ts","../src/model/table.ts","../src/model/define.ts","../src/router/nanoka.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { D1Database } from '@cloudflare/workers-types'\nimport { drizzle } from 'drizzle-orm/d1'\nimport type { Adapter } from './types'\n\n/**\n * D1-specific adapter factory. Wraps Cloudflare D1 binding in the generic\n * Adapter abstraction, enabling the core query path and router to remain\n * implementation-agnostic.\n */\nexport function d1Adapter<TSchema extends Record<string, unknown> = Record<string, never>>(\n d1: D1Database,\n): Adapter<TSchema> {\n // drizzle-orm/d1's drizzle() returns DrizzleD1Database which embeds D1-specific\n // result types. We cast to BaseSQLiteDatabase via the Adapter interface to keep\n // the D1 dependency local to this file (see CLAUDE.md rule #3). Consumers receive\n // the typed Adapter interface, so this `any` is intentionally local.\n //\n // Note: drizzle-orm/d1 internally stores the D1 binding in db.$client. When\n // handling multiple D1 bindings, call d1Adapter() fresh for each binding rather\n // than caching the return value across binding switches.\n // biome-ignore lint/suspicious/noExplicitAny: intentional - Drizzle returns complex union type\n const db = drizzle(d1) as any\n\n return {\n drizzle: db,\n batch: (queries) => db.batch(queries),\n }\n}\n","import { and, asc, desc, eq } from 'drizzle-orm'\nimport type { SQLiteTable } from 'drizzle-orm/sqlite-core'\nimport { HTTPException } from 'hono/http-exception'\nimport type { Adapter } from '../adapter/types'\nimport type { Field, InferFieldType } from '../field/types'\nimport type { CreateInput, IdOrWhere, RowType, Where } from './types'\n\nconst MAX_LIMIT = 100\n\n/**\n * Validates and guards limit/offset parameters.\n * @internal\n */\nfunction guardLimit(limit: unknown): number {\n if (!Number.isInteger(limit)) {\n throw new HTTPException(400, { message: 'limit must be an integer' })\n }\n const n = limit as number\n if (n < 0) {\n throw new HTTPException(400, { message: 'limit must be >= 0' })\n }\n if (n > MAX_LIMIT) {\n throw new HTTPException(400, { message: `limit must be <= ${MAX_LIMIT}` })\n }\n return n\n}\n\nfunction guardOffset(offset: unknown): number {\n if (!Number.isInteger(offset)) {\n throw new HTTPException(400, { message: 'offset must be an integer' })\n }\n const n = offset as number\n if (n < 0) {\n throw new HTTPException(400, { message: 'offset must be >= 0' })\n }\n return n\n}\n\n/**\n * Finds the primary key field from fields record.\n * @internal\n */\nfunction findPrimaryKey(\n fields: Record<string, Field<any, any, any>>,\n): [string, Field<any, any, any>] | null {\n let pkCount = 0\n let pkKey: string | null = null\n let pkField: Field<any, any, any> | null = null\n\n for (const [key, field] of Object.entries(fields)) {\n if (field.modifiers.primary === true) {\n pkCount++\n if (pkCount === 1) {\n pkKey = key\n pkField = field\n }\n }\n }\n\n if (pkCount === 0) {\n return null\n }\n if (pkCount > 1) {\n throw new HTTPException(500, { message: 'model has multiple primary keys' })\n }\n\n return [pkKey!, pkField!]\n}\n\n/**\n * Constructs a Drizzle where clause from an object.\n * All entries are combined with AND.\n * @internal\n */\nfunction buildWhereClause(\n table: SQLiteTable,\n fields: Record<string, Field<any, any, any>>,\n where: Where<Record<string, any>>,\n): ReturnType<typeof and> | null {\n const conditions: any[] = []\n\n for (const [key, value] of Object.entries(where)) {\n // Multi-layered guard against identifier injection\n if (!Object.hasOwn(fields, key)) {\n throw new HTTPException(400, { message: 'invalid field in where clause' })\n }\n if (!Object.hasOwn(table, key)) {\n throw new HTTPException(400, { message: 'invalid column in table' })\n }\n // biome-ignore lint/suspicious/noExplicitAny: table column access is runtime-guarded\n const col: any = (table as any)[key]\n conditions.push(eq(col, value))\n }\n\n if (conditions.length === 0) {\n return null\n }\n if (conditions.length === 1) {\n return conditions[0]\n }\n return and(...conditions)\n}\n\n/**\n * Fetches multiple rows with limit, offset, and optional ordering.\n * @internal\n */\nexport async function findManyImpl<Fields extends Record<string, Field<any, any, any>>>(\n adapter: Adapter,\n table: SQLiteTable,\n fields: Fields,\n options: {\n limit: unknown\n offset?: unknown\n orderBy?: any\n },\n): Promise<RowType<Fields>[]> {\n const limit = guardLimit(options.limit)\n const offset = options.offset !== undefined ? guardOffset(options.offset) : 0\n\n // biome-ignore lint/suspicious/noExplicitAny: drizzle query builder type narrowing\n let query: any = adapter.drizzle.select().from(table).limit(limit).offset(offset)\n\n // Apply ordering\n if (options.orderBy !== undefined) {\n const orderByList = Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy]\n\n for (const orderByItem of orderByList) {\n let column: string\n let direction: 'asc' | 'desc' = 'asc'\n\n if (typeof orderByItem === 'string') {\n column = orderByItem\n } else if (\n typeof orderByItem === 'object' &&\n orderByItem !== null &&\n 'column' in orderByItem\n ) {\n column = orderByItem.column\n if (orderByItem.direction) {\n direction = orderByItem.direction\n }\n } else {\n throw new HTTPException(400, { message: 'invalid orderBy format' })\n }\n\n // Runtime guard against identifier injection\n if (!Object.hasOwn(table, column)) {\n throw new HTTPException(400, { message: 'invalid field in orderBy' })\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: table column access is runtime-guarded\n const col: any = (table as any)[column]\n query = query.orderBy(direction === 'asc' ? asc(col) : desc(col))\n }\n }\n\n const rows = await query\n return rows\n}\n\n/**\n * Fetches a single row by primary key or where clause.\n * @internal\n */\nexport async function findOneImpl<Fields extends Record<string, Field<any, any, any>>>(\n adapter: Adapter,\n table: SQLiteTable,\n fields: Fields,\n idOrWhere: IdOrWhere<Fields>,\n): Promise<RowType<Fields> | null> {\n let where: Where<Fields> | null = null\n\n if (typeof idOrWhere === 'string' || typeof idOrWhere === 'number') {\n // PK mode\n const pk = findPrimaryKey(fields)\n if (!pk) {\n throw new HTTPException(500, { message: 'model has no primary key' })\n }\n where = { [pk[0]]: idOrWhere } as Where<Fields>\n } else {\n where = idOrWhere as Where<Fields>\n }\n\n const whereClause = buildWhereClause(table, fields, where)\n if (!whereClause) {\n throw new HTTPException(400, { message: 'where clause must not be empty' })\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: drizzle query type\n const rows = await (adapter.drizzle.select().from(table).where(whereClause).limit(1) as any)\n return rows.length > 0 ? rows[0] : null\n}\n\n/**\n * Creates a new row and returns the created record.\n * @internal\n */\nexport async function createImpl<Fields extends Record<string, Field<any, any, any>>>(\n adapter: Adapter,\n table: SQLiteTable,\n data: CreateInput<Fields>,\n): Promise<RowType<Fields>> {\n // biome-ignore lint/suspicious/noExplicitAny: drizzle query type\n const rows = await (adapter.drizzle\n .insert(table)\n .values(data as any)\n .returning() as any)\n return rows[0]\n}\n\n/**\n * Updates rows matching the given id or where clause.\n * @internal\n */\nexport async function updateImpl<Fields extends Record<string, Field<any, any, any>>>(\n adapter: Adapter,\n table: SQLiteTable,\n fields: Fields,\n idOrWhere: IdOrWhere<Fields>,\n data: Partial<RowType<Fields>>,\n): Promise<RowType<Fields> | null> {\n let where: Where<Fields> | null = null\n\n if (typeof idOrWhere === 'string' || typeof idOrWhere === 'number') {\n // PK mode\n const pk = findPrimaryKey(fields)\n if (!pk) {\n throw new HTTPException(500, { message: 'model has no primary key' })\n }\n where = { [pk[0]]: idOrWhere } as Where<Fields>\n } else {\n where = idOrWhere as Where<Fields>\n }\n\n const whereClause = buildWhereClause(table, fields, where)\n if (!whereClause) {\n throw new HTTPException(400, { message: 'where clause must not be empty' })\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: drizzle query type\n const rows = await (adapter.drizzle\n .update(table)\n .set(data as any)\n .where(whereClause)\n .returning() as any)\n return rows.length > 0 ? rows[0] : null\n}\n\n/**\n * Deletes rows matching the given id or where clause.\n * @internal\n */\nexport async function deleteImpl<Fields extends Record<string, Field<any, any, any>>>(\n adapter: Adapter,\n table: SQLiteTable,\n fields: Fields,\n idOrWhere: IdOrWhere<Fields>,\n): Promise<{ readonly deleted: number }> {\n let where: Where<Fields> | null = null\n\n if (typeof idOrWhere === 'string' || typeof idOrWhere === 'number') {\n // PK mode\n const pk = findPrimaryKey(fields)\n if (!pk) {\n throw new HTTPException(500, { message: 'model has no primary key' })\n }\n where = { [pk[0]]: idOrWhere } as Where<Fields>\n } else {\n where = idOrWhere as Where<Fields>\n }\n\n const whereClause = buildWhereClause(table, fields, where)\n if (!whereClause) {\n throw new HTTPException(400, { message: 'where clause must not be empty' })\n }\n\n const result = await adapter.drizzle.delete(table).where(whereClause).returning()\n return { deleted: result.length }\n}\n","import { z } from 'zod'\nimport type { Field } from '../field/types'\nimport type { SchemaOptions } from './types'\n\n/**\n * Builds a Zod object schema from a fields definition.\n * Returns a ZodObject with all fields required and present.\n */\n// biome-ignore lint/suspicious/noExplicitAny: any is necessary for generic Field handling and Zod shape\nexport function buildBaseObject(fields: Record<string, Field<any, any, any>>): z.ZodObject<any> {\n // biome-ignore lint/suspicious/noExplicitAny: Record key-value pairs have any type for shape\n const shape: Record<string, any> = {}\n for (const [key, field] of Object.entries(fields)) {\n shape[key] = field.zodBase\n }\n return z.object(shape)\n}\n\n/**\n * Applies pick, omit, and partial transformations to a Zod object schema.\n * Order: pick → omit → partial\n *\n * Returns the transformed schema, cast to Apply<Shape, Opts> at the call site.\n */\nexport function applySchemaOptions(\n // biome-ignore lint/suspicious/noExplicitAny: any is necessary for generic Zod shape operations\n baseSchema: z.ZodObject<any>,\n opts: SchemaOptions | undefined,\n // biome-ignore lint/suspicious/noExplicitAny: any is necessary for return type\n): z.ZodObject<any> {\n if (!opts) {\n return baseSchema\n }\n\n let schema = baseSchema\n\n // Apply pick\n if (opts.pick !== undefined) {\n // Convert readonly array to string array for .pick()\n const pickArray = Array.from(opts.pick)\n schema = schema.pick(\n // biome-ignore lint/suspicious/noExplicitAny: Zod API\n Object.fromEntries(pickArray.map((k) => [k, true])) as any,\n )\n }\n\n // Apply omit\n if (opts.omit !== undefined) {\n // Convert readonly array to string array for .omit()\n const omitArray = Array.from(opts.omit)\n schema = schema.omit(\n // biome-ignore lint/suspicious/noExplicitAny: Zod API\n Object.fromEntries(omitArray.map((k) => [k, true])) as any,\n )\n }\n\n // Apply partial\n if (opts.partial === true) {\n schema = schema.partial()\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Cast to Apply type at call site\n return schema as any\n}\n","import { type SQLiteTable, sqliteTable } from 'drizzle-orm/sqlite-core'\nimport type { Field } from '../field/types'\n\n/**\n * Constructs a Drizzle SQLiteTable from a tableName and fields.\n * Each field is converted to a Drizzle column via field.drizzleColumn(key).\n * @internal\n */\nexport function buildTable(\n tableName: string,\n fields: Record<string, Field<any, any, any>>,\n): SQLiteTable {\n const columns: Record<string, any> = {}\n for (const [key, field] of Object.entries(fields)) {\n columns[key] = field.drizzleColumn(key)\n }\n return sqliteTable(tableName, columns)\n}\n","import { type Hook, zValidator } from '@hono/zod-validator'\nimport type { Env, ValidationTargets } from 'hono'\nimport type { z } from 'zod'\nimport type { Adapter } from '../adapter/types'\nimport type { Field } from '../field/types'\nimport { createImpl, deleteImpl, findManyImpl, findOneImpl, updateImpl } from './crud'\nimport { applySchemaOptions, buildBaseObject } from './schema'\nimport { buildTable } from './table'\nimport type {\n Apply,\n CreateInput,\n FieldsToZodShape,\n FindManyOptions,\n IdOrWhere,\n Model,\n ModelValidatorReturn,\n RowType,\n SchemaOptions,\n} from './types'\n\n/**\n * Creates a Model instance from a tableName and fields definition.\n * The model supports deriving Zod schemas, Hono validators, and CRUD operations.\n *\n * @example\n * const User = defineModel('users', {\n * id: t.uuid().primary(),\n * name: t.string(),\n * email: t.string().email(),\n * passwordHash: t.string(),\n * })\n *\n * const CreateSchema = User.schema({ omit: ['passwordHash'] })\n * const users = await User.findMany(adapter, { limit: 20 })\n */\n// biome-ignore lint/suspicious/noExplicitAny: any is necessary for generic Field constraint\nexport function defineModel<Fields extends Record<string, Field<any, any, any>>>(\n tableName: string,\n fields: Fields,\n): Model<Fields> {\n const table = buildTable(tableName, fields)\n\n return {\n fields,\n tableName,\n table,\n\n schema<Opts extends SchemaOptions<keyof Fields & string> | undefined = undefined>(\n opts?: Opts,\n ): Apply<FieldsToZodShape<Fields>, Opts> {\n const baseSchema = buildBaseObject(fields)\n const schema = applySchemaOptions(baseSchema, opts)\n // biome-ignore lint/suspicious/noExplicitAny: Zod .pick()/.omit() return type narrowing requires cast at boundary\n return schema as any\n },\n\n validator<\n Target extends keyof ValidationTargets,\n Opts extends SchemaOptions<keyof Fields & string> | undefined = undefined,\n E extends Env = Env,\n P extends string = string,\n >(\n target: Target,\n opts?: Opts,\n hook?: Hook<z.output<Apply<FieldsToZodShape<Fields>, Opts>>, E, P, Target>,\n ): ModelValidatorReturn<Fields, Target, Opts> {\n const schema = this.schema(opts)\n // biome-ignore lint/suspicious/noExplicitAny: zValidator requires casting for proper operation\n return zValidator(target, schema as any, hook as any) as any\n },\n\n findMany(adapter: Adapter, options: FindManyOptions<Fields>): Promise<RowType<Fields>[]> {\n return findManyImpl(adapter, table, fields, options)\n },\n\n findOne(adapter: Adapter, idOrWhere: IdOrWhere<Fields>): Promise<RowType<Fields> | null> {\n return findOneImpl(adapter, table, fields, idOrWhere)\n },\n\n create(adapter: Adapter, data: CreateInput<Fields>): Promise<RowType<Fields>> {\n return createImpl(adapter, table, data)\n },\n\n update(\n adapter: Adapter,\n idOrWhere: IdOrWhere<Fields>,\n data: Partial<RowType<Fields>>,\n ): Promise<RowType<Fields> | null> {\n return updateImpl(adapter, table, fields, idOrWhere, data)\n },\n\n delete(adapter: Adapter, idOrWhere: IdOrWhere<Fields>): Promise<{ readonly deleted: number }> {\n return deleteImpl(adapter, table, fields, idOrWhere)\n },\n }\n}\n","import { Hono } from 'hono'\nimport type { Adapter } from '../adapter/types'\nimport type { Field } from '../field/types'\nimport { defineModel } from '../model/define'\nimport type { Nanoka, NanokaModel } from './types'\n\n/**\n * Creates a Nanoka application instance with the given database adapter.\n *\n * @param adapter - The database adapter (e.g., d1Adapter(env.DB))\n * @returns A Nanoka application (Hono extended with model(), db, and batch)\n *\n * @example\n * export default {\n * fetch(req: Request, env: Env, ctx: ExecutionContext) {\n * const app = nanoka(d1Adapter(env.DB))\n * const User = app.model('users', { id, name, email, passwordHash })\n * app.get('/users', async (c) => {\n * const users = await User.findMany({ limit: 20 })\n * return c.json(users)\n * })\n * return app.fetch(req, env, ctx)\n * }\n * }\n */\nexport function nanoka(adapter: Adapter): Nanoka {\n const app = new Hono() as Nanoka\n\n Object.defineProperty(app, 'db', {\n get: () => adapter.drizzle,\n enumerable: true,\n })\n\n app.batch = ((queries: Parameters<Adapter['batch']>[0]) =>\n adapter.batch(queries)) as unknown as Adapter['batch']\n\n // biome-ignore lint/suspicious/noExplicitAny: any is necessary for Field generic constraint\n app.model = function model<Fields extends Record<string, Field<any, any, any>>>(\n name: string,\n fields: Fields,\n ): NanokaModel<Fields> {\n const inner = defineModel(name, fields)\n\n return {\n fields: inner.fields,\n tableName: inner.tableName,\n table: inner.table,\n\n schema: (opts) => inner.schema(opts),\n\n validator: (target, opts, hook) => inner.validator(target, opts, hook),\n\n findMany: (options) => inner.findMany(adapter, options),\n\n findOne: (idOrWhere) => inner.findOne(adapter, idOrWhere),\n\n create: (data) => inner.create(adapter, data),\n\n update: (idOrWhere, data) => inner.update(adapter, idOrWhere, data),\n\n delete: (idOrWhere) => inner.delete(adapter, idOrWhere),\n }\n }\n\n return app\n}\n"]}
1
+ {"version":3,"sources":["../src/adapter/d1.ts","../src/model/crud.ts","../src/model/schema.ts","../src/model/table.ts","../src/model/define.ts","../src/router/nanoka.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { D1Database } from '@cloudflare/workers-types'\nimport { drizzle } from 'drizzle-orm/d1'\nimport type { Adapter } from './types'\n\n/**\n * D1-specific adapter factory. Wraps Cloudflare D1 binding in the generic\n * Adapter abstraction, enabling the core query path and router to remain\n * implementation-agnostic.\n */\nexport function d1Adapter<TSchema extends Record<string, unknown> = Record<string, never>>(\n d1: D1Database,\n): Adapter<TSchema> {\n // drizzle-orm/d1's drizzle() returns DrizzleD1Database which embeds D1-specific\n // result types. We cast to BaseSQLiteDatabase via the Adapter interface to keep\n // the D1 dependency local to this file (see CLAUDE.md rule #3). Consumers receive\n // the typed Adapter interface, so this `any` is intentionally local.\n //\n // Note: drizzle-orm/d1 internally stores the D1 binding in db.$client. When\n // handling multiple D1 bindings, call d1Adapter() fresh for each binding rather\n // than caching the return value across binding switches.\n // biome-ignore lint/suspicious/noExplicitAny: intentional - Drizzle returns complex union type\n const db = drizzle(d1) as any\n\n return {\n drizzle: db,\n batch: (queries) => db.batch(queries),\n }\n}\n","import { and, asc, desc, eq } from 'drizzle-orm'\nimport type { SQLiteTable } from 'drizzle-orm/sqlite-core'\nimport { HTTPException } from 'hono/http-exception'\nimport type { Adapter } from '../adapter/types'\nimport type { Field } from '../field/types'\nimport type { CreateInput, IdOrWhere, RowType, Where } from './types'\n\nconst MAX_LIMIT = 100\n\n/**\n * Validates and guards limit/offset parameters.\n * @internal\n */\nfunction guardLimit(limit: unknown): number {\n if (!Number.isInteger(limit)) {\n throw new HTTPException(400, { message: 'limit must be an integer' })\n }\n const n = limit as number\n if (n < 0) {\n throw new HTTPException(400, { message: 'limit must be >= 0' })\n }\n if (n > MAX_LIMIT) {\n throw new HTTPException(400, { message: `limit must be <= ${MAX_LIMIT}` })\n }\n return n\n}\n\nfunction guardOffset(offset: unknown): number {\n if (!Number.isInteger(offset)) {\n throw new HTTPException(400, { message: 'offset must be an integer' })\n }\n const n = offset as number\n if (n < 0) {\n throw new HTTPException(400, { message: 'offset must be >= 0' })\n }\n return n\n}\n\n/**\n * Finds the primary key field from fields record.\n * @internal\n */\nfunction findPrimaryKey(\n fields: Record<string, Field<any, any, any>>,\n): [string, Field<any, any, any>] | null {\n let pkCount = 0\n let pkKey: string | null = null\n let pkField: Field<any, any, any> | null = null\n\n for (const [key, field] of Object.entries(fields)) {\n if (field.modifiers.primary === true) {\n pkCount++\n if (pkCount === 1) {\n pkKey = key\n pkField = field\n }\n }\n }\n\n if (pkCount === 0) {\n return null\n }\n if (pkCount > 1) {\n throw new HTTPException(500, { message: 'model has multiple primary keys' })\n }\n\n return [pkKey!, pkField!]\n}\n\n/**\n * Constructs a Drizzle where clause from an object.\n * All entries are combined with AND.\n * @internal\n */\nfunction buildWhereClause(\n table: SQLiteTable,\n fields: Record<string, Field<any, any, any>>,\n where: Where<Record<string, any>>,\n): ReturnType<typeof and> | null {\n const conditions: any[] = []\n\n for (const [key, value] of Object.entries(where)) {\n // Multi-layered guard against identifier injection\n if (!Object.hasOwn(fields, key)) {\n throw new HTTPException(400, { message: 'invalid field in where clause' })\n }\n if (!Object.hasOwn(table, key)) {\n throw new HTTPException(400, { message: 'invalid column in table' })\n }\n // biome-ignore lint/suspicious/noExplicitAny: table column access is runtime-guarded\n const col: any = (table as any)[key]\n conditions.push(eq(col, value))\n }\n\n if (conditions.length === 0) {\n return null\n }\n if (conditions.length === 1) {\n return conditions[0]\n }\n return and(...conditions)\n}\n\n/**\n * Fetches multiple rows with limit, offset, and optional ordering.\n * @internal\n */\nexport async function findManyImpl<Fields extends Record<string, Field<any, any, any>>>(\n adapter: Adapter,\n table: SQLiteTable,\n _fields: Fields,\n options: {\n limit: unknown\n offset?: unknown\n orderBy?: any\n },\n): Promise<RowType<Fields>[]> {\n const limit = guardLimit(options.limit)\n const offset = options.offset !== undefined ? guardOffset(options.offset) : 0\n\n // biome-ignore lint/suspicious/noExplicitAny: drizzle query builder type narrowing\n let query: any = adapter.drizzle.select().from(table).limit(limit).offset(offset)\n\n // Apply ordering\n if (options.orderBy !== undefined) {\n const orderByList = Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy]\n\n for (const orderByItem of orderByList) {\n let column: string\n let direction: 'asc' | 'desc' = 'asc'\n\n if (typeof orderByItem === 'string') {\n column = orderByItem\n } else if (\n typeof orderByItem === 'object' &&\n orderByItem !== null &&\n 'column' in orderByItem\n ) {\n column = orderByItem.column\n if (orderByItem.direction) {\n direction = orderByItem.direction\n }\n } else {\n throw new HTTPException(400, { message: 'invalid orderBy format' })\n }\n\n // Runtime guard against identifier injection\n if (!Object.hasOwn(table, column)) {\n throw new HTTPException(400, { message: 'invalid field in orderBy' })\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: table column access is runtime-guarded\n const col: any = (table as any)[column]\n query = query.orderBy(direction === 'asc' ? asc(col) : desc(col))\n }\n }\n\n const rows = await query\n return rows\n}\n\n/**\n * Fetches a single row by primary key or where clause.\n * @internal\n */\nexport async function findOneImpl<Fields extends Record<string, Field<any, any, any>>>(\n adapter: Adapter,\n table: SQLiteTable,\n fields: Fields,\n idOrWhere: IdOrWhere<Fields>,\n): Promise<RowType<Fields> | null> {\n let where: Where<Fields> | null = null\n\n if (typeof idOrWhere === 'string' || typeof idOrWhere === 'number') {\n // PK mode\n const pk = findPrimaryKey(fields)\n if (!pk) {\n throw new HTTPException(500, { message: 'model has no primary key' })\n }\n where = { [pk[0]]: idOrWhere } as Where<Fields>\n } else {\n where = idOrWhere as Where<Fields>\n }\n\n const whereClause = buildWhereClause(table, fields, where)\n if (!whereClause) {\n throw new HTTPException(400, { message: 'where clause must not be empty' })\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: drizzle query type\n const rows = await (adapter.drizzle.select().from(table).where(whereClause).limit(1) as any)\n return rows.length > 0 ? rows[0] : null\n}\n\n/**\n * Creates a new row and returns the created record.\n * @internal\n */\nexport async function createImpl<Fields extends Record<string, Field<any, any, any>>>(\n adapter: Adapter,\n table: SQLiteTable,\n data: CreateInput<Fields>,\n): Promise<RowType<Fields>> {\n const rows = await (adapter.drizzle\n .insert(table)\n .values(data as any)\n .returning() as any)\n return rows[0]\n}\n\n/**\n * Updates rows matching the given id or where clause.\n * @internal\n */\nexport async function updateImpl<Fields extends Record<string, Field<any, any, any>>>(\n adapter: Adapter,\n table: SQLiteTable,\n fields: Fields,\n idOrWhere: IdOrWhere<Fields>,\n data: Partial<RowType<Fields>>,\n): Promise<RowType<Fields> | null> {\n let where: Where<Fields> | null = null\n\n if (typeof idOrWhere === 'string' || typeof idOrWhere === 'number') {\n // PK mode\n const pk = findPrimaryKey(fields)\n if (!pk) {\n throw new HTTPException(500, { message: 'model has no primary key' })\n }\n where = { [pk[0]]: idOrWhere } as Where<Fields>\n } else {\n where = idOrWhere as Where<Fields>\n }\n\n const whereClause = buildWhereClause(table, fields, where)\n if (!whereClause) {\n throw new HTTPException(400, { message: 'where clause must not be empty' })\n }\n\n const rows = await (adapter.drizzle\n .update(table)\n .set(data as any)\n .where(whereClause)\n .returning() as any)\n return rows.length > 0 ? rows[0] : null\n}\n\n/**\n * Deletes rows matching the given id or where clause.\n * @internal\n */\nexport async function deleteImpl<Fields extends Record<string, Field<any, any, any>>>(\n adapter: Adapter,\n table: SQLiteTable,\n fields: Fields,\n idOrWhere: IdOrWhere<Fields>,\n): Promise<{ readonly deleted: number }> {\n let where: Where<Fields> | null = null\n\n if (typeof idOrWhere === 'string' || typeof idOrWhere === 'number') {\n // PK mode\n const pk = findPrimaryKey(fields)\n if (!pk) {\n throw new HTTPException(500, { message: 'model has no primary key' })\n }\n where = { [pk[0]]: idOrWhere } as Where<Fields>\n } else {\n where = idOrWhere as Where<Fields>\n }\n\n const whereClause = buildWhereClause(table, fields, where)\n if (!whereClause) {\n throw new HTTPException(400, { message: 'where clause must not be empty' })\n }\n\n const result = await adapter.drizzle.delete(table).where(whereClause).returning()\n return { deleted: result.length }\n}\n","import { z } from 'zod'\nimport type { Field } from '../field/types'\nimport type { SchemaOptions } from './types'\n\n/**\n * Builds a Zod object schema from a fields definition.\n * Returns a ZodObject with all fields required and present.\n */\n// biome-ignore lint/suspicious/noExplicitAny: any is necessary for generic Field handling and Zod shape\nexport function buildBaseObject(fields: Record<string, Field<any, any, any>>): z.ZodObject<any> {\n // biome-ignore lint/suspicious/noExplicitAny: Record key-value pairs have any type for shape\n const shape: Record<string, any> = {}\n for (const [key, field] of Object.entries(fields)) {\n shape[key] = field.zodBase\n }\n return z.object(shape)\n}\n\n/**\n * Applies pick, omit, and partial transformations to a Zod object schema.\n * Order: pick → omit → partial\n *\n * Returns the transformed schema, cast to Apply<Shape, Opts> at the call site.\n */\nexport function applySchemaOptions(\n // biome-ignore lint/suspicious/noExplicitAny: any is necessary for generic Zod shape operations\n baseSchema: z.ZodObject<any>,\n opts: SchemaOptions | undefined,\n // biome-ignore lint/suspicious/noExplicitAny: any is necessary for return type\n): z.ZodObject<any> {\n if (!opts) {\n return baseSchema\n }\n\n let schema = baseSchema\n\n // Apply pick\n if (opts.pick !== undefined) {\n // Convert readonly array to string array for .pick()\n const pickArray = Array.from(opts.pick)\n schema = schema.pick(\n // biome-ignore lint/suspicious/noExplicitAny: Zod API\n Object.fromEntries(pickArray.map((k) => [k, true])) as any,\n )\n }\n\n // Apply omit\n if (opts.omit !== undefined) {\n // Convert readonly array to string array for .omit()\n const omitArray = Array.from(opts.omit)\n schema = schema.omit(\n // biome-ignore lint/suspicious/noExplicitAny: Zod API\n Object.fromEntries(omitArray.map((k) => [k, true])) as any,\n )\n }\n\n // Apply partial\n if (opts.partial === true) {\n schema = schema.partial()\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Cast to Apply type at call site\n return schema as any\n}\n","import { type SQLiteTable, sqliteTable } from 'drizzle-orm/sqlite-core'\nimport type { Field } from '../field/types'\n\n/**\n * Constructs a Drizzle SQLiteTable from a tableName and fields.\n * Each field is converted to a Drizzle column via field.drizzleColumn(key).\n * @internal\n */\nexport function buildTable(\n tableName: string,\n fields: Record<string, Field<any, any, any>>,\n): SQLiteTable {\n const columns: Record<string, any> = {}\n for (const [key, field] of Object.entries(fields)) {\n columns[key] = field.drizzleColumn(key)\n }\n return sqliteTable(tableName, columns)\n}\n","import { type Hook, zValidator } from '@hono/zod-validator'\nimport type { Env, ValidationTargets } from 'hono'\nimport type { z } from 'zod'\nimport type { Adapter } from '../adapter/types'\nimport type { Field } from '../field/types'\nimport { createImpl, deleteImpl, findManyImpl, findOneImpl, updateImpl } from './crud'\nimport { applySchemaOptions, buildBaseObject } from './schema'\nimport { buildTable } from './table'\nimport type {\n Apply,\n CreateInput,\n FieldsToZodShape,\n FindManyOptions,\n IdOrWhere,\n Model,\n ModelValidatorReturn,\n RowType,\n SchemaOptions,\n} from './types'\n\n/**\n * Creates a Model instance from a tableName and fields definition.\n * The model supports deriving Zod schemas, Hono validators, and CRUD operations.\n *\n * @example\n * const User = defineModel('users', {\n * id: t.uuid().primary(),\n * name: t.string(),\n * email: t.string().email(),\n * passwordHash: t.string(),\n * })\n *\n * const CreateSchema = User.schema({ omit: ['passwordHash'] })\n * const users = await User.findMany(adapter, { limit: 20 })\n */\n// biome-ignore lint/suspicious/noExplicitAny: any is necessary for generic Field constraint\nexport function defineModel<Fields extends Record<string, Field<any, any, any>>>(\n tableName: string,\n fields: Fields,\n): Model<Fields> {\n const table = buildTable(tableName, fields)\n\n return {\n fields,\n tableName,\n table,\n\n schema<Opts extends SchemaOptions<keyof Fields & string> | undefined = undefined>(\n opts?: Opts,\n ): Apply<FieldsToZodShape<Fields>, Opts> {\n const baseSchema = buildBaseObject(fields)\n const schema = applySchemaOptions(baseSchema, opts)\n // biome-ignore lint/suspicious/noExplicitAny: Zod .pick()/.omit() return type narrowing requires cast at boundary\n return schema as any\n },\n\n validator<\n Target extends keyof ValidationTargets,\n Opts extends SchemaOptions<keyof Fields & string> | undefined = undefined,\n E extends Env = Env,\n P extends string = string,\n >(\n target: Target,\n opts?: Opts,\n hook?: Hook<z.output<Apply<FieldsToZodShape<Fields>, Opts>>, E, P, Target>,\n ): ModelValidatorReturn<Fields, Target, Opts> {\n const schema = this.schema(opts)\n // biome-ignore lint/suspicious/noExplicitAny: zValidator requires casting for proper operation\n return zValidator(target, schema as any, hook as any) as any\n },\n\n findMany(adapter: Adapter, options: FindManyOptions<Fields>): Promise<RowType<Fields>[]> {\n return findManyImpl(adapter, table, fields, options)\n },\n\n findOne(adapter: Adapter, idOrWhere: IdOrWhere<Fields>): Promise<RowType<Fields> | null> {\n return findOneImpl(adapter, table, fields, idOrWhere)\n },\n\n create(adapter: Adapter, data: CreateInput<Fields>): Promise<RowType<Fields>> {\n return createImpl(adapter, table, data)\n },\n\n update(\n adapter: Adapter,\n idOrWhere: IdOrWhere<Fields>,\n data: Partial<RowType<Fields>>,\n ): Promise<RowType<Fields> | null> {\n return updateImpl(adapter, table, fields, idOrWhere, data)\n },\n\n delete(adapter: Adapter, idOrWhere: IdOrWhere<Fields>): Promise<{ readonly deleted: number }> {\n return deleteImpl(adapter, table, fields, idOrWhere)\n },\n }\n}\n","import { Hono } from 'hono'\nimport type { BlankEnv, Env } from 'hono/types'\nimport type { Adapter } from '../adapter/types'\nimport type { Field } from '../field/types'\nimport { defineModel } from '../model/define'\nimport type { Nanoka, NanokaModel } from './types'\n\n/**\n * Creates a Nanoka application instance with the given database adapter.\n *\n * @param adapter - The database adapter (e.g., d1Adapter(env.DB))\n * @returns A Nanoka application (Hono extended with model(), db, and batch)\n *\n * @example\n * export default {\n * fetch(req: Request, env: Env, ctx: ExecutionContext) {\n * const app = nanoka(d1Adapter(env.DB))\n * const User = app.model('users', { id, name, email, passwordHash })\n * app.get('/users', async (c) => {\n * const users = await User.findMany({ limit: 20 })\n * return c.json(users)\n * })\n * return app.fetch(req, env, ctx)\n * }\n * }\n */\nexport function nanoka<E extends Env = BlankEnv>(adapter: Adapter): Nanoka<E> {\n const app = new Hono<E>() as Nanoka<E>\n\n Object.defineProperty(app, 'db', {\n get: () => adapter.drizzle,\n enumerable: true,\n })\n\n app.batch = ((queries: Parameters<Adapter['batch']>[0]) =>\n adapter.batch(queries)) as unknown as Adapter['batch']\n\n // biome-ignore lint/suspicious/noExplicitAny: any is necessary for Field generic constraint\n app.model = function model<Fields extends Record<string, Field<any, any, any>>>(\n name: string,\n fields: Fields,\n ): NanokaModel<Fields> {\n const inner = defineModel(name, fields)\n\n return {\n fields: inner.fields,\n tableName: inner.tableName,\n table: inner.table,\n\n schema: (opts) => inner.schema(opts),\n\n validator: (target, opts, hook) => inner.validator(target, opts, hook),\n\n findMany: (options) => inner.findMany(adapter, options),\n\n findOne: (idOrWhere) => inner.findOne(adapter, idOrWhere),\n\n create: (data) => inner.create(adapter, data),\n\n update: (idOrWhere, data) => inner.update(adapter, idOrWhere, data),\n\n delete: (idOrWhere) => inner.delete(adapter, idOrWhere),\n }\n }\n\n return app\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanokajs/core",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "Thin wrapper over Hono + Drizzle + Zod for Cloudflare Workers + D1. 80% automatic, 20% explicit.",
6
6
  "author": "Eiichiro Iriguchi <eiichiro_iriguchi@a-1ro.dev>",
@@ -48,6 +48,14 @@
48
48
  "publishConfig": {
49
49
  "access": "public"
50
50
  },
51
+ "scripts": {
52
+ "build": "tsup && node ./scripts/prepend-shebang.mjs",
53
+ "test": "vitest run -c vitest.config.ts && vitest run -c vitest.node.config.ts",
54
+ "test:workers": "vitest run -c vitest.config.ts",
55
+ "test:node": "vitest run -c vitest.node.config.ts",
56
+ "typecheck": "tsc --noEmit",
57
+ "prepublishOnly": "pnpm build && pnpm test && pnpm typecheck"
58
+ },
51
59
  "peerDependencies": {
52
60
  "@cloudflare/workers-types": "^4.20240925.0",
53
61
  "drizzle-orm": "^0.45.2",
@@ -69,12 +77,5 @@
69
77
  "vitest": "^2.1.0",
70
78
  "wrangler": "^3.80.0",
71
79
  "zod": "^3.23.0"
72
- },
73
- "scripts": {
74
- "build": "tsup && node ./scripts/prepend-shebang.mjs",
75
- "test": "vitest run -c vitest.config.ts && vitest run -c vitest.node.config.ts",
76
- "test:workers": "vitest run -c vitest.config.ts",
77
- "test:node": "vitest run -c vitest.node.config.ts",
78
- "typecheck": "tsc --noEmit"
79
80
  }
80
- }
81
+ }