d1-kyt 0.9.3 → 0.9.5

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
@@ -3,28 +3,44 @@
3
3
  Type-safe [Cloudflare D1](https://developers.cloudflare.com/d1/) toolkit built on [Kysely](https://kysely.dev/) and [Valibot](https://valibot.dev/). Define your schema once in Valibot — get SQL migrations and fully-typed queries with no code generation.
4
4
 
5
5
  ```typescript
6
- import { defineTable, InferDB, createQueryBuilder } from 'd1-kyt';
7
- import { queryAll, queryFirst, queryRun } from 'd1-kyt';
6
+ import { defineTable, defineIndex, InferDB, createQueryBuilder, queryAll, queryFirst } from 'd1-kyt';
8
7
  import * as v from 'valibot';
9
8
 
9
+ export const users = defineTable('users', {
10
+ email: v.string(),
11
+ verified: v.boolean(), // stored 0/1 → returned as boolean
12
+ prefs: v.object({ theme: v.string() }), // stored as JSON → returned as object
13
+ });
14
+
10
15
  export const posts = defineTable('posts', {
11
- title: v.string(),
12
- published: v.boolean(),
13
- views: v.pipe(v.number(), v.integer()),
14
- categoryId: v.optional(v.pipe(v.number(), v.integer())),
15
- meta: v.object({ og: v.string() }), // stored as JSON
16
+ title: v.string(),
17
+ views: v.pipe(v.number(), v.integer()),
18
+ authorId: v.pipe(v.number(), v.integer()),
16
19
  }, {
17
- foreignKeys: [{ columns: ['categoryId'], references: categories, onDelete: 'CASCADE' }],
18
- indexes: [{ columns: ['title'], unique: true }],
20
+ foreignKeys: [{ columns: ['authorId'], references: users }],
19
21
  });
20
22
 
21
- export type DB = InferDB<{ posts: typeof posts }>;
23
+ export const postsTitleIdx = defineIndex(posts, ['title'], { unique: true });
24
+
25
+ export type DB = InferDB<{ users: typeof users; posts: typeof posts }>;
22
26
  export const db = createQueryBuilder<DB>();
23
27
 
24
- // Type-safe queries JSON and boolean columns deserialize automatically
25
- const all = await queryAll(env.DB, db.selectFrom('posts').selectAll().compile());
26
- const one = await queryFirst(env.DB, db.selectFrom('posts').where('id', '=', 1).selectAll().compile());
27
- const done = await queryRun(env.DB, db.deleteFrom('posts').where('id', '=', 1).compile());
28
+ // prefs is { theme: string }, verified is boolean deserialized automatically
29
+ const verified = await queryAll(
30
+ env.DB,
31
+ db.selectFrom('users').selectAll().where('verified', '=', true).compile(),
32
+ );
33
+
34
+ // full Kysely — joins, subqueries, window functions, all type-checked
35
+ const popular = await queryFirst(
36
+ env.DB,
37
+ db.selectFrom('posts')
38
+ .innerJoin('users', 'users.id', 'posts.authorId')
39
+ .select(['posts.title', 'posts.views', 'users.email'])
40
+ .where('posts.views', '>', 1000)
41
+ .orderBy('posts.views', 'desc')
42
+ .compile(),
43
+ );
28
44
  ```
29
45
 
30
46
  ## Install
@@ -112,7 +128,13 @@ export const posts = defineTable('posts', {
112
128
  title: v.string(),
113
129
  categoryId: v.pipe(v.number(), v.integer()),
114
130
  }, {
115
- foreignKeys: [{ columns: ['categoryId'], references: categories, onDelete: 'CASCADE' }],
131
+ foreignKeys: [{
132
+ columns: ['categoryId'],
133
+ references: categories,
134
+ refColumns: ['id'], // optional — defaults to the referenced table's PK
135
+ onDelete: 'CASCADE', // CASCADE | SET NULL | RESTRICT | NO ACTION
136
+ onUpdate: 'NO ACTION',
137
+ }],
116
138
  });
117
139
  ```
118
140
 
@@ -126,6 +148,30 @@ deptId: v.optional(v.pipe(v.number(), v.integer())) // ✓ nullable allows inli
126
148
 
127
149
  ---
128
150
 
151
+ ## Indexes
152
+
153
+ ```typescript
154
+ // Basic unique index
155
+ export const postsSlugIdx = defineIndex(posts, ['slug'], { unique: true });
156
+
157
+ // Composite index
158
+ export const postsAuthorViewsIdx = defineIndex(posts, ['authorId', 'views']);
159
+
160
+ // Partial index — only indexes rows matching the WHERE clause
161
+ export const postsPublishedIdx = defineIndex(posts, ['createdAt'], {
162
+ where: 'published = 1',
163
+ });
164
+
165
+ // Custom index name
166
+ export const postsSearchIdx = defineIndex(posts, ['title'], {
167
+ name: 'posts_title_fts_idx',
168
+ });
169
+ ```
170
+
171
+ Columns are type-checked against the table definition at compile time.
172
+
173
+ ---
174
+
129
175
  ## CLI
130
176
 
131
177
  ```bash
@@ -182,7 +228,7 @@ await queryAll(env.DB, query, undefined, []); // disable all checks
182
228
  | Export | Description |
183
229
  |--------|-------------|
184
230
  | `defineTable(name, columns, opts?)` | Define a table; returns `SchemaTable` with `$inferSelect` / `$inferInsert` |
185
- | `defineIndex(table, columns, opts?)` | Define an index (columns are type-checked) |
231
+ | `defineIndex(table, columns, opts?)` | Define an index; columns are type-checked. `opts`: `unique`, `name`, `where` (partial index) |
186
232
  | `defineTrigger(name, opts)` | Define a custom trigger |
187
233
  | `InferDB<Tables>` | Infer a Kysely-compatible `DB` type |
188
234
 
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
3
3
  import { dirname, join, resolve } from 'node:path';
4
- const VERSION = '0.9.3';
4
+ const VERSION = '0.9.5';
5
5
  const HELP = `
6
6
  d1-kyt v${VERSION} - Opinionated Cloudflare D1 + Kysely toolkit
7
7
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "d1-kyt",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "Opinionated Cloudflare D1 + Kysely toolkit",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,7 +16,7 @@ schema.ts → schema:diff → .sql migration → wrangler apply → type
16
16
  ## 1. Define Schema
17
17
 
18
18
  ```typescript
19
- import { defineTable } from 'd1-kyt';
19
+ import { defineTable, defineIndex } from 'd1-kyt';
20
20
  import * as v from 'valibot';
21
21
 
22
22
  export const posts = defineTable(
@@ -33,10 +33,20 @@ export const posts = defineTable(
33
33
  categoryId: v.pipe(v.number(), v.integer()),
34
34
  },
35
35
  {
36
- foreignKeys: [{ columns: ['categoryId'], references: categories, onDelete: 'CASCADE' }],
37
- indexes: [{ columns: ['slug'], unique: true }],
36
+ foreignKeys: [{
37
+ columns: ['categoryId'],
38
+ references: categories,
39
+ refColumns: ['id'], // optional, defaults to referenced table's PK
40
+ onDelete: 'CASCADE', // CASCADE | SET NULL | RESTRICT | NO ACTION
41
+ onUpdate: 'NO ACTION', // same options
42
+ }],
38
43
  }
39
44
  );
45
+
46
+ // Indexes are defined separately with defineIndex (NOT inside defineTable options)
47
+ export const postsSlugIdx = defineIndex(posts, ['slug'], { unique: true });
48
+ export const postsPublishedIdx = defineIndex(posts, ['views'], { where: 'published = 1' }); // partial index
49
+ export const postsCustomIdx = defineIndex(posts, ['title'], { name: 'posts_title_search_idx' });
40
50
  ```
41
51
 
42
52
  **Column type mapping:**