silosdk 0.0.0 → 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.
Files changed (85) hide show
  1. package/README.md +3 -1
  2. package/dist/_virtual/rolldown_runtime.cjs +29 -0
  3. package/dist/cli/d1.cjs +93 -0
  4. package/dist/cli/d1.mjs +92 -0
  5. package/dist/cli/index.cjs +93 -0
  6. package/dist/cli/index.d.cts +1 -0
  7. package/dist/cli/index.d.mts +1 -0
  8. package/dist/cli/index.mjs +94 -0
  9. package/dist/cli/init.cjs +134 -0
  10. package/dist/cli/init.mjs +133 -0
  11. package/dist/cli/kv.cjs +63 -0
  12. package/dist/cli/kv.mjs +60 -0
  13. package/dist/cli/r2.cjs +83 -0
  14. package/dist/cli/r2.mjs +82 -0
  15. package/dist/cli/wrangler.cjs +93 -0
  16. package/dist/cli/wrangler.mjs +89 -0
  17. package/dist/local/adapters/cloudflare.cjs +200 -0
  18. package/dist/local/adapters/cloudflare.d.cts +50 -0
  19. package/dist/local/adapters/cloudflare.d.mts +50 -0
  20. package/dist/local/adapters/cloudflare.mjs +200 -0
  21. package/dist/local/auth-context.cjs +14 -0
  22. package/dist/local/auth-context.d.cts +7 -0
  23. package/dist/local/auth-context.d.mts +7 -0
  24. package/dist/local/auth-context.mjs +12 -0
  25. package/dist/local/auth.cjs +109 -0
  26. package/dist/local/auth.d.cts +26 -0
  27. package/dist/local/auth.d.mts +26 -0
  28. package/dist/local/auth.mjs +99 -0
  29. package/dist/local/commit.cjs +350 -0
  30. package/dist/local/commit.d.cts +59 -0
  31. package/dist/local/commit.d.mts +59 -0
  32. package/dist/local/commit.mjs +349 -0
  33. package/dist/local/config.cjs +17 -0
  34. package/dist/local/config.mjs +15 -0
  35. package/dist/local/index.cjs +16 -0
  36. package/dist/local/index.d.cts +10 -0
  37. package/dist/local/index.d.mts +10 -0
  38. package/dist/local/index.mjs +9 -0
  39. package/dist/local/provider.cjs +204 -0
  40. package/dist/local/provider.d.cts +25 -0
  41. package/dist/local/provider.d.mts +25 -0
  42. package/dist/local/provider.mjs +203 -0
  43. package/dist/local/query-store.cjs +276 -0
  44. package/dist/local/query-store.mjs +274 -0
  45. package/dist/local/storage.cjs +71 -0
  46. package/dist/local/storage.d.cts +7 -0
  47. package/dist/local/storage.d.mts +7 -0
  48. package/dist/local/storage.mjs +68 -0
  49. package/dist/local/sync.cjs +124 -0
  50. package/dist/local/sync.d.cts +36 -0
  51. package/dist/local/sync.d.mts +36 -0
  52. package/dist/local/sync.mjs +122 -0
  53. package/dist/local/view.cjs +257 -0
  54. package/dist/local/view.d.cts +24 -0
  55. package/dist/local/view.d.mts +24 -0
  56. package/dist/local/view.mjs +254 -0
  57. package/dist/package.cjs +11 -0
  58. package/dist/package.mjs +5 -0
  59. package/dist/schema/index.cjs +276 -0
  60. package/dist/schema/index.d.cts +207 -0
  61. package/dist/schema/index.d.mts +207 -0
  62. package/dist/schema/index.mjs +265 -0
  63. package/dist/server/auth.cjs +132 -0
  64. package/dist/server/auth.d.cts +49 -0
  65. package/dist/server/auth.d.mts +49 -0
  66. package/dist/server/auth.mjs +122 -0
  67. package/dist/server/d1.cjs +120 -0
  68. package/dist/server/d1.mjs +116 -0
  69. package/dist/server/do.cjs +132 -0
  70. package/dist/server/do.d.cts +21 -0
  71. package/dist/server/do.d.mts +21 -0
  72. package/dist/server/do.mjs +131 -0
  73. package/dist/server/index.cjs +355 -0
  74. package/dist/server/index.d.cts +65 -0
  75. package/dist/server/index.d.mts +65 -0
  76. package/dist/server/index.mjs +348 -0
  77. package/dist/server/protect.cjs +34 -0
  78. package/dist/server/protect.d.cts +32 -0
  79. package/dist/server/protect.d.mts +32 -0
  80. package/dist/server/protect.mjs +33 -0
  81. package/dist/server/r2.cjs +58 -0
  82. package/dist/server/r2.d.cts +4 -0
  83. package/dist/server/r2.d.mts +4 -0
  84. package/dist/server/r2.mjs +53 -0
  85. package/package.json +55 -2
