@nyte-ai/host 0.0.0-stage → 0.0.18

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 (235) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -2
  3. package/THIRD-PARTY-NOTICES.md +800 -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-0nrnbt76.js +497 -0
  7. package/dist/chunks/canvas-preview-1sq7z9jq.js +17 -0
  8. package/dist/chunks/canvas-preview-1ta3avah.js +56 -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-5hxat7hh.js +239 -0
  15. package/dist/chunks/canvas-preview-5mex725c.js +14 -0
  16. package/dist/chunks/canvas-preview-5z63am63.js +295 -0
  17. package/dist/chunks/canvas-preview-65xqjw7f.js +20 -0
  18. package/dist/chunks/canvas-preview-69fkk9yn.js +95 -0
  19. package/dist/chunks/canvas-preview-75db60xy.js +457 -0
  20. package/dist/chunks/canvas-preview-76fwvd6e.js +272 -0
  21. package/dist/chunks/canvas-preview-8xgt67ka.js +598 -0
  22. package/dist/chunks/canvas-preview-98aj5p34.js +176 -0
  23. package/dist/chunks/canvas-preview-9aqczacs.js +73 -0
  24. package/dist/chunks/canvas-preview-a3yw0j70.js +72 -0
  25. package/dist/chunks/canvas-preview-azg56359.js +68 -0
  26. package/dist/chunks/canvas-preview-d9wwhbtz.js +240 -0
  27. package/dist/chunks/canvas-preview-ehf3vp31.js +411 -0
  28. package/dist/chunks/canvas-preview-evh92ywd.js +369 -0
  29. package/dist/chunks/canvas-preview-ewgspmsd.js +81 -0
  30. package/dist/chunks/canvas-preview-g3qj3y3k.js +156 -0
  31. package/dist/chunks/canvas-preview-h8hshnff.js +63 -0
  32. package/dist/chunks/canvas-preview-j1tnf51w.js +36 -0
  33. package/dist/chunks/canvas-preview-kdqgrstc.js +194 -0
  34. package/dist/chunks/canvas-preview-kn7kvbfj.js +12 -0
  35. package/dist/chunks/canvas-preview-m02v4yy6.js +386 -0
  36. package/dist/chunks/canvas-preview-mgcx2b2p.js +271 -0
  37. package/dist/chunks/canvas-preview-rp4x6y1h.js +144 -0
  38. package/dist/chunks/canvas-preview-rp6fz0w5.js +130 -0
  39. package/dist/chunks/canvas-preview-s94ebwf5.js +112 -0
  40. package/dist/chunks/canvas-preview-senpjwep.js +518 -0
  41. package/dist/chunks/canvas-preview-skzwvw4d.js +23 -0
  42. package/dist/chunks/canvas-preview-srgfpdmf.js +60 -0
  43. package/dist/chunks/canvas-preview-tjhabynd.js +252 -0
  44. package/dist/chunks/canvas-preview-vaatfznm.js +189 -0
  45. package/dist/chunks/canvas-preview-vax9ne4e.js +341 -0
  46. package/dist/chunks/canvas-preview-wg80rbc2.js +46 -0
  47. package/dist/chunks/canvas-preview-xn10d4f8.js +608 -0
  48. package/dist/chunks/canvas-preview-yjz0f7n4.js +50 -0
  49. package/dist/chunks/canvas-preview-ymgz0937.js +587 -0
  50. package/dist/chunks/canvas-preview-yrfz2ak6.js +134 -0
  51. package/dist/chunks/canvas-preview-zaaj99wd.js +244 -0
  52. package/dist/chunks/canvas-preview-zn2d0k9d.js +133 -0
  53. package/dist/src/canvas-preview.d.ts +29 -0
  54. package/dist/src/canvas-preview.js +267 -0
  55. package/dist/src/credentials.d.ts +44 -0
  56. package/dist/src/credentials.js +10 -0
  57. package/dist/src/deploy/cloudflare/worker.d.ts +50 -0
  58. package/dist/src/deploy/cloudflare/worker.js +196 -0
  59. package/dist/src/deploy/vercel/nitro.config.d.ts +2 -0
  60. package/dist/src/deploy/vercel/nitro.config.js +30 -0
  61. package/dist/src/deploy/vercel/server/routes/[...].d.ts +2 -0
  62. package/dist/src/deploy/vercel/server/routes/[...].js +18 -0
  63. package/dist/src/deploy/vercel/server/utils/host.d.ts +13 -0
  64. package/dist/src/deploy/vercel/server/utils/host.js +7 -0
  65. package/dist/src/deploy/vercel/server/utils/repair.d.ts +1 -0
  66. package/dist/src/deploy/vercel/server/utils/repair.js +6 -0
  67. package/dist/src/deploy/vercel/server/utils/step.d.ts +10 -0
  68. package/dist/src/deploy/vercel/server/utils/step.js +6 -0
  69. package/dist/src/deploy/vercel/server/workflows/drive.d.ts +14 -0
  70. package/dist/src/deploy/vercel/server/workflows/drive.js +13 -0
  71. package/dist/src/durable-object.d.ts +14 -0
  72. package/dist/src/durable-object.js +231 -0
  73. package/dist/src/hash.d.ts +1 -0
  74. package/dist/src/hash.js +6 -0
  75. package/dist/src/identity-key.d.ts +11 -0
  76. package/dist/src/identity-key.js +9 -0
  77. package/dist/src/identity.d.ts +12 -0
  78. package/dist/src/identity.js +6 -0
  79. package/dist/src/index.d.ts +21 -0
  80. package/dist/src/index.js +120 -0
  81. package/dist/src/key-file.d.ts +1 -0
  82. package/dist/src/key-file.js +7 -0
  83. package/dist/src/listen-fetch.d.ts +8 -0
  84. package/dist/src/listen-fetch.js +9 -0
  85. package/dist/src/listen.d.ts +4 -0
  86. package/dist/src/listen.js +10 -0
  87. package/dist/src/local/command.d.ts +15 -0
  88. package/dist/src/local/command.js +8 -0
  89. package/dist/src/local/feed.d.ts +7 -0
  90. package/dist/src/local/feed.js +6 -0
  91. package/dist/src/local/files.d.ts +18 -0
  92. package/dist/src/local/files.js +13 -0
  93. package/dist/src/local/git-watch.d.ts +15 -0
  94. package/dist/src/local/git-watch.js +7 -0
  95. package/dist/src/local/git.d.ts +5 -0
  96. package/dist/src/local/git.js +13 -0
  97. package/dist/src/local/github.d.ts +30 -0
  98. package/dist/src/local/github.js +9 -0
  99. package/dist/src/local/identity.d.ts +3 -0
  100. package/dist/src/local/identity.js +7 -0
  101. package/dist/src/local/jobs.d.ts +7 -0
  102. package/dist/src/local/jobs.js +6 -0
  103. package/dist/src/local/legacy-trust.d.ts +10 -0
  104. package/dist/src/local/legacy-trust.js +6 -0
  105. package/dist/src/local/patch.d.ts +16 -0
  106. package/dist/src/local/patch.js +17 -0
  107. package/dist/src/local/provider.d.ts +11 -0
  108. package/dist/src/local/provider.js +20 -0
  109. package/dist/src/local/snapshots.d.ts +7 -0
  110. package/dist/src/local/snapshots.js +11 -0
  111. package/dist/src/local/text.d.ts +1 -0
  112. package/dist/src/local/text.js +6 -0
  113. package/dist/src/local/tree.d.ts +50 -0
  114. package/dist/src/local/tree.js +16 -0
  115. package/dist/src/local/workspace-migration.d.ts +2 -0
  116. package/dist/src/local/workspace-migration.js +7 -0
  117. package/dist/src/local/worktrees.d.ts +46 -0
  118. package/dist/src/local/worktrees.js +14 -0
  119. package/dist/src/mcp/config.d.ts +75 -0
  120. package/dist/src/mcp/config.js +12 -0
  121. package/dist/src/mcp/connect.d.ts +28 -0
  122. package/dist/src/mcp/connect.js +9 -0
  123. package/dist/src/mcp/hub.d.ts +19 -0
  124. package/dist/src/mcp/hub.js +11 -0
  125. package/dist/src/mcp/oauth.d.ts +46 -0
  126. package/dist/src/mcp/oauth.js +12 -0
  127. package/dist/src/mcp/tools.d.ts +29 -0
  128. package/dist/src/mcp/tools.js +12 -0
  129. package/dist/src/open.d.ts +42 -0
  130. package/dist/src/open.js +44 -0
  131. package/dist/src/plugins/loader.d.ts +21 -0
  132. package/dist/src/plugins/loader.js +11 -0
  133. package/dist/src/postgres.d.ts +19 -0
  134. package/dist/src/postgres.js +8 -0
  135. package/dist/src/providers.d.ts +16 -0
  136. package/dist/src/providers.js +112 -0
  137. package/dist/src/secrets.d.ts +12 -0
  138. package/dist/src/secrets.js +8 -0
  139. package/dist/src/serve/binding.d.ts +39 -0
  140. package/dist/src/serve/binding.js +6 -0
  141. package/dist/src/serve/handler.d.ts +15 -0
  142. package/dist/src/serve/handler.js +8 -0
  143. package/dist/src/serve/stream-server.d.ts +6 -0
  144. package/dist/src/serve/stream-server.js +6 -0
  145. package/dist/src/session-index.d.ts +11 -0
  146. package/dist/src/session-index.js +11 -0
  147. package/dist/src/shell-env.d.ts +14 -0
  148. package/dist/src/shell-env.js +108 -0
  149. package/dist/src/sqlite-workers.d.ts +6 -0
  150. package/dist/src/sqlite-workers.js +6 -0
  151. package/dist/src/sqlite.d.ts +7 -0
  152. package/dist/src/sqlite.js +10 -0
  153. package/dist/src/store-owner.d.ts +15 -0
  154. package/dist/src/store-owner.js +9 -0
  155. package/dist/src/store-rows.d.ts +13 -0
  156. package/dist/src/store-rows.js +26 -0
  157. package/dist/src/store-usage.d.ts +25 -0
  158. package/dist/src/store-usage.js +16 -0
  159. package/dist/src/usage-scan.d.ts +24 -0
  160. package/dist/src/usage-scan.js +65 -0
  161. package/dist/src/usage-service.d.ts +22 -0
  162. package/dist/src/usage-service.js +12 -0
  163. package/dist/src/usage-summary.d.ts +9 -0
  164. package/dist/src/usage-summary.js +16 -0
  165. package/dist/src/usage-types.d.ts +86 -0
  166. package/dist/src/usage-types.js +0 -0
  167. package/dist/src/usage-worker.d.ts +1 -0
  168. package/dist/src/usage-worker.js +25 -0
  169. package/dist/src/usage.d.ts +72 -0
  170. package/dist/src/usage.js +13 -0
  171. package/package.json +132 -3
  172. package/src/canvas-preview.ts +402 -0
  173. package/src/credentials.ts +294 -0
  174. package/src/deploy/cloudflare/README.md +3 -0
  175. package/src/deploy/cloudflare/worker.ts +306 -0
  176. package/src/deploy/cloudflare/wrangler.jsonc +12 -0
  177. package/src/deploy/vercel/README.md +3 -0
  178. package/src/deploy/vercel/nitro.config.ts +29 -0
  179. package/src/deploy/vercel/server/routes/[...].ts +10 -0
  180. package/src/deploy/vercel/server/utils/host.ts +104 -0
  181. package/src/deploy/vercel/server/utils/repair.ts +35 -0
  182. package/src/deploy/vercel/server/utils/step.ts +25 -0
  183. package/src/deploy/vercel/server/workflows/drive.ts +46 -0
  184. package/src/deploy/vercel/tsconfig.json +9 -0
  185. package/src/deploy/vercel/types/bun.d.ts +2 -0
  186. package/src/durable-object.ts +386 -0
  187. package/src/hash.ts +5 -0
  188. package/src/identity-key.ts +20 -0
  189. package/src/identity.ts +66 -0
  190. package/src/index.ts +21 -0
  191. package/src/key-file.ts +53 -0
  192. package/src/listen-fetch.ts +409 -0
  193. package/src/listen.ts +228 -0
  194. package/src/local/command.ts +109 -0
  195. package/src/local/feed.ts +70 -0
  196. package/src/local/files.ts +812 -0
  197. package/src/local/git-watch.ts +189 -0
  198. package/src/local/git.ts +914 -0
  199. package/src/local/github.ts +692 -0
  200. package/src/local/identity.ts +21 -0
  201. package/src/local/jobs.ts +587 -0
  202. package/src/local/legacy-trust.ts +105 -0
  203. package/src/local/patch.ts +334 -0
  204. package/src/local/provider.ts +184 -0
  205. package/src/local/snapshots.ts +776 -0
  206. package/src/local/text.ts +9 -0
  207. package/src/local/tree.ts +398 -0
  208. package/src/local/workspace-migration.ts +95 -0
  209. package/src/local/worktrees.ts +239 -0
  210. package/src/mcp/config.ts +330 -0
  211. package/src/mcp/connect.ts +105 -0
  212. package/src/mcp/hub.ts +836 -0
  213. package/src/mcp/oauth.ts +436 -0
  214. package/src/mcp/tools.ts +333 -0
  215. package/src/open.ts +469 -0
  216. package/src/plugins/loader.ts +186 -0
  217. package/src/postgres.ts +492 -0
  218. package/src/providers.ts +135 -0
  219. package/src/secrets.ts +31 -0
  220. package/src/serve/binding.ts +628 -0
  221. package/src/serve/handler.ts +362 -0
  222. package/src/serve/stream-server.ts +176 -0
  223. package/src/session-index.ts +99 -0
  224. package/src/shell-env.ts +153 -0
  225. package/src/sqlite-workers.ts +283 -0
  226. package/src/sqlite.ts +528 -0
  227. package/src/store-owner.ts +98 -0
  228. package/src/store-rows.ts +110 -0
  229. package/src/store-usage.ts +344 -0
  230. package/src/usage-scan.ts +81 -0
  231. package/src/usage-service.ts +73 -0
  232. package/src/usage-summary.ts +79 -0
  233. package/src/usage-types.ts +87 -0
  234. package/src/usage-worker.ts +24 -0
  235. package/src/usage.ts +701 -0
