@nyte-ai/host 0.0.0-stage → 0.0.19

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 (250) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -2
  3. package/THIRD-PARTY-NOTICES.md +866 -0
  4. package/dist/chunks/canvas-preview-0cmbkp2j.js +65 -0
  5. package/dist/chunks/canvas-preview-0nq75h9k.js +7 -0
  6. package/dist/chunks/canvas-preview-11nadadc.js +241 -0
  7. package/dist/chunks/canvas-preview-1sq7z9jq.js +17 -0
  8. package/dist/chunks/canvas-preview-2jwbe14k.js +130 -0
  9. package/dist/chunks/canvas-preview-2zdyh9ny.js +26 -0
  10. package/dist/chunks/canvas-preview-31w7xm8z.js +34 -0
  11. package/dist/chunks/canvas-preview-4b6sayhg.js +295 -0
  12. package/dist/chunks/canvas-preview-4e0pxa32.js +63 -0
  13. package/dist/chunks/canvas-preview-518dh8by.js +169 -0
  14. package/dist/chunks/canvas-preview-55s0y68g.js +387 -0
  15. package/dist/chunks/canvas-preview-5ekjx6sw.js +613 -0
  16. package/dist/chunks/canvas-preview-5mex725c.js +14 -0
  17. package/dist/chunks/canvas-preview-5z63am63.js +295 -0
  18. package/dist/chunks/canvas-preview-65xqjw7f.js +20 -0
  19. package/dist/chunks/canvas-preview-69fkk9yn.js +95 -0
  20. package/dist/chunks/canvas-preview-8xc0vn7v.js +458 -0
  21. package/dist/chunks/canvas-preview-9aqczacs.js +73 -0
  22. package/dist/chunks/canvas-preview-af86jz8t.js +141 -0
  23. package/dist/chunks/canvas-preview-aq5j4p25.js +240 -0
  24. package/dist/chunks/canvas-preview-b2v8wfnz.js +36 -0
  25. package/dist/chunks/canvas-preview-befkjea2.js +60 -0
  26. package/dist/chunks/canvas-preview-bxnqt8cd.js +103 -0
  27. package/dist/chunks/canvas-preview-cawy6vbz.js +352 -0
  28. package/dist/chunks/canvas-preview-crmzxxt8.js +518 -0
  29. package/dist/chunks/canvas-preview-drctegz4.js +591 -0
  30. package/dist/chunks/canvas-preview-e3vnkkc5.js +134 -0
  31. package/dist/chunks/canvas-preview-ec2mt7es.js +108 -0
  32. package/dist/chunks/canvas-preview-fxa0rfb0.js +61 -0
  33. package/dist/chunks/canvas-preview-g5fkjs34.js +598 -0
  34. package/dist/chunks/canvas-preview-jeddjex8.js +166 -0
  35. package/dist/chunks/canvas-preview-k1yg4fgp.js +571 -0
  36. package/dist/chunks/canvas-preview-kdqgrstc.js +194 -0
  37. package/dist/chunks/canvas-preview-kn7kvbfj.js +12 -0
  38. package/dist/chunks/canvas-preview-kp75gb63.js +141 -0
  39. package/dist/chunks/canvas-preview-mgcx2b2p.js +271 -0
  40. package/dist/chunks/canvas-preview-n2a35zv5.js +81 -0
  41. package/dist/chunks/canvas-preview-pkzqg96v.js +63 -0
  42. package/dist/chunks/canvas-preview-rp4x6y1h.js +144 -0
  43. package/dist/chunks/canvas-preview-s94ebwf5.js +112 -0
  44. package/dist/chunks/canvas-preview-skzwvw4d.js +23 -0
  45. package/dist/chunks/canvas-preview-srgfpdmf.js +60 -0
  46. package/dist/chunks/canvas-preview-tjhabynd.js +252 -0
  47. package/dist/chunks/canvas-preview-tnqe7k0b.js +374 -0
  48. package/dist/chunks/canvas-preview-vaatfznm.js +189 -0
  49. package/dist/chunks/canvas-preview-vm9zdp2t.js +398 -0
  50. package/dist/chunks/canvas-preview-w54ymtb3.js +272 -0
  51. package/dist/chunks/canvas-preview-wg80rbc2.js +46 -0
  52. package/dist/chunks/canvas-preview-y5y6ghaw.js +125 -0
  53. package/dist/chunks/canvas-preview-ycepb05s.js +176 -0
  54. package/dist/chunks/canvas-preview-yjz0f7n4.js +50 -0
  55. package/dist/chunks/canvas-preview-zaaj99wd.js +244 -0
  56. package/dist/src/canvas-preview.d.ts +29 -0
  57. package/dist/src/canvas-preview.js +271 -0
  58. package/dist/src/credentials.d.ts +44 -0
  59. package/dist/src/credentials.js +10 -0
  60. package/dist/src/deploy/cloudflare/worker.d.ts +50 -0
  61. package/dist/src/deploy/cloudflare/worker.js +196 -0
  62. package/dist/src/deploy/vercel/nitro.config.d.ts +2 -0
  63. package/dist/src/deploy/vercel/nitro.config.js +30 -0
  64. package/dist/src/deploy/vercel/server/routes/[...].d.ts +2 -0
  65. package/dist/src/deploy/vercel/server/routes/[...].js +18 -0
  66. package/dist/src/deploy/vercel/server/utils/host.d.ts +13 -0
  67. package/dist/src/deploy/vercel/server/utils/host.js +7 -0
  68. package/dist/src/deploy/vercel/server/utils/repair.d.ts +1 -0
  69. package/dist/src/deploy/vercel/server/utils/repair.js +6 -0
  70. package/dist/src/deploy/vercel/server/utils/step.d.ts +10 -0
  71. package/dist/src/deploy/vercel/server/utils/step.js +6 -0
  72. package/dist/src/deploy/vercel/server/workflows/drive.d.ts +14 -0
  73. package/dist/src/deploy/vercel/server/workflows/drive.js +13 -0
  74. package/dist/src/durable-object.d.ts +14 -0
  75. package/dist/src/durable-object.js +231 -0
  76. package/dist/src/hash.d.ts +1 -0
  77. package/dist/src/hash.js +6 -0
  78. package/dist/src/identity-key.d.ts +11 -0
  79. package/dist/src/identity-key.js +9 -0
  80. package/dist/src/identity.d.ts +12 -0
  81. package/dist/src/identity.js +6 -0
  82. package/dist/src/images/exif.d.ts +1 -0
  83. package/dist/src/images/exif.js +6 -0
  84. package/dist/src/images/node.d.ts +37 -0
  85. package/dist/src/images/node.js +12 -0
  86. package/dist/src/images/photon.d.ts +3 -0
  87. package/dist/src/images/photon.js +7 -0
  88. package/dist/src/images/worker.d.ts +1 -0
  89. package/dist/src/images/worker.js +23 -0
  90. package/dist/src/index.d.ts +20 -0
  91. package/dist/src/index.js +121 -0
  92. package/dist/src/key-file.d.ts +1 -0
  93. package/dist/src/key-file.js +7 -0
  94. package/dist/src/listen-fetch.d.ts +8 -0
  95. package/dist/src/listen-fetch.js +9 -0
  96. package/dist/src/listen.d.ts +4 -0
  97. package/dist/src/listen.js +10 -0
  98. package/dist/src/local/command.d.ts +20 -0
  99. package/dist/src/local/command.js +12 -0
  100. package/dist/src/local/feed.d.ts +7 -0
  101. package/dist/src/local/feed.js +6 -0
  102. package/dist/src/local/files.d.ts +18 -0
  103. package/dist/src/local/files.js +13 -0
  104. package/dist/src/local/git-watch.d.ts +15 -0
  105. package/dist/src/local/git-watch.js +7 -0
  106. package/dist/src/local/git.d.ts +5 -0
  107. package/dist/src/local/git.js +13 -0
  108. package/dist/src/local/github.d.ts +30 -0
  109. package/dist/src/local/github.js +9 -0
  110. package/dist/src/local/identity.d.ts +3 -0
  111. package/dist/src/local/identity.js +7 -0
  112. package/dist/src/local/jobs.d.ts +7 -0
  113. package/dist/src/local/jobs.js +7 -0
  114. package/dist/src/local/legacy-trust.d.ts +10 -0
  115. package/dist/src/local/legacy-trust.js +6 -0
  116. package/dist/src/local/patch.d.ts +16 -0
  117. package/dist/src/local/patch.js +17 -0
  118. package/dist/src/local/provider.d.ts +11 -0
  119. package/dist/src/local/provider.js +20 -0
  120. package/dist/src/local/snapshots.d.ts +7 -0
  121. package/dist/src/local/snapshots.js +11 -0
  122. package/dist/src/local/text.d.ts +1 -0
  123. package/dist/src/local/text.js +6 -0
  124. package/dist/src/local/tree.d.ts +50 -0
  125. package/dist/src/local/tree.js +16 -0
  126. package/dist/src/local/workspace-migration.d.ts +2 -0
  127. package/dist/src/local/workspace-migration.js +7 -0
  128. package/dist/src/local/worktrees.d.ts +46 -0
  129. package/dist/src/local/worktrees.js +14 -0
  130. package/dist/src/mcp/config.d.ts +75 -0
  131. package/dist/src/mcp/config.js +12 -0
  132. package/dist/src/mcp/connect.d.ts +15 -0
  133. package/dist/src/mcp/connect.js +9 -0
  134. package/dist/src/mcp/hub.d.ts +19 -0
  135. package/dist/src/mcp/hub.js +11 -0
  136. package/dist/src/mcp/oauth.d.ts +46 -0
  137. package/dist/src/mcp/oauth.js +12 -0
  138. package/dist/src/mcp/tools.d.ts +29 -0
  139. package/dist/src/mcp/tools.js +12 -0
  140. package/dist/src/open.d.ts +14 -0
  141. package/dist/src/open.js +46 -0
  142. package/dist/src/plugins/loader.d.ts +21 -0
  143. package/dist/src/plugins/loader.js +11 -0
  144. package/dist/src/postgres.d.ts +19 -0
  145. package/dist/src/postgres.js +8 -0
  146. package/dist/src/providers.d.ts +16 -0
  147. package/dist/src/providers.js +112 -0
  148. package/dist/src/secrets.d.ts +12 -0
  149. package/dist/src/secrets.js +8 -0
  150. package/dist/src/serve/binding.d.ts +39 -0
  151. package/dist/src/serve/binding.js +6 -0
  152. package/dist/src/serve/handler.d.ts +15 -0
  153. package/dist/src/serve/handler.js +8 -0
  154. package/dist/src/serve/stream-server.d.ts +6 -0
  155. package/dist/src/serve/stream-server.js +6 -0
  156. package/dist/src/session-index.d.ts +11 -0
  157. package/dist/src/session-index.js +11 -0
  158. package/dist/src/shell-env.d.ts +14 -0
  159. package/dist/src/shell-env.js +108 -0
  160. package/dist/src/sqlite-workers.d.ts +6 -0
  161. package/dist/src/sqlite-workers.js +6 -0
  162. package/dist/src/sqlite.d.ts +8 -0
  163. package/dist/src/sqlite.js +11 -0
  164. package/dist/src/store-rows.d.ts +13 -0
  165. package/dist/src/store-rows.js +26 -0
  166. package/dist/src/store-usage.d.ts +25 -0
  167. package/dist/src/store-usage.js +19 -0
  168. package/dist/src/usage-history.d.ts +54 -0
  169. package/dist/src/usage-history.js +14 -0
  170. package/dist/src/usage-scan.d.ts +24 -0
  171. package/dist/src/usage-scan.js +65 -0
  172. package/dist/src/usage-service.d.ts +23 -0
  173. package/dist/src/usage-service.js +16 -0
  174. package/dist/src/usage-summary.d.ts +9 -0
  175. package/dist/src/usage-summary.js +16 -0
  176. package/dist/src/usage-types.d.ts +86 -0
  177. package/dist/src/usage-types.js +0 -0
  178. package/dist/src/usage-worker.d.ts +1 -0
  179. package/dist/src/usage-worker.js +29 -0
  180. package/dist/src/usage.d.ts +195 -0
  181. package/dist/src/usage.js +23 -0
  182. package/package.json +140 -3
  183. package/src/canvas-preview.ts +403 -0
  184. package/src/credentials.ts +294 -0
  185. package/src/deploy/cloudflare/README.md +3 -0
  186. package/src/deploy/cloudflare/worker.ts +306 -0
  187. package/src/deploy/cloudflare/wrangler.jsonc +12 -0
  188. package/src/deploy/vercel/README.md +3 -0
  189. package/src/deploy/vercel/nitro.config.ts +29 -0
  190. package/src/deploy/vercel/server/routes/[...].ts +10 -0
  191. package/src/deploy/vercel/server/utils/host.ts +104 -0
  192. package/src/deploy/vercel/server/utils/repair.ts +35 -0
  193. package/src/deploy/vercel/server/utils/step.ts +25 -0
  194. package/src/deploy/vercel/server/workflows/drive.ts +46 -0
  195. package/src/deploy/vercel/tsconfig.json +9 -0
  196. package/src/deploy/vercel/types/bun.d.ts +2 -0
  197. package/src/durable-object.ts +386 -0
  198. package/src/hash.ts +5 -0
  199. package/src/identity-key.ts +20 -0
  200. package/src/identity.ts +66 -0
  201. package/src/images/exif.ts +69 -0
  202. package/src/images/node.ts +163 -0
  203. package/src/images/photon.ts +141 -0
  204. package/src/images/worker.ts +29 -0
  205. package/src/index.ts +39 -0
  206. package/src/key-file.ts +53 -0
  207. package/src/listen-fetch.ts +409 -0
  208. package/src/listen.ts +228 -0
  209. package/src/local/command.ts +161 -0
  210. package/src/local/feed.ts +70 -0
  211. package/src/local/files.ts +817 -0
  212. package/src/local/git-watch.ts +199 -0
  213. package/src/local/git.ts +919 -0
  214. package/src/local/github.ts +691 -0
  215. package/src/local/identity.ts +21 -0
  216. package/src/local/jobs.ts +573 -0
  217. package/src/local/legacy-trust.ts +105 -0
  218. package/src/local/patch.ts +334 -0
  219. package/src/local/provider.ts +184 -0
  220. package/src/local/snapshots.ts +778 -0
  221. package/src/local/text.ts +9 -0
  222. package/src/local/tree.ts +398 -0
  223. package/src/local/workspace-migration.ts +95 -0
  224. package/src/local/worktrees.ts +239 -0
  225. package/src/mcp/config.ts +330 -0
  226. package/src/mcp/connect.ts +92 -0
  227. package/src/mcp/hub.ts +836 -0
  228. package/src/mcp/oauth.ts +436 -0
  229. package/src/mcp/tools.ts +333 -0
  230. package/src/open.ts +449 -0
  231. package/src/plugins/loader.ts +186 -0
  232. package/src/postgres.ts +492 -0
  233. package/src/providers.ts +135 -0
  234. package/src/secrets.ts +31 -0
  235. package/src/serve/binding.ts +629 -0
  236. package/src/serve/handler.ts +362 -0
  237. package/src/serve/stream-server.ts +176 -0
  238. package/src/session-index.ts +99 -0
  239. package/src/shell-env.ts +153 -0
  240. package/src/sqlite-workers.ts +283 -0
  241. package/src/sqlite.ts +553 -0
  242. package/src/store-rows.ts +110 -0
  243. package/src/store-usage.ts +347 -0
  244. package/src/usage-history.ts +219 -0
  245. package/src/usage-scan.ts +81 -0
  246. package/src/usage-service.ts +82 -0
  247. package/src/usage-summary.ts +79 -0
  248. package/src/usage-types.ts +87 -0
  249. package/src/usage-worker.ts +24 -0
  250. package/src/usage.ts +776 -0