@@ -0,0 +1,120 @@
1
+
2
+ //#region src/server/d1.ts
3
+ function jex(field) {
4
+ return `json_extract(data, '$.${field}')`;
5
+ }
6
+ function compileWhere(where) {
7
+ const params = [];
8
+ const compileObject = (obj) => {
9
+ const parts = [];
10
+ for (const field of Object.keys(obj)) {
11
+ const value = obj[field];
12
+ if (Array.isArray(value)) {
13
+ const placeholders = value.map(() => "?").join(", ");
14
+ parts.push(`${jex(field)} IN (${placeholders})`);
15
+ params.push(...value);
16
+ } else if (value && typeof value === "object") for (const op of Object.keys(value)) {
17
+ parts.push(`${jex(field)} ${op} ?`);
18
+ params.push(value[op]);
19
+ }
20
+ else {
21
+ parts.push(`${jex(field)} = ?`);
22
+ params.push(value);
23
+ }
24
+ }
25
+ return parts.join(" AND ");
26
+ };
27
+ if (Array.isArray(where)) return {
28
+ clause: where.map((o) => `(${compileObject(o)})`).join(" OR "),
29
+ params
30
+ };
31
+ return {
32
+ clause: compileObject(where),
33
+ params
34
+ };
35
+ }
36
+ async function d1GetRow(db, viewName, options) {
37
+ const row = await db.prepare(`SELECT id, view, data, createdAt, updatedAt, version
38
+ FROM views WHERE view = ? AND id = ?`).bind(viewName, options.id).first();
39
+ if (!row) return null;
40
+ return {
41
+ ...row,
42
+ data: JSON.parse(row.data)
43
+ };
44
+ }
45
+ async function d1GetRows(db, viewName, options) {
46
+ const parentView = options.parentView;
47
+ const parentId = options.parentId;
48
+ const childView = options.childView;
49
+ const childId = options.childId;
50
+ let statement = `SELECT id, view, data, createdAt, updatedAt, version FROM views WHERE view = ?`;
51
+ const params = [viewName];
52
+ if (parentView && parentId) {
53
+ statement += ` AND id IN (SELECT cid FROM relations WHERE child = ? AND parent = ? AND pid = ?)`;
54
+ params.push(viewName, parentView, parentId);
55
+ }
56
+ if (childView && childId) {
57
+ statement += ` AND id IN (SELECT pid FROM relations WHERE parent = ? AND child = ? AND cid = ?)`;
58
+ params.push(viewName, childView, childId);
59
+ }
60
+ if (options.where) {
61
+ const { clause, params: wp } = compileWhere(options.where);
62
+ statement += ` AND (${clause})`;
63
+ params.push(...wp);
64
+ }
65
+ if (options.order) {
66
+ const orderClauses = [];
67
+ for (const [field, direction] of Object.entries(options.order)) orderClauses.push(`${jex(field)} ${String(direction).toUpperCase()}`);
68
+ if (orderClauses.length > 0) statement += ` ORDER BY ${orderClauses.join(", ")}`;
69
+ }
70
+ if (options.take !== void 0) {
71
+ statement += ` LIMIT ?`;
72
+ params.push(options.take);
73
+ }
74
+ return ((await db.prepare(statement).bind(...params).all()).results ?? []).map((row) => ({
75
+ ...row,
76
+ data: JSON.parse(row.data)
77
+ }));
78
+ }
79
+ async function d1ApplyOps(db, ops) {
80
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
81
+ const stmts = [];
82
+ for (const op of ops) if (op.kind === "add") stmts.push(db.prepare(`INSERT INTO views (id, view, data, createdAt, updatedAt, version)
83
+ VALUES (?, ?, ?, ?, ?, ?)
84
+ ON CONFLICT(id) DO NOTHING`).bind(op.id, op.view.name, JSON.stringify(op.value), timestamp, timestamp, 1));
85
+ else if (op.kind === "update") stmts.push(db.prepare(`UPDATE views
86
+ SET data = json_patch(data, ?), updatedAt = ?, version = version + 1
87
+ WHERE id = ? AND view = ?`).bind(JSON.stringify(op.value), timestamp, op.id, op.view.name));
88
+ else if (op.kind === "remove") {
89
+ stmts.push(db.prepare(`DELETE FROM views WHERE id = ? AND view = ?`).bind(op.id, op.view.name));
90
+ stmts.push(db.prepare(`DELETE FROM relations
91
+ WHERE (parent = ? AND pid = ?) OR (child = ? AND cid = ?)`).bind(op.view.name, op.id, op.view.name, op.id));
92
+ } else if (op.kind === "link") stmts.push(db.prepare(`INSERT INTO relations (parent, pid, child, cid)
93
+ VALUES (?, ?, ?, ?)
94
+ ON CONFLICT DO NOTHING`).bind(op.parent.view.name, op.parent.id, op.child.view.name, op.child.id));
95
+ else if (op.kind === "unlink") stmts.push(db.prepare(`DELETE FROM relations
96
+ WHERE parent = ? AND pid = ? AND child = ? AND cid = ?`).bind(op.parent.view.name, op.parent.id, op.child.view.name, op.child.id));
97
+ if (stmts.length > 0) await db.batch(stmts);
98
+ }
99
+ async function d1EnsureSchema(db) {
100
+ await db.prepare(`CREATE TABLE IF NOT EXISTS views (
101
+ id TEXT PRIMARY KEY,
102
+ view TEXT NOT NULL,
103
+ data BLOB NOT NULL,
104
+ createdAt TEXT NOT NULL,
105
+ updatedAt TEXT NOT NULL,
106
+ version INTEGER DEFAULT 0
107
+ )`).run();
108
+ await db.prepare(`CREATE TABLE IF NOT EXISTS relations (
109
+ parent TEXT NOT NULL,
110
+ pid TEXT NOT NULL,
111
+ child TEXT NOT NULL,
112
+ cid TEXT NOT NULL
113
+ )`).run();
114
+ }
115
+
116
+ //#endregion
117
+ exports.d1ApplyOps = d1ApplyOps;
118
+ exports.d1EnsureSchema = d1EnsureSchema;
119
+ exports.d1GetRow = d1GetRow;
120
+ exports.d1GetRows = d1GetRows;
@@ -0,0 +1,116 @@
1
+ //#region src/server/d1.ts
2
+ function jex(field) {
3
+ return `json_extract(data, '$.${field}')`;
4
+ }
5
+ function compileWhere(where) {
6
+ const params = [];
7
+ const compileObject = (obj) => {
8
+ const parts = [];
9
+ for (const field of Object.keys(obj)) {
10
+ const value = obj[field];
11
+ if (Array.isArray(value)) {
12
+ const placeholders = value.map(() => "?").join(", ");
13
+ parts.push(`${jex(field)} IN (${placeholders})`);
14
+ params.push(...value);
15
+ } else if (value && typeof value === "object") for (const op of Object.keys(value)) {
16
+ parts.push(`${jex(field)} ${op} ?`);
17
+ params.push(value[op]);
18
+ }
19
+ else {
20
+ parts.push(`${jex(field)} = ?`);
21
+ params.push(value);
22
+ }
23
+ }
24
+ return parts.join(" AND ");
25
+ };
26
+ if (Array.isArray(where)) return {
27
+ clause: where.map((o) => `(${compileObject(o)})`).join(" OR "),
28
+ params
29
+ };
30
+ return {
31
+ clause: compileObject(where),
32
+ params
33
+ };
34
+ }
35
+ async function d1GetRow(db, viewName, options) {
36
+ const row = await db.prepare(`SELECT id, view, data, createdAt, updatedAt, version
37
+ FROM views WHERE view = ? AND id = ?`).bind(viewName, options.id).first();
38
+ if (!row) return null;
39
+ return {
40
+ ...row,
41
+ data: JSON.parse(row.data)
42
+ };
43
+ }
44
+ async function d1GetRows(db, viewName, options) {
45
+ const parentView = options.parentView;
46
+ const parentId = options.parentId;
47
+ const childView = options.childView;
48
+ const childId = options.childId;
49
+ let statement = `SELECT id, view, data, createdAt, updatedAt, version FROM views WHERE view = ?`;
50
+ const params = [viewName];
51
+ if (parentView && parentId) {
52
+ statement += ` AND id IN (SELECT cid FROM relations WHERE child = ? AND parent = ? AND pid = ?)`;
53
+ params.push(viewName, parentView, parentId);
54
+ }
55
+ if (childView && childId) {
56
+ statement += ` AND id IN (SELECT pid FROM relations WHERE parent = ? AND child = ? AND cid = ?)`;
57
+ params.push(viewName, childView, childId);
58
+ }
59
+ if (options.where) {
60
+ const { clause, params: wp } = compileWhere(options.where);
61
+ statement += ` AND (${clause})`;
62
+ params.push(...wp);
63
+ }
64
+ if (options.order) {
65
+ const orderClauses = [];
66
+ for (const [field, direction] of Object.entries(options.order)) orderClauses.push(`${jex(field)} ${String(direction).toUpperCase()}`);
67
+ if (orderClauses.length > 0) statement += ` ORDER BY ${orderClauses.join(", ")}`;
68
+ }
69
+ if (options.take !== void 0) {
70
+ statement += ` LIMIT ?`;
71
+ params.push(options.take);
72
+ }
73
+ return ((await db.prepare(statement).bind(...params).all()).results ?? []).map((row) => ({
74
+ ...row,
75
+ data: JSON.parse(row.data)
76
+ }));
77
+ }
78
+ async function d1ApplyOps(db, ops) {
79
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
80
+ const stmts = [];
81
+ for (const op of ops) if (op.kind === "add") stmts.push(db.prepare(`INSERT INTO views (id, view, data, createdAt, updatedAt, version)
82
+ VALUES (?, ?, ?, ?, ?, ?)
83
+ ON CONFLICT(id) DO NOTHING`).bind(op.id, op.view.name, JSON.stringify(op.value), timestamp, timestamp, 1));
84
+ else if (op.kind === "update") stmts.push(db.prepare(`UPDATE views
85
+ SET data = json_patch(data, ?), updatedAt = ?, version = version + 1
86
+ WHERE id = ? AND view = ?`).bind(JSON.stringify(op.value), timestamp, op.id, op.view.name));
87
+ else if (op.kind === "remove") {
88
+ stmts.push(db.prepare(`DELETE FROM views WHERE id = ? AND view = ?`).bind(op.id, op.view.name));
89
+ stmts.push(db.prepare(`DELETE FROM relations
90
+ WHERE (parent = ? AND pid = ?) OR (child = ? AND cid = ?)`).bind(op.view.name, op.id, op.view.name, op.id));
91
+ } else if (op.kind === "link") stmts.push(db.prepare(`INSERT INTO relations (parent, pid, child, cid)
92
+ VALUES (?, ?, ?, ?)
93
+ ON CONFLICT DO NOTHING`).bind(op.parent.view.name, op.parent.id, op.child.view.name, op.child.id));
94
+ else if (op.kind === "unlink") stmts.push(db.prepare(`DELETE FROM relations
95
+ WHERE parent = ? AND pid = ? AND child = ? AND cid = ?`).bind(op.parent.view.name, op.parent.id, op.child.view.name, op.child.id));
96
+ if (stmts.length > 0) await db.batch(stmts);
97
+ }
98
+ async function d1EnsureSchema(db) {
99
+ await db.prepare(`CREATE TABLE IF NOT EXISTS views (
100
+ id TEXT PRIMARY KEY,
101
+ view TEXT NOT NULL,
102
+ data BLOB NOT NULL,
103
+ createdAt TEXT NOT NULL,
104
+ updatedAt TEXT NOT NULL,
105
+ version INTEGER DEFAULT 0
106
+ )`).run();
107
+ await db.prepare(`CREATE TABLE IF NOT EXISTS relations (
108
+ parent TEXT NOT NULL,
109
+ pid TEXT NOT NULL,
110
+ child TEXT NOT NULL,
111
+ cid TEXT NOT NULL
112
+ )`).run();
113
+ }
114
+
115
+ //#endregion
116
+ export { d1ApplyOps, d1EnsureSchema, d1GetRow, d1GetRows };
@@ -0,0 +1,132 @@
1
+
2
+ //#region src/server/do.ts
3
+ var SyncObject = class {
4
+ constructor(state, env) {
5
+ this.state = state;
6
+ this.env = env;
7
+ this.sessions = /* @__PURE__ */ new Map();
8
+ this.sql = state.storage.sql;
9
+ this.sql.exec(`
10
+ CREATE TABLE IF NOT EXISTS views (
11
+ id TEXT PRIMARY KEY,
12
+ view TEXT NOT NULL,
13
+ data BLOB NOT NULL,
14
+ createdAt TEXT NOT NULL,
15
+ updatedAt TEXT NOT NULL,
16
+ version INTEGER DEFAULT 0
17
+ );
18
+ CREATE TABLE IF NOT EXISTS relations (
19
+ parent TEXT NOT NULL,
20
+ pid TEXT NOT NULL,
21
+ child TEXT NOT NULL,
22
+ cid TEXT NOT NULL
23
+ );
24
+ `);
25
+ }
26
+ async fetch(request) {
27
+ const upgradeHeader = request.headers.get("Upgrade");
28
+ if (!upgradeHeader || upgradeHeader !== "websocket") return new Response("Expected WebSocket", { status: 426 });
29
+ const { 0: client, 1: server } = new WebSocketPair();
30
+ this.state.acceptWebSocket(server);
31
+ return new Response(null, {
32
+ status: 101,
33
+ webSocket: client
34
+ });
35
+ }
36
+ async webSocketMessage(ws, message) {
37
+ let msg;
38
+ try {
39
+ msg = JSON.parse(typeof message === "string" ? message : "");
40
+ } catch {
41
+ this.send(ws, {
42
+ type: "error",
43
+ message: "Invalid JSON"
44
+ });
45
+ return;
46
+ }
47
+ if (msg.type === "subscribe") await this.handleSubscribe(ws, msg.views);
48
+ else if (msg.type === "commit") await this.handleCommit(ws, msg.ops, msg.requestId);
49
+ }
50
+ async webSocketClose(ws) {
51
+ this.sessions.delete(ws);
52
+ }
53
+ async webSocketError(ws) {
54
+ this.sessions.delete(ws);
55
+ ws.close();
56
+ }
57
+ async handleSubscribe(ws, views) {
58
+ this.sessions.set(ws, { views });
59
+ for (const viewName of views) {
60
+ const parsed = this.sql.exec(`SELECT id, view, data, createdAt, updatedAt, version
61
+ FROM views WHERE view = ?`, viewName).toArray().map((row) => ({
62
+ ...row,
63
+ data: JSON.parse(row.data)
64
+ }));
65
+ this.send(ws, {
66
+ type: "hydrate",
67
+ view: viewName,
68
+ rows: parsed
69
+ });
70
+ }
71
+ }
72
+ async handleCommit(ws, ops, requestId) {
73
+ try {
74
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
75
+ this.sql.exec("BEGIN");
76
+ try {
77
+ for (const op of ops) if (op.kind === "add") this.sql.exec(`INSERT INTO views (id, view, data, createdAt, updatedAt, version)
78
+ VALUES (?, ?, ?, ?, ?, ?)
79
+ ON CONFLICT(id) DO NOTHING`, op.id, op.view.name, JSON.stringify(op.value), timestamp, timestamp, 1);
80
+ else if (op.kind === "update") this.sql.exec(`UPDATE views
81
+ SET data = json_patch(data, ?), updatedAt = ?, version = version + 1
82
+ WHERE id = ? AND view = ?`, JSON.stringify(op.value), timestamp, op.id, op.view.name);
83
+ else if (op.kind === "remove") {
84
+ this.sql.exec(`DELETE FROM views WHERE id = ? AND view = ?`, op.id, op.view.name);
85
+ this.sql.exec(`DELETE FROM relations
86
+ WHERE (parent = ? AND pid = ?) OR (child = ? AND cid = ?)`, op.view.name, op.id, op.view.name, op.id);
87
+ } else if (op.kind === "link") this.sql.exec(`INSERT OR IGNORE INTO relations (parent, pid, child, cid)
88
+ VALUES (?, ?, ?, ?)`, op.parent.view.name, op.parent.id, op.child.view.name, op.child.id);
89
+ else if (op.kind === "unlink") this.sql.exec(`DELETE FROM relations
90
+ WHERE parent = ? AND pid = ? AND child = ? AND cid = ?`, op.parent.view.name, op.parent.id, op.child.view.name, op.child.id);
91
+ this.sql.exec("COMMIT");
92
+ } catch (err) {
93
+ this.sql.exec("ROLLBACK");
94
+ throw err;
95
+ }
96
+ this.send(ws, {
97
+ type: "committed",
98
+ requestId,
99
+ ops
100
+ });
101
+ const touchedViews = new Set(ops.map((op) => op.kind === "link" || op.kind === "unlink" ? op.parent.view.name : op.view.name));
102
+ for (const [otherWs, session] of this.sessions) {
103
+ if (otherWs === ws) continue;
104
+ if (session.views.some((v) => touchedViews.has(v))) this.send(otherWs, {
105
+ type: "push",
106
+ ops
107
+ });
108
+ }
109
+ for (const otherWs of this.state.getWebSockets()) {
110
+ if (otherWs === ws) continue;
111
+ if (!this.sessions.has(otherWs)) this.send(otherWs, {
112
+ type: "push",
113
+ ops
114
+ });
115
+ }
116
+ } catch (err) {
117
+ this.send(ws, {
118
+ type: "rejected",
119
+ requestId,
120
+ reason: err instanceof Error ? err.message : "Unknown error"
121
+ });
122
+ }
123
+ }
124
+ send(ws, msg) {
125
+ try {
126
+ ws.send(JSON.stringify(msg));
127
+ } catch {}
128
+ }
129
+ };
130
+
131
+ //#endregion
132
+ exports.SyncObject = SyncObject;
@@ -0,0 +1,21 @@
1
+ //#region src/server/do.d.ts
2
+ interface Env {
3
+ SILO_SYNC: DurableObjectNamespace;
4
+ SILO_DB: D1Database;
5
+ }
6
+ declare class SyncObject implements DurableObject {
7
+ private readonly state;
8
+ private readonly env;
9
+ private sql;
10
+ private sessions;
11
+ constructor(state: DurableObjectState, env: Env);
12
+ fetch(request: Request): Promise<Response>;
13
+ webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void>;
14
+ webSocketClose(ws: WebSocket): Promise<void>;
15
+ webSocketError(ws: WebSocket): Promise<void>;
16
+ private handleSubscribe;
17
+ private handleCommit;
18
+ private send;
19
+ }
20
+ //#endregion
21
+ export { SyncObject };
@@ -0,0 +1,21 @@
1
+ //#region src/server/do.d.ts
2
+ interface Env {
3
+ SILO_SYNC: DurableObjectNamespace;
4
+ SILO_DB: D1Database;
5
+ }
6
+ declare class SyncObject implements DurableObject {
7
+ private readonly state;
8
+ private readonly env;
9
+ private sql;
10
+ private sessions;
11
+ constructor(state: DurableObjectState, env: Env);
12
+ fetch(request: Request): Promise<Response>;
13
+ webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void>;
14
+ webSocketClose(ws: WebSocket): Promise<void>;
15
+ webSocketError(ws: WebSocket): Promise<void>;
16
+ private handleSubscribe;
17
+ private handleCommit;
18
+ private send;
19
+ }
20
+ //#endregion
21
+ export { SyncObject };
@@ -0,0 +1,131 @@
1
+ //#region src/server/do.ts
2
+ var SyncObject = class {
3
+ constructor(state, env) {
4
+ this.state = state;
5
+ this.env = env;
6
+ this.sessions = /* @__PURE__ */ new Map();
7
+ this.sql = state.storage.sql;
8
+ this.sql.exec(`
9
+ CREATE TABLE IF NOT EXISTS views (
10
+ id TEXT PRIMARY KEY,
11
+ view TEXT NOT NULL,
12
+ data BLOB NOT NULL,
13
+ createdAt TEXT NOT NULL,
14
+ updatedAt TEXT NOT NULL,
15
+ version INTEGER DEFAULT 0
16
+ );
17
+ CREATE TABLE IF NOT EXISTS relations (
18
+ parent TEXT NOT NULL,
19
+ pid TEXT NOT NULL,
20
+ child TEXT NOT NULL,
21
+ cid TEXT NOT NULL
22
+ );
23
+ `);
24
+ }
25
+ async fetch(request) {
26
+ const upgradeHeader = request.headers.get("Upgrade");
27
+ if (!upgradeHeader || upgradeHeader !== "websocket") return new Response("Expected WebSocket", { status: 426 });
28
+ const { 0: client, 1: server } = new WebSocketPair();
29
+ this.state.acceptWebSocket(server);
30
+ return new Response(null, {
31
+ status: 101,
32
+ webSocket: client
33
+ });
34
+ }
35
+ async webSocketMessage(ws, message) {
36
+ let msg;
37
+ try {
38
+ msg = JSON.parse(typeof message === "string" ? message : "");
39
+ } catch {
40
+ this.send(ws, {
41
+ type: "error",
42
+ message: "Invalid JSON"
43
+ });
44
+ return;
45
+ }
46
+ if (msg.type === "subscribe") await this.handleSubscribe(ws, msg.views);
47
+ else if (msg.type === "commit") await this.handleCommit(ws, msg.ops, msg.requestId);
48
+ }
49
+ async webSocketClose(ws) {
50
+ this.sessions.delete(ws);
51
+ }
52
+ async webSocketError(ws) {
53
+ this.sessions.delete(ws);
54
+ ws.close();
55
+ }
56
+ async handleSubscribe(ws, views) {
57
+ this.sessions.set(ws, { views });
58
+ for (const viewName of views) {
59
+ const parsed = this.sql.exec(`SELECT id, view, data, createdAt, updatedAt, version
60
+ FROM views WHERE view = ?`, viewName).toArray().map((row) => ({
61
+ ...row,
62
+ data: JSON.parse(row.data)
63
+ }));
64
+ this.send(ws, {
65
+ type: "hydrate",
66
+ view: viewName,
67
+ rows: parsed
68
+ });
69
+ }
70
+ }
71
+ async handleCommit(ws, ops, requestId) {
72
+ try {
73
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
74
+ this.sql.exec("BEGIN");
75
+ try {
76
+ for (const op of ops) if (op.kind === "add") this.sql.exec(`INSERT INTO views (id, view, data, createdAt, updatedAt, version)
77
+ VALUES (?, ?, ?, ?, ?, ?)
78
+ ON CONFLICT(id) DO NOTHING`, op.id, op.view.name, JSON.stringify(op.value), timestamp, timestamp, 1);
79
+ else if (op.kind === "update") this.sql.exec(`UPDATE views
80
+ SET data = json_patch(data, ?), updatedAt = ?, version = version + 1
81
+ WHERE id = ? AND view = ?`, JSON.stringify(op.value), timestamp, op.id, op.view.name);
82
+ else if (op.kind === "remove") {
83
+ this.sql.exec(`DELETE FROM views WHERE id = ? AND view = ?`, op.id, op.view.name);
84
+ this.sql.exec(`DELETE FROM relations
85
+ WHERE (parent = ? AND pid = ?) OR (child = ? AND cid = ?)`, op.view.name, op.id, op.view.name, op.id);
86
+ } else if (op.kind === "link") this.sql.exec(`INSERT OR IGNORE INTO relations (parent, pid, child, cid)
87
+ VALUES (?, ?, ?, ?)`, op.parent.view.name, op.parent.id, op.child.view.name, op.child.id);
88
+ else if (op.kind === "unlink") this.sql.exec(`DELETE FROM relations
89
+ WHERE parent = ? AND pid = ? AND child = ? AND cid = ?`, op.parent.view.name, op.parent.id, op.child.view.name, op.child.id);
90
+ this.sql.exec("COMMIT");
91
+ } catch (err) {
92
+ this.sql.exec("ROLLBACK");
93
+ throw err;
94
+ }
95
+ this.send(ws, {
96
+ type: "committed",
97
+ requestId,
98
+ ops
99
+ });
100
+ const touchedViews = new Set(ops.map((op) => op.kind === "link" || op.kind === "unlink" ? op.parent.view.name : op.view.name));
101
+ for (const [otherWs, session] of this.sessions) {
102
+ if (otherWs === ws) continue;
103
+ if (session.views.some((v) => touchedViews.has(v))) this.send(otherWs, {
104
+ type: "push",
105
+ ops
106
+ });
107
+ }
108
+ for (const otherWs of this.state.getWebSockets()) {
109
+ if (otherWs === ws) continue;
110
+ if (!this.sessions.has(otherWs)) this.send(otherWs, {
111
+ type: "push",
112
+ ops
113
+ });
114
+ }
115
+ } catch (err) {
116
+ this.send(ws, {
117
+ type: "rejected",
118
+ requestId,
119
+ reason: err instanceof Error ? err.message : "Unknown error"
120
+ });
121
+ }
122
+ }
123
+ send(ws, msg) {
124
+ try {
125
+ ws.send(JSON.stringify(msg));
126
+ } catch {}
127
+ }
128
+ };
129
+
130
+ //#endregion
131
+ export { SyncObject };