@sedrino/drizzle 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  Utilities for using Temporal with Drizzle (SQLite for now).
4
4
 
5
+ ## Docs
6
+
7
+ - `docs/sqlite.md` - SQLite + Drizzle best practices (and how to use this library).
8
+ - `docs/plan.md` - Proposed additions aligned with Mostica best practices.
9
+
5
10
  ## Install
6
11
 
7
12
  ```bash
@@ -22,9 +27,11 @@ import {
22
27
  export const tasks = sqliteTable("tasks", {
23
28
  id: text("id").primaryKey(),
24
29
  title: text("title").notNull(),
25
- dueDate: temporalPlainDateText("due_date").nullable(),
30
+ // You can either pass explicit DB column names (as shown here), or
31
+ // omit them and use Drizzle `casing: "snake_case"` (see docs/sqlite.md).
32
+ dueDate: temporalPlainDateText("due_date"),
26
33
  createdAt: temporalInstantEpochMs("created_at_ms").notNull(),
27
- startsAt: temporalZonedDateTimeText("starts_at").nullable(),
34
+ startsAt: temporalZonedDateTimeText("starts_at"),
28
35
  // startsAt: zonedDateTimeParts("starts_at"),
29
36
  });
30
37
  ```
package/dist/sqlite.d.ts CHANGED
@@ -2,7 +2,7 @@ import { customType, integer, text } from "drizzle-orm/sqlite-core";
2
2
  import "temporal-polyfill/global";
3
3
  /**
4
4
  * Runtime Temporal resolution:
5
- * - pass { Temporal } from "@js-temporal/polyfill" if needed
5
+ * - pass { Temporal } from "temporal-polyfill" if needed
6
6
  * - otherwise uses globalThis.Temporal
7
7
  */
8
8
  export type TemporalRuntime = typeof Temporal;
@@ -11,15 +11,27 @@ type ToStringPrecision = {
11
11
  smallestUnit?: SmallestUnit;
12
12
  roundingMode?: "ceil" | "floor" | "trunc" | "halfExpand";
13
13
  };
14
+ /**
15
+ * SQLite expression for "now" in epoch milliseconds.
16
+ *
17
+ * Uses strftime so we include milliseconds (not just whole seconds).
18
+ * Suitable for `.default(epochMsNow())` on INTEGER columns.
19
+ */
20
+ export declare const epochMsNow: () => import("drizzle-orm").SQL<number>;
14
21
  /** PlainDate -> TEXT "YYYY-MM-DD" */