@@ -0,0 +1,386 @@
1
+ import { asSeq, SdkError } from "@nyte-ai/schema";
2
+ import type { ObjectId, Ref, Revision, Seq, SessionId, SetOutcome } from "@nyte-ai/schema";
3
+ import {
4
+ admits,
5
+ fenceNames,
6
+ assertStoreVersion,
7
+ blobId,
8
+ decodeObject,
9
+ collectSessionEvents,
10
+ migrateStore,
11
+ objectId,
12
+ sessionIndexRef,
13
+ parseSessionRef,
14
+ } from "@nyte-ai/core/store";
15
+ import type { Store, StoreConnection } from "@nyte-ai/core";
16
+ import type { CommitOutcome, MigrationTarget } from "@nyte-ai/core/store";
17
+ import {
18
+ collect,
19
+ decodeEvent,
20
+ loggedRow,
21
+ memoryReader,
22
+ mintRevision,
23
+ parseNumber,
24
+ prefixEnd,
25
+ parseRef,
26
+ parseStoredObject,
27
+ parseStoredRecord,
28
+ parseText,
29
+ } from "./store-rows.ts";
30
+ import { sessionIndexChanges, sqlSessionIndex } from "./session-index.ts";
31
+
32
+ /** Returns one value Durable Object SQL binds and answers: text, a number, a blob as an `ArrayBuffer`, or null. */
33
+ export type SqlValue = ArrayBuffer | string | number | null;
34
+
35
+ /** Returns the part of a Durable Object's storage `durableObjectStore` uses, structurally, so a Worker passes `state.storage` as it is and a test passes a shim: `sql.exec` runs one statement with `?` bindings and answers its rows keyed by column name, and `transactionSync` runs a synchronous closure whose writes land all together or, when it throws, not at all (Durable Object SQL refuses `BEGIN`, so this is the only transaction). */
36
+ export type DurableObjectSql = {
37
+ readonly sql: {
38
+ exec(query: string, ...bindings: SqlValue[]): { toArray(): Record<string, SqlValue>[] };
39
+ };
40
+ transactionSync<T>(closure: () => T): T;
41
+ };
42
+
43
+ const ROW_LIMIT = 2_000_000;
44
+
45
+ const encoder = new TextEncoder();
46
+
47
+ /** Returns a `Store` over a Durable Object's SQLite storage, node-free, with the sqlite store's tables, row helpers (`store-rows.ts`) and semantics (§41f): `objects(id TEXT PRIMARY KEY, body TEXT NULL, bytes BLOB NULL)`, `refs(name TEXT PRIMARY KEY, target TEXT NULL, revision TEXT NOT NULL)` and `log(session TEXT, seq INTEGER, at INTEGER, event TEXT, PRIMARY KEY (session, seq))`, brought to core's `STORE_VERSION` by `migrateStore` on first `open()`, each commit one `transactionSync` and no backup; the open rejects with `SdkError("unavailable")` for a store a newer Nyte migrated. `put` ids the value with core's `objectId` and stores its JSON once; `putBlob` buffers the stream, ids it with `blobId` and stores the bytes once as an `ArrayBuffer`; an object and a blob that share an id share the row, each column filled once; `blob` reads the bytes back with their size and a null mime; a body, blob or logged event past the Durable Object's 2 MB row limit throws `SdkError("invalid")` before anything is written (§42k). `refs.set` runs inside one `transactionSync` with no await between its reads and writes, first rejecting with `SdkError("unavailable")` when a newer Nyte has migrated the store, with `RefStore.set`'s semantics, each revision a random UUID, any throw inside rolling the whole batch back. `list(prefix)` reads the name range from `prefix` in name order; `log` returns the events after `after` in seq order up to `limit`, each validated as a `SessionEvent`; `tail` is the session's last seq, or 0. Every `open()` returns a connection over the one storage; `close()` releases nothing, the storage being the object's. In workerd, which allows code generation only while modules load, the Worker turns TypeBox acceleration off at startup (`Settings.Set({ useAcceleration: false })` from `typebox/system`, as `deploy/cloudflare/worker.ts` does), or `log`'s validation throws inside a request. */
48
+ export function durableObjectStore(storage: DurableObjectSql): Store {
49
+ let connection: StoreConnection | null = null;
50
+
51
+ return {
52
+ async open() {
53
+ if (connection !== null) return connection;
54
+
55
+ await migrateStore(durableObjectTarget(storage));
56
+ connection = connect(storage);
57
+
58
+ return connection;
59
+ },
60
+ async close() {
61
+ connection = null;
62
+ },
63
+ };
64
+ }
65
+
66
+ function storedVersion(storage: DurableObjectSql): number {
67
+ const stamped = storage.sql
68
+ .exec("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'store_meta'")
69
+ .toArray();
70
+
71
+ if (stamped.length === 0) return 0;
72
+ const version = storage.sql
73
+ .exec("SELECT version FROM store_meta WHERE id = 1")
74
+ .toArray()[0]?.version;
75
+
76
+ return version === undefined ? 0 : parseNumber(version);
77
+ }
78
+
79
+ function durableObjectTarget(storage: DurableObjectSql): MigrationTarget {
80
+ const rows = (query: string, ...bindings: SqlValue[]): Record<string, SqlValue>[] =>
81
+ storage.sql.exec(query, ...bindings).toArray();
82
+
83
+ return {
84
+ dialect: "sqlite",
85
+ async stamp() {
86
+ const present =
87
+ rows("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'refs'").length > 0;
88
+
89
+ return {
90
+ version: storedVersion(storage),
91
+ empty: !present || rows("SELECT 1 FROM refs LIMIT 1").length === 0,
92
+ };
93
+ },
94
+ async records() {
95
+ return rows(
96
+ "SELECT refs.name, refs.target, refs.revision, objects.body FROM refs JOIN objects ON objects.id = refs.target WHERE objects.body IS NOT NULL",
97
+ ).map(parseStoredRecord);
98
+ },
99
+ async *objects(kind) {
100
+ yield* rows(
101
+ "SELECT id, body FROM objects WHERE body IS NOT NULL AND instr(body, ?) > 0",
102
+ `"kind":${JSON.stringify(kind)}`,
103
+ ).map(parseStoredObject);
104
+ },
105
+ async *logs() {
106
+ if (rows("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'log'").length === 0)
107
+ return;
108
+
109
+ for (const row of rows("SELECT DISTINCT session FROM log")) {
110
+ const session = parseText(row.session);
111
+
112
+ if (session !== null)
113
+ yield {
114
+ session,
115
+ events: rows("SELECT seq, event FROM log WHERE session = ? ORDER BY seq", session).map(
116
+ loggedRow,
117
+ ),
118
+ };
119
+ }
120
+ },
121
+ async commit(change) {
122
+ return storage.transactionSync((): CommitOutcome => {
123
+ if (storedVersion(storage) !== change.from) return "moved";
124
+
125
+ const conflicted = change.repoints.some(
126
+ (moved) =>
127
+ rows("SELECT 1 FROM refs WHERE name = ? AND revision = ?", moved.name, moved.expect)
128
+ .length === 0,
129
+ );
130
+
131
+ const relogged = change.relogs.every(
132
+ (relog) =>
133
+ rows(
134
+ "SELECT 1 FROM log WHERE session = ? AND seq = ? AND event = ?",
135
+ relog.session,
136
+ relog.seq,
137
+ relog.expect,
138
+ ).length > 0,
139
+ );
140
+
141
+ if (conflicted || !relogged) return "conflict";
142
+
143
+ for (const statement of change.sql) rows(statement);
144
+
145
+ for (const moved of change.repoints) {
146
+ rows(
147
+ "INSERT INTO objects (id, body) VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET body = excluded.body WHERE objects.body IS NULL",
148
+ moved.id,
149
+ moved.body,
150
+ );
151
+ rows(
152
+ "UPDATE refs SET target = ?, revision = ? WHERE name = ?",
153
+ moved.id,
154
+ mintRevision(),
155
+ moved.name,
156
+ );
157
+ updateSessionIndex(storage, moved.name, moved.id, mintRevision());
158
+ }
159
+
160
+ for (const created of change.creates) {
161
+ rows(
162
+ "INSERT INTO objects (id, body) VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET body = excluded.body WHERE objects.body IS NULL",
163
+ created.target,
164
+ created.body,
165
+ );
166
+ const revision = mintRevision();
167
+
168
+ if (
169
+ rows(
170
+ "INSERT INTO refs (name, target, revision) VALUES (?, ?, ?) ON CONFLICT (name) DO NOTHING RETURNING name",
171
+ created.name,
172
+ created.target,
173
+ revision,
174
+ ).length > 0
175
+ )
176
+ updateSessionIndex(storage, created.name, created.target, revision);
177
+ }
178
+
179
+ for (const relog of change.relogs)
180
+ rows(
181
+ "UPDATE log SET event = ? WHERE session = ? AND seq = ?",
182
+ relog.event,
183
+ relog.session,
184
+ relog.seq,
185
+ );
186
+
187
+ if (change.reindexSessions)
188
+ for (const row of rows("SELECT name, target, revision FROM refs")) {
189
+ const ref = parseRef(row);
190
+ updateSessionIndex(storage, ref.name, ref.target, ref.revision);
191
+ }
192
+
193
+ rows(
194
+ "INSERT INTO store_meta (id, version) VALUES (1, ?) ON CONFLICT (id) DO UPDATE SET version = excluded.version",
195
+ change.to,
196
+ );
197
+
198
+ return "committed";
199
+ });
200
+ },
201
+ };
202
+ }
203
+
204
+ function connect(storage: DurableObjectSql): StoreConnection {
205
+ const rows = (query: string, ...bindings: SqlValue[]): Record<string, SqlValue>[] =>
206
+ storage.sql.exec(query, ...bindings).toArray();
207
+
208
+ const tail = (session: SessionId): Seq => {
209
+ const value = rows("SELECT MAX(seq) AS tail FROM log WHERE session = ?", session)[0]?.tail;
210
+
211
+ return asSeq(value === null || value === undefined ? 0 : parseNumber(value));
212
+ };
213
+
214
+ const ref = (name: string): Ref | null => {
215
+ const row = rows("SELECT name, target, revision FROM refs WHERE name = ?", name)[0];
216
+
217
+ return row === undefined ? null : parseRef(row);
218
+ };
219
+
220
+ return {
221
+ sessions: sqlSessionIndex(async (text, values) => rows(text, ...values)),
222
+ objects: {
223
+ async put(object) {
224
+ const id = await objectId(object);
225
+ const body = JSON.stringify(object);
226
+ fits(encoder.encode(body).byteLength, `Object ${id}`);
227
+ rows(
228
+ "INSERT INTO objects (id, body) VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET body = excluded.body WHERE objects.body IS NULL",
229
+ id,
230
+ body,
231
+ );
232
+
233
+ return id;
234
+ },
235
+ async get(id) {
236
+ const body = parseText(rows("SELECT body FROM objects WHERE id = ?", id)[0]?.body);
237
+
238
+ return body === null ? null : decodeObject(body);
239
+ },
240
+ async putBlob(bytes) {
241
+ const joined = await collect(bytes);
242
+ const id = await blobId(joined);
243
+ fits(joined.byteLength, `Blob ${id}`);
244
+ rows(
245
+ "INSERT INTO objects (id, bytes) VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET bytes = excluded.bytes WHERE objects.bytes IS NULL",
246
+ id,
247
+ joined.slice().buffer,
248
+ );
249
+
250
+ return id;
251
+ },
252
+ async blob(id) {
253
+ const bytes = rows("SELECT bytes FROM objects WHERE id = ?", id)[0]?.bytes;
254
+
255
+ return bytes instanceof ArrayBuffer ? memoryReader(new Uint8Array(bytes)) : null;
256
+ },
257
+ },
258
+ refs: {
259
+ get: async (name) => ref(name),
260
+ async list(prefix) {
261
+ const end = prefixEnd(prefix);
262
+
263
+ const found =
264
+ end === null
265
+ ? rows("SELECT name, target, revision FROM refs WHERE name >= ? ORDER BY name", prefix)
266
+ : rows(
267
+ "SELECT name, target, revision FROM refs WHERE name >= ? AND name < ? ORDER BY name",
268
+ prefix,
269
+ end,
270
+ );
271
+
272
+ return found.map(parseRef);
273
+ },
274
+ async set(updates) {
275
+ const logged = collectSessionEvents(updates).map(({ session, event }) => {
276
+ const text = JSON.stringify(event);
277
+ fits(encoder.encode(text).byteLength, `An event on ${session}`);
278
+
279
+ return { session, text };
280
+ });
281
+
282
+ return storage.transactionSync((): SetOutcome => {
283
+ assertStoreVersion(storedVersion(storage));
284
+ const before = new Map<string, Ref | undefined>();
285
+
286
+ for (const update of updates)
287
+ if (!before.has(update.name)) before.set(update.name, ref(update.name) ?? undefined);
288
+ const fenced = fenceNames(updates).find((name) => ref(name) === null);
289
+
290
+ if (fenced !== undefined) return { kind: "fenced", name: fenced };
291
+ const stale = updates.find((update) => !admits(update, before.get(update.name)));
292
+
293
+ if (stale !== undefined)
294
+ return {
295
+ kind: "stale",
296
+ name: stale.name,
297
+ revision: before.get(stale.name)?.revision ?? null,
298
+ };
299
+
300
+ const touched = new Map<SessionId, Revision>();
301
+ const revisions = updates.map((update) => {
302
+ const revision = mintRevision();
303
+
304
+ if (update.target === null) rows("DELETE FROM refs WHERE name = ?", update.name);
305
+ else
306
+ rows(
307
+ "INSERT INTO refs (name, target, revision) VALUES (?, ?, ?) ON CONFLICT (name) DO UPDATE SET target = excluded.target, revision = excluded.revision",
308
+ update.name,
309
+ update.target,
310
+ revision,
311
+ );
312
+ updateSessionIndex(storage, update.name, update.target, revision);
313
+ const session = parseSessionRef(update.name);
314
+
315
+ if (session !== null) touched.set(session, revision);
316
+
317
+ return revision;
318
+ });
319
+
320
+ for (const [session, version] of touched)
321
+ rows("UPDATE session_index SET version = ? WHERE session = ?", version, session);
322
+
323
+ const at = Date.now();
324
+ const tails = new Map<SessionId, number>();
325
+
326
+ const appended = logged.map(({ session, text }) => {
327
+ const seq = (tails.get(session) ?? tail(session)) + 1;
328
+ tails.set(session, seq);
329
+ rows(
330
+ "INSERT INTO log (session, seq, at, event) VALUES (?, ?, ?, ?)",
331
+ session,
332
+ seq,
333
+ at,
334
+ text,
335
+ );
336
+
337
+ return asSeq(seq);
338
+ });
339
+
340
+ return { kind: "set", logged: appended, at, revisions };
341
+ });
342
+ },
343
+ async log(session, after, limit) {
344
+ return rows(
345
+ "SELECT seq, at, event FROM log WHERE session = ? AND seq > ? ORDER BY seq LIMIT ?",
346
+ session,
347
+ after,
348
+ limit,
349
+ ).map((row) =>
350
+ decodeEvent(parseNumber(row.seq), parseNumber(row.at), parseText(row.event) ?? ""),
351
+ );
352
+ },
353
+ tail: async (session) => tail(session),
354
+ },
355
+ };
356
+ }
357
+
358
+ function updateSessionIndex(
359
+ storage: DurableObjectSql,
360
+ name: string,
361
+ target: ObjectId | null,
362
+ revision: Revision,
363
+ ): void {
364
+ if (sessionIndexRef(name) === null) return;
365
+ const body =
366
+ target === null
367
+ ? null
368
+ : parseText(
369
+ storage.sql.exec("SELECT body FROM objects WHERE id = ?", target).toArray()[0]?.body,
370
+ );
371
+
372
+ for (const change of sessionIndexChanges(
373
+ name,
374
+ body === null ? null : decodeObject(body),
375
+ revision,
376
+ ))
377
+ storage.sql.exec(change.text, ...change.values);
378
+ }
379
+
380
+ function fits(bytes: number, what: string): void {
381
+ if (bytes > ROW_LIMIT)
382
+ throw new SdkError(
383
+ "invalid",
384
+ `${what} is ${bytes} bytes, past the Durable Object store's 2 MB row limit.`,
385
+ );
386
+ }
package/src/hash.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ export function sha256(data: string | Uint8Array): string {
4
+ return createHash("sha256").update(data).digest("hex");
5
+ }
@@ -0,0 +1,20 @@
1
+ import { join } from "node:path";
2
+ import type { HostId } from "@nyte-ai/schema";
3
+ import type { ListenOptions } from "@nyte-ai/core";
4
+ import { seedSigner } from "./identity.ts";
5
+ import { readKeyFile } from "./key-file.ts";
6
+
7
+ /**
8
+ * Returns the Node host's identity signer (§50a): `seedSigner` over the
9
+ * Ed25519 seed in `<home>/.nyte/identity.key`, 32 raw bytes minted `0600` by
10
+ * `link` on first use as the secrets key is. The file is not encrypted; it is
11
+ * a signing key, protected as ssh protects one, and it never touches the
12
+ * secrets key. Named apart from core's `hostIdentity(store)`, which reads or
13
+ * mints the host's id.
14
+ */
15
+ export async function identitySigner(
16
+ id: HostId,
17
+ home: string,
18
+ ): Promise<NonNullable<ListenOptions["identity"]>> {
19
+ return seedSigner(id, await readKeyFile(join(home, ".nyte", "identity.key"), "identity key"));
20
+ }
@@ -0,0 +1,66 @@
1
+ import { SdkError } from "@nyte-ai/schema";
2
+ import type { HostId } from "@nyte-ai/schema";
3
+ import type { ListenOptions } from "@nyte-ai/core";
4
+
5
+ const PKCS8_PREFIX = Uint8Array.fromHex("302e020100300506032b657004220420");
6
+
7
+ const encoder = new TextEncoder();
8
+
9
+ /**
10
+ * Returns the signer a listener answers `GET /.nyte/identity?nonce=` with
11
+ * (`ListenOptions.identity`, §43a), over a 32-byte Ed25519 seed: `id` the
12
+ * host's id and `sign(nonce)` a signature over the UTF-8 bytes of
13
+ * `nyte/identity\n<id>\n<nonce>`, answered with the raw public key, both
14
+ * base64url without padding (§43k). The Node host reads the seed from
15
+ * `<home>/.nyte/identity.key` (`identitySigner`); a deploy passes one from its
16
+ * environment (§50a). Web Crypto only, no `node:*` (the `./identity` subpath).
17
+ */
18
+ export async function seedSigner(
19
+ id: HostId,
20
+ seed: Uint8Array,
21
+ ): Promise<NonNullable<ListenOptions["identity"]>> {
22
+ const { signing, publicKey } = await identityKeys(seed);
23
+
24
+ return {
25
+ id,
26
+ async sign(nonce) {
27
+ const signature = new Uint8Array(
28
+ await crypto.subtle.sign(
29
+ "Ed25519",
30
+ signing,
31
+ encoder.encode(`nyte/identity\n${id}\n${nonce}`),
32
+ ),
33
+ );
34
+
35
+ return {
36
+ hostId: id,
37
+ nonce,
38
+ signature: signature.toBase64({ alphabet: "base64url", omitPadding: true }),
39
+ publicKey,
40
+ };
41
+ },
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Imports an Ed25519 seed in its PKCS#8 form (the fixed prefix
47
+ * `302e020100300506032b657004220420` and the seed; Web Crypto imports no raw
48
+ * private seed). It imports it twice (§43j): once extractable, only to read
49
+ * the public key back through its JWK `x`, and once non-extractable with
50
+ * usage `sign`, the only key it keeps. A seed that is not 32 bytes rejects
51
+ * `SdkError("invalid")`.
52
+ */
53
+ async function identityKeys(
54
+ seed: Uint8Array,
55
+ ): Promise<{ readonly signing: CryptoKey; readonly publicKey: string }> {
56
+ if (seed.length !== 32)
57
+ throw new SdkError("invalid", `The identity seed is ${seed.length} bytes, not 32.`);
58
+ const pkcs8 = new Uint8Array([...PKCS8_PREFIX, ...seed]);
59
+ const readable = await crypto.subtle.importKey("pkcs8", pkcs8, "Ed25519", true, ["sign"]);
60
+ const { x } = await crypto.subtle.exportKey("jwk", readable);
61
+
62
+ if (x === undefined) throw new SdkError("internal", "The identity key has no public half.");
63
+ const signing = await crypto.subtle.importKey("pkcs8", pkcs8, "Ed25519", false, ["sign"]);
64
+
65
+ return { signing, publicKey: x };
66
+ }
package/src/index.ts ADDED
@@ -0,0 +1,21 @@
1
+ export * from "./credentials.ts";
2
+ export * from "./identity.ts";
3
+ export { identitySigner } from "./identity-key.ts";
4
+ export * from "./listen.ts";
5
+ export * from "./listen-fetch.ts";
6
+ export * from "./local/files.ts";
7
+ export * from "./local/git.ts";
8
+ export * from "./local/github.ts";
9
+ export * from "./local/identity.ts";
10
+ export * from "./local/jobs.ts";
11
+ export * from "./local/provider.ts";
12
+ export { createMcpHub } from "./mcp/hub.ts";
13
+ export type { McpConnector } from "./mcp/connect.ts";
14
+ export * from "./local/snapshots.ts";
15
+ export * from "./local/worktrees.ts";
16
+ export * from "./open.ts";
17
+ export * from "./plugins/loader.ts";
18
+ export * from "./postgres.ts";
19
+ export * from "./secrets.ts";
20
+ export * from "./sqlite.ts";
21
+ export { StoreOwned } from "./store-owner.ts";
@@ -0,0 +1,53 @@
1
+ import { link, mkdir, open, readFile, unlink } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import { SdkError } from "@nyte-ai/schema";
4
+ import { readErrorCode } from "./local/tree.ts";
5
+
6
+ export async function readKeyFile(path: string, name: string): Promise<Uint8Array> {
7
+ const bytes = await readFile(path)
8
+ .then(
9
+ (stored) => new Uint8Array(stored),
10
+ (error: unknown) => {
11
+ if (readErrorCode(error) === "ENOENT") return mintKeyFile(path);
12
+
13
+ throw error;
14
+ },
15
+ )
16
+ .catch((error: unknown) => {
17
+ throw new SdkError(
18
+ "unavailable",
19
+ `The ${name} at ${path} cannot be read: ${error instanceof Error ? error.message : String(error)}`,
20
+ );
21
+ });
22
+
23
+ if (bytes.length !== 32)
24
+ throw new SdkError("invalid", `The ${name} at ${path} is ${bytes.length} bytes, not 32.`);
25
+
26
+ return bytes;
27
+ }
28
+
29
+ async function mintKeyFile(path: string): Promise<Uint8Array> {
30
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
31
+ const bytes = crypto.getRandomValues(new Uint8Array(32));
32
+ const temp = `${path}.${crypto.randomUUID()}`;
33
+ const file = await open(temp, "wx", 0o600);
34
+
35
+ try {
36
+ await file.writeFile(bytes);
37
+ await file.sync();
38
+ } finally {
39
+ await file.close();
40
+ }
41
+
42
+ try {
43
+ await link(temp, path);
44
+
45
+ return bytes;
46
+ } catch (error) {
47
+ if (readErrorCode(error) !== "EEXIST") throw error;
48
+
49
+ return new Uint8Array(await readFile(path));
50
+ } finally {
51
+ await unlink(temp).catch(() => undefined);
52
+ }
53
+ }