@sedrino/drizzle 0.0.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 +55 -0
- package/dist/sqlite.d.ts +117 -0
- package/dist/sqlite.js +142 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# @sedrino/drizzle
|
|
2
|
+
|
|
3
|
+
Utilities for using Temporal with Drizzle (SQLite for now).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @sedrino/drizzle
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage (SQLite)
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
15
|
+
import {
|
|
16
|
+
temporalPlainDateText,
|
|
17
|
+
temporalInstantEpochMs,
|
|
18
|
+
temporalZonedDateTimeText,
|
|
19
|
+
zonedDateTimeParts,
|
|
20
|
+
} from "@sedrino/drizzle/sqlite";
|
|
21
|
+
|
|
22
|
+
export const tasks = sqliteTable("tasks", {
|
|
23
|
+
id: text("id").primaryKey(),
|
|
24
|
+
title: text("title").notNull(),
|
|
25
|
+
dueDate: temporalPlainDateText("due_date").nullable(),
|
|
26
|
+
createdAt: temporalInstantEpochMs("created_at_ms").notNull(),
|
|
27
|
+
startsAt: temporalZonedDateTimeText("starts_at").nullable(),
|
|
28
|
+
// startsAt: zonedDateTimeParts("starts_at"),
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Temporal runtime
|
|
33
|
+
|
|
34
|
+
Temporal is not universally available yet. We recommend `temporal-polyfill`.
|
|
35
|
+
|
|
36
|
+
Option A: Install the global polyfill:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import "temporal-polyfill/global";
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Option B: Inject the runtime explicitly:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { Temporal } from "temporal-polyfill";
|
|
46
|
+
import { temporalPlainDateTimeText } from "@sedrino/drizzle/sqlite";
|
|
47
|
+
|
|
48
|
+
const createdAt = temporalPlainDateTimeText("created_at", { Temporal });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Types
|
|
52
|
+
|
|
53
|
+
This package relies on the `temporal-polyfill` type definitions for `Temporal`.
|
|
54
|
+
If your app uses a different runtime, you can still use these types without
|
|
55
|
+
installing a polyfill globally.
|
package/dist/sqlite.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { customType, integer, text } from "drizzle-orm/sqlite-core";
|
|
2
|
+
import "temporal-polyfill/global";
|
|
3
|
+
/**
|
|
4
|
+
* Runtime Temporal resolution:
|
|
5
|
+
* - pass { Temporal } from "@js-temporal/polyfill" if needed
|
|
6
|
+
* - otherwise uses globalThis.Temporal
|
|
7
|
+
*/
|
|
8
|
+
export type TemporalRuntime = typeof Temporal;
|
|
9
|
+
type SmallestUnit = "minute" | "second" | "millisecond" | "microsecond" | "nanosecond";
|
|
10
|
+
type ToStringPrecision = {
|
|
11
|
+
smallestUnit?: SmallestUnit;
|
|
12
|
+
roundingMode?: "ceil" | "floor" | "trunc" | "halfExpand";
|
|
13
|
+
};
|
|
14
|
+
/** PlainDate -> TEXT "YYYY-MM-DD" */
|
|
15
|
+
export declare const temporalPlainDateText: (name: string, opts?: {
|
|
16
|
+
Temporal?: TemporalRuntime;
|
|
17
|
+
}) => ReturnType<ReturnType<typeof customType<{
|
|
18
|
+
data: Temporal.PlainDate;
|
|
19
|
+
driverData: string;
|
|
20
|
+
}>>>;
|
|
21
|
+
/** PlainTime -> TEXT "HH:mm:ss.sssssssss" (precision controlled via smallestUnit) */
|
|
22
|
+
export declare const temporalPlainTimeText: (name: string, opts?: {
|
|
23
|
+
Temporal?: TemporalRuntime;
|
|
24
|
+
precision?: ToStringPrecision;
|
|
25
|
+
}) => ReturnType<ReturnType<typeof customType<{
|
|
26
|
+
data: Temporal.PlainTime;
|
|
27
|
+
driverData: string;
|
|
28
|
+
}>>>;
|
|
29
|
+
/** PlainDateTime -> TEXT "YYYY-MM-DDTHH:mm:ss.sssssssss" */
|
|
30
|
+
export declare const temporalPlainDateTimeText: (name: string, opts?: {
|
|
31
|
+
Temporal?: TemporalRuntime;
|
|
32
|
+
precision?: ToStringPrecision;
|
|
33
|
+
}) => ReturnType<ReturnType<typeof customType<{
|
|
34
|
+
data: Temporal.PlainDateTime;
|
|
35
|
+
driverData: string;
|
|
36
|
+
}>>>;
|
|
37
|
+
/** PlainYearMonth -> TEXT "YYYY-MM" (plus optional calendar annotation if non-ISO) */
|
|
38
|
+
export declare const temporalPlainYearMonthText: (name: string, opts?: {
|
|
39
|
+
Temporal?: TemporalRuntime;
|
|
40
|
+
}) => ReturnType<ReturnType<typeof customType<{
|
|
41
|
+
data: Temporal.PlainYearMonth;
|
|
42
|
+
driverData: string;
|
|
43
|
+
}>>>;
|
|
44
|
+
/** PlainMonthDay -> TEXT "MM-DD" (plus optional calendar annotation if non-ISO) */
|
|
45
|
+
export declare const temporalPlainMonthDayText: (name: string, opts?: {
|
|
46
|
+
Temporal?: TemporalRuntime;
|
|
47
|
+
}) => ReturnType<ReturnType<typeof customType<{
|
|
48
|
+
data: Temporal.PlainMonthDay;
|
|
49
|
+
driverData: string;
|
|
50
|
+
}>>>;
|
|
51
|
+
/**
|
|
52
|
+
* Duration -> TEXT ISO-8601, e.g. "PT15M", "P2DT3H"
|
|
53
|
+
* Note: if millis/micros/nanos exceed 999, parsing back yields an
|
|
54
|
+
* equal-but-differently-balanced Duration.
|
|
55
|
+
*/
|
|
56
|
+
export declare const temporalDurationText: (name: string, opts?: {
|
|
57
|
+
Temporal?: TemporalRuntime;
|
|
58
|
+
}) => ReturnType<ReturnType<typeof customType<{
|
|
59
|
+
data: Temporal.Duration;
|
|
60
|
+
driverData: string;
|
|
61
|
+
}>>>;
|
|
62
|
+
/**
|
|
63
|
+
* Instant (recommended) -> INTEGER epoch milliseconds
|
|
64
|
+
* Great for sorting/filtering by actual time.
|
|
65
|
+
* Tradeoff: millisecond precision only.
|
|
66
|
+
*/
|
|
67
|
+
export declare const temporalInstantEpochMs: (name: string, opts?: {
|
|
68
|
+
Temporal?: TemporalRuntime;
|
|
69
|
+
}) => ReturnType<ReturnType<typeof customType<{
|
|
70
|
+
data: Temporal.Instant;
|
|
71
|
+
driverData: number;
|
|
72
|
+
driverOutput: number | string;
|
|
73
|
+
}>>>;
|
|
74
|
+
/**
|
|
75
|
+
* Instant (lossless) -> TEXT ISO string, e.g. "2026-01-25T12:34:56.123456789Z"
|
|
76
|
+
* Tradeoff: sorting by TEXT is chronological only if the format is stable.
|
|
77
|
+
*/
|
|
78
|
+
export declare const temporalInstantIsoText: (name: string, opts?: {
|
|
79
|
+
Temporal?: TemporalRuntime;
|
|
80
|
+
precision?: ToStringPrecision;
|
|
81
|
+
}) => ReturnType<ReturnType<typeof customType<{
|
|
82
|
+
data: Temporal.Instant;
|
|
83
|
+
driverData: string;
|
|
84
|
+
}>>>;
|
|
85
|
+
/**
|
|
86
|
+
* ZonedDateTime (single-column) -> TEXT with zone annotation.
|
|
87
|
+
* Example: "2026-01-25T09:00:00-05:00[America/New_York]"
|
|
88
|
+
*
|
|
89
|
+
* This is convenient, but if you need sorting/filtering by actual moment,
|
|
90
|
+
* prefer the multi-column helper below (epochMs + timeZoneId + calendarId).
|
|
91
|
+
*/
|
|
92
|
+
export declare const temporalZonedDateTimeText: (name: string, opts?: {
|
|
93
|
+
Temporal?: TemporalRuntime;
|
|
94
|
+
precision?: ToStringPrecision;
|
|
95
|
+
}) => ReturnType<ReturnType<typeof customType<{
|
|
96
|
+
data: Temporal.ZonedDateTime;
|
|
97
|
+
driverData: string;
|
|
98
|
+
}>>>;
|
|
99
|
+
/** Recommended storage shape for ZonedDateTime: epochMs + timeZoneId (+ calendarId). */
|
|
100
|
+
export declare const zonedDateTimeParts: (prefix: string) => {
|
|
101
|
+
epochMs: ReturnType<typeof integer>;
|
|
102
|
+
timeZoneId: ReturnType<typeof text>;
|
|
103
|
+
calendarId: ReturnType<typeof text>;
|
|
104
|
+
};
|
|
105
|
+
export declare function packZonedDateTime(zdt: Temporal.ZonedDateTime): {
|
|
106
|
+
epochMs: number;
|
|
107
|
+
timeZoneId: string;
|
|
108
|
+
calendarId: string;
|
|
109
|
+
};
|
|
110
|
+
export declare function unpackZonedDateTime(parts: {
|
|
111
|
+
epochMs: number;
|
|
112
|
+
timeZoneId: string;
|
|
113
|
+
calendarId?: string;
|
|
114
|
+
}, opts?: {
|
|
115
|
+
Temporal?: TemporalRuntime;
|
|
116
|
+
}): Temporal.ZonedDateTime;
|
|
117
|
+
export {};
|
package/dist/sqlite.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { customType, integer, text } from "drizzle-orm/sqlite-core";
|
|
2
|
+
import "temporal-polyfill/global";
|
|
3
|
+
function requireTemporal(runtime) {
|
|
4
|
+
const T = runtime ?? globalThis.Temporal;
|
|
5
|
+
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
|
+
}
|
|
8
|
+
return T;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Stable fixed-width-ish defaults for DB persistence.
|
|
12
|
+
* (Change smallestUnit to "millisecond" if you want shorter strings.)
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_PRECISION = {
|
|
15
|
+
smallestUnit: "nanosecond",
|
|
16
|
+
roundingMode: "trunc",
|
|
17
|
+
};
|
|
18
|
+
/** PlainDate -> TEXT "YYYY-MM-DD" */
|
|
19
|
+
export const temporalPlainDateText = (name, opts) => {
|
|
20
|
+
const T = requireTemporal(opts?.Temporal);
|
|
21
|
+
return customType({
|
|
22
|
+
dataType: () => "text",
|
|
23
|
+
toDriver: (v) => v.toString(),
|
|
24
|
+
fromDriver: (v) => T.PlainDate.from(v),
|
|
25
|
+
})(name);
|
|
26
|
+
};
|
|
27
|
+
/** PlainTime -> TEXT "HH:mm:ss.sssssssss" (precision controlled via smallestUnit) */
|
|
28
|
+
export const temporalPlainTimeText = (name, opts) => {
|
|
29
|
+
const T = requireTemporal(opts?.Temporal);
|
|
30
|
+
const p = { ...DEFAULT_PRECISION, ...opts?.precision };
|
|
31
|
+
return customType({
|
|
32
|
+
dataType: () => "text",
|
|
33
|
+
toDriver: (v) => v.toString(p),
|
|
34
|
+
fromDriver: (v) => T.PlainTime.from(v),
|
|
35
|
+
})(name);
|
|
36
|
+
};
|
|
37
|
+
/** PlainDateTime -> TEXT "YYYY-MM-DDTHH:mm:ss.sssssssss" */
|
|
38
|
+
export const temporalPlainDateTimeText = (name, opts) => {
|
|
39
|
+
const T = requireTemporal(opts?.Temporal);
|
|
40
|
+
const p = { ...DEFAULT_PRECISION, ...opts?.precision };
|
|
41
|
+
return customType({
|
|
42
|
+
dataType: () => "text",
|
|
43
|
+
toDriver: (v) => v.toString(p),
|
|
44
|
+
fromDriver: (v) => T.PlainDateTime.from(v),
|
|
45
|
+
})(name);
|
|
46
|
+
};
|
|
47
|
+
/** PlainYearMonth -> TEXT "YYYY-MM" (plus optional calendar annotation if non-ISO) */
|
|
48
|
+
export const temporalPlainYearMonthText = (name, opts) => {
|
|
49
|
+
const T = requireTemporal(opts?.Temporal);
|
|
50
|
+
return customType({
|
|
51
|
+
dataType: () => "text",
|
|
52
|
+
toDriver: (v) => v.toString(),
|
|
53
|
+
fromDriver: (v) => T.PlainYearMonth.from(v),
|
|
54
|
+
})(name);
|
|
55
|
+
};
|
|
56
|
+
/** PlainMonthDay -> TEXT "MM-DD" (plus optional calendar annotation if non-ISO) */
|
|
57
|
+
export const temporalPlainMonthDayText = (name, opts) => {
|
|
58
|
+
const T = requireTemporal(opts?.Temporal);
|
|
59
|
+
return customType({
|
|
60
|
+
dataType: () => "text",
|
|
61
|
+
toDriver: (v) => v.toString(),
|
|
62
|
+
fromDriver: (v) => T.PlainMonthDay.from(v),
|
|
63
|
+
})(name);
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Duration -> TEXT ISO-8601, e.g. "PT15M", "P2DT3H"
|
|
67
|
+
* Note: if millis/micros/nanos exceed 999, parsing back yields an
|
|
68
|
+
* equal-but-differently-balanced Duration.
|
|
69
|
+
*/
|
|
70
|
+
export const temporalDurationText = (name, opts) => {
|
|
71
|
+
const T = requireTemporal(opts?.Temporal);
|
|
72
|
+
return customType({
|
|
73
|
+
dataType: () => "text",
|
|
74
|
+
toDriver: (v) => v.toString(),
|
|
75
|
+
fromDriver: (v) => T.Duration.from(v),
|
|
76
|
+
})(name);
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Instant (recommended) -> INTEGER epoch milliseconds
|
|
80
|
+
* Great for sorting/filtering by actual time.
|
|
81
|
+
* Tradeoff: millisecond precision only.
|
|
82
|
+
*/
|
|
83
|
+
export const temporalInstantEpochMs = (name, opts) => {
|
|
84
|
+
const T = requireTemporal(opts?.Temporal);
|
|
85
|
+
return customType({
|
|
86
|
+
dataType: () => "integer",
|
|
87
|
+
toDriver: (v) => v.epochMilliseconds,
|
|
88
|
+
fromDriver: (v) => T.Instant.fromEpochMilliseconds(typeof v === "string" ? Number(v) : v),
|
|
89
|
+
fromJson: (v) => T.Instant.fromEpochMilliseconds(typeof v === "string" ? Number(v) : v),
|
|
90
|
+
})(name);
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Instant (lossless) -> TEXT ISO string, e.g. "2026-01-25T12:34:56.123456789Z"
|
|
94
|
+
* Tradeoff: sorting by TEXT is chronological only if the format is stable.
|
|
95
|
+
*/
|
|
96
|
+
export const temporalInstantIsoText = (name, opts) => {
|
|
97
|
+
const T = requireTemporal(opts?.Temporal);
|
|
98
|
+
const p = { ...DEFAULT_PRECISION, ...opts?.precision };
|
|
99
|
+
return customType({
|
|
100
|
+
dataType: () => "text",
|
|
101
|
+
toDriver: (v) => v.toString(p),
|
|
102
|
+
fromDriver: (v) => T.Instant.from(v),
|
|
103
|
+
})(name);
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* ZonedDateTime (single-column) -> TEXT with zone annotation.
|
|
107
|
+
* Example: "2026-01-25T09:00:00-05:00[America/New_York]"
|
|
108
|
+
*
|
|
109
|
+
* This is convenient, but if you need sorting/filtering by actual moment,
|
|
110
|
+
* prefer the multi-column helper below (epochMs + timeZoneId + calendarId).
|
|
111
|
+
*/
|
|
112
|
+
export const temporalZonedDateTimeText = (name, opts) => {
|
|
113
|
+
const T = requireTemporal(opts?.Temporal);
|
|
114
|
+
const p = { ...DEFAULT_PRECISION, ...opts?.precision };
|
|
115
|
+
return customType({
|
|
116
|
+
dataType: () => "text",
|
|
117
|
+
toDriver: (v) => v.toString({ ...p, timeZoneName: "auto" }),
|
|
118
|
+
fromDriver: (v) => T.ZonedDateTime.from(v),
|
|
119
|
+
})(name);
|
|
120
|
+
};
|
|
121
|
+
/* ------------------------------------------------------------------------------------------------
|
|
122
|
+
* Multi-column (“flattened”) helpers
|
|
123
|
+
* customType is 1-column <-> 1-value, so for “parts” you define multiple columns + pack/unpack.
|
|
124
|
+
* ------------------------------------------------------------------------------------------------ */
|
|
125
|
+
/** Recommended storage shape for ZonedDateTime: epochMs + timeZoneId (+ calendarId). */
|
|
126
|
+
export const zonedDateTimeParts = (prefix) => ({
|
|
127
|
+
epochMs: integer(`${prefix}_epoch_ms`).notNull(),
|
|
128
|
+
timeZoneId: text(`${prefix}_tz`).notNull(),
|
|
129
|
+
calendarId: text(`${prefix}_cal`).notNull().default("iso8601"),
|
|
130
|
+
});
|
|
131
|
+
export function packZonedDateTime(zdt) {
|
|
132
|
+
return {
|
|
133
|
+
epochMs: zdt.epochMilliseconds,
|
|
134
|
+
timeZoneId: zdt.timeZoneId,
|
|
135
|
+
calendarId: zdt.calendarId,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
export function unpackZonedDateTime(parts, opts) {
|
|
139
|
+
const T = requireTemporal(opts?.Temporal);
|
|
140
|
+
const zdt = T.Instant.fromEpochMilliseconds(parts.epochMs).toZonedDateTimeISO(parts.timeZoneId);
|
|
141
|
+
return parts.calendarId ? zdt.withCalendar(parts.calendarId) : zdt;
|
|
142
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sedrino/drizzle",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "./dist/sqlite.js",
|
|
5
|
+
"types": "./dist/sqlite.d.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./sqlite": {
|
|
8
|
+
"types": "./dist/sqlite.d.ts",
|
|
9
|
+
"default": "./dist/sqlite.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"temporal-polyfill": "^0.3.0",
|
|
18
|
+
"drizzle-orm": "^1.0.0-beta.9"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/bun": "latest",
|
|
22
|
+
"typescript": "^5.6.3"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "bunx tsc -p tsconfig.build.json"
|
|
26
|
+
}
|
|
27
|
+
}
|