@haverstack/record-adapter-do-sqlite 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
2
+
3
+ To the extent possible under law, the Haverstack contributors have waived all
4
+ copyright and related or neighboring rights to Haverstack. This work is
5
+ published from the United States.
6
+
7
+ https://creativecommons.org/publicdomain/zero/1.0/
@@ -0,0 +1,134 @@
1
+ import { StackRecord, RecordVersion, ActorOptions, Permission, TypeId, StackQuery, QueryResult, FileId, StackType, Association } from '@haverstack/core';
2
+ import { StackRecordAdapter, AdapterCapabilities } from '@haverstack/core/adapter';
3
+ import { SqlExecutor } from '@haverstack/sqlite-shared/record';
4
+
5
+ /**
6
+ * Normalizes a Durable Object's ctx.storage.sql to SqlExecutor.
7
+ *
8
+ * Two things don't map onto node:sqlite's shape the way the rest of this
9
+ * interface does:
10
+ *
11
+ * - There is no get/all/run split — sql.exec() always returns a cursor.
12
+ * get()/all() read it via .toArray(); run() reads .rowsWritten for the
13
+ * affected-row count run()'s contract promises.
14
+ * - Raw BEGIN/COMMIT/ROLLBACK are rejected outright by DO SQLite (verified
15
+ * against the real Workers runtime, not assumed) — its storage has no
16
+ * notion of a transaction left open across separate exec() calls, and
17
+ * each statement auto-commits the instant it runs, so no-op'ing those
18
+ * three strings would silently break atomicity, not preserve it. The
19
+ * platform's real primitive is ctx.storage.transactionSync(fn), which
20
+ * SqlExecutor.transaction() reaches directly — see docs/spec/adapters.md
21
+ * § Concurrency & storage ownership.
22
+ */
23
+ declare class DurableObjectSqliteExecutor implements SqlExecutor {
24
+ private readonly storage;
25
+ constructor(storage: DurableObjectStorage);
26
+ private get sql();
27
+ exec(sql: string): void;
28
+ run(sql: string, params?: readonly unknown[]): number;
29
+ get<T = Record<string, unknown>>(sql: string, params?: readonly unknown[]): T | undefined;
30
+ all<T = Record<string, unknown>>(sql: string, params?: readonly unknown[]): T[];
31
+ transaction<T>(fn: () => T): T;
32
+ }
33
+
34
+ /**
35
+ * Haverstack — Durable Object SQLite Record Adapter
36
+ * -------------------------------------------------------
37
+ * Implements StackRecordAdapter over a Cloudflare Durable Object's SQLite
38
+ * storage (ctx.storage.sql). Full-text search uses FTS5, same as
39
+ * record-adapter-sqlite — DO's SQLite build ships it.
40
+ *
41
+ * Ownership and durability come from the platform, not from anything this
42
+ * class does: a Durable Object id maps to exactly one running instance,
43
+ * so there is no separate lock file the way record-adapter-sqlite needs
44
+ * one for real files (see docs/spec/adapters.md § Concurrency & storage
45
+ * ownership) — the DO *is* the lock. There is likewise no persist/flush
46
+ * step: every write through ctx.storage.sql is durable by the time the
47
+ * call returns, so flush()/close() are no-ops kept only to satisfy the
48
+ * optional StackRecordAdapter methods.
49
+ *
50
+ * This class itself is a thin binding: schema setup and the one piece of
51
+ * genuinely engine-specific wiring — SqlExecutor.transaction() reaching
52
+ * ctx.storage.transactionSync() instead of raw SQL BEGIN/COMMIT/ROLLBACK,
53
+ * which DO SQLite rejects outright — live here (see executor.ts). The
54
+ * actual StackRecordAdapter logic lives in @haverstack/sqlite-shared's
55
+ * SharedSqlRecordLogic, exactly as it does for record-adapter-sqlite.
56
+ */
57
+
58
+ type DoRecordCreateOptions = {
59
+ /** Entity ID of the stack owner. Ignored if the DO's storage already has a config record. */
60
+ entityId: string;
61
+ /** IANA timezone string e.g. "America/New_York". Optional passthrough app metadata — no default. */
62
+ timezone?: string;
63
+ };
64
+ declare class DoSQLiteRecordAdapter implements StackRecordAdapter {
65
+ private readonly exec;
66
+ readonly capabilities: AdapterCapabilities;
67
+ ownerEntityId: string;
68
+ timezone: string | undefined;
69
+ private readonly record;
70
+ private constructor();
71
+ /**
72
+ * Create (or reattach to) the adapter for a DO instance. There is no
73
+ * initialize()/open() split the way file-based adapters need one: a DO
74
+ * id either already has a config record (a previous call created it —
75
+ * reattach, opts.entityId/timezone ignored in favor of what's stored)
76
+ * or it doesn't (first call — opts.entityId/timezone become the config).
77
+ * Schema DDL is `CREATE TABLE IF NOT EXISTS`, so running it every call
78
+ * is idempotent and cheap.
79
+ */
80
+ static create(storage: DurableObjectStorage, opts: DoRecordCreateOptions): Promise<DoSQLiteRecordAdapter>;
81
+ createRecord(record: StackRecord): Promise<StackRecord>;
82
+ getRecord(id: string): Promise<StackRecord | null>;
83
+ patchContent(id: string, patch: Record<string, unknown | null>, opts?: {
84
+ expectedVersion?: number;
85
+ snapshot?: RecordVersion;
86
+ } & ActorOptions): Promise<StackRecord>;
87
+ deleteRecord(id: string, opts?: {
88
+ hard?: boolean;
89
+ expectedVersion?: number;
90
+ snapshot?: RecordVersion;
91
+ } & ActorOptions): Promise<StackRecord | null>;
92
+ undeleteRecord(id: string, opts?: {
93
+ expectedVersion?: number;
94
+ snapshot?: RecordVersion;
95
+ } & ActorOptions): Promise<StackRecord>;
96
+ setPermissions(id: string, permissions: Permission[], opts?: {
97
+ expectedVersion?: number;
98
+ snapshot?: RecordVersion;
99
+ } & ActorOptions): Promise<StackRecord>;
100
+ setUnlisted(id: string, unlisted: boolean, opts?: {
101
+ expectedVersion?: number;
102
+ snapshot?: RecordVersion;
103
+ } & ActorOptions): Promise<StackRecord>;
104
+ restoreVersion(id: string, version: number, opts?: {
105
+ expectedVersion?: number;
106
+ snapshot?: RecordVersion;
107
+ } & ActorOptions): Promise<StackRecord>;
108
+ commitMigration(id: string, toTypeId: TypeId, content: Record<string, unknown>, opts?: {
109
+ expectedVersion?: number;
110
+ snapshot?: RecordVersion;
111
+ } & ActorOptions): Promise<StackRecord>;
112
+ queryRecords(query: StackQuery): Promise<QueryResult>;
113
+ deleteUnreferencedAttachmentRecords(fileId: FileId, metadataTypeId: TypeId): Promise<StackRecord[]>;
114
+ getVersions(id: string): Promise<RecordVersion[]>;
115
+ getVersion(id: string, version: number): Promise<RecordVersion | null>;
116
+ saveVersion(id: string, version: RecordVersion): Promise<void>;
117
+ saveType(type: StackType): Promise<void>;
118
+ getType(id: TypeId): Promise<StackType | null>;
119
+ listTypes(): Promise<StackType[]>;
120
+ associate(recordId: string, association: Association, opts?: {
121
+ expectedVersion?: number;
122
+ snapshot?: RecordVersion;
123
+ } & ActorOptions): Promise<StackRecord>;
124
+ dissociate(recordId: string, association: Association, opts?: {
125
+ expectedVersion?: number;
126
+ snapshot?: RecordVersion;
127
+ } & ActorOptions): Promise<StackRecord>;
128
+ /** No-op: every write through ctx.storage.sql is already durable. */
129
+ flush(): Promise<void>;
130
+ /** No-op: no lock file, no connection to release — the DO's own lifecycle governs storage. */
131
+ close(): Promise<void>;
132
+ }
133
+
134
+ export { type DoRecordCreateOptions, DoSQLiteRecordAdapter, DurableObjectSqliteExecutor };