@smonn/ids 0.15.0 → 1.0.0-rc.1
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 +3 -3
- package/dist/{adapter-types-7wWdELSh.mjs → adapter-types-CjzFNDcJ.mjs} +7 -2
- package/dist/adapter-types-CjzFNDcJ.mjs.map +1 -0
- package/dist/cli.mjs +30 -22
- package/dist/cli.mjs.map +1 -1
- package/dist/drizzle.d.mts +202 -3
- package/dist/drizzle.d.mts.map +1 -1
- package/dist/drizzle.mjs +245 -5
- package/dist/drizzle.mjs.map +1 -1
- package/dist/express.d.mts +44 -2
- package/dist/express.d.mts.map +1 -1
- package/dist/express.mjs +60 -2
- package/dist/express.mjs.map +1 -1
- package/dist/fastify.d.mts +49 -2
- package/dist/fastify.d.mts.map +1 -1
- package/dist/fastify.mjs +61 -2
- package/dist/fastify.mjs.map +1 -1
- package/dist/hono.d.mts +44 -2
- package/dist/hono.d.mts.map +1 -1
- package/dist/hono.mjs +54 -2
- package/dist/hono.mjs.map +1 -1
- package/dist/kysely.d.mts +103 -2
- package/dist/kysely.d.mts.map +1 -1
- package/dist/kysely.mjs +105 -2
- package/dist/kysely.mjs.map +1 -1
- package/dist/mikro-orm.d.mts +81 -3
- package/dist/mikro-orm.d.mts.map +1 -1
- package/dist/mikro-orm.mjs +86 -4
- package/dist/mikro-orm.mjs.map +1 -1
- package/dist/nestjs.mjs +1 -1
- package/dist/{opaque-COAcIIY4.mjs → opaque-Dle3CmSE.mjs} +18 -10
- package/dist/opaque-Dle3CmSE.mjs.map +1 -0
- package/dist/opaque.d.mts +16 -10
- package/dist/opaque.d.mts.map +1 -1
- package/dist/opaque.mjs +1 -1
- package/dist/prisma.d.mts +135 -3
- package/dist/prisma.d.mts.map +1 -1
- package/dist/prisma.mjs +141 -3
- package/dist/prisma.mjs.map +1 -1
- package/dist/typeorm.d.mts +84 -1
- package/dist/typeorm.d.mts.map +1 -1
- package/dist/typeorm.mjs +87 -2
- package/dist/typeorm.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/adapter-types-7wWdELSh.mjs.map +0 -1
- package/dist/opaque-COAcIIY4.mjs.map +0 -1
package/dist/drizzle.d.mts
CHANGED
|
@@ -2,10 +2,23 @@ import { t as Id } from "./types-hGBnCpJj.mjs";
|
|
|
2
2
|
import { n as IdColumnCodec } from "./adapter-types-Bia_w9sg.mjs";
|
|
3
3
|
import { n as IdsErrorCode, r as isIdsError, t as IdsError } from "./error-CifcKKOG.mjs";
|
|
4
4
|
import { ConvertCustomConfig, PgCustomColumnBuilder } from "drizzle-orm/pg-core";
|
|
5
|
+
import { ConvertCustomConfig as ConvertCustomConfig$1, MySqlCustomColumnBuilder } from "drizzle-orm/mysql-core";
|
|
6
|
+
import { ConvertCustomConfig as ConvertCustomConfig$2, SQLiteCustomColumnBuilder } from "drizzle-orm/sqlite-core";
|
|
7
|
+
import { HasDefault, HasRuntimeDefault } from "drizzle-orm/column-builder";
|
|
5
8
|
|
|
6
9
|
//#region src/adapters/drizzle.d.ts
|
|
7
10
|
/**
|
|
8
|
-
*
|
|
11
|
+
* Extension of {@link IdColumnCodec} that also exposes synchronous `generate()`.
|
|
12
|
+
* Required by the `generatedIdColumn` family so that Drizzle's `.$defaultFn` can
|
|
13
|
+
* produce IDs at write time without explicit call-site wiring. Only the **Timestamp
|
|
14
|
+
* codec** and **Reverse Timestamp codec** satisfy this; async-generate codecs
|
|
15
|
+
* (Opaque, Signed, Wrapped, Digest) do not and are a compile-time error.
|
|
16
|
+
*/
|
|
17
|
+
type IdGeneratingCodec<Brand extends string> = IdColumnCodec<Brand> & {
|
|
18
|
+
generate(): Id<Brand>;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Drizzle custom column type that stores an `Id<Brand>` as a canonical SQL string value in PostgreSQL.
|
|
9
22
|
*
|
|
10
23
|
* **Write path:** passes the `Id<Brand>` directly to the driver — it is already
|
|
11
24
|
* the canonical string form.
|
|
@@ -15,20 +28,206 @@ import { ConvertCustomConfig, PgCustomColumnBuilder } from "drizzle-orm/pg-core"
|
|
|
15
28
|
* is a safe boundary in case stale non-canonical values exist. Throws if the
|
|
16
29
|
* value from the database does not parse as a valid `Id<Brand>`.
|
|
17
30
|
*
|
|
31
|
+
* @param codec - The brand-scoped codec used to parse values read from the database.
|
|
32
|
+
* @param options - Optional column configuration.
|
|
33
|
+
* @param options.columnType - SQL column type to use (default: `"text"`). Pass
|
|
34
|
+
* `"varchar(30)"` or `"char(26)"` to match an existing DDL or index strategy.
|
|
35
|
+
* The value is passed through verbatim — no validation is performed.
|
|
36
|
+
*
|
|
18
37
|
* @example
|
|
19
38
|
* ```ts
|
|
20
39
|
* import { idColumn } from "@smonn/ids/drizzle";
|
|
21
40
|
* import { createTimestampId } from "@smonn/ids";
|
|
22
41
|
*
|
|
23
42
|
* const usr = createTimestampId("usr");
|
|
43
|
+
* // default: text column
|
|
24
44
|
* export const users = pgTable("users", { id: idColumn(usr).primaryKey() });
|
|
45
|
+
* // explicit varchar column
|
|
46
|
+
* export const orgs = pgTable("orgs", { id: idColumn(usr, { columnType: "varchar(30)" }).primaryKey() });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
declare function idColumn<Brand extends string>(codec: IdColumnCodec<Brand>, options?: {
|
|
50
|
+
columnType?: string;
|
|
51
|
+
}): PgCustomColumnBuilder<ConvertCustomConfig<"", {
|
|
52
|
+
data: Id<Brand>;
|
|
53
|
+
driverData: string;
|
|
54
|
+
}>>;
|
|
55
|
+
/**
|
|
56
|
+
* Drizzle custom column type that stores an `Id<Brand>` as a canonical `text` value in MySQL.
|
|
57
|
+
*
|
|
58
|
+
* **Write path:** passes the `Id<Brand>` directly to the driver — it is already
|
|
59
|
+
* the canonical string form.
|
|
60
|
+
*
|
|
61
|
+
* **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict
|
|
62
|
+
* `is()`. Throws `IdsError("invalid_id")` if the value from the database does not
|
|
63
|
+
* parse as a valid `Id<Brand>`.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* import { idColumnMysql } from "@smonn/ids/drizzle";
|
|
68
|
+
* import { createTimestampId } from "@smonn/ids";
|
|
69
|
+
*
|
|
70
|
+
* const usr = createTimestampId("usr");
|
|
71
|
+
* export const users = mysqlTable("users", { id: idColumnMysql(usr).primaryKey() });
|
|
25
72
|
* // users.id is Id<"usr"> end-to-end
|
|
26
73
|
* ```
|
|
27
74
|
*/
|
|
28
|
-
declare function
|
|
75
|
+
declare function idColumnMysql<Brand extends string>(codec: IdColumnCodec<Brand>): MySqlCustomColumnBuilder<ConvertCustomConfig$1<"", {
|
|
29
76
|
data: Id<Brand>;
|
|
30
77
|
driverData: string;
|
|
31
78
|
}>>;
|
|
79
|
+
/**
|
|
80
|
+
* Drizzle custom column type that stores an `Id<Brand>` as a canonical `text` value in SQLite.
|
|
81
|
+
*
|
|
82
|
+
* **Write path:** passes the `Id<Brand>` directly to the driver — it is already
|
|
83
|
+
* the canonical string form.
|
|
84
|
+
*
|
|
85
|
+
* **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict
|
|
86
|
+
* `is()`. Throws `IdsError("invalid_id")` if the value from the database does not
|
|
87
|
+
* parse as a valid `Id<Brand>`.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* import { idColumnSqlite } from "@smonn/ids/drizzle";
|
|
92
|
+
* import { createTimestampId } from "@smonn/ids";
|
|
93
|
+
*
|
|
94
|
+
* const usr = createTimestampId("usr");
|
|
95
|
+
* export const users = sqliteTable("users", { id: idColumnSqlite(usr).primaryKey() });
|
|
96
|
+
* // users.id is Id<"usr"> end-to-end
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
declare function idColumnSqlite<Brand extends string>(codec: IdColumnCodec<Brand>): SQLiteCustomColumnBuilder<ConvertCustomConfig$2<"", {
|
|
100
|
+
data: Id<Brand>;
|
|
101
|
+
driverData: string;
|
|
102
|
+
}>>;
|
|
103
|
+
/**
|
|
104
|
+
* Drizzle custom column type for a **nullable** `Id<Brand>` column.
|
|
105
|
+
*
|
|
106
|
+
* Behaves identically to {@link idColumn} except that `null` and `undefined`
|
|
107
|
+
* driver values are passed through as `null` rather than throwing. Use for
|
|
108
|
+
* optional foreign keys, `LEFT JOIN` results, and any column that is
|
|
109
|
+
* legitimately absent.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* import { nullableIdColumn } from "@smonn/ids/drizzle";
|
|
114
|
+
* import { createTimestampId } from "@smonn/ids";
|
|
115
|
+
*
|
|
116
|
+
* const usr = createTimestampId("usr");
|
|
117
|
+
* export const posts = pgTable("posts", {
|
|
118
|
+
* authorId: nullableIdColumn(usr),
|
|
119
|
+
* });
|
|
120
|
+
* // posts.authorId is Id<"usr"> | null end-to-end
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
declare function nullableIdColumn<Brand extends string>(codec: IdColumnCodec<Brand>): PgCustomColumnBuilder<ConvertCustomConfig<"", {
|
|
124
|
+
data: Id<Brand> | null;
|
|
125
|
+
driverData: string | null;
|
|
126
|
+
}>>;
|
|
127
|
+
/**
|
|
128
|
+
* Drizzle custom column type that stores an `Id<Brand>` as a canonical SQL string value
|
|
129
|
+
* in PostgreSQL, with a client-side `.$defaultFn` so inserts that omit the field receive
|
|
130
|
+
* a freshly generated ID automatically.
|
|
131
|
+
*
|
|
132
|
+
* **Write path:** `.$defaultFn(() => codec.generate())` is wired — if the field is absent
|
|
133
|
+
* on insert, Drizzle calls `codec.generate()` to produce a new `Id<Brand>`.
|
|
134
|
+
*
|
|
135
|
+
* **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict `is()`.
|
|
136
|
+
* Throws `IdsError("invalid_id")` if the value from the database does not parse as a
|
|
137
|
+
* valid `Id<Brand>`.
|
|
138
|
+
*
|
|
139
|
+
* Requires a codec that exposes synchronous `generate()` — see {@link IdGeneratingCodec}.
|
|
140
|
+
* Only the **Timestamp codec** and **Reverse Timestamp codec** qualify; Opaque, Signed,
|
|
141
|
+
* Wrapped, and Digest codecs are a compile-time error.
|
|
142
|
+
*
|
|
143
|
+
* @param codec - The brand-scoped codec used to generate and parse values.
|
|
144
|
+
* @param options - Optional column configuration.
|
|
145
|
+
* @param options.columnType - SQL column type to use (default: `"text"`). Pass
|
|
146
|
+
* `"varchar(30)"` or `"char(26)"` to match an existing DDL or index strategy.
|
|
147
|
+
* The value is passed through verbatim — no validation is performed.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* import { generatedIdColumn } from "@smonn/ids/drizzle";
|
|
152
|
+
* import { createTimestampId } from "@smonn/ids";
|
|
153
|
+
*
|
|
154
|
+
* const usr = createTimestampId("usr");
|
|
155
|
+
* export const users = pgTable("users", { id: generatedIdColumn(usr).primaryKey() });
|
|
156
|
+
* // id is auto-filled on insert — no hand-supplied id needed
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
declare function generatedIdColumn<Brand extends string>(codec: IdGeneratingCodec<Brand>, options?: {
|
|
160
|
+
columnType?: string;
|
|
161
|
+
}): HasRuntimeDefault<HasDefault<PgCustomColumnBuilder<ConvertCustomConfig<"", {
|
|
162
|
+
data: Id<Brand>;
|
|
163
|
+
driverData: string;
|
|
164
|
+
}>>>>;
|
|
165
|
+
/**
|
|
166
|
+
* Drizzle custom column type that stores an `Id<Brand>` as a canonical `text` value
|
|
167
|
+
* in MySQL, with a client-side `.$defaultFn` so inserts that omit the field receive
|
|
168
|
+
* a freshly generated ID automatically.
|
|
169
|
+
*
|
|
170
|
+
* **Write path:** `.$defaultFn(() => codec.generate())` is wired — if the field is absent
|
|
171
|
+
* on insert, Drizzle calls `codec.generate()` to produce a new `Id<Brand>`.
|
|
172
|
+
*
|
|
173
|
+
* **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict `is()`.
|
|
174
|
+
* Throws `IdsError("invalid_id")` if the value from the database does not parse as a
|
|
175
|
+
* valid `Id<Brand>`.
|
|
176
|
+
*
|
|
177
|
+
* Requires a codec that exposes synchronous `generate()` — see {@link IdGeneratingCodec}.
|
|
178
|
+
* Only the **Timestamp codec** and **Reverse Timestamp codec** qualify; Opaque, Signed,
|
|
179
|
+
* Wrapped, and Digest codecs are a compile-time error.
|
|
180
|
+
*
|
|
181
|
+
* @param codec - The brand-scoped codec used to generate and parse values.
|
|
182
|
+
* Column type is always `text` and cannot be overridden.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* import { generatedIdColumnMysql } from "@smonn/ids/drizzle";
|
|
187
|
+
* import { createTimestampId } from "@smonn/ids";
|
|
188
|
+
*
|
|
189
|
+
* const usr = createTimestampId("usr");
|
|
190
|
+
* export const users = mysqlTable("users", { id: generatedIdColumnMysql(usr).primaryKey() });
|
|
191
|
+
* // id is auto-filled on insert — no hand-supplied id needed
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
declare function generatedIdColumnMysql<Brand extends string>(codec: IdGeneratingCodec<Brand>): HasRuntimeDefault<HasDefault<MySqlCustomColumnBuilder<ConvertCustomConfig$1<"", {
|
|
195
|
+
data: Id<Brand>;
|
|
196
|
+
driverData: string;
|
|
197
|
+
}>>>>;
|
|
198
|
+
/**
|
|
199
|
+
* Drizzle custom column type that stores an `Id<Brand>` as a canonical `text` value
|
|
200
|
+
* in SQLite, with a client-side `.$defaultFn` so inserts that omit the field receive
|
|
201
|
+
* a freshly generated ID automatically.
|
|
202
|
+
*
|
|
203
|
+
* **Write path:** `.$defaultFn(() => codec.generate())` is wired — if the field is absent
|
|
204
|
+
* on insert, Drizzle calls `codec.generate()` to produce a new `Id<Brand>`.
|
|
205
|
+
*
|
|
206
|
+
* **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict `is()`.
|
|
207
|
+
* Throws `IdsError("invalid_id")` if the value from the database does not parse as a
|
|
208
|
+
* valid `Id<Brand>`.
|
|
209
|
+
*
|
|
210
|
+
* Requires a codec that exposes synchronous `generate()` — see {@link IdGeneratingCodec}.
|
|
211
|
+
* Only the **Timestamp codec** and **Reverse Timestamp codec** qualify; Opaque, Signed,
|
|
212
|
+
* Wrapped, and Digest codecs are a compile-time error.
|
|
213
|
+
*
|
|
214
|
+
* @param codec - The brand-scoped codec used to generate and parse values.
|
|
215
|
+
* Column type is always `text` and cannot be overridden.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```ts
|
|
219
|
+
* import { generatedIdColumnSqlite } from "@smonn/ids/drizzle";
|
|
220
|
+
* import { createTimestampId } from "@smonn/ids";
|
|
221
|
+
*
|
|
222
|
+
* const usr = createTimestampId("usr");
|
|
223
|
+
* export const users = sqliteTable("users", { id: generatedIdColumnSqlite(usr).primaryKey() });
|
|
224
|
+
* // id is auto-filled on insert — no hand-supplied id needed
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
declare function generatedIdColumnSqlite<Brand extends string>(codec: IdGeneratingCodec<Brand>): HasRuntimeDefault<HasDefault<SQLiteCustomColumnBuilder<ConvertCustomConfig$2<"", {
|
|
228
|
+
data: Id<Brand>;
|
|
229
|
+
driverData: string;
|
|
230
|
+
}>>>>;
|
|
32
231
|
//#endregion
|
|
33
|
-
export { type IdColumnCodec, IdsError, type IdsErrorCode, idColumn, isIdsError };
|
|
232
|
+
export { type IdColumnCodec, IdGeneratingCodec, IdsError, type IdsErrorCode, generatedIdColumn, generatedIdColumnMysql, generatedIdColumnSqlite, idColumn, idColumnMysql, idColumnSqlite, isIdsError, nullableIdColumn };
|
|
34
233
|
//# sourceMappingURL=drizzle.d.mts.map
|
package/dist/drizzle.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drizzle.d.mts","names":[],"sources":["../src/adapters/drizzle.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"drizzle.d.mts","names":[],"sources":["../src/adapters/drizzle.ts"],"mappings":";;;;;;;;;AA+BA;;;;;;;AAAA,KAAY,iBAAA,yBAA0C,aAAA,CAAc,KAAA;EAClE,QAAA,IAAY,EAAA,CAAG,KAAA;AAAA;;;;;;;AAAA;AAgCjB;;;;;;;;;;;;;;;;;;;;;;iBAAgB,QAAA,uBACd,KAAA,EAAO,aAAA,CAAc,KAAA,GACrB,OAAA;EAAY,UAAA;AAAA,IACX,qBAAA,CAAsB,mBAAA;EAA0B,IAAA,EAAM,EAAA,CAAG,KAAA;EAAQ,UAAA;AAAA;;;;;;;;;;;;;;;;;;;;AAqCQ;iBAF5D,aAAA,uBACd,KAAA,EAAO,aAAA,CAAc,KAAA,IACpB,wBAAA,CAAyB,qBAAA;EAA+B,IAAA,EAAM,EAAA,CAAG,KAAA;EAAQ,UAAA;AAAA;;;;;;;;;;;;;;;;;;;AAqCzB;AAmCnD;iBAtCgB,cAAA,uBACd,KAAA,EAAO,aAAA,CAAc,KAAA,IACpB,yBAAA,CACD,qBAAA;EAAgC,IAAA,EAAM,EAAA,CAAG,KAAA;EAAQ,UAAA;AAAA;;;;;;;;;;;;;;;;;;AAsCC;AA+CpD;;iBAlDgB,gBAAA,uBACd,KAAA,EAAO,aAAA,CAAc,KAAA,IACpB,qBAAA,CACD,mBAAA;EAA0B,IAAA,EAAM,EAAA,CAAG,KAAA;EAAe,UAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;AAoDiB;AA8CrE;;;;;;;;;iBAnDgB,iBAAA,uBACd,KAAA,EAAO,iBAAA,CAAkB,KAAA,GACzB,OAAA;EAAY,UAAA;AAAA,IACX,iBAAA,CACD,UAAA,CACE,qBAAA,CAAsB,mBAAA;EAA0B,IAAA,EAAM,EAAA,CAAG,KAAA;EAAQ,UAAA;AAAA;;;;;;;;;;;;AAkDQ;AA6C7E;;;;;;;;;;;;;;;;;iBAjDgB,sBAAA,uBACd,KAAA,EAAO,iBAAA,CAAkB,KAAA,IACxB,iBAAA,CACD,UAAA,CACE,wBAAA,CAAyB,qBAAA;EAA+B,IAAA,EAAM,EAAA,CAAG,KAAA;EAAQ,UAAA;AAAA;;;;;;AAkDtB;;;;;;;;;;;;;;;;;;;;;;;;iBALvC,uBAAA,uBACd,KAAA,EAAO,iBAAA,CAAkB,KAAA,IACxB,iBAAA,CACD,UAAA,CACE,yBAAA,CACE,qBAAA;EAAgC,IAAA,EAAM,EAAA,CAAG,KAAA;EAAQ,UAAA;AAAA"}
|
package/dist/drizzle.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { n as isIdsError, t as IdsError } from "./error-Cp5qYZcv.mjs";
|
|
2
|
-
import { t as readIdColumn } from "./adapter-types-
|
|
2
|
+
import { n as readIdColumnNullable, t as readIdColumn } from "./adapter-types-CjzFNDcJ.mjs";
|
|
3
3
|
import { customType } from "drizzle-orm/pg-core";
|
|
4
|
+
import { customType as customType$1 } from "drizzle-orm/mysql-core";
|
|
5
|
+
import { customType as customType$2 } from "drizzle-orm/sqlite-core";
|
|
4
6
|
//#region src/adapters/drizzle.ts
|
|
5
7
|
/**
|
|
6
|
-
* Drizzle custom column type that stores an `Id<Brand>` as a canonical
|
|
8
|
+
* Drizzle custom column type that stores an `Id<Brand>` as a canonical SQL string value in PostgreSQL.
|
|
7
9
|
*
|
|
8
10
|
* **Write path:** passes the `Id<Brand>` directly to the driver — it is already
|
|
9
11
|
* the canonical string form.
|
|
@@ -13,18 +15,93 @@ import { customType } from "drizzle-orm/pg-core";
|
|
|
13
15
|
* is a safe boundary in case stale non-canonical values exist. Throws if the
|
|
14
16
|
* value from the database does not parse as a valid `Id<Brand>`.
|
|
15
17
|
*
|
|
18
|
+
* @param codec - The brand-scoped codec used to parse values read from the database.
|
|
19
|
+
* @param options - Optional column configuration.
|
|
20
|
+
* @param options.columnType - SQL column type to use (default: `"text"`). Pass
|
|
21
|
+
* `"varchar(30)"` or `"char(26)"` to match an existing DDL or index strategy.
|
|
22
|
+
* The value is passed through verbatim — no validation is performed.
|
|
23
|
+
*
|
|
16
24
|
* @example
|
|
17
25
|
* ```ts
|
|
18
26
|
* import { idColumn } from "@smonn/ids/drizzle";
|
|
19
27
|
* import { createTimestampId } from "@smonn/ids";
|
|
20
28
|
*
|
|
21
29
|
* const usr = createTimestampId("usr");
|
|
30
|
+
* // default: text column
|
|
22
31
|
* export const users = pgTable("users", { id: idColumn(usr).primaryKey() });
|
|
23
|
-
* //
|
|
32
|
+
* // explicit varchar column
|
|
33
|
+
* export const orgs = pgTable("orgs", { id: idColumn(usr, { columnType: "varchar(30)" }).primaryKey() });
|
|
24
34
|
* ```
|
|
25
35
|
*/
|
|
26
|
-
function idColumn(codec) {
|
|
36
|
+
function idColumn(codec, options) {
|
|
37
|
+
const columnType = options?.columnType ?? "text";
|
|
27
38
|
return customType({
|
|
39
|
+
dataType() {
|
|
40
|
+
return columnType;
|
|
41
|
+
},
|
|
42
|
+
toDriver(value) {
|
|
43
|
+
return value;
|
|
44
|
+
},
|
|
45
|
+
fromDriver(value) {
|
|
46
|
+
return readIdColumn(codec, value);
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Drizzle custom column type that stores an `Id<Brand>` as a canonical `text` value in MySQL.
|
|
52
|
+
*
|
|
53
|
+
* **Write path:** passes the `Id<Brand>` directly to the driver — it is already
|
|
54
|
+
* the canonical string form.
|
|
55
|
+
*
|
|
56
|
+
* **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict
|
|
57
|
+
* `is()`. Throws `IdsError("invalid_id")` if the value from the database does not
|
|
58
|
+
* parse as a valid `Id<Brand>`.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* import { idColumnMysql } from "@smonn/ids/drizzle";
|
|
63
|
+
* import { createTimestampId } from "@smonn/ids";
|
|
64
|
+
*
|
|
65
|
+
* const usr = createTimestampId("usr");
|
|
66
|
+
* export const users = mysqlTable("users", { id: idColumnMysql(usr).primaryKey() });
|
|
67
|
+
* // users.id is Id<"usr"> end-to-end
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
function idColumnMysql(codec) {
|
|
71
|
+
return customType$1({
|
|
72
|
+
dataType() {
|
|
73
|
+
return "text";
|
|
74
|
+
},
|
|
75
|
+
toDriver(value) {
|
|
76
|
+
return value;
|
|
77
|
+
},
|
|
78
|
+
fromDriver(value) {
|
|
79
|
+
return readIdColumn(codec, value);
|
|
80
|
+
}
|
|
81
|
+
})();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Drizzle custom column type that stores an `Id<Brand>` as a canonical `text` value in SQLite.
|
|
85
|
+
*
|
|
86
|
+
* **Write path:** passes the `Id<Brand>` directly to the driver — it is already
|
|
87
|
+
* the canonical string form.
|
|
88
|
+
*
|
|
89
|
+
* **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict
|
|
90
|
+
* `is()`. Throws `IdsError("invalid_id")` if the value from the database does not
|
|
91
|
+
* parse as a valid `Id<Brand>`.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* import { idColumnSqlite } from "@smonn/ids/drizzle";
|
|
96
|
+
* import { createTimestampId } from "@smonn/ids";
|
|
97
|
+
*
|
|
98
|
+
* const usr = createTimestampId("usr");
|
|
99
|
+
* export const users = sqliteTable("users", { id: idColumnSqlite(usr).primaryKey() });
|
|
100
|
+
* // users.id is Id<"usr"> end-to-end
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
function idColumnSqlite(codec) {
|
|
104
|
+
return customType$2({
|
|
28
105
|
dataType() {
|
|
29
106
|
return "text";
|
|
30
107
|
},
|
|
@@ -36,7 +113,170 @@ function idColumn(codec) {
|
|
|
36
113
|
}
|
|
37
114
|
})();
|
|
38
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Drizzle custom column type for a **nullable** `Id<Brand>` column.
|
|
118
|
+
*
|
|
119
|
+
* Behaves identically to {@link idColumn} except that `null` and `undefined`
|
|
120
|
+
* driver values are passed through as `null` rather than throwing. Use for
|
|
121
|
+
* optional foreign keys, `LEFT JOIN` results, and any column that is
|
|
122
|
+
* legitimately absent.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* import { nullableIdColumn } from "@smonn/ids/drizzle";
|
|
127
|
+
* import { createTimestampId } from "@smonn/ids";
|
|
128
|
+
*
|
|
129
|
+
* const usr = createTimestampId("usr");
|
|
130
|
+
* export const posts = pgTable("posts", {
|
|
131
|
+
* authorId: nullableIdColumn(usr),
|
|
132
|
+
* });
|
|
133
|
+
* // posts.authorId is Id<"usr"> | null end-to-end
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
function nullableIdColumn(codec) {
|
|
137
|
+
return customType({
|
|
138
|
+
dataType() {
|
|
139
|
+
return "text";
|
|
140
|
+
},
|
|
141
|
+
toDriver(value) {
|
|
142
|
+
return value;
|
|
143
|
+
},
|
|
144
|
+
fromDriver(value) {
|
|
145
|
+
return readIdColumnNullable(codec, value);
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Drizzle custom column type that stores an `Id<Brand>` as a canonical SQL string value
|
|
151
|
+
* in PostgreSQL, with a client-side `.$defaultFn` so inserts that omit the field receive
|
|
152
|
+
* a freshly generated ID automatically.
|
|
153
|
+
*
|
|
154
|
+
* **Write path:** `.$defaultFn(() => codec.generate())` is wired — if the field is absent
|
|
155
|
+
* on insert, Drizzle calls `codec.generate()` to produce a new `Id<Brand>`.
|
|
156
|
+
*
|
|
157
|
+
* **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict `is()`.
|
|
158
|
+
* Throws `IdsError("invalid_id")` if the value from the database does not parse as a
|
|
159
|
+
* valid `Id<Brand>`.
|
|
160
|
+
*
|
|
161
|
+
* Requires a codec that exposes synchronous `generate()` — see {@link IdGeneratingCodec}.
|
|
162
|
+
* Only the **Timestamp codec** and **Reverse Timestamp codec** qualify; Opaque, Signed,
|
|
163
|
+
* Wrapped, and Digest codecs are a compile-time error.
|
|
164
|
+
*
|
|
165
|
+
* @param codec - The brand-scoped codec used to generate and parse values.
|
|
166
|
+
* @param options - Optional column configuration.
|
|
167
|
+
* @param options.columnType - SQL column type to use (default: `"text"`). Pass
|
|
168
|
+
* `"varchar(30)"` or `"char(26)"` to match an existing DDL or index strategy.
|
|
169
|
+
* The value is passed through verbatim — no validation is performed.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```ts
|
|
173
|
+
* import { generatedIdColumn } from "@smonn/ids/drizzle";
|
|
174
|
+
* import { createTimestampId } from "@smonn/ids";
|
|
175
|
+
*
|
|
176
|
+
* const usr = createTimestampId("usr");
|
|
177
|
+
* export const users = pgTable("users", { id: generatedIdColumn(usr).primaryKey() });
|
|
178
|
+
* // id is auto-filled on insert — no hand-supplied id needed
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
function generatedIdColumn(codec, options) {
|
|
182
|
+
const columnType = options?.columnType ?? "text";
|
|
183
|
+
return customType({
|
|
184
|
+
dataType() {
|
|
185
|
+
return columnType;
|
|
186
|
+
},
|
|
187
|
+
toDriver(value) {
|
|
188
|
+
return value;
|
|
189
|
+
},
|
|
190
|
+
fromDriver(value) {
|
|
191
|
+
return readIdColumn(codec, value);
|
|
192
|
+
}
|
|
193
|
+
})().$defaultFn(() => codec.generate());
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Drizzle custom column type that stores an `Id<Brand>` as a canonical `text` value
|
|
197
|
+
* in MySQL, with a client-side `.$defaultFn` so inserts that omit the field receive
|
|
198
|
+
* a freshly generated ID automatically.
|
|
199
|
+
*
|
|
200
|
+
* **Write path:** `.$defaultFn(() => codec.generate())` is wired — if the field is absent
|
|
201
|
+
* on insert, Drizzle calls `codec.generate()` to produce a new `Id<Brand>`.
|
|
202
|
+
*
|
|
203
|
+
* **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict `is()`.
|
|
204
|
+
* Throws `IdsError("invalid_id")` if the value from the database does not parse as a
|
|
205
|
+
* valid `Id<Brand>`.
|
|
206
|
+
*
|
|
207
|
+
* Requires a codec that exposes synchronous `generate()` — see {@link IdGeneratingCodec}.
|
|
208
|
+
* Only the **Timestamp codec** and **Reverse Timestamp codec** qualify; Opaque, Signed,
|
|
209
|
+
* Wrapped, and Digest codecs are a compile-time error.
|
|
210
|
+
*
|
|
211
|
+
* @param codec - The brand-scoped codec used to generate and parse values.
|
|
212
|
+
* Column type is always `text` and cannot be overridden.
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```ts
|
|
216
|
+
* import { generatedIdColumnMysql } from "@smonn/ids/drizzle";
|
|
217
|
+
* import { createTimestampId } from "@smonn/ids";
|
|
218
|
+
*
|
|
219
|
+
* const usr = createTimestampId("usr");
|
|
220
|
+
* export const users = mysqlTable("users", { id: generatedIdColumnMysql(usr).primaryKey() });
|
|
221
|
+
* // id is auto-filled on insert — no hand-supplied id needed
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
function generatedIdColumnMysql(codec) {
|
|
225
|
+
return customType$1({
|
|
226
|
+
dataType() {
|
|
227
|
+
return "text";
|
|
228
|
+
},
|
|
229
|
+
toDriver(value) {
|
|
230
|
+
return value;
|
|
231
|
+
},
|
|
232
|
+
fromDriver(value) {
|
|
233
|
+
return readIdColumn(codec, value);
|
|
234
|
+
}
|
|
235
|
+
})().$defaultFn(() => codec.generate());
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Drizzle custom column type that stores an `Id<Brand>` as a canonical `text` value
|
|
239
|
+
* in SQLite, with a client-side `.$defaultFn` so inserts that omit the field receive
|
|
240
|
+
* a freshly generated ID automatically.
|
|
241
|
+
*
|
|
242
|
+
* **Write path:** `.$defaultFn(() => codec.generate())` is wired — if the field is absent
|
|
243
|
+
* on insert, Drizzle calls `codec.generate()` to produce a new `Id<Brand>`.
|
|
244
|
+
*
|
|
245
|
+
* **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict `is()`.
|
|
246
|
+
* Throws `IdsError("invalid_id")` if the value from the database does not parse as a
|
|
247
|
+
* valid `Id<Brand>`.
|
|
248
|
+
*
|
|
249
|
+
* Requires a codec that exposes synchronous `generate()` — see {@link IdGeneratingCodec}.
|
|
250
|
+
* Only the **Timestamp codec** and **Reverse Timestamp codec** qualify; Opaque, Signed,
|
|
251
|
+
* Wrapped, and Digest codecs are a compile-time error.
|
|
252
|
+
*
|
|
253
|
+
* @param codec - The brand-scoped codec used to generate and parse values.
|
|
254
|
+
* Column type is always `text` and cannot be overridden.
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```ts
|
|
258
|
+
* import { generatedIdColumnSqlite } from "@smonn/ids/drizzle";
|
|
259
|
+
* import { createTimestampId } from "@smonn/ids";
|
|
260
|
+
*
|
|
261
|
+
* const usr = createTimestampId("usr");
|
|
262
|
+
* export const users = sqliteTable("users", { id: generatedIdColumnSqlite(usr).primaryKey() });
|
|
263
|
+
* // id is auto-filled on insert — no hand-supplied id needed
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
function generatedIdColumnSqlite(codec) {
|
|
267
|
+
return customType$2({
|
|
268
|
+
dataType() {
|
|
269
|
+
return "text";
|
|
270
|
+
},
|
|
271
|
+
toDriver(value) {
|
|
272
|
+
return value;
|
|
273
|
+
},
|
|
274
|
+
fromDriver(value) {
|
|
275
|
+
return readIdColumn(codec, value);
|
|
276
|
+
}
|
|
277
|
+
})().$defaultFn(() => codec.generate());
|
|
278
|
+
}
|
|
39
279
|
//#endregion
|
|
40
|
-
export { IdsError, idColumn, isIdsError };
|
|
280
|
+
export { IdsError, generatedIdColumn, generatedIdColumnMysql, generatedIdColumnSqlite, idColumn, idColumnMysql, idColumnSqlite, isIdsError, nullableIdColumn };
|
|
41
281
|
|
|
42
282
|
//# sourceMappingURL=drizzle.mjs.map
|
package/dist/drizzle.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drizzle.mjs","names":[],"sources":["../src/adapters/drizzle.ts"],"sourcesContent":["import {\n customType,\n type ConvertCustomConfig,\n type PgCustomColumnBuilder,\n} from \"drizzle-orm/pg-core\";\nimport { readIdColumn, type IdColumnCodec } from \"./adapter-types.js\";\nimport type { Id } from \"../types.js\";\n\n/** {@link IdsError} class, {@link isIdsError} type guard, and {@link IdsErrorCode} union — re-exported from `\"@smonn/ids\"` for convenience. */\nexport { IdsError, isIdsError, type IdsErrorCode } from \"../error.js\";\n\nexport type { IdColumnCodec };\n\n/**\n * Drizzle custom column type that stores an `Id<Brand>` as a canonical `text` value.\n *\n * **Write path:** passes the `Id<Brand>` directly to the driver — it is already\n * the canonical string form.\n *\n * **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict\n * `is()`. Data at rest should already be canonical per ADR-0003, but `safeParse`\n * is a safe boundary in case stale non-canonical values exist. Throws if the\n * value from the database does not parse as a valid `Id<Brand>`.\n *\n * @example\n * ```ts\n * import { idColumn } from \"@smonn/ids/drizzle\";\n * import { createTimestampId } from \"@smonn/ids\";\n *\n * const usr = createTimestampId(\"usr\");\n * export const users = pgTable(\"users\", { id: idColumn(usr).primaryKey() });\n * // users.id is Id<\"usr\"> end-to-end\n * ```\n */\nexport function idColumn<Brand extends string>(\n codec: IdColumnCodec<Brand>,\n): PgCustomColumnBuilder<ConvertCustomConfig<\"\", { data: Id<Brand>; driverData: string }>> {\n return customType<{ data: Id<Brand>; driverData: string }>({\n dataType() {\n return \"text\";\n },\n toDriver(value: Id<Brand>): string {\n return value;\n },\n fromDriver(value: string): Id<Brand> {\n return readIdColumn(codec, value);\n },\n })();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,SACd,OACyF;CACzF,OAAO,WAAoD;EACzD,WAAW;GACT,OAAO;EACT;EACA,SAAS,OAA0B;GACjC,OAAO;EACT;EACA,WAAW,OAA0B;GACnC,OAAO,aAAa,OAAO,KAAK;EAClC;CACF,CAAC,CAAC,CAAC;AACL"}
|
|
1
|
+
{"version":3,"file":"drizzle.mjs","names":["customTypeMysql","customTypeSqlite"],"sources":["../src/adapters/drizzle.ts"],"sourcesContent":["import {\n customType,\n type ConvertCustomConfig,\n type PgCustomColumnBuilder,\n} from \"drizzle-orm/pg-core\";\nimport {\n customType as customTypeMysql,\n type ConvertCustomConfig as ConvertCustomConfigMysql,\n type MySqlCustomColumnBuilder,\n} from \"drizzle-orm/mysql-core\";\nimport {\n customType as customTypeSqlite,\n type ConvertCustomConfig as ConvertCustomConfigSqlite,\n type SQLiteCustomColumnBuilder,\n} from \"drizzle-orm/sqlite-core\";\nimport type { HasDefault, HasRuntimeDefault } from \"drizzle-orm/column-builder\";\nimport { readIdColumn, readIdColumnNullable, type IdColumnCodec } from \"./adapter-types.js\";\nimport type { Id } from \"../types.js\";\n\n/** {@link IdsError} class, {@link isIdsError} type guard, and {@link IdsErrorCode} union — re-exported from `\"@smonn/ids\"` for convenience. */\nexport { IdsError, isIdsError, type IdsErrorCode } from \"../error.js\";\n\nexport type { IdColumnCodec };\n\n/**\n * Extension of {@link IdColumnCodec} that also exposes synchronous `generate()`.\n * Required by the `generatedIdColumn` family so that Drizzle's `.$defaultFn` can\n * produce IDs at write time without explicit call-site wiring. Only the **Timestamp\n * codec** and **Reverse Timestamp codec** satisfy this; async-generate codecs\n * (Opaque, Signed, Wrapped, Digest) do not and are a compile-time error.\n */\nexport type IdGeneratingCodec<Brand extends string> = IdColumnCodec<Brand> & {\n generate(): Id<Brand>;\n};\n\n/**\n * Drizzle custom column type that stores an `Id<Brand>` as a canonical SQL string value in PostgreSQL.\n *\n * **Write path:** passes the `Id<Brand>` directly to the driver — it is already\n * the canonical string form.\n *\n * **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict\n * `is()`. Data at rest should already be canonical per ADR-0003, but `safeParse`\n * is a safe boundary in case stale non-canonical values exist. Throws if the\n * value from the database does not parse as a valid `Id<Brand>`.\n *\n * @param codec - The brand-scoped codec used to parse values read from the database.\n * @param options - Optional column configuration.\n * @param options.columnType - SQL column type to use (default: `\"text\"`). Pass\n * `\"varchar(30)\"` or `\"char(26)\"` to match an existing DDL or index strategy.\n * The value is passed through verbatim — no validation is performed.\n *\n * @example\n * ```ts\n * import { idColumn } from \"@smonn/ids/drizzle\";\n * import { createTimestampId } from \"@smonn/ids\";\n *\n * const usr = createTimestampId(\"usr\");\n * // default: text column\n * export const users = pgTable(\"users\", { id: idColumn(usr).primaryKey() });\n * // explicit varchar column\n * export const orgs = pgTable(\"orgs\", { id: idColumn(usr, { columnType: \"varchar(30)\" }).primaryKey() });\n * ```\n */\nexport function idColumn<Brand extends string>(\n codec: IdColumnCodec<Brand>,\n options?: { columnType?: string },\n): PgCustomColumnBuilder<ConvertCustomConfig<\"\", { data: Id<Brand>; driverData: string }>> {\n const columnType = options?.columnType ?? \"text\";\n return customType<{ data: Id<Brand>; driverData: string }>({\n dataType() {\n return columnType;\n },\n toDriver(value: Id<Brand>): string {\n return value;\n },\n fromDriver(value: string): Id<Brand> {\n return readIdColumn(codec, value);\n },\n })();\n}\n\n/**\n * Drizzle custom column type that stores an `Id<Brand>` as a canonical `text` value in MySQL.\n *\n * **Write path:** passes the `Id<Brand>` directly to the driver — it is already\n * the canonical string form.\n *\n * **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict\n * `is()`. Throws `IdsError(\"invalid_id\")` if the value from the database does not\n * parse as a valid `Id<Brand>`.\n *\n * @example\n * ```ts\n * import { idColumnMysql } from \"@smonn/ids/drizzle\";\n * import { createTimestampId } from \"@smonn/ids\";\n *\n * const usr = createTimestampId(\"usr\");\n * export const users = mysqlTable(\"users\", { id: idColumnMysql(usr).primaryKey() });\n * // users.id is Id<\"usr\"> end-to-end\n * ```\n */\nexport function idColumnMysql<Brand extends string>(\n codec: IdColumnCodec<Brand>,\n): MySqlCustomColumnBuilder<ConvertCustomConfigMysql<\"\", { data: Id<Brand>; driverData: string }>> {\n return customTypeMysql<{ data: Id<Brand>; driverData: string }>({\n dataType() {\n return \"text\";\n },\n toDriver(value: Id<Brand>): string {\n return value;\n },\n fromDriver(value: string): Id<Brand> {\n return readIdColumn(codec, value);\n },\n })();\n}\n\n/**\n * Drizzle custom column type that stores an `Id<Brand>` as a canonical `text` value in SQLite.\n *\n * **Write path:** passes the `Id<Brand>` directly to the driver — it is already\n * the canonical string form.\n *\n * **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict\n * `is()`. Throws `IdsError(\"invalid_id\")` if the value from the database does not\n * parse as a valid `Id<Brand>`.\n *\n * @example\n * ```ts\n * import { idColumnSqlite } from \"@smonn/ids/drizzle\";\n * import { createTimestampId } from \"@smonn/ids\";\n *\n * const usr = createTimestampId(\"usr\");\n * export const users = sqliteTable(\"users\", { id: idColumnSqlite(usr).primaryKey() });\n * // users.id is Id<\"usr\"> end-to-end\n * ```\n */\nexport function idColumnSqlite<Brand extends string>(\n codec: IdColumnCodec<Brand>,\n): SQLiteCustomColumnBuilder<\n ConvertCustomConfigSqlite<\"\", { data: Id<Brand>; driverData: string }>\n> {\n return customTypeSqlite<{ data: Id<Brand>; driverData: string }>({\n dataType() {\n return \"text\";\n },\n toDriver(value: Id<Brand>): string {\n return value;\n },\n fromDriver(value: string): Id<Brand> {\n return readIdColumn(codec, value);\n },\n })();\n}\n\n/**\n * Drizzle custom column type for a **nullable** `Id<Brand>` column.\n *\n * Behaves identically to {@link idColumn} except that `null` and `undefined`\n * driver values are passed through as `null` rather than throwing. Use for\n * optional foreign keys, `LEFT JOIN` results, and any column that is\n * legitimately absent.\n *\n * @example\n * ```ts\n * import { nullableIdColumn } from \"@smonn/ids/drizzle\";\n * import { createTimestampId } from \"@smonn/ids\";\n *\n * const usr = createTimestampId(\"usr\");\n * export const posts = pgTable(\"posts\", {\n * authorId: nullableIdColumn(usr),\n * });\n * // posts.authorId is Id<\"usr\"> | null end-to-end\n * ```\n */\nexport function nullableIdColumn<Brand extends string>(\n codec: IdColumnCodec<Brand>,\n): PgCustomColumnBuilder<\n ConvertCustomConfig<\"\", { data: Id<Brand> | null; driverData: string | null }>\n> {\n return customType<{ data: Id<Brand> | null; driverData: string | null }>({\n dataType() {\n return \"text\";\n },\n toDriver(value: Id<Brand> | null): string | null {\n return value;\n },\n fromDriver(value: string | null): Id<Brand> | null {\n return readIdColumnNullable(codec, value);\n },\n })();\n}\n\n/**\n * Drizzle custom column type that stores an `Id<Brand>` as a canonical SQL string value\n * in PostgreSQL, with a client-side `.$defaultFn` so inserts that omit the field receive\n * a freshly generated ID automatically.\n *\n * **Write path:** `.$defaultFn(() => codec.generate())` is wired — if the field is absent\n * on insert, Drizzle calls `codec.generate()` to produce a new `Id<Brand>`.\n *\n * **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict `is()`.\n * Throws `IdsError(\"invalid_id\")` if the value from the database does not parse as a\n * valid `Id<Brand>`.\n *\n * Requires a codec that exposes synchronous `generate()` — see {@link IdGeneratingCodec}.\n * Only the **Timestamp codec** and **Reverse Timestamp codec** qualify; Opaque, Signed,\n * Wrapped, and Digest codecs are a compile-time error.\n *\n * @param codec - The brand-scoped codec used to generate and parse values.\n * @param options - Optional column configuration.\n * @param options.columnType - SQL column type to use (default: `\"text\"`). Pass\n * `\"varchar(30)\"` or `\"char(26)\"` to match an existing DDL or index strategy.\n * The value is passed through verbatim — no validation is performed.\n *\n * @example\n * ```ts\n * import { generatedIdColumn } from \"@smonn/ids/drizzle\";\n * import { createTimestampId } from \"@smonn/ids\";\n *\n * const usr = createTimestampId(\"usr\");\n * export const users = pgTable(\"users\", { id: generatedIdColumn(usr).primaryKey() });\n * // id is auto-filled on insert — no hand-supplied id needed\n * ```\n */\nexport function generatedIdColumn<Brand extends string>(\n codec: IdGeneratingCodec<Brand>,\n options?: { columnType?: string },\n): HasRuntimeDefault<\n HasDefault<\n PgCustomColumnBuilder<ConvertCustomConfig<\"\", { data: Id<Brand>; driverData: string }>>\n >\n> {\n const columnType = options?.columnType ?? \"text\";\n return customType<{ data: Id<Brand>; driverData: string }>({\n dataType() {\n return columnType;\n },\n toDriver(value: Id<Brand>): string {\n return value;\n },\n fromDriver(value: string): Id<Brand> {\n return readIdColumn(codec, value);\n },\n })().$defaultFn(() => codec.generate());\n}\n\n/**\n * Drizzle custom column type that stores an `Id<Brand>` as a canonical `text` value\n * in MySQL, with a client-side `.$defaultFn` so inserts that omit the field receive\n * a freshly generated ID automatically.\n *\n * **Write path:** `.$defaultFn(() => codec.generate())` is wired — if the field is absent\n * on insert, Drizzle calls `codec.generate()` to produce a new `Id<Brand>`.\n *\n * **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict `is()`.\n * Throws `IdsError(\"invalid_id\")` if the value from the database does not parse as a\n * valid `Id<Brand>`.\n *\n * Requires a codec that exposes synchronous `generate()` — see {@link IdGeneratingCodec}.\n * Only the **Timestamp codec** and **Reverse Timestamp codec** qualify; Opaque, Signed,\n * Wrapped, and Digest codecs are a compile-time error.\n *\n * @param codec - The brand-scoped codec used to generate and parse values.\n * Column type is always `text` and cannot be overridden.\n *\n * @example\n * ```ts\n * import { generatedIdColumnMysql } from \"@smonn/ids/drizzle\";\n * import { createTimestampId } from \"@smonn/ids\";\n *\n * const usr = createTimestampId(\"usr\");\n * export const users = mysqlTable(\"users\", { id: generatedIdColumnMysql(usr).primaryKey() });\n * // id is auto-filled on insert — no hand-supplied id needed\n * ```\n */\nexport function generatedIdColumnMysql<Brand extends string>(\n codec: IdGeneratingCodec<Brand>,\n): HasRuntimeDefault<\n HasDefault<\n MySqlCustomColumnBuilder<ConvertCustomConfigMysql<\"\", { data: Id<Brand>; driverData: string }>>\n >\n> {\n return customTypeMysql<{ data: Id<Brand>; driverData: string }>({\n dataType() {\n return \"text\";\n },\n toDriver(value: Id<Brand>): string {\n return value;\n },\n fromDriver(value: string): Id<Brand> {\n return readIdColumn(codec, value);\n },\n })().$defaultFn(() => codec.generate());\n}\n\n/**\n * Drizzle custom column type that stores an `Id<Brand>` as a canonical `text` value\n * in SQLite, with a client-side `.$defaultFn` so inserts that omit the field receive\n * a freshly generated ID automatically.\n *\n * **Write path:** `.$defaultFn(() => codec.generate())` is wired — if the field is absent\n * on insert, Drizzle calls `codec.generate()` to produce a new `Id<Brand>`.\n *\n * **Read path:** normalises the raw DB string via `codec.safeParse()`, not strict `is()`.\n * Throws `IdsError(\"invalid_id\")` if the value from the database does not parse as a\n * valid `Id<Brand>`.\n *\n * Requires a codec that exposes synchronous `generate()` — see {@link IdGeneratingCodec}.\n * Only the **Timestamp codec** and **Reverse Timestamp codec** qualify; Opaque, Signed,\n * Wrapped, and Digest codecs are a compile-time error.\n *\n * @param codec - The brand-scoped codec used to generate and parse values.\n * Column type is always `text` and cannot be overridden.\n *\n * @example\n * ```ts\n * import { generatedIdColumnSqlite } from \"@smonn/ids/drizzle\";\n * import { createTimestampId } from \"@smonn/ids\";\n *\n * const usr = createTimestampId(\"usr\");\n * export const users = sqliteTable(\"users\", { id: generatedIdColumnSqlite(usr).primaryKey() });\n * // id is auto-filled on insert — no hand-supplied id needed\n * ```\n */\nexport function generatedIdColumnSqlite<Brand extends string>(\n codec: IdGeneratingCodec<Brand>,\n): HasRuntimeDefault<\n HasDefault<\n SQLiteCustomColumnBuilder<\n ConvertCustomConfigSqlite<\"\", { data: Id<Brand>; driverData: string }>\n >\n >\n> {\n return customTypeSqlite<{ data: Id<Brand>; driverData: string }>({\n dataType() {\n return \"text\";\n },\n toDriver(value: Id<Brand>): string {\n return value;\n },\n fromDriver(value: string): Id<Brand> {\n return readIdColumn(codec, value);\n },\n })().$defaultFn(() => codec.generate());\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,SAAgB,SACd,OACA,SACyF;CACzF,MAAM,aAAa,SAAS,cAAc;CAC1C,OAAO,WAAoD;EACzD,WAAW;GACT,OAAO;EACT;EACA,SAAS,OAA0B;GACjC,OAAO;EACT;EACA,WAAW,OAA0B;GACnC,OAAO,aAAa,OAAO,KAAK;EAClC;CACF,CAAC,CAAC,CAAC;AACL;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,cACd,OACiG;CACjG,OAAOA,aAAyD;EAC9D,WAAW;GACT,OAAO;EACT;EACA,SAAS,OAA0B;GACjC,OAAO;EACT;EACA,WAAW,OAA0B;GACnC,OAAO,aAAa,OAAO,KAAK;EAClC;CACF,CAAC,CAAC,CAAC;AACL;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,eACd,OAGA;CACA,OAAOC,aAA0D;EAC/D,WAAW;GACT,OAAO;EACT;EACA,SAAS,OAA0B;GACjC,OAAO;EACT;EACA,WAAW,OAA0B;GACnC,OAAO,aAAa,OAAO,KAAK;EAClC;CACF,CAAC,CAAC,CAAC;AACL;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,iBACd,OAGA;CACA,OAAO,WAAkE;EACvE,WAAW;GACT,OAAO;EACT;EACA,SAAS,OAAwC;GAC/C,OAAO;EACT;EACA,WAAW,OAAwC;GACjD,OAAO,qBAAqB,OAAO,KAAK;EAC1C;CACF,CAAC,CAAC,CAAC;AACL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,kBACd,OACA,SAKA;CACA,MAAM,aAAa,SAAS,cAAc;CAC1C,OAAO,WAAoD;EACzD,WAAW;GACT,OAAO;EACT;EACA,SAAS,OAA0B;GACjC,OAAO;EACT;EACA,WAAW,OAA0B;GACnC,OAAO,aAAa,OAAO,KAAK;EAClC;CACF,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,MAAM,SAAS,CAAC;AACxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,uBACd,OAKA;CACA,OAAOD,aAAyD;EAC9D,WAAW;GACT,OAAO;EACT;EACA,SAAS,OAA0B;GACjC,OAAO;EACT;EACA,WAAW,OAA0B;GACnC,OAAO,aAAa,OAAO,KAAK;EAClC;CACF,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,MAAM,SAAS,CAAC;AACxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,wBACd,OAOA;CACA,OAAOC,aAA0D;EAC/D,WAAW;GACT,OAAO;EACT;EACA,SAAS,OAA0B;GACjC,OAAO;EACT;EACA,WAAW,OAA0B;GACnC,OAAO,aAAa,OAAO,KAAK;EAClC;CACF,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,MAAM,SAAS,CAAC;AACxC"}
|
package/dist/express.d.mts
CHANGED
|
@@ -12,7 +12,7 @@ declare class IdParamError extends Error {
|
|
|
12
12
|
readonly reason: "brand_mismatch" | "malformed";
|
|
13
13
|
constructor(reason: "brand_mismatch" | "malformed", status: number);
|
|
14
14
|
}
|
|
15
|
-
/** Options for `idParam`. All fields are optional. */
|
|
15
|
+
/** Options for `idParam` and `idQuery`. All fields are optional. */
|
|
16
16
|
type IdParamOptions = {
|
|
17
17
|
/**
|
|
18
18
|
* Called instead of forwarding to `next(err)` when provided. The hook owns the response
|
|
@@ -77,6 +77,48 @@ type IdParamOptions = {
|
|
|
77
77
|
* ```
|
|
78
78
|
*/
|
|
79
79
|
declare function idParam<ParamKey extends string, Brand extends string>(paramName: ParamKey, codec: IdCodec<Brand>, options?: IdParamOptions): (req: Request, res: Response<unknown, Record<ParamKey, Id<Brand>>>, next: NextFunction) => void;
|
|
80
|
+
/**
|
|
81
|
+
* Express middleware that validates a named query-string param against a codec via `safeParse`.
|
|
82
|
+
*
|
|
83
|
+
* Same failure contract as `idParam` — same `IdParamOptions` / `IdParamFailure` shape, same
|
|
84
|
+
* `IdParamError` forwarded to `next(err)` — but reads `req.query[queryName]` instead of
|
|
85
|
+
* `req.params[queryName]`.
|
|
86
|
+
*
|
|
87
|
+
* **Default (no options):** calls `next(err)` with an `IdParamError` carrying `status` and
|
|
88
|
+
* `reason`, so the app's existing error-handling middleware controls rendering. The adapter
|
|
89
|
+
* does not write a response body itself.
|
|
90
|
+
*
|
|
91
|
+
* **`options.onError`:** when provided, the hook owns the response entirely — the adapter does
|
|
92
|
+
* not call `next(err)`.
|
|
93
|
+
*
|
|
94
|
+
* **`options.status`:** remaps the default HTTP status for a reason without a full handler.
|
|
95
|
+
*
|
|
96
|
+
* - **Brand mismatch (`invalid_prefix`) → `reason: "brand_mismatch"`, default 404**
|
|
97
|
+
* - **Malformed or missing query param → `reason: "malformed"`, default 400**
|
|
98
|
+
*
|
|
99
|
+
* On success, stores the canonical `Id<Brand>` in `res.locals` under `queryName`
|
|
100
|
+
* and calls `next()`.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* import { idQuery, IdParamError } from "@smonn/ids/express";
|
|
105
|
+
* import { createTimestampId } from "@smonn/ids";
|
|
106
|
+
*
|
|
107
|
+
* const usr = createTimestampId("usr");
|
|
108
|
+
*
|
|
109
|
+
* // Default: forwards error to app error-handling middleware
|
|
110
|
+
* // GET /users?userId=usr_...
|
|
111
|
+
* app.get("/users", idQuery("userId", usr), (req, res) => {
|
|
112
|
+
* const userId = res.locals.userId; // Id<"usr">, canonical
|
|
113
|
+
* });
|
|
114
|
+
*
|
|
115
|
+
* // Override: consumer fully owns the response
|
|
116
|
+
* app.get("/search", idQuery("cursor", usr, {
|
|
117
|
+
* onError: (failure, req, res) => res.status(failure.status).json({ error: failure.reason }),
|
|
118
|
+
* }), handler);
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
declare function idQuery<ParamKey extends string, Brand extends string>(queryName: ParamKey, codec: IdCodec<Brand>, options?: IdParamOptions): (req: Request, res: Response<unknown, Record<ParamKey, Id<Brand>>>, next: NextFunction) => void;
|
|
80
122
|
//#endregion
|
|
81
|
-
export { IdParamError, type IdParamFailure, IdParamOptions, idParam };
|
|
123
|
+
export { IdParamError, type IdParamFailure, IdParamOptions, idParam, idQuery };
|
|
82
124
|
//# sourceMappingURL=express.d.mts.map
|
package/dist/express.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.d.mts","names":[],"sources":["../src/adapters/express.ts"],"mappings":";;;;;;AAUA;;;cAAa,YAAA,SAAqB,KAAA;EAAA,SACvB,MAAA;EAAA,SACA,MAAA;EAET,WAAA,CAAY,MAAA,kCAAwC,MAAA;AAAA;;KAS1C,cAAA;EAT0C;AAAA;AAStD;;EAKE,OAAA,IAAW,OAAA,EAAS,cAAA,EAAgB,GAAA,EAAK,OAAA,EAAS,GAAA,EAAK,QAAA,EAAU,IAAA,EAAM,YAAA;;;;;EAKvE,MAAA;IAAW,cAAA;IAAyB,SAAA;EAAA;AAAA;;;;;;;;;;;AAAA;AAmDtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAI6E;iBAJ7D,OAAA,gDACd,SAAA,EAAW,QAAA,EACX,KAAA,EAAO,OAAA,CAAQ,KAAA,GACf,OAAA,GAAU,cAAA,IACR,GAAA,EAAK,OAAA,EAAS,GAAA,EAAK,QAAA,UAAkB,MAAA,CAAO,QAAA,EAAU,EAAA,CAAG,KAAA,KAAU,IAAA,EAAM,YAAA"}
|
|
1
|
+
{"version":3,"file":"express.d.mts","names":[],"sources":["../src/adapters/express.ts"],"mappings":";;;;;;AAUA;;;cAAa,YAAA,SAAqB,KAAA;EAAA,SACvB,MAAA;EAAA,SACA,MAAA;EAET,WAAA,CAAY,MAAA,kCAAwC,MAAA;AAAA;;KAS1C,cAAA;EAT0C;AAAA;AAStD;;EAKE,OAAA,IAAW,OAAA,EAAS,cAAA,EAAgB,GAAA,EAAK,OAAA,EAAS,GAAA,EAAK,QAAA,EAAU,IAAA,EAAM,YAAA;;;;;EAKvE,MAAA;IAAW,cAAA;IAAyB,SAAA;EAAA;AAAA;;;;;;;;;;;AAAA;AAmDtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAI6E;iBAJ7D,OAAA,gDACd,SAAA,EAAW,QAAA,EACX,KAAA,EAAO,OAAA,CAAQ,KAAA,GACf,OAAA,GAAU,cAAA,IACR,GAAA,EAAK,OAAA,EAAS,GAAA,EAAK,QAAA,UAAkB,MAAA,CAAO,QAAA,EAAU,EAAA,CAAG,KAAA,KAAU,IAAA,EAAM,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA;;;;;;;;;iBAJ7D,OAAA,gDACd,SAAA,EAAW,QAAA,EACX,KAAA,EAAO,OAAA,CAAQ,KAAA,GACf,OAAA,GAAU,cAAA,IACR,GAAA,EAAK,OAAA,EAAS,GAAA,EAAK,QAAA,UAAkB,MAAA,CAAO,QAAA,EAAU,EAAA,CAAG,KAAA,KAAU,IAAA,EAAM,YAAA"}
|