@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
@@ -0,0 +1,153 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import { isAbsolute } from "node:path";
4
+ import { holdProcessEnvironment } from "@nyte-ai/core/server";
5
+
6
+ const TIMEOUT_MS = 10_000;
7
+
8
+ const OUTPUT_LIMIT = 64 * 1024;
9
+
10
+ const SHELL_LOCAL = new Set(["PWD", "OLDPWD", "SHLVL", "_"]);
11
+
12
+ let shared: (() => Promise<void>) | undefined;
13
+
14
+ /**
15
+ * Starts reading the login shell's environment into `process.env`, once per process, and holds
16
+ * every child spawn and provider auth env read (`processEnvironment()`) until it lands. The
17
+ * process itself goes on: a desktop app opened from the Dock inherits launchd's bare PATH and
18
+ * none of the rc files' API keys, and only what it spawns and what auth reads need the shell's.
19
+ * The read gives up after 10 s and keeps the inherited environment.
20
+ */
21
+ export function completeShellEnvironment(): Promise<void> {
22
+ if (shared === undefined) {
23
+ shared = createShellEnvironmentRepair();
24
+ holdProcessEnvironment(shared());
25
+ }
26
+
27
+ return shared();
28
+ }
29
+
30
+ /** Returns a repair that merges the login shell's environment into `env` on its first call; later calls share that attempt. */
31
+ export function createShellEnvironmentRepair({
32
+ env = process.env,
33
+ platform = process.platform,
34
+ timeoutMs = TIMEOUT_MS,
35
+ }: {
36
+ env?: NodeJS.ProcessEnv;
37
+ platform?: NodeJS.Platform;
38
+ timeoutMs?: number;
39
+ } = {}): () => Promise<void> {
40
+ let attempt: Promise<void> | undefined;
41
+
42
+ return () => {
43
+ attempt ??= repairShellEnvironment({ env, platform, timeoutMs });
44
+
45
+ return attempt;
46
+ };
47
+ }
48
+
49
+ async function repairShellEnvironment({
50
+ env,
51
+ platform,
52
+ timeoutMs,
53
+ }: {
54
+ env: NodeJS.ProcessEnv;
55
+ platform: NodeJS.Platform;
56
+ timeoutMs: number;
57
+ }): Promise<void> {
58
+ const shell = env.SHELL || "/bin/sh";
59
+
60
+ if (platform === "win32" || !isAbsolute(shell)) return;
61
+ const inheritedPath = env.PATH;
62
+ const loaded = await readLoginShellEnvironment({ shell, env, timeoutMs });
63
+
64
+ if (loaded === undefined) return;
65
+
66
+ for (const [name, value] of loaded) {
67
+ if (name !== "PATH" && !SHELL_LOCAL.has(name)) env[name] = value;
68
+ }
69
+
70
+ const path = loaded.get("PATH");
71
+
72
+ if (path === undefined || path === "") return;
73
+ env.PATH = inheritedPath ? `${path}:${inheritedPath}` : path;
74
+ }
75
+
76
+ /**
77
+ * Runs `shell -ilc` and reads the environment it prints between two random fences. It settles
78
+ * at the first of exit, the timeout or 64 KiB of output, and kills the shell then without
79
+ * waiting for descendants a startup script left holding stdout open.
80
+ */
81
+ function readLoginShellEnvironment({
82
+ shell,
83
+ env,
84
+ timeoutMs,
85
+ }: {
86
+ shell: string;
87
+ env: NodeJS.ProcessEnv;
88
+ timeoutMs: number;
89
+ }): Promise<Map<string, string> | undefined> {
90
+ return new Promise((resolve) => {
91
+ const marker = `nyte-env-${randomUUID()}`;
92
+ const fence = `\0${marker}\0`;
93
+ const printFence = `printf '\\000${marker}\\000'`;
94
+
95
+ const child = spawn(shell, ["-ilc", `${printFence}; /usr/bin/env -0; ${printFence}`], {
96
+ env,
97
+ detached: true,
98
+ stdio: ["ignore", "pipe", "ignore"],
99
+ });
100
+
101
+ const chunks: Buffer[] = [];
102
+ let bytes = 0;
103
+ let settled = false;
104
+
105
+ const finish = (loaded: Map<string, string> | undefined): void => {
106
+ if (settled) return;
107
+ settled = true;
108
+ clearTimeout(timer);
109
+ child.stdout.destroy();
110
+ child.kill("SIGKILL");
111
+
112
+ resolve(loaded);
113
+ };
114
+
115
+ const timer = setTimeout(() => finish(undefined), timeoutMs);
116
+ child.on("error", () => finish(undefined));
117
+ child.stdout.on("error", () => finish(undefined));
118
+ child.stdout.on("data", (chunk: Buffer) => {
119
+ bytes += chunk.byteLength;
120
+
121
+ if (bytes > OUTPUT_LIMIT) {
122
+ finish(undefined);
123
+
124
+ return;
125
+ }
126
+
127
+ chunks.push(chunk);
128
+ });
129
+ child.on("close", (code) => {
130
+ const [, fenced, ...after] = Buffer.concat(chunks).toString("utf8").split(fence);
131
+
132
+ if (code !== 0 || fenced === undefined || after.length === 0) {
133
+ finish(undefined);
134
+
135
+ return;
136
+ }
137
+
138
+ finish(parseEnvironment(fenced));
139
+ });
140
+ });
141
+ }
142
+
143
+ function parseEnvironment(output: string): Map<string, string> {
144
+ const loaded = new Map<string, string>();
145
+
146
+ for (const entry of output.split("\0")) {
147
+ const [name = "", ...value] = entry.split("=");
148
+
149
+ if (name !== "" && value.length > 0) loaded.set(name, value.join("="));
150
+ }
151
+
152
+ return loaded;
153
+ }
@@ -0,0 +1,283 @@
1
+ import type { DatabaseSync, SQLInputValue } from "node:sqlite";
2
+ import { Worker } from "node:worker_threads";
3
+ import { dev } from "@nyte-ai/core";
4
+ import { Type } from "typebox";
5
+ import type { Static } from "typebox";
6
+ import { Value } from "typebox/value";
7
+
8
+ type Role = "reader" | "checkpoint";
9
+
10
+ const rowSchema = Type.Record(
11
+ Type.String(),
12
+ Type.Union([Type.Null(), Type.String(), Type.Number(), Type.BigInt()]),
13
+ );
14
+
15
+ type Row = Static<typeof rowSchema>;
16
+
17
+ const replySchema = Type.Union([
18
+ Type.Object({ ready: Type.Literal(true) }),
19
+ Type.Object({ id: Type.Integer(), rows: Type.Array(rowSchema), executionMs: Type.Number() }),
20
+ Type.Object({
21
+ id: Type.Integer(),
22
+ error: Type.String(),
23
+ code: Type.Optional(Type.String()),
24
+ errcode: Type.Optional(Type.Number()),
25
+ errstr: Type.Optional(Type.String()),
26
+ }),
27
+ ]);
28
+
29
+ const source = String.raw`
30
+ const { parentPort, workerData } = require("node:worker_threads");
31
+ const { DatabaseSync } = require("node:sqlite");
32
+ const db = new DatabaseSync(workerData.path, {
33
+ readOnly: workerData.role === "reader",
34
+ timeout: 5000,
35
+ });
36
+ if (String(db.prepare("PRAGMA journal_mode").get().journal_mode).toLowerCase() !== "wal")
37
+ throw new Error("SQLite workers require WAL mode");
38
+ if (workerData.role === "checkpoint") {
39
+ const version = db.prepare("SELECT sqlite_version() AS version").get().version;
40
+ const [major, minor] = version.split(".").map(Number);
41
+ if (major < 3 || (major === 3 && minor < 51))
42
+ throw new Error("SQLite " + version + " has no NOOP checkpoint");
43
+ db.exec("PRAGMA synchronous = NORMAL; PRAGMA checkpoint_fullfsync = ON");
44
+ }
45
+ const statements = new Map();
46
+ const frames = workerData.role === "checkpoint" ? db.prepare("PRAGMA wal_checkpoint(NOOP)") : null;
47
+ const checkpoint = workerData.role === "checkpoint" ? db.prepare("PRAGMA wal_checkpoint(PASSIVE)") : null;
48
+ let idle;
49
+ parentPort.on("message", ({ id, sql, params }) => {
50
+ const started = performance.now();
51
+ try {
52
+ let rows = [];
53
+ if (workerData.role === "checkpoint") {
54
+ const { log, checkpointed } = frames.get();
55
+ if (log - checkpointed >= 1000) checkpoint.get();
56
+ clearTimeout(idle);
57
+ idle = setTimeout(() => {
58
+ const { log, checkpointed } = frames.get();
59
+ if (log >= 1000 && checkpointed < log) checkpoint.get();
60
+ }, 100);
61
+ } else {
62
+ const size = params.reduce((total, value) => total + (typeof value === "string" ? value.length : ArrayBuffer.isView(value) ? value.byteLength : 0), 0);
63
+ let statement = size > 65536 ? undefined : statements.get(sql);
64
+ if (statement === undefined) {
65
+ statement = db.prepare(sql);
66
+ if (size <= 65536) {
67
+ if (statements.size >= 16) statements.delete(statements.keys().next().value);
68
+ statements.set(sql, statement);
69
+ }
70
+ }
71
+ rows = statement.all(...params);
72
+ }
73
+ parentPort.postMessage({ id, rows, executionMs: performance.now() - started });
74
+ } catch (error) {
75
+ parentPort.postMessage({ id, error: String(error.message), code: error.code, errcode: error.errcode, errstr: error.errstr });
76
+ }
77
+ });
78
+ parentPort.postMessage({ ready: true });
79
+ `;
80
+
81
+ class SqliteWorker {
82
+ readonly ready: Promise<boolean>;
83
+ private worker: Worker | null;
84
+ private nextId = 0;
85
+ private readonly pending = new Map<
86
+ number,
87
+ { resolve: (rows: readonly Row[]) => void; reject: (cause: Error) => void; at: number }
88
+ >();
89
+ private readonly stop: (cause: Error, notify: boolean) => Promise<number> | undefined;
90
+
91
+ constructor(path: string, role: Role, unavailable: (cause: Error, started: boolean) => void) {
92
+ const worker = new Worker(source, { eval: true, workerData: { path, role } });
93
+ this.worker = worker;
94
+ let started = false;
95
+ let announce: (ready: boolean) => void = () => undefined;
96
+ this.ready = new Promise((resolve) => {
97
+ announce = resolve;
98
+ });
99
+
100
+ const timeout = setTimeout(() => {
101
+ void this.stop(new Error("SQLite worker did not start within 10 seconds"), true);
102
+ }, 10_000);
103
+
104
+ timeout.unref();
105
+
106
+ this.stop = (cause, notify) => {
107
+ if (this.worker === null) return;
108
+ this.worker = null;
109
+ clearTimeout(timeout);
110
+ announce(false);
111
+
112
+ for (const request of this.pending.values()) request.reject(cause);
113
+ this.pending.clear();
114
+
115
+ if (notify) unavailable(cause, started);
116
+
117
+ return worker.terminate();
118
+ };
119
+
120
+ worker.on("message", (reply: unknown) => {
121
+ if (!Value.Check(replySchema, reply)) {
122
+ void this.stop(new Error("Malformed SQLite worker reply"), true);
123
+
124
+ return;
125
+ }
126
+
127
+ if ("ready" in reply) {
128
+ clearTimeout(timeout);
129
+ started = true;
130
+ announce(true);
131
+
132
+ if (this.pending.size === 0) worker.unref();
133
+
134
+ return;
135
+ }
136
+
137
+ const request = this.pending.get(reply.id);
138
+
139
+ if (request === undefined) return;
140
+ this.pending.delete(reply.id);
141
+
142
+ if (this.pending.size === 0) worker.unref();
143
+
144
+ if ("error" in reply) {
145
+ request.reject(Object.assign(new Error(reply.error), reply));
146
+ } else {
147
+ dev("sqlite", "query", {
148
+ connection: role,
149
+ durationMs: performance.now() - request.at,
150
+ executionMs: reply.executionMs,
151
+ });
152
+ request.resolve(reply.rows);
153
+ }
154
+ });
155
+ worker.on("error", (cause: unknown) => {
156
+ void this.stop(cause instanceof Error ? cause : new Error(String(cause)), true);
157
+ });
158
+ worker.on("exit", (code) => {
159
+ void this.stop(new Error(`SQLite ${role} worker exited with code ${code}`), true);
160
+ });
161
+
162
+ if (role === "checkpoint") worker.unref();
163
+ }
164
+
165
+ async query(sql: string, params: readonly SQLInputValue[]): Promise<readonly Row[] | undefined> {
166
+ if (!(await this.ready)) return undefined;
167
+ const worker = this.worker;
168
+
169
+ if (worker === null) throw new Error("SQLite worker stopped");
170
+ const id = this.nextId++;
171
+
172
+ return new Promise((resolve, reject) => {
173
+ this.pending.set(id, { resolve, reject, at: performance.now() });
174
+ worker.ref();
175
+
176
+ try {
177
+ worker.postMessage({ id, sql, params });
178
+ } catch (cause) {
179
+ this.pending.delete(id);
180
+
181
+ if (this.pending.size === 0) worker.unref();
182
+ reject(cause);
183
+ }
184
+ });
185
+ }
186
+
187
+ async close(): Promise<void> {
188
+ await this.stop(new Error("SQLite worker closed"), false);
189
+ }
190
+ }
191
+
192
+ export function sqliteWorkers(database: DatabaseSync, path: string) {
193
+ let reader: SqliteWorker | null = null;
194
+ let readerUnavailable = false;
195
+ let closed = false;
196
+ let checking = false;
197
+ let checkAgain = false;
198
+ let checkpoint: SqliteWorker | null = null;
199
+
200
+ const warn = (role: Role, cause: Error) => {
201
+ process.emitWarning(
202
+ `SQLite ${role} worker unavailable; using the main connection: ${cause.message}`,
203
+ {
204
+ code: "NYTE_SQLITE_WORKER_UNAVAILABLE",
205
+ },
206
+ );
207
+ };
208
+
209
+ const handBack = (cause: Error) => {
210
+ const stopped = checkpoint;
211
+ checkpoint = null;
212
+ void stopped?.close();
213
+
214
+ if (closed) return;
215
+ database.exec("PRAGMA wal_autocheckpoint = 1000");
216
+ warn("checkpoint", cause);
217
+ };
218
+
219
+ try {
220
+ checkpoint = new SqliteWorker(path, "checkpoint", handBack);
221
+ void checkpoint.ready.then((ready) => {
222
+ if (ready && !closed) database.exec("PRAGMA wal_autocheckpoint = 10000");
223
+ });
224
+ } catch (cause) {
225
+ warn("checkpoint", cause instanceof Error ? cause : new Error(String(cause)));
226
+ }
227
+
228
+ const check = () => {
229
+ if (checkpoint === null || closed) return;
230
+
231
+ if (checking) {
232
+ checkAgain = true;
233
+
234
+ return;
235
+ }
236
+
237
+ checking = true;
238
+ void checkpoint
239
+ .query("", [])
240
+ .catch((cause: unknown) => {
241
+ if (checkpoint !== null)
242
+ handBack(cause instanceof Error ? cause : new Error(String(cause)));
243
+ })
244
+ .finally(() => {
245
+ checking = false;
246
+
247
+ if (checkAgain) {
248
+ checkAgain = false;
249
+ check();
250
+ }
251
+ });
252
+ };
253
+
254
+ return {
255
+ check,
256
+ async read(sql: string, params: readonly SQLInputValue[]) {
257
+ if (closed) throw new Error("SQLite store closed");
258
+
259
+ if (readerUnavailable) return undefined;
260
+
261
+ if (reader === null) {
262
+ try {
263
+ reader = new SqliteWorker(path, "reader", (cause, started) => {
264
+ reader = null;
265
+ readerUnavailable = !started;
266
+ warn("reader", cause);
267
+ });
268
+ } catch (cause) {
269
+ readerUnavailable = true;
270
+ warn("reader", cause instanceof Error ? cause : new Error(String(cause)));
271
+
272
+ return undefined;
273
+ }
274
+ }
275
+
276
+ return reader.query(sql, params);
277
+ },
278
+ async close() {
279
+ closed = true;
280
+ await Promise.all([reader?.close(), checkpoint?.close()]);
281
+ },
282
+ };
283
+ }