15
- export declare const temporalPlainDateText: (name: string, opts?: {
22
+ export declare const temporalPlainDateText: (nameOrOpts?: string | {
23
+ Temporal?: TemporalRuntime;
24
+ }, maybeOpts?: {
16
25
  Temporal?: TemporalRuntime;
17
26
  }) => ReturnType<ReturnType<typeof customType<{
18
27
  data: Temporal.PlainDate;
19
28
  driverData: string;
20
29
  }>>>;
21
30
  /** PlainTime -> TEXT "HH:mm:ss.sssssssss" (precision controlled via smallestUnit) */
22
- export declare const temporalPlainTimeText: (name: string, opts?: {
31
+ export declare const temporalPlainTimeText: (nameOrOpts?: string | {
32
+ Temporal?: TemporalRuntime;
33
+ precision?: ToStringPrecision;
34
+ }, maybeOpts?: {
23
35
  Temporal?: TemporalRuntime;
24
36
  precision?: ToStringPrecision;
25
37
  }) => ReturnType<ReturnType<typeof customType<{
@@ -27,7 +39,10 @@ export declare const temporalPlainTimeText: (name: string, opts?: {
27
39
  driverData: string;
28
40
  }>>>;
29
41
  /** PlainDateTime -> TEXT "YYYY-MM-DDTHH:mm:ss.sssssssss" */
30
- export declare const temporalPlainDateTimeText: (name: string, opts?: {
42
+ export declare const temporalPlainDateTimeText: (nameOrOpts?: string | {
43
+ Temporal?: TemporalRuntime;
44
+ precision?: ToStringPrecision;
45
+ }, maybeOpts?: {
31
46
  Temporal?: TemporalRuntime;
32
47
  precision?: ToStringPrecision;
33
48
  }) => ReturnType<ReturnType<typeof customType<{
@@ -35,14 +50,18 @@ export declare const temporalPlainDateTimeText: (name: string, opts?: {
35
50
  driverData: string;
36
51
  }>>>;
37
52
  /** PlainYearMonth -> TEXT "YYYY-MM" (plus optional calendar annotation if non-ISO) */
38
- export declare const temporalPlainYearMonthText: (name: string, opts?: {
53
+ export declare const temporalPlainYearMonthText: (nameOrOpts?: string | {
54
+ Temporal?: TemporalRuntime;
55
+ }, maybeOpts?: {
39
56
  Temporal?: TemporalRuntime;
40
57
  }) => ReturnType<ReturnType<typeof customType<{
41
58
  data: Temporal.PlainYearMonth;
42
59
  driverData: string;
43
60
  }>>>;
44
61
  /** PlainMonthDay -> TEXT "MM-DD" (plus optional calendar annotation if non-ISO) */
45
- export declare const temporalPlainMonthDayText: (name: string, opts?: {
62
+ export declare const temporalPlainMonthDayText: (nameOrOpts?: string | {
63
+ Temporal?: TemporalRuntime;
64
+ }, maybeOpts?: {
46
65
  Temporal?: TemporalRuntime;
47
66
  }) => ReturnType<ReturnType<typeof customType<{
48
67
  data: Temporal.PlainMonthDay;
@@ -53,7 +72,9 @@ export declare const temporalPlainMonthDayText: (name: string, opts?: {
53
72
  * Note: if millis/micros/nanos exceed 999, parsing back yields an
54
73
  * equal-but-differently-balanced Duration.
55
74
  */
56
- export declare const temporalDurationText: (name: string, opts?: {
75
+ export declare const temporalDurationText: (nameOrOpts?: string | {
76
+ Temporal?: TemporalRuntime;
77
+ }, maybeOpts?: {
57
78
  Temporal?: TemporalRuntime;
58
79
  }) => ReturnType<ReturnType<typeof customType<{
59
80
  data: Temporal.Duration;
@@ -64,7 +85,9 @@ export declare const temporalDurationText: (name: string, opts?: {
64
85
  * Great for sorting/filtering by actual time.
65
86
  * Tradeoff: millisecond precision only.
66
87
  */
67
- export declare const temporalInstantEpochMs: (name: string, opts?: {
88
+ export declare const temporalInstantEpochMs: (nameOrOpts?: string | {
89
+ Temporal?: TemporalRuntime;
90
+ }, maybeOpts?: {
68
91
  Temporal?: TemporalRuntime;
69
92
  }) => ReturnType<ReturnType<typeof customType<{
70
93
  data: Temporal.Instant;
@@ -75,7 +98,10 @@ export declare const temporalInstantEpochMs: (name: string, opts?: {
75
98
  * Instant (lossless) -> TEXT ISO string, e.g. "2026-01-25T12:34:56.123456789Z"
76
99
  * Tradeoff: sorting by TEXT is chronological only if the format is stable.
77
100
  */
78
- export declare const temporalInstantIsoText: (name: string, opts?: {
101
+ export declare const temporalInstantIsoText: (nameOrOpts?: string | {
102
+ Temporal?: TemporalRuntime;
103
+ precision?: ToStringPrecision;
104
+ }, maybeOpts?: {
79
105
  Temporal?: TemporalRuntime;
80
106
  precision?: ToStringPrecision;
81
107
  }) => ReturnType<ReturnType<typeof customType<{
@@ -89,7 +115,10 @@ export declare const temporalInstantIsoText: (name: string, opts?: {
89
115
  * This is convenient, but if you need sorting/filtering by actual moment,
90
116
  * prefer the multi-column helper below (epochMs + timeZoneId + calendarId).
91
117
  */
92
- export declare const temporalZonedDateTimeText: (name: string, opts?: {
118
+ export declare const temporalZonedDateTimeText: (nameOrOpts?: string | {
119
+ Temporal?: TemporalRuntime;
120
+ precision?: ToStringPrecision;
121
+ }, maybeOpts?: {
93
122
  Temporal?: TemporalRuntime;
94
123
  precision?: ToStringPrecision;
95
124
  }) => ReturnType<ReturnType<typeof customType<{
@@ -114,4 +143,186 @@ export declare function unpackZonedDateTime(parts: {
114
143
  }, opts?: {
115
144
  Temporal?: TemporalRuntime;
116
145
  }): Temporal.ZonedDateTime;
146
+ type StringKeyOf<T> = Extract<keyof T, string>;
147
+ export type InstantPrefix<R> = {
148
+ [K in StringKeyOf<R>]: K extends `${infer P}EpochS` ? `${P}Nano` extends StringKeyOf<R> ? P : never : never;
149
+ }[StringKeyOf<R>];
150
+ export type ZonedDateTimeDualPrefix<R> = {
151
+ [K in StringKeyOf<R>]: K extends `${infer P}EpochS` ? `${P}Nano` extends StringKeyOf<R> ? `${P}TimeZoneId` extends StringKeyOf<R> ? `${P}Local` extends StringKeyOf<R> ? `${P}Offset` extends StringKeyOf<R> ? `${P}CalendarId` extends StringKeyOf<R> ? P : never : never : never : never : never : never;
152
+ }[StringKeyOf<R>];
153
+ export type UnpackFromRowOptions = {
154
+ Temporal?: TemporalRuntime;
155
+ /**
156
+ * When true, throws on missing/invalid values (recommended at the boundary).
157
+ * Defaults to true for unpack-from-row helpers.
158
+ */
159
+ validate?: boolean;
160
+ /**
161
+ * When true, additionally verifies that dual-write local/offset match what
162
+ * Temporal computes from (epoch, zone). This can fail across tzdb changes.
163
+ */
164
+ validateDualWriteLocalAndOffset?: boolean;
165
+ /**
166
+ * Used to canonicalize local/plain strings when validating dual-write fields.
167
+ * If not provided, uses the library defaults (nanosecond + trunc).
168
+ */
169
+ precision?: ToStringPrecision;
170
+ };
171
+ export declare const instantParts: <P extends string>(prefix: P, opts?: {
172
+ /**
173
+ * "explicit" (default): helpers choose DB column names (snake_case) unless overridden.
174
+ * "casing": omit DB column names and let Drizzle `casing` handle mapping.
175
+ */
176
+ mode?: "explicit" | "casing";
177
+ /** Overrides the derived DB prefix used in explicit mode. */
178
+ columnPrefix?: string;
179
+ /**
180
+ * Per-column DB name overrides (for existing schemas that don't match conventions).
181
+ * If provided for a column, it takes precedence over `mode`/`columnPrefix`.
182
+ */
183
+ columns?: Partial<{
184
+ epochS: string;
185
+ nano: string;
186
+ }>;
187
+ }) => { [K in `${P}EpochS` | `${P}Nano`]: ReturnType<typeof integer>; };
188
+ export declare const instantPartsNullable: <P extends string>(prefix: P, opts?: {
189
+ /**
190
+ * "explicit" (default): helpers choose DB column names (snake_case) unless overridden.
191
+ * "casing": omit DB column names and let Drizzle `casing` handle mapping.
192
+ */
193
+ mode?: "explicit" | "casing";
194
+ /** Overrides the derived DB prefix used in explicit mode. */
195
+ columnPrefix?: string;
196
+ /**
197
+ * Per-column DB name overrides (for existing schemas that don't match conventions).
198
+ * If provided for a column, it takes precedence over `mode`/`columnPrefix`.
199
+ */
200
+ columns?: Partial<{
201
+ epochS: string;
202
+ nano: string;
203
+ }>;
204
+ }) => { [K in `${P}EpochS` | `${P}Nano`]: ReturnType<typeof integer>; };
205
+ export declare function packInstantParts(instant: Temporal.Instant): {
206
+ epochS: number;
207
+ nano: number;
208
+ };
209
+ export declare function unpackInstantParts(parts: {
210
+ epochS: number;
211
+ nano: number;
212
+ }, opts?: {
213
+ Temporal?: TemporalRuntime;
214
+ }): Temporal.Instant;
215
+ /**
216
+ * Pack helper (type-safe): constrains `prefix` to actual columns present on the table/row type.
217
+ * This catches typos like "ocurdAt" at compile-time.
218
+ */
219
+ export declare const packInstant: <T extends object, P extends InstantPrefix<T>>(_tableOrRow: T, prefix: P, instant: Temporal.Instant) => { [K in `${P}EpochS` | `${P}Nano`]: number; };
220
+ export declare const packInstantNullable: <T extends object, P extends InstantPrefix<T>>(_tableOrRow: T, prefix: P, instant: Temporal.Instant | null | undefined) => { [K in `${P}EpochS` | `${P}Nano`]: number | null; };
221
+ type InstantKeyMap<R> = {
222
+ epochS: keyof R;
223
+ nano: keyof R;
224
+ };
225
+ export declare function unpackInstantFromRow<R extends Record<string, unknown>, P extends InstantPrefix<R>>(row: R, prefix: P, opts?: UnpackFromRowOptions): Temporal.Instant;
226
+ export declare function unpackInstantFromRow<R extends Record<string, unknown>, M extends InstantKeyMap<R>>(row: R, map: M, opts?: UnpackFromRowOptions): Temporal.Instant;
227
+ export declare function unpackInstantNullableFromRow<R extends Record<string, unknown>, P extends InstantPrefix<R>>(row: R, prefix: P, opts?: UnpackFromRowOptions): Temporal.Instant | null;
228
+ export declare function unpackInstantNullableFromRow<R extends Record<string, unknown>, M extends InstantKeyMap<R>>(row: R, map: M, opts?: UnpackFromRowOptions): Temporal.Instant | null;
229
+ export declare const zonedDateTimeDualParts: <P extends string>(prefix: P, opts?: {
230
+ /**
231
+ * "explicit" (default): helpers choose DB column names (snake_case) unless overridden.
232
+ * "casing": omit DB column names and let Drizzle `casing` handle mapping.
233
+ */
234
+ mode?: "explicit" | "casing";
235
+ /** Overrides the derived DB prefix used in explicit mode. */
236
+ columnPrefix?: string;
237
+ /**
238
+ * Per-column DB name overrides (for existing schemas that don't match conventions).
239
+ * If provided for a column, it takes precedence over `mode`/`columnPrefix`.
240
+ */
241
+ columns?: Partial<{
242
+ epochS: string;
243
+ nano: string;
244
+ timeZoneId: string;
245
+ local: string;
246
+ offset: string;
247
+ calendarId: string;
248
+ }>;
249
+ }) => { [K in `${P}EpochS` | `${P}Nano` | `${P}TimeZoneId` | `${P}Local` | `${P}Offset` | `${P}CalendarId`]: K extends `${string}EpochS` | `${string}Nano` ? ReturnType<typeof integer> : ReturnType<typeof text>; };
250
+ export declare const zonedDateTimeDualPartsNullable: <P extends string>(prefix: P, opts?: {
251
+ /**
252
+ * "explicit" (default): helpers choose DB column names (snake_case) unless overridden.
253
+ * "casing": omit DB column names and let Drizzle `casing` handle mapping.
254
+ */
255
+ mode?: "explicit" | "casing";
256
+ /** Overrides the derived DB prefix used in explicit mode. */
257
+ columnPrefix?: string;
258
+ /**
259
+ * Per-column DB name overrides (for existing schemas that don't match conventions).
260
+ * If provided for a column, it takes precedence over `mode`/`columnPrefix`.
261
+ */
262
+ columns?: Partial<{
263
+ epochS: string;
264
+ nano: string;
265
+ timeZoneId: string;
266
+ local: string;
267
+ offset: string;
268
+ calendarId: string;
269
+ }>;
270
+ }) => { [K in `${P}EpochS` | `${P}Nano` | `${P}TimeZoneId` | `${P}Local` | `${P}Offset` | `${P}CalendarId`]: K extends `${string}EpochS` | `${string}Nano` ? ReturnType<typeof integer> : ReturnType<typeof text>; };
271
+ export declare function packZonedDateTimeDualParts(zdt: Temporal.ZonedDateTime, opts?: {
272
+ precision?: ToStringPrecision;
273
+ }): {
274
+ epochS: number;
275
+ nano: number;
276
+ timeZoneId: string;
277
+ local: string;
278
+ offset: string;
279
+ calendarId: string;
280
+ };
281
+ export declare function packZonedDateTimeDualNullableParts(zdt: Temporal.ZonedDateTime | null | undefined, opts?: {
282
+ precision?: ToStringPrecision;
283
+ }): {
284
+ epochS: number;
285
+ nano: number;
286
+ timeZoneId: string;
287
+ local: string;
288
+ offset: string;
289
+ calendarId: string;
290
+ } | {
291
+ epochS: null;
292
+ nano: null;
293
+ timeZoneId: null;
294
+ local: null;
295
+ offset: null;
296
+ calendarId: null;
297
+ };
298
+ export declare function unpackZonedDateTimeDualParts(parts: {
299
+ epochS: number;
300
+ nano: number;
301
+ timeZoneId: string;
302
+ local: string;
303
+ offset: string;
304
+ calendarId?: string;
305
+ }, opts?: UnpackFromRowOptions): Temporal.ZonedDateTime;
306
+ /**
307
+ * Safer pack helper: constrains `prefix` to actual columns present on the table/row type.
308
+ * This is the easiest way to catch typos like "starstAt" at compile-time.
309
+ */
310
+ export declare const packZonedDateTimeDual: <T extends object, P extends ZonedDateTimeDualPrefix<T>>(_tableOrRow: T, prefix: P, zdt: Temporal.ZonedDateTime, opts?: {
311
+ precision?: ToStringPrecision;
312
+ }) => { [K in `${P}EpochS` | `${P}Nano` | `${P}TimeZoneId` | `${P}Local` | `${P}Offset` | `${P}CalendarId`]: K extends `${string}EpochS` | `${string}Nano` ? number : string; };
313
+ export declare const packZonedDateTimeDualNullable: <T extends object, P extends ZonedDateTimeDualPrefix<T>>(_tableOrRow: T, prefix: P, zdt: Temporal.ZonedDateTime | null | undefined, opts?: {
314
+ precision?: ToStringPrecision;
315
+ }) => { [K in `${P}EpochS` | `${P}Nano` | `${P}TimeZoneId` | `${P}Local` | `${P}Offset` | `${P}CalendarId`]: K extends `${string}EpochS` | `${string}Nano` ? number | null : string | null; };
316
+ type ZonedDateTimeDualKeyMap<R> = {
317
+ epochS: keyof R;
318
+ nano: keyof R;
319
+ timeZoneId: keyof R;
320
+ local: keyof R;
321
+ offset: keyof R;
322
+ calendarId?: keyof R;
323
+ };
324
+ export declare function unpackZonedDateTimeDualFromRow<R extends Record<string, unknown>, P extends ZonedDateTimeDualPrefix<R>>(row: R, prefix: P, opts?: UnpackFromRowOptions): Temporal.ZonedDateTime;
325
+ export declare function unpackZonedDateTimeDualFromRow<R extends Record<string, unknown>, M extends ZonedDateTimeDualKeyMap<R>>(row: R, map: M, opts?: UnpackFromRowOptions): Temporal.ZonedDateTime;
326
+ export declare function unpackZonedDateTimeDualNullableFromRow<R extends Record<string, unknown>, P extends ZonedDateTimeDualPrefix<R>>(row: R, prefix: P, opts?: UnpackFromRowOptions): Temporal.ZonedDateTime | null;
327
+ export declare function unpackZonedDateTimeDualNullableFromRow<R extends Record<string, unknown>, M extends ZonedDateTimeDualKeyMap<R>>(row: R, map: M, opts?: UnpackFromRowOptions): Temporal.ZonedDateTime | null;
117
328
  export {};
package/dist/sqlite.js CHANGED
@@ -1,12 +1,22 @@
1
+ import { sql } from "drizzle-orm";
1
2
  import { customType, integer, text } from "drizzle-orm/sqlite-core";
2
3
  import "temporal-polyfill/global";
3
4
  function requireTemporal(runtime) {
4
5
  const T = runtime ?? globalThis.Temporal;
5
6
  if (!T) {
6
- throw new Error("Temporal is not available at runtime. Pass { Temporal } from @js-temporal/polyfill or run in an environment with Temporal.");
7
+ throw new Error("Temporal is not available at runtime. Import temporal-polyfill/global (recommended) or pass { Temporal } from temporal-polyfill.");
7
8
  }
8
9
  return T;
9
10
  }
11
+ function toSnakeCase(s) {
12
+ // Lightweight camelCase/PascalCase -> snake_case for DB column names.
13
+ // If users already pass snake_case, this is effectively a no-op.
14
+ return s
15
+ .replace(/-/g, "_")
16
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
17
+ .replace(/([A-Z]+)([A-Z][a-z0-9]+)/g, "$1_$2")
18
+ .toLowerCase();
19
+ }
10
20
  /**
11
21
  * Stable fixed-width-ish defaults for DB persistence.
12
22
  * (Change smallestUnit to "millisecond" if you want shorter strings.)
@@ -15,92 +25,130 @@ const DEFAULT_PRECISION = {
15
25
  smallestUnit: "nanosecond",
16
26
  roundingMode: "trunc",
17
27
  };
28
+ /**
29
+ * SQLite expression for "now" in epoch milliseconds.
30
+ *
31
+ * Uses strftime so we include milliseconds (not just whole seconds).
32
+ * Suitable for `.default(epochMsNow())` on INTEGER columns.
33
+ */
34
+ export const epochMsNow = () => sql `CAST(strftime('%s','now') AS integer) * 1000 + CAST((strftime('%f','now') - strftime('%S','now')) * 1000 AS integer)`;
18
35
  /** PlainDate -> TEXT "YYYY-MM-DD" */
19
- export const temporalPlainDateText = (name, opts) => {
36
+ export const temporalPlainDateText = (nameOrOpts, maybeOpts) => {
37
+ const name = typeof nameOrOpts === "string" ? nameOrOpts : undefined;
38
+ const opts = typeof nameOrOpts === "string" ? maybeOpts : nameOrOpts;
20
39
  const T = requireTemporal(opts?.Temporal);
21
- return customType({
40
+ const col = customType({
22
41
  dataType: () => "text",
23
42
  toDriver: (v) => v.toString(),
24
43
  fromDriver: (v) => T.PlainDate.from(v),
25
- })(name);
44
+ fromJson: (v) => T.PlainDate.from(v),
45
+ });
46
+ return name ? col(name) : col();
26
47
  };
27
48
  /** PlainTime -> TEXT "HH:mm:ss.sssssssss" (precision controlled via smallestUnit) */
28
- export const temporalPlainTimeText = (name, opts) => {
49
+ export const temporalPlainTimeText = (nameOrOpts, maybeOpts) => {
50
+ const name = typeof nameOrOpts === "string" ? nameOrOpts : undefined;
51
+ const opts = typeof nameOrOpts === "string" ? maybeOpts : nameOrOpts;
29
52
  const T = requireTemporal(opts?.Temporal);
30
53
  const p = { ...DEFAULT_PRECISION, ...opts?.precision };
31
- return customType({
54
+ const col = customType({
32
55
  dataType: () => "text",
33
56
  toDriver: (v) => v.toString(p),
34
57
  fromDriver: (v) => T.PlainTime.from(v),
35
- })(name);
58
+ fromJson: (v) => T.PlainTime.from(v),
59
+ });
60
+ return name ? col(name) : col();
36
61
  };
37
62
  /** PlainDateTime -> TEXT "YYYY-MM-DDTHH:mm:ss.sssssssss" */
38
- export const temporalPlainDateTimeText = (name, opts) => {
63
+ export const temporalPlainDateTimeText = (nameOrOpts, maybeOpts) => {
64
+ const name = typeof nameOrOpts === "string" ? nameOrOpts : undefined;
65
+ const opts = typeof nameOrOpts === "string" ? maybeOpts : nameOrOpts;
39
66
  const T = requireTemporal(opts?.Temporal);
40
67
  const p = { ...DEFAULT_PRECISION, ...opts?.precision };
41
- return customType({
68
+ const col = customType({
42
69
  dataType: () => "text",
43
70
  toDriver: (v) => v.toString(p),
44
71
  fromDriver: (v) => T.PlainDateTime.from(v),
45
- })(name);
72
+ fromJson: (v) => T.PlainDateTime.from(v),
73
+ });
74
+ return name ? col(name) : col();
46
75
  };
47
76
  /** PlainYearMonth -> TEXT "YYYY-MM" (plus optional calendar annotation if non-ISO) */
48
- export const temporalPlainYearMonthText = (name, opts) => {
77
+ export const temporalPlainYearMonthText = (nameOrOpts, maybeOpts) => {
78
+ const name = typeof nameOrOpts === "string" ? nameOrOpts : undefined;
79
+ const opts = typeof nameOrOpts === "string" ? maybeOpts : nameOrOpts;
49
80
  const T = requireTemporal(opts?.Temporal);
50
- return customType({
81
+ const col = customType({
51
82
  dataType: () => "text",
52
83
  toDriver: (v) => v.toString(),
53
84
  fromDriver: (v) => T.PlainYearMonth.from(v),
54
- })(name);
85
+ fromJson: (v) => T.PlainYearMonth.from(v),
86
+ });
87
+ return name ? col(name) : col();
55
88
  };
56
89
  /** PlainMonthDay -> TEXT "MM-DD" (plus optional calendar annotation if non-ISO) */
57
- export const temporalPlainMonthDayText = (name, opts) => {
90
+ export const temporalPlainMonthDayText = (nameOrOpts, maybeOpts) => {
91
+ const name = typeof nameOrOpts === "string" ? nameOrOpts : undefined;
92
+ const opts = typeof nameOrOpts === "string" ? maybeOpts : nameOrOpts;
58
93
  const T = requireTemporal(opts?.Temporal);
59
- return customType({
94
+ const col = customType({
60
95
  dataType: () => "text",
61
96
  toDriver: (v) => v.toString(),
62
97
  fromDriver: (v) => T.PlainMonthDay.from(v),
63
- })(name);
98
+ fromJson: (v) => T.PlainMonthDay.from(v),
99
+ });
100
+ return name ? col(name) : col();
64
101
  };
65
102
  /**
66
103
  * Duration -> TEXT ISO-8601, e.g. "PT15M", "P2DT3H"
67
104
  * Note: if millis/micros/nanos exceed 999, parsing back yields an
68
105
  * equal-but-differently-balanced Duration.
69
106
  */
70
- export const temporalDurationText = (name, opts) => {
107
+ export const temporalDurationText = (nameOrOpts, maybeOpts) => {
108
+ const name = typeof nameOrOpts === "string" ? nameOrOpts : undefined;
109
+ const opts = typeof nameOrOpts === "string" ? maybeOpts : nameOrOpts;
71
110
  const T = requireTemporal(opts?.Temporal);
72
- return customType({
111
+ const col = customType({
73
112
  dataType: () => "text",
74
113
  toDriver: (v) => v.toString(),
75
114
  fromDriver: (v) => T.Duration.from(v),
76
- })(name);
115
+ fromJson: (v) => T.Duration.from(v),
116
+ });
117
+ return name ? col(name) : col();
77
118
  };
78
119
  /**
79
120
  * Instant (recommended) -> INTEGER epoch milliseconds
80
121
  * Great for sorting/filtering by actual time.
81
122
  * Tradeoff: millisecond precision only.
82
123
  */
83
- export const temporalInstantEpochMs = (name, opts) => {
124
+ export const temporalInstantEpochMs = (nameOrOpts, maybeOpts) => {
125
+ const name = typeof nameOrOpts === "string" ? nameOrOpts : undefined;
126
+ const opts = typeof nameOrOpts === "string" ? maybeOpts : nameOrOpts;
84
127
  const T = requireTemporal(opts?.Temporal);
85
- return customType({
128
+ const col = customType({
86
129
  dataType: () => "integer",
87
130
  toDriver: (v) => v.epochMilliseconds,
88
131
  fromDriver: (v) => T.Instant.fromEpochMilliseconds(typeof v === "string" ? Number(v) : v),
89
132
  fromJson: (v) => T.Instant.fromEpochMilliseconds(typeof v === "string" ? Number(v) : v),
90
- })(name);
133
+ });
134
+ return name ? col(name) : col();
91
135
  };
92
136
  /**
93
137
  * Instant (lossless) -> TEXT ISO string, e.g. "2026-01-25T12:34:56.123456789Z"
94
138
  * Tradeoff: sorting by TEXT is chronological only if the format is stable.
95
139
  */
96
- export const temporalInstantIsoText = (name, opts) => {
140
+ export const temporalInstantIsoText = (nameOrOpts, maybeOpts) => {
141
+ const name = typeof nameOrOpts === "string" ? nameOrOpts : undefined;
142
+ const opts = typeof nameOrOpts === "string" ? maybeOpts : nameOrOpts;
97
143
  const T = requireTemporal(opts?.Temporal);
98
144
  const p = { ...DEFAULT_PRECISION, ...opts?.precision };
99
- return customType({
145
+ const col = customType({
100
146
  dataType: () => "text",
101
147
  toDriver: (v) => v.toString(p),
102
148
  fromDriver: (v) => T.Instant.from(v),
103
- })(name);
149
+ fromJson: (v) => T.Instant.from(v),
150
+ });
151
+ return name ? col(name) : col();
104
152
  };
105
153
  /**
106
154
  * ZonedDateTime (single-column) -> TEXT with zone annotation.
@@ -109,14 +157,18 @@ export const temporalInstantIsoText = (name, opts) => {
109
157
  * This is convenient, but if you need sorting/filtering by actual moment,
110
158
  * prefer the multi-column helper below (epochMs + timeZoneId + calendarId).
111
159
  */
112
- export const temporalZonedDateTimeText = (name, opts) => {
160
+ export const temporalZonedDateTimeText = (nameOrOpts, maybeOpts) => {
161
+ const name = typeof nameOrOpts === "string" ? nameOrOpts : undefined;
162
+ const opts = typeof nameOrOpts === "string" ? maybeOpts : nameOrOpts;
113
163
  const T = requireTemporal(opts?.Temporal);
114
164
  const p = { ...DEFAULT_PRECISION, ...opts?.precision };
115
- return customType({
165
+ const col = customType({
116
166
  dataType: () => "text",
117
167
  toDriver: (v) => v.toString({ ...p, timeZoneName: "auto" }),
118
168
  fromDriver: (v) => T.ZonedDateTime.from(v),
119
- })(name);
169
+ fromJson: (v) => T.ZonedDateTime.from(v),
170
+ });
171
+ return name ? col(name) : col();
120
172
  };
121
173
  /* ------------------------------------------------------------------------------------------------
122
174
  * Multi-column (“flattened”) helpers
@@ -140,3 +192,343 @@ export function unpackZonedDateTime(parts, opts) {
140
192
  const zdt = T.Instant.fromEpochMilliseconds(parts.epochMs).toZonedDateTimeISO(parts.timeZoneId);
141
193
  return parts.calendarId ? zdt.withCalendar(parts.calendarId) : zdt;
142
194
  }
195
+ const NS_PER_S = 1000000000n;
196
+ function joinEpochNs(epochS, nano) {
197
+ return BigInt(epochS) * NS_PER_S + BigInt(nano);
198
+ }
199
+ function splitEpochNs(epochNs) {
200
+ // Keep nano in [0..999,999,999] even for negative instants.
201
+ let s = epochNs / NS_PER_S;
202
+ let n = epochNs % NS_PER_S;
203
+ if (n < 0n) {
204
+ s -= 1n;
205
+ n += NS_PER_S;
206
+ }
207
+ const epochS = Number(s);
208
+ if (!Number.isSafeInteger(epochS)) {
209
+ throw new Error("Instant is outside the safe integer range for epoch seconds. Use a bigint-based representation instead.");
210
+ }
211
+ return { epochS, nano: Number(n) };
212
+ }
213
+ export const instantParts = (prefix, opts) => {
214
+ const out = {};
215
+ const epochSName = opts?.columns?.epochS ??
216
+ (opts?.mode === "casing"
217
+ ? undefined
218
+ : `${(opts?.columnPrefix ?? toSnakeCase(prefix))}_epoch_s`);
219
+ const nanoName = opts?.columns?.nano ??
220
+ (opts?.mode === "casing"
221
+ ? undefined
222
+ : `${(opts?.columnPrefix ?? toSnakeCase(prefix))}_nano`);
223
+ out[`${prefix}EpochS`] = (epochSName
224
+ ? integer(epochSName)
225
+ : integer()).notNull();
226
+ out[`${prefix}Nano`] = (nanoName ? integer(nanoName) : integer()).notNull();
227
+ return out;
228
+ };
229
+ export const instantPartsNullable = (prefix, opts) => {
230
+ const out = {};
231
+ const epochSName = opts?.columns?.epochS ??
232
+ (opts?.mode === "casing"
233
+ ? undefined
234
+ : `${(opts?.columnPrefix ?? toSnakeCase(prefix))}_epoch_s`);
235
+ const nanoName = opts?.columns?.nano ??
236
+ (opts?.mode === "casing"
237
+ ? undefined
238
+ : `${(opts?.columnPrefix ?? toSnakeCase(prefix))}_nano`);
239
+ // Nullable columns: we enforce "all-or-none" in unpack helpers.
240
+ out[`${prefix}EpochS`] = epochSName
241
+ ? integer(epochSName)
242
+ : integer();
243
+ out[`${prefix}Nano`] = nanoName ? integer(nanoName) : integer();
244
+ return out;
245
+ };
246
+ export function packInstantParts(instant) {
247
+ const { epochS, nano } = splitEpochNs(instant.epochNanoseconds);
248
+ return { epochS, nano };
249
+ }
250
+ export function unpackInstantParts(parts, opts) {
251
+ const T = requireTemporal(opts?.Temporal);
252
+ return T.Instant.fromEpochNanoseconds(joinEpochNs(parts.epochS, parts.nano));
253
+ }
254
+ /**
255
+ * Pack helper (type-safe): constrains `prefix` to actual columns present on the table/row type.
256
+ * This catches typos like "ocurdAt" at compile-time.
257
+ */
258
+ export const packInstant = (_tableOrRow, prefix, instant) => {
259
+ const { epochS, nano } = packInstantParts(instant);
260
+ const out = {};
261
+ out[`${prefix}EpochS`] = epochS;
262
+ out[`${prefix}Nano`] = nano;
263
+ return out;
264
+ };
265
+ export const packInstantNullable = (_tableOrRow, prefix, instant) => {
266
+ const out = {};
267
+ if (instant == null) {
268
+ out[`${prefix}EpochS`] = null;
269
+ out[`${prefix}Nano`] = null;
270
+ return out;
271
+ }
272
+ const { epochS, nano } = packInstantParts(instant);
273
+ out[`${prefix}EpochS`] = epochS;
274
+ out[`${prefix}Nano`] = nano;
275
+ return out;
276
+ };
277
+ export function unpackInstantFromRow(row, prefixOrMap, opts) {
278
+ const validate = opts?.validate ?? true;
279
+ const map = typeof prefixOrMap === "string"
280
+ ? { epochS: `${prefixOrMap}EpochS`, nano: `${prefixOrMap}Nano` }
281
+ : { epochS: String(prefixOrMap.epochS), nano: String(prefixOrMap.nano) };
282
+ const epochRaw = row[map.epochS];
283
+ const nanoRaw = row[map.nano];
284
+ const epochS = typeof epochRaw === "string" ? Number(epochRaw) : epochRaw;
285
+ const nano = typeof nanoRaw === "string" ? Number(nanoRaw) : nanoRaw;
286
+ if (validate) {
287
+ if (!Number.isSafeInteger(epochS))
288
+ throw new Error("Invalid epochS");
289
+ if (!Number.isInteger(nano) || nano < 0 || nano > 999_999_999) {
290
+ throw new Error("Invalid nano");
291
+ }
292
+ }
293
+ return unpackInstantParts({ epochS, nano }, { Temporal: opts?.Temporal });
294
+ }
295
+ export function unpackInstantNullableFromRow(row, prefixOrMap, opts) {
296
+ const validate = opts?.validate ?? true;
297
+ const map = typeof prefixOrMap === "string"
298
+ ? { epochS: `${prefixOrMap}EpochS`, nano: `${prefixOrMap}Nano` }
299
+ : { epochS: String(prefixOrMap.epochS), nano: String(prefixOrMap.nano) };
300
+ const epochRaw = row[map.epochS];
301
+ const nanoRaw = row[map.nano];
302
+ const epochIsNull = epochRaw == null;
303
+ const nanoIsNull = nanoRaw == null;
304
+ if (epochIsNull && nanoIsNull)
305
+ return null;
306
+ if (validate && epochIsNull !== nanoIsNull) {
307
+ throw new Error("Nullable Instant parts must be all-null or all-set");
308
+ }
309
+ const epochS = typeof epochRaw === "string" ? Number(epochRaw) : epochRaw;
310
+ const nano = typeof nanoRaw === "string" ? Number(nanoRaw) : nanoRaw;
311
+ if (validate) {
312
+ if (!Number.isSafeInteger(epochS))
313
+ throw new Error("Invalid epochS");
314
+ if (!Number.isInteger(nano) || nano < 0 || nano > 999_999_999) {
315
+ throw new Error("Invalid nano");
316
+ }
317
+ }
318
+ return unpackInstantParts({ epochS, nano }, { Temporal: opts?.Temporal });
319
+ }
320
+ export const zonedDateTimeDualParts = (prefix, opts) => {
321
+ const out = {};
322
+ const colPrefix = opts?.mode === "casing" ? undefined : (opts?.columnPrefix ?? toSnakeCase(prefix));
323
+ const epochSName = opts?.columns?.epochS ?? (colPrefix ? `${colPrefix}_epoch_s` : undefined);
324
+ const nanoName = opts?.columns?.nano ?? (colPrefix ? `${colPrefix}_nano` : undefined);
325
+ const timeZoneIdName = opts?.columns?.timeZoneId ?? (colPrefix ? `${colPrefix}_tz` : undefined);
326
+ const localName = opts?.columns?.local ?? (colPrefix ? `${colPrefix}_local` : undefined);
327
+ const offsetName = opts?.columns?.offset ?? (colPrefix ? `${colPrefix}_offset` : undefined);
328
+ const calendarIdName = opts?.columns?.calendarId ?? (colPrefix ? `${colPrefix}_cal` : undefined);
329
+ out[`${prefix}EpochS`] = (epochSName ? integer(epochSName) : integer()).notNull();
330
+ out[`${prefix}Nano`] = (nanoName ? integer(nanoName) : integer()).notNull();
331
+ out[`${prefix}TimeZoneId`] = (timeZoneIdName ? text(timeZoneIdName) : text()).notNull();
332
+ out[`${prefix}Local`] = (localName ? text(localName) : text()).notNull();
333
+ out[`${prefix}Offset`] = (offsetName ? text(offsetName) : text()).notNull();
334
+ out[`${prefix}CalendarId`] = (calendarIdName ? text(calendarIdName) : text())
335
+ .notNull()
336
+ .default("iso8601");
337
+ return out;
338
+ };
339
+ export const zonedDateTimeDualPartsNullable = (prefix, opts) => {
340
+ const out = {};
341
+ const colPrefix = opts?.mode === "casing" ? undefined : (opts?.columnPrefix ?? toSnakeCase(prefix));
342
+ const epochSName = opts?.columns?.epochS ?? (colPrefix ? `${colPrefix}_epoch_s` : undefined);
343
+ const nanoName = opts?.columns?.nano ?? (colPrefix ? `${colPrefix}_nano` : undefined);
344
+ const timeZoneIdName = opts?.columns?.timeZoneId ?? (colPrefix ? `${colPrefix}_tz` : undefined);
345
+ const localName = opts?.columns?.local ?? (colPrefix ? `${colPrefix}_local` : undefined);
346
+ const offsetName = opts?.columns?.offset ?? (colPrefix ? `${colPrefix}_offset` : undefined);
347
+ const calendarIdName = opts?.columns?.calendarId ?? (colPrefix ? `${colPrefix}_cal` : undefined);
348
+ // Nullable columns: no defaults, and unpack helpers enforce "all-or-none".
349
+ out[`${prefix}EpochS`] = (epochSName ? integer(epochSName) : integer());
350
+ out[`${prefix}Nano`] = (nanoName ? integer(nanoName) : integer());
351
+ out[`${prefix}TimeZoneId`] = (timeZoneIdName ? text(timeZoneIdName) : text());
352
+ out[`${prefix}Local`] = (localName ? text(localName) : text());
353
+ out[`${prefix}Offset`] = (offsetName ? text(offsetName) : text());
354
+ out[`${prefix}CalendarId`] = (calendarIdName ? text(calendarIdName) : text());
355
+ return out;
356
+ };
357
+ export function packZonedDateTimeDualParts(zdt, opts) {
358
+ const { epochS, nano } = splitEpochNs(zdt.epochNanoseconds);
359
+ const p = { ...DEFAULT_PRECISION, ...opts?.precision };
360
+ return {
361
+ epochS,
362
+ nano,
363
+ timeZoneId: zdt.timeZoneId,
364
+ local: zdt.toPlainDateTime().toString(p),
365
+ offset: zdt.offset,
366
+ calendarId: zdt.calendarId,
367
+ };
368
+ }
369
+ export function packZonedDateTimeDualNullableParts(zdt, opts) {
370
+ if (zdt == null) {
371
+ return {
372
+ epochS: null,
373
+ nano: null,
374
+ timeZoneId: null,
375
+ local: null,
376
+ offset: null,
377
+ calendarId: null,
378
+ };
379
+ }
380
+ return packZonedDateTimeDualParts(zdt, opts);
381
+ }
382
+ export function unpackZonedDateTimeDualParts(parts, opts) {
383
+ const T = requireTemporal(opts?.Temporal);
384
+ const instant = T.Instant.fromEpochNanoseconds(joinEpochNs(parts.epochS, parts.nano));
385
+ const zdt = instant.toZonedDateTimeISO(parts.timeZoneId);
386
+ const zdtWithCal = parts.calendarId ? zdt.withCalendar(parts.calendarId) : zdt;
387
+ const validate = opts?.validate ?? true;
388
+ if (validate && opts?.validateDualWriteLocalAndOffset) {
389
+ const p = { ...DEFAULT_PRECISION, ...opts?.precision };
390
+ const expectedLocal = zdtWithCal.toPlainDateTime().toString(p);
391
+ const expectedOffset = zdtWithCal.offset;
392
+ if (expectedLocal !== parts.local)
393
+ throw new Error("Dual-write local does not match");
394
+ if (expectedOffset !== parts.offset)
395
+ throw new Error("Dual-write offset does not match");
396
+ }
397
+ return zdtWithCal;
398
+ }
399
+ /**
400
+ * Safer pack helper: constrains `prefix` to actual columns present on the table/row type.
401
+ * This is the easiest way to catch typos like "starstAt" at compile-time.
402
+ */
403
+ export const packZonedDateTimeDual = (_tableOrRow, prefix, zdt, opts) => {
404
+ // Delegate to parts + typed prefix keys.
405
+ const parts = packZonedDateTimeDualParts(zdt, opts);
406
+ const out = {};
407
+ out[`${prefix}EpochS`] = parts.epochS;
408
+ out[`${prefix}Nano`] = parts.nano;
409
+ out[`${prefix}TimeZoneId`] = parts.timeZoneId;
410
+ out[`${prefix}Local`] = parts.local;
411
+ out[`${prefix}Offset`] = parts.offset;
412
+ out[`${prefix}CalendarId`] = parts.calendarId;
413
+ return out;
414
+ };
415
+ export const packZonedDateTimeDualNullable = (_tableOrRow, prefix, zdt, opts) => {
416
+ const parts = packZonedDateTimeDualNullableParts(zdt, opts);
417
+ const out = {};
418
+ out[`${prefix}EpochS`] = parts.epochS;
419
+ out[`${prefix}Nano`] = parts.nano;
420
+ out[`${prefix}TimeZoneId`] = parts.timeZoneId;
421
+ out[`${prefix}Local`] = parts.local;
422
+ out[`${prefix}Offset`] = parts.offset;
423
+ out[`${prefix}CalendarId`] = parts.calendarId;
424
+ return out;
425
+ };
426
+ export function unpackZonedDateTimeDualFromRow(row, prefixOrMap, opts) {
427
+ const validate = opts?.validate ?? true;
428
+ const map = typeof prefixOrMap === "string"
429
+ ? {
430
+ epochS: `${prefixOrMap}EpochS`,
431
+ nano: `${prefixOrMap}Nano`,
432
+ timeZoneId: `${prefixOrMap}TimeZoneId`,
433
+ local: `${prefixOrMap}Local`,
434
+ offset: `${prefixOrMap}Offset`,
435
+ calendarId: `${prefixOrMap}CalendarId`,
436
+ }
437
+ : {
438
+ epochS: String(prefixOrMap.epochS),
439
+ nano: String(prefixOrMap.nano),
440
+ timeZoneId: String(prefixOrMap.timeZoneId),
441
+ local: String(prefixOrMap.local),
442
+ offset: String(prefixOrMap.offset),
443
+ calendarId: prefixOrMap.calendarId ? String(prefixOrMap.calendarId) : undefined,
444
+ };
445
+ const epochRaw = row[map.epochS];
446
+ const nanoRaw = row[map.nano];
447
+ const epochS = typeof epochRaw === "string" ? Number(epochRaw) : epochRaw;
448
+ const nano = typeof nanoRaw === "string" ? Number(nanoRaw) : nanoRaw;
449
+ const timeZoneId = row[map.timeZoneId];
450
+ const local = row[map.local];
451
+ const offset = row[map.offset];
452
+ const calendarId = map.calendarId ? row[map.calendarId] : undefined;
453
+ if (validate) {
454
+ if (!Number.isSafeInteger(epochS))
455
+ throw new Error("Invalid epochS");
456
+ if (!Number.isInteger(nano) || nano < 0 || nano > 999_999_999) {
457
+ throw new Error("Invalid nano");
458
+ }
459
+ if (typeof timeZoneId !== "string" || !timeZoneId)
460
+ throw new Error("Invalid timeZoneId");
461
+ if (typeof local !== "string" || !local)
462
+ throw new Error("Invalid local");
463
+ if (typeof offset !== "string" || !offset)
464
+ throw new Error("Invalid offset");
465
+ if (calendarId != null && typeof calendarId !== "string")
466
+ throw new Error("Invalid calendarId");
467
+ }
468
+ return unpackZonedDateTimeDualParts({ epochS, nano, timeZoneId, local, offset, calendarId }, opts);
469
+ }
470
+ export function unpackZonedDateTimeDualNullableFromRow(row, prefixOrMap, opts) {
471
+ const validate = opts?.validate ?? true;
472
+ const map = typeof prefixOrMap === "string"
473
+ ? {
474
+ epochS: `${prefixOrMap}EpochS`,
475
+ nano: `${prefixOrMap}Nano`,
476
+ timeZoneId: `${prefixOrMap}TimeZoneId`,
477
+ local: `${prefixOrMap}Local`,
478
+ offset: `${prefixOrMap}Offset`,
479
+ calendarId: `${prefixOrMap}CalendarId`,
480
+ }
481
+ : {
482
+ epochS: String(prefixOrMap.epochS),
483
+ nano: String(prefixOrMap.nano),
484
+ timeZoneId: String(prefixOrMap.timeZoneId),
485
+ local: String(prefixOrMap.local),
486
+ offset: String(prefixOrMap.offset),
487
+ calendarId: prefixOrMap.calendarId ? String(prefixOrMap.calendarId) : undefined,
488
+ };
489
+ const epochRaw = row[map.epochS];
490
+ const nanoRaw = row[map.nano];
491
+ const timeZoneIdRaw = row[map.timeZoneId];
492
+ const localRaw = row[map.local];
493
+ const offsetRaw = row[map.offset];
494
+ const calendarIdRaw = map.calendarId ? row[map.calendarId] : undefined;
495
+ const allNull = epochRaw == null &&
496
+ nanoRaw == null &&
497
+ timeZoneIdRaw == null &&
498
+ localRaw == null &&
499
+ offsetRaw == null &&
500
+ calendarIdRaw == null;
501
+ if (allNull)
502
+ return null;
503
+ if (validate &&
504
+ (epochRaw == null ||
505
+ nanoRaw == null ||
506
+ timeZoneIdRaw == null ||
507
+ localRaw == null ||
508
+ offsetRaw == null ||
509
+ calendarIdRaw == null)) {
510
+ throw new Error("Nullable ZonedDateTime parts must be all-null or all-set");
511
+ }
512
+ const epochS = typeof epochRaw === "string" ? Number(epochRaw) : epochRaw;
513
+ const nano = typeof nanoRaw === "string" ? Number(nanoRaw) : nanoRaw;
514
+ const timeZoneId = timeZoneIdRaw;
515
+ const local = localRaw;
516
+ const offset = offsetRaw;
517
+ const calendarId = calendarIdRaw;
518
+ if (validate) {
519
+ if (!Number.isSafeInteger(epochS))
520
+ throw new Error("Invalid epochS");
521
+ if (!Number.isInteger(nano) || nano < 0 || nano > 999_999_999) {
522
+ throw new Error("Invalid nano");
523
+ }
524
+ if (typeof timeZoneId !== "string" || !timeZoneId)
525
+ throw new Error("Invalid timeZoneId");
526
+ if (typeof local !== "string" || !local)
527
+ throw new Error("Invalid local");
528
+ if (typeof offset !== "string" || !offset)
529
+ throw new Error("Invalid offset");
530
+ if (typeof calendarId !== "string" || !calendarId)
531
+ throw new Error("Invalid calendarId");
532
+ }
533
+ return unpackZonedDateTimeDualParts({ epochS, nano, timeZoneId, local, offset, calendarId }, opts);
534
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sedrino/drizzle",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "main": "./dist/sqlite.js",
5
5
  "types": "./dist/sqlite.d.ts",
6
6
  "exports": {
@@ -19,9 +19,13 @@
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/bun": "latest",
22
+ "@libsql/client": "^0.15.0",
22
23
  "typescript": "^5.6.3"
23
24
  },
24
25
  "scripts": {
25
- "build": "bunx tsc -p tsconfig.build.json"
26
+ "build": "bunx tsc -p tsconfig.build.json",
27
+ "test": "bun test",
28
+ "test:types": "bunx tsc -p tsconfig.types.json",
29
+ "test:all": "bun test && bunx tsc -p tsconfig.types.json"
26
30
  }
27
31
  }