package/src/sqlite.ts ADDED
@@ -0,0 +1,553 @@
1
+ import { mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import process from "node:process";
4
+ import { DatabaseSync } from "node:sqlite";
5
+ import type { SQLInputValue, StatementSync } from "node:sqlite";
6
+ import { asSeq } from "@nyte-ai/schema";
7
+ import type { ObjectId, Ref, Revision, Seq, SessionId } from "@nyte-ai/schema";
8
+ import { dev } from "@nyte-ai/core";
9
+ import {
10
+ admits,
11
+ fenceNames,
12
+ assertStoreVersion,
13
+ blobId,
14
+ decodeObject,
15
+ collectSessionEvents,
16
+ migrateStore,
17
+ objectId,
18
+ sessionIndexRef,
19
+ parseSessionRef,
20
+ } from "@nyte-ai/core/store";
21
+ import type { Store, StoreConnection } from "@nyte-ai/core";
22
+ import type { MigrationTarget, Relog } from "@nyte-ai/core/store";
23
+ import {
24
+ collect,
25
+ decodeEvent,
26
+ loggedRow,
27
+ memoryReader,
28
+ mintRevision,
29
+ parseNumber,
30
+ prefixEnd,
31
+ parseRef,
32
+ parseStoredObject,
33
+ parseStoredRecord,
34
+ parseText,
35
+ } from "./store-rows.ts";
36
+ import { sqliteWorkers } from "./sqlite-workers.ts";
37
+ import { sessionIndexChanges, sqlSessionIndex } from "./session-index.ts";
38
+
39
+ const BUSY_TIMEOUT_MS = 5_000;
40
+
41
+ const BACKUPS_KEPT = 3;
42
+
43
+ const PUT_BODY =
44
+ "INSERT INTO objects (id, body) VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET body = excluded.body WHERE objects.body IS NULL";
45
+
46
+ /** The local store's file: `NYTE_DB` when set, so a source run keeps its own chats, else `.nyte/nyte.db` under `home`. */
47
+ export function storePath(home: string): string {
48
+ return process.env["NYTE_DB"] ?? join(home, ".nyte", "nyte.db");
49
+ }
50
+
51
+ export function sqliteStore(input: { readonly path: string } | { readonly memory: true }): Store {
52
+ let database: DatabaseSync | null = null;
53
+ let opening: Promise<StoreConnection> | null = null;
54
+ let closing: Promise<void> | null = null;
55
+ let workers: ReturnType<typeof sqliteWorkers> | null = null;
56
+
57
+ const open = async (): Promise<StoreConnection> => {
58
+ let created: DatabaseSync | undefined;
59
+
60
+ try {
61
+ created = new DatabaseSync("path" in input ? input.path : ":memory:", {
62
+ timeout: BUSY_TIMEOUT_MS,
63
+ });
64
+
65
+ if ("path" in input) created.exec("PRAGMA journal_mode = WAL");
66
+ created.exec("PRAGMA synchronous = NORMAL");
67
+ created.exec("PRAGMA checkpoint_fullfsync = ON");
68
+ created.exec("PRAGMA journal_size_limit = 33554432");
69
+
70
+ await migrateStore(sqliteTarget(created, "path" in input ? input.path : null));
71
+ created.exec("PRAGMA optimize = 0x10012");
72
+ } catch (error) {
73
+ created?.close();
74
+
75
+ throw error;
76
+ }
77
+
78
+ database = created;
79
+ workers = "path" in input ? sqliteWorkers(created, input.path) : null;
80
+
81
+ return connect(created, workers);
82
+ };
83
+
84
+ const connection = (): Promise<StoreConnection> => {
85
+ if (closing !== null) return closing.then(connection);
86
+ opening ??= open().catch((error: unknown) => {
87
+ opening = null;
88
+
89
+ throw error;
90
+ });
91
+
92
+ return opening;
93
+ };
94
+
95
+ return {
96
+ open: connection,
97
+ close() {
98
+ closing ??= (async () => {
99
+ await opening?.catch(() => undefined);
100
+ await workers?.close();
101
+ database?.close();
102
+ workers = null;
103
+ database = null;
104
+ opening = null;
105
+ })().finally(() => {
106
+ closing = null;
107
+ });
108
+
109
+ return closing;
110
+ },
111
+ };
112
+ }
113
+
114
+ function hasTable(database: DatabaseSync, name: string): boolean {
115
+ return (
116
+ database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(name) !==
117
+ undefined
118
+ );
119
+ }
120
+
121
+ function storedVersion(database: DatabaseSync): number {
122
+ if (!hasTable(database, "store_meta")) return 0;
123
+ const row = database.prepare("SELECT version FROM store_meta WHERE id = 1").get();
124
+
125
+ return row === undefined ? 0 : parseNumber(row.version);
126
+ }
127
+
128
+ function sqliteTarget(database: DatabaseSync, path: string | null): MigrationTarget {
129
+ const target: MigrationTarget = {
130
+ dialect: "sqlite",
131
+ async stamp() {
132
+ return {
133
+ version: storedVersion(database),
134
+ empty:
135
+ !hasTable(database, "refs") ||
136
+ database.prepare("SELECT 1 FROM refs LIMIT 1").get() === undefined,
137
+ };
138
+ },
139
+ async records() {
140
+ return database
141
+ .prepare(
142
+ "SELECT refs.name, refs.target, refs.revision, objects.body FROM refs JOIN objects ON objects.id = refs.target WHERE objects.body IS NOT NULL",
143
+ )
144
+ .all()
145
+ .map(parseStoredRecord);
146
+ },
147
+ async *objects(kind) {
148
+ yield* database
149
+ .prepare("SELECT id, body FROM objects WHERE body IS NOT NULL AND instr(body, ?) > 0")
150
+ .all(`"kind":${JSON.stringify(kind)}`)
151
+ .map(parseStoredObject);
152
+ },
153
+ async *logs() {
154
+ if (!hasTable(database, "log")) return;
155
+ const read = database.prepare("SELECT seq, event FROM log WHERE session = ? ORDER BY seq");
156
+
157
+ for (const row of database.prepare("SELECT DISTINCT session FROM log").all()) {
158
+ const session = parseText(row.session);
159
+
160
+ if (session !== null) yield { session, events: read.all(session).map(loggedRow) };
161
+ }
162
+ },
163
+ async commit(change) {
164
+ database.exec("BEGIN IMMEDIATE");
165
+
166
+ try {
167
+ if (storedVersion(database) !== change.from) {
168
+ database.exec("ROLLBACK");
169
+
170
+ return "moved";
171
+ }
172
+
173
+ for (const statement of change.sql) database.exec(statement);
174
+ const putBody = database.prepare(PUT_BODY);
175
+
176
+ const repoint = database.prepare(
177
+ "UPDATE refs SET target = ?, revision = ? WHERE name = ? AND revision = ?",
178
+ );
179
+
180
+ for (const moved of change.repoints) {
181
+ write(database, putBody, moved.id, moved.body);
182
+
183
+ if (
184
+ Number(repoint.run(moved.id, mintRevision(), moved.name, moved.expect).changes) === 0
185
+ ) {
186
+ database.exec("ROLLBACK");
187
+
188
+ return "conflict";
189
+ }
190
+
191
+ updateSessionIndex(database, moved.name, moved.id, mintRevision());
192
+ }
193
+
194
+ const create = database.prepare(
195
+ "INSERT INTO refs (name, target, revision) VALUES (?, ?, ?) ON CONFLICT (name) DO NOTHING",
196
+ );
197
+
198
+ for (const created of change.creates) {
199
+ write(database, putBody, created.target, created.body);
200
+ const revision = mintRevision();
201
+
202
+ if (Number(create.run(created.name, created.target, revision).changes) > 0)
203
+ updateSessionIndex(database, created.name, created.target, revision);
204
+ }
205
+
206
+ const relog = database.prepare(
207
+ "UPDATE log SET event = ? WHERE session = ? AND seq = ? AND event = ?",
208
+ );
209
+
210
+ for (const row of change.relogs) {
211
+ if (Number(relog.run(row.event, row.session, row.seq, row.expect).changes) === 0) {
212
+ database.exec("ROLLBACK");
213
+
214
+ return "conflict";
215
+ }
216
+ }
217
+
218
+ if (change.reindexSessions)
219
+ for (const row of database.prepare("SELECT name, target, revision FROM refs").all()) {
220
+ const ref = parseRef(row);
221
+ updateSessionIndex(database, ref.name, ref.target, ref.revision);
222
+ }
223
+
224
+ database
225
+ .prepare(
226
+ "INSERT INTO store_meta (id, version) VALUES (1, ?) ON CONFLICT (id) DO UPDATE SET version = excluded.version",
227
+ )
228
+ .run(change.to);
229
+ database.exec("COMMIT");
230
+
231
+ return "committed";
232
+ } catch (error) {
233
+ if (database.isTransaction) database.exec("ROLLBACK");
234
+
235
+ throw error;
236
+ }
237
+ },
238
+ };
239
+
240
+ if (path === null) return target;
241
+
242
+ return {
243
+ ...target,
244
+ backup: async (from, to, names, log) => backup(database, path, from, to, names, log),
245
+ };
246
+ }
247
+
248
+ function backup(
249
+ database: DatabaseSync,
250
+ path: string,
251
+ from: number,
252
+ to: number,
253
+ names: readonly string[],
254
+ log: readonly Relog[],
255
+ ): void {
256
+ const folder = join(dirname(path), "backups");
257
+ mkdirSync(folder, { recursive: true, mode: 0o700 });
258
+ const at = new Date().toISOString().replaceAll(":", "-");
259
+ const read = database.prepare("SELECT name, target, revision FROM refs WHERE name = ?");
260
+
261
+ const rows = names.flatMap((name) => {
262
+ const row = read.get(name);
263
+
264
+ return row === undefined ? [] : [parseRef(row)];
265
+ });
266
+
267
+ writeFileSync(
268
+ join(folder, `store-${at}-${process.pid}-${crypto.randomUUID()}-v${from}-v${to}.json`),
269
+ JSON.stringify({
270
+ refs: rows,
271
+ log: log.map((row) => ({ session: row.session, seq: row.seq, event: row.expect })),
272
+ }),
273
+ { mode: 0o600 },
274
+ );
275
+
276
+ const copies = readdirSync(folder)
277
+ .filter((file) => /^store-.+-v\d+-v\d+\.(?:db|json)$/u.test(file))
278
+ .sort();
279
+
280
+ for (const file of copies.slice(0, -BACKUPS_KEPT)) rmSync(join(folder, file), { force: true });
281
+ }
282
+
283
+ function connect(
284
+ database: DatabaseSync,
285
+ workers: ReturnType<typeof sqliteWorkers> | null,
286
+ ): StoreConnection {
287
+ const putBody = database.prepare(PUT_BODY);
288
+
289
+ const putBytes = database.prepare(
290
+ "INSERT INTO objects (id, bytes) VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET bytes = excluded.bytes WHERE objects.bytes IS NULL",
291
+ );
292
+
293
+ const getBody = database.prepare("SELECT body FROM objects WHERE id = ?");
294
+ const getBytes = database.prepare("SELECT bytes FROM objects WHERE id = ?");
295
+ const getRef = database.prepare("SELECT name, target, revision FROM refs WHERE name = ?");
296
+
297
+ const listFrom = database.prepare(
298
+ "SELECT name, target, revision FROM refs WHERE name >= ? ORDER BY name",
299
+ );
300
+
301
+ const listRange = database.prepare(
302
+ "SELECT name, target, revision FROM refs WHERE name >= ? AND name < ? ORDER BY name",
303
+ );
304
+
305
+ const putRef = database.prepare(
306
+ "INSERT INTO refs (name, target, revision) VALUES (?, ?, ?) ON CONFLICT (name) DO UPDATE SET target = excluded.target, revision = excluded.revision",
307
+ );
308
+
309
+ const deleteRef = database.prepare("DELETE FROM refs WHERE name = ?");
310
+ const audit = auditing(database);
311
+ const append = database.prepare("INSERT INTO log (session, seq, at, event) VALUES (?, ?, ?, ?)");
312
+
313
+ const readLog = database.prepare(
314
+ "SELECT seq, at, event FROM log WHERE session = ? AND seq > ? ORDER BY seq LIMIT ?",
315
+ );
316
+
317
+ const readTail = database.prepare("SELECT MAX(seq) AS tail FROM log WHERE session = ?");
318
+
319
+ const readCheckpoint = database.prepare(
320
+ "SELECT seq, body FROM fold_checkpoints WHERE session = ?",
321
+ );
322
+
323
+ const writeCheckpoint = database.prepare(
324
+ "INSERT INTO fold_checkpoints (session, seq, body) VALUES (?, ?, ?) ON CONFLICT (session) DO UPDATE SET seq = excluded.seq, body = excluded.body WHERE excluded.seq >= fold_checkpoints.seq",
325
+ );
326
+
327
+ const readGap = database.prepare(
328
+ "SELECT COUNT(*) AS events, COALESCE(SUM(LENGTH(CAST(event AS BLOB))), 0) AS bytes FROM log WHERE session = ? AND seq > ?",
329
+ );
330
+
331
+ const readVersion = database.prepare("SELECT version FROM store_meta WHERE id = 1");
332
+ const readDataVersion = database.prepare("PRAGMA data_version");
333
+
334
+ const read = async (statement: StatementSync, ...params: SQLInputValue[]) =>
335
+ (await workers?.read(statement.sourceSQL, params)) ?? statement.all(...params);
336
+
337
+ const tail = (session: SessionId): Seq => {
338
+ const row = readTail.get(session);
339
+
340
+ return asSeq(row?.tail === null || row?.tail === undefined ? 0 : parseNumber(row.tail));
341
+ };
342
+
343
+ const ref = (name: string): Ref | null => {
344
+ const row = getRef.get(name);
345
+
346
+ return row === undefined ? null : parseRef(row);
347
+ };
348
+
349
+ return {
350
+ commitMark: async () => parseNumber(readDataVersion.get()?.data_version),
351
+ sessions: sqlSessionIndex(
352
+ async (text, values) =>
353
+ (await workers?.read(text, values)) ?? database.prepare(text).all(...values),
354
+ ),
355
+ objects: {
356
+ async put(object) {
357
+ const id = await objectId(object);
358
+ write(database, putBody, id, JSON.stringify(object));
359
+ workers?.check();
360
+
361
+ return id;
362
+ },
363
+ async get(id) {
364
+ const body = parseText(getBody.get(id)?.body);
365
+
366
+ return body === null ? null : decodeObject(body);
367
+ },
368
+ async putBlob(bytes) {
369
+ const joined = await collect(bytes);
370
+ const id = await blobId(joined);
371
+ write(database, putBytes, id, joined);
372
+ workers?.check();
373
+
374
+ return id;
375
+ },
376
+ async blob(id) {
377
+ const bytes = getBytes.get(id)?.bytes;
378
+
379
+ return bytes instanceof Uint8Array ? memoryReader(bytes) : null;
380
+ },
381
+ },
382
+ refs: {
383
+ get: async (name) => ref(name),
384
+ async list(prefix) {
385
+ const end = prefixEnd(prefix);
386
+
387
+ const rows =
388
+ end === null ? await read(listFrom, prefix) : await read(listRange, prefix, end);
389
+
390
+ return rows.map(parseRef);
391
+ },
392
+ async set(updates) {
393
+ const logged = collectSessionEvents(updates);
394
+
395
+ const waiting = performance.now();
396
+ database.exec("BEGIN IMMEDIATE");
397
+ const acquired = performance.now();
398
+
399
+ try {
400
+ assertStoreVersion(parseNumber(readVersion.get()?.version ?? 0));
401
+ const before = new Map<string, Ref | undefined>();
402
+
403
+ for (const update of updates)
404
+ if (!before.has(update.name)) before.set(update.name, ref(update.name) ?? undefined);
405
+ const fenced = fenceNames(updates).find((name) => ref(name) === null);
406
+
407
+ if (fenced !== undefined) {
408
+ database.exec("ROLLBACK");
409
+
410
+ return { kind: "fenced", name: fenced };
411
+ }
412
+
413
+ const stale = updates.find((update) => !admits(update, before.get(update.name)));
414
+
415
+ if (stale !== undefined) {
416
+ database.exec("ROLLBACK");
417
+
418
+ return {
419
+ kind: "stale",
420
+ name: stale.name,
421
+ revision: before.get(stale.name)?.revision ?? null,
422
+ };
423
+ }
424
+
425
+ const touched = new Map<SessionId, Revision>();
426
+ const current = new Map([...before].map(([name, row]) => [name, row?.revision ?? null]));
427
+
428
+ const revisions = updates.map((update) => {
429
+ const revision = mintRevision();
430
+
431
+ if (update.target === null) deleteRef.run(update.name);
432
+ else putRef.run(update.name, update.target, revision);
433
+ audit?.run(
434
+ Date.now(),
435
+ process.pid,
436
+ fenceNames([update]).find((name) => name.startsWith("processes/")) ?? null,
437
+ update.name,
438
+ current.get(update.name) ?? null,
439
+ update.target === null ? null : revision,
440
+ update.target,
441
+ );
442
+ current.set(update.name, update.target === null ? null : revision);
443
+ updateSessionIndex(database, update.name, update.target, revision);
444
+ const session = parseSessionRef(update.name);
445
+
446
+ if (session !== null) touched.set(session, revision);
447
+
448
+ return revision;
449
+ });
450
+
451
+ for (const [session, version] of touched)
452
+ database
453
+ .prepare("UPDATE session_index SET version = ? WHERE session = ?")
454
+ .run(version, session);
455
+
456
+ const at = Date.now();
457
+ const tails = new Map<SessionId, number>();
458
+
459
+ const appended = logged.map(({ session, event }) => {
460
+ const seq = (tails.get(session) ?? tail(session)) + 1;
461
+ tails.set(session, seq);
462
+ write(database, append, session, seq, at, JSON.stringify(event));
463
+
464
+ return asSeq(seq);
465
+ });
466
+
467
+ database.exec("COMMIT");
468
+ workers?.check();
469
+
470
+ return { kind: "set", logged: appended, at, revisions };
471
+ } catch (error) {
472
+ if (database.isTransaction) database.exec("ROLLBACK");
473
+
474
+ throw error;
475
+ } finally {
476
+ dev("sqlite", "transaction", {
477
+ connection: "writer",
478
+ lockWaitMs: acquired - waiting,
479
+ executionMs: performance.now() - acquired,
480
+ });
481
+ }
482
+ },
483
+ async log(session, after, limit) {
484
+ return (await read(readLog, session, after, limit)).map((row) =>
485
+ decodeEvent(parseNumber(row.seq), parseNumber(row.at), parseText(row.event) ?? ""),
486
+ );
487
+ },
488
+ tail: async (session) => tail(session),
489
+ },
490
+ checkpoints: {
491
+ async read(session) {
492
+ const [row] = await read(readCheckpoint, session);
493
+ const body = parseText(row?.body);
494
+
495
+ return row === undefined || body === null
496
+ ? null
497
+ : { seq: asSeq(parseNumber(row.seq)), body };
498
+ },
499
+ async write(session, checkpoint) {
500
+ write(database, writeCheckpoint, session, checkpoint.seq, checkpoint.body);
501
+ workers?.check();
502
+ },
503
+ async gap(session, after) {
504
+ const [row] = await read(readGap, session, after);
505
+
506
+ return { events: parseNumber(row?.events ?? 0), bytes: parseNumber(row?.bytes ?? 0) };
507
+ },
508
+ },
509
+ };
510
+ }
511
+
512
+ function auditing(database: DatabaseSync): StatementSync | null {
513
+ if (process.env.NYTE_STORE_AUDIT !== "1") return null;
514
+ database.exec(
515
+ "CREATE TABLE IF NOT EXISTS store_audit (id INTEGER PRIMARY KEY, at INTEGER NOT NULL, pid INTEGER NOT NULL, writer TEXT, name TEXT NOT NULL, before TEXT, after TEXT, target TEXT)",
516
+ );
517
+
518
+ return database.prepare(
519
+ "INSERT INTO store_audit (at, pid, writer, name, before, after, target) VALUES (?, ?, ?, ?, ?, ?, ?)",
520
+ );
521
+ }
522
+
523
+ function updateSessionIndex(
524
+ database: DatabaseSync,
525
+ name: string,
526
+ target: ObjectId | null,
527
+ revision: Revision,
528
+ ): void {
529
+ if (sessionIndexRef(name) === null) return;
530
+
531
+ const body =
532
+ target === null
533
+ ? null
534
+ : parseText(database.prepare("SELECT body FROM objects WHERE id = ?").get(target)?.body);
535
+
536
+ for (const change of sessionIndexChanges(
537
+ name,
538
+ body === null ? null : decodeObject(body),
539
+ revision,
540
+ ))
541
+ database.prepare(change.text).run(...change.values);
542
+ }
543
+
544
+ function write(database: DatabaseSync, statement: StatementSync, ...params: SQLInputValue[]) {
545
+ const size = params.reduce<number>(
546
+ (total, value) =>
547
+ total +
548
+ (typeof value === "string" ? value.length : ArrayBuffer.isView(value) ? value.byteLength : 0),
549
+ 0,
550
+ );
551
+
552
+ return (size > 64 * 1024 ? database.prepare(statement.sourceSQL) : statement).run(...params);
553
+ }
@@ -0,0 +1,110 @@
1
+ import { asId, SdkError } from "@nyte-ai/schema";
2
+ import type { ByteReader, Ref, Revision, SessionEvent } from "@nyte-ai/schema";
3
+ import { loggedEvent } from "@nyte-ai/core/store";
4
+ import type { LoggedRow, StoredObject, StoredRecord } from "@nyte-ai/core/store";
5
+
6
+ export function prefixEnd(prefix: string): string | null {
7
+ const points = Array.from(prefix);
8
+ const last = points.pop()?.codePointAt(0);
9
+
10
+ if (last === undefined) return null;
11
+
12
+ if (last === 0x10ffff) return prefixEnd(points.join(""));
13
+
14
+ return points.join("") + String.fromCodePoint(last === 0xd7ff ? 0xe000 : last + 1);
15
+ }
16
+
17
+ export function mintRevision(): Revision {
18
+ return asId<"revision">(crypto.randomUUID());
19
+ }
20
+
21
+ export function decodeEvent(seq: number, at: number, text: string): SessionEvent {
22
+ return loggedEvent(seq, at, JSON.parse(text));
23
+ }
24
+
25
+ export function parseNumber(value: unknown): number {
26
+ if (typeof value === "number") return value;
27
+
28
+ if (typeof value === "bigint" || typeof value === "string") return Number(value);
29
+
30
+ throw new SdkError("internal", "A stored number is malformed.");
31
+ }
32
+
33
+ export function parseText(value: unknown): string | null {
34
+ if (value === null || value === undefined) return null;
35
+
36
+ if (typeof value === "string") return value;
37
+
38
+ throw new SdkError("internal", "A stored text column is malformed.");
39
+ }
40
+
41
+ export function parseRef(row: Readonly<Record<string, unknown>>): Ref {
42
+ const name = parseText(row.name);
43
+ const revision = parseText(row.revision);
44
+ const target = parseText(row.target);
45
+
46
+ if (name === null || revision === null)
47
+ throw new SdkError("internal", "A stored ref is malformed.");
48
+
49
+ return {
50
+ name,
51
+ target: target === null ? null : asId<"object">(target),
52
+ revision: asId<"revision">(revision),
53
+ };
54
+ }
55
+
56
+ export function parseStoredRecord(row: Readonly<Record<string, unknown>>): StoredRecord {
57
+ const name = parseText(row.name);
58
+ const target = parseText(row.target);
59
+ const revision = parseText(row.revision);
60
+ const body = parseText(row.body);
61
+
62
+ if (name === null || target === null || revision === null || body === null)
63
+ throw new SdkError("internal", "A stored ref is malformed.");
64
+
65
+ return { name, target: asId<"object">(target), revision, body };
66
+ }
67
+
68
+ export function parseStoredObject(row: Readonly<Record<string, unknown>>): StoredObject {
69
+ const id = parseText(row.id);
70
+ const body = parseText(row.body);
71
+
72
+ if (id === null || body === null) throw new SdkError("internal", "A stored object is malformed.");
73
+
74
+ return { id: asId<"object">(id), body };
75
+ }
76
+
77
+ export function loggedRow(row: Readonly<Record<string, unknown>>): LoggedRow {
78
+ const event = parseText(row.event);
79
+
80
+ if (event === null) throw new SdkError("internal", "A logged event is malformed.");
81
+
82
+ return { seq: parseNumber(row.seq), event };
83
+ }
84
+
85
+ export async function collect(bytes: AsyncIterable<Uint8Array>): Promise<Uint8Array> {
86
+ const chunks: Uint8Array[] = [];
87
+
88
+ for await (const chunk of bytes) chunks.push(chunk);
89
+ const joined = new Uint8Array(chunks.reduce((size, chunk) => size + chunk.byteLength, 0));
90
+ let offset = 0;
91
+
92
+ for (const chunk of chunks) {
93
+ joined.set(chunk, offset);
94
+ offset += chunk.byteLength;
95
+ }
96
+
97
+ return joined;
98
+ }
99
+
100
+ export function memoryReader(bytes: Uint8Array): ByteReader {
101
+ return {
102
+ size: bytes.byteLength,
103
+ mime: null,
104
+ async *chunks() {
105
+ yield bytes;
106
+ },
107
+ close: async () => undefined,
108
+ [Symbol.asyncDispose]: async () => undefined,
109
+ };
110
+ }