@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.
Files changed (46) hide show
  1. package/README.md +3 -3
  2. package/dist/{adapter-types-7wWdELSh.mjs → adapter-types-CjzFNDcJ.mjs} +7 -2
  3. package/dist/adapter-types-CjzFNDcJ.mjs.map +1 -0
  4. package/dist/cli.mjs +30 -22
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/drizzle.d.mts +202 -3
  7. package/dist/drizzle.d.mts.map +1 -1
  8. package/dist/drizzle.mjs +245 -5
  9. package/dist/drizzle.mjs.map +1 -1
  10. package/dist/express.d.mts +44 -2
  11. package/dist/express.d.mts.map +1 -1
  12. package/dist/express.mjs +60 -2
  13. package/dist/express.mjs.map +1 -1
  14. package/dist/fastify.d.mts +49 -2
  15. package/dist/fastify.d.mts.map +1 -1
  16. package/dist/fastify.mjs +61 -2
  17. package/dist/fastify.mjs.map +1 -1
  18. package/dist/hono.d.mts +44 -2
  19. package/dist/hono.d.mts.map +1 -1
  20. package/dist/hono.mjs +54 -2
  21. package/dist/hono.mjs.map +1 -1
  22. package/dist/kysely.d.mts +103 -2
  23. package/dist/kysely.d.mts.map +1 -1
  24. package/dist/kysely.mjs +105 -2
  25. package/dist/kysely.mjs.map +1 -1
  26. package/dist/mikro-orm.d.mts +81 -3
  27. package/dist/mikro-orm.d.mts.map +1 -1
  28. package/dist/mikro-orm.mjs +86 -4
  29. package/dist/mikro-orm.mjs.map +1 -1
  30. package/dist/nestjs.mjs +1 -1
  31. package/dist/{opaque-COAcIIY4.mjs → opaque-Dle3CmSE.mjs} +18 -10
  32. package/dist/opaque-Dle3CmSE.mjs.map +1 -0
  33. package/dist/opaque.d.mts +16 -10
  34. package/dist/opaque.d.mts.map +1 -1
  35. package/dist/opaque.mjs +1 -1
  36. package/dist/prisma.d.mts +135 -3
  37. package/dist/prisma.d.mts.map +1 -1
  38. package/dist/prisma.mjs +141 -3
  39. package/dist/prisma.mjs.map +1 -1
  40. package/dist/typeorm.d.mts +84 -1
  41. package/dist/typeorm.d.mts.map +1 -1
  42. package/dist/typeorm.mjs +87 -2
  43. package/dist/typeorm.mjs.map +1 -1
  44. package/package.json +1 -1
  45. package/dist/adapter-types-7wWdELSh.mjs.map +0 -1
  46. package/dist/opaque-COAcIIY4.mjs.map +0 -1
@@ -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
- * Drizzle custom column type that stores an `Id<Brand>` as a canonical `text` value.
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 idColumn<Brand extends string>(codec: IdColumnCodec<Brand>): PgCustomColumnBuilder<ConvertCustomConfig<"", {
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
@@ -1 +1 @@
1
- {"version":3,"file":"drizzle.d.mts","names":[],"sources":["../src/adapters/drizzle.ts"],"mappings":";;;;;;AAkCA;;;;;;;;;;;;;;;;;;;;;AAAA,iBAAgB,QAAA,uBACd,KAAA,EAAO,aAAA,CAAc,KAAA,IACpB,qBAAA,CAAsB,mBAAA;EAA0B,IAAA,EAAM,EAAA,CAAG,KAAA;EAAQ,UAAA;AAAA"}
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-7wWdELSh.mjs";
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 `text` value.
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
- * // users.id is Id<"usr"> end-to-end
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
@@ -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"}
@@ -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
@@ -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"}