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 +62 -16
- package/dist/cli.js +1 -1
- package/package.json +1 -1
- package/skills/use-d1-kyt/SKILL.md +13 -3
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:
|
|
12
|
-
|
|
13
|
-
|
|
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: ['
|
|
18
|
-
indexes: [{ columns: ['title'], unique: true }],
|
|
20
|
+
foreignKeys: [{ columns: ['authorId'], references: users }],
|
|
19
21
|
});
|
|
20
22
|
|
|
21
|
-
export
|
|
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
|
-
//
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
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: [{
|
|
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
|
|
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.
|
|
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
|
@@ -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: [{
|
|
37
|
-
|
|
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:**
|