@opendatalabs/personal-server-ts-core 0.2.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) hide show
  1. package/dist/api/index.d.ts +160 -0
  2. package/dist/api/index.d.ts.map +1 -0
  3. package/dist/api/index.js +774 -0
  4. package/dist/api/index.js.map +1 -0
  5. package/dist/auth/index.d.ts +2 -0
  6. package/dist/auth/index.d.ts.map +1 -0
  7. package/dist/auth/index.js +2 -0
  8. package/dist/auth/index.js.map +1 -0
  9. package/dist/auth/request.d.ts +28 -0
  10. package/dist/auth/request.d.ts.map +1 -0
  11. package/dist/auth/request.js +106 -0
  12. package/dist/auth/request.js.map +1 -0
  13. package/dist/client.d.ts +233 -0
  14. package/dist/client.d.ts.map +1 -0
  15. package/dist/client.js +243 -0
  16. package/dist/client.js.map +1 -0
  17. package/dist/config/defaults.d.ts +5 -5
  18. package/dist/config/defaults.d.ts.map +1 -1
  19. package/dist/config/defaults.js +3 -5
  20. package/dist/config/defaults.js.map +1 -1
  21. package/dist/config/index.d.ts +0 -1
  22. package/dist/config/index.d.ts.map +1 -1
  23. package/dist/config/index.js +0 -1
  24. package/dist/config/index.js.map +1 -1
  25. package/dist/config/paths.d.ts.map +1 -1
  26. package/dist/config/paths.js +1 -9
  27. package/dist/config/paths.js.map +1 -1
  28. package/dist/contracts/access-logs.d.ts +8 -0
  29. package/dist/contracts/access-logs.d.ts.map +1 -0
  30. package/dist/contracts/access-logs.js +7 -0
  31. package/dist/contracts/access-logs.js.map +1 -0
  32. package/dist/contracts/auth-device.d.ts +70 -0
  33. package/dist/contracts/auth-device.d.ts.map +1 -0
  34. package/dist/contracts/auth-device.js +163 -0
  35. package/dist/contracts/auth-device.js.map +1 -0
  36. package/dist/contracts/binary.d.ts +54 -0
  37. package/dist/contracts/binary.d.ts.map +1 -0
  38. package/dist/contracts/binary.js +92 -0
  39. package/dist/contracts/binary.js.map +1 -0
  40. package/dist/contracts/config.d.ts +5 -0
  41. package/dist/contracts/config.d.ts.map +1 -0
  42. package/dist/contracts/config.js +30 -0
  43. package/dist/contracts/config.js.map +1 -0
  44. package/dist/contracts/data.d.ts +115 -0
  45. package/dist/contracts/data.d.ts.map +1 -0
  46. package/dist/contracts/data.js +237 -0
  47. package/dist/contracts/data.js.map +1 -0
  48. package/dist/contracts/grants.d.ts +45 -0
  49. package/dist/contracts/grants.d.ts.map +1 -0
  50. package/dist/contracts/grants.js +177 -0
  51. package/dist/contracts/grants.js.map +1 -0
  52. package/dist/contracts/http.d.ts +21 -0
  53. package/dist/contracts/http.d.ts.map +1 -0
  54. package/dist/contracts/http.js +49 -0
  55. package/dist/contracts/http.js.map +1 -0
  56. package/dist/contracts/index.d.ts +16 -0
  57. package/dist/contracts/index.d.ts.map +1 -0
  58. package/dist/contracts/index.js +10 -0
  59. package/dist/contracts/index.js.map +1 -0
  60. package/dist/contracts/oauth.d.ts +38 -0
  61. package/dist/contracts/oauth.d.ts.map +1 -0
  62. package/dist/contracts/oauth.js +130 -0
  63. package/dist/contracts/oauth.js.map +1 -0
  64. package/dist/contracts/sync.d.ts +10 -0
  65. package/dist/contracts/sync.d.ts.map +1 -0
  66. package/dist/contracts/sync.js +34 -0
  67. package/dist/contracts/sync.js.map +1 -0
  68. package/dist/errors/catalog.d.ts +9 -0
  69. package/dist/errors/catalog.d.ts.map +1 -1
  70. package/dist/errors/catalog.js +15 -0
  71. package/dist/errors/catalog.js.map +1 -1
  72. package/dist/grants/index.d.ts +2 -4
  73. package/dist/grants/index.d.ts.map +1 -1
  74. package/dist/grants/index.js +1 -2
  75. package/dist/grants/index.js.map +1 -1
  76. package/dist/keys/index.d.ts +1 -2
  77. package/dist/keys/index.d.ts.map +1 -1
  78. package/dist/keys/index.js +1 -2
  79. package/dist/keys/index.js.map +1 -1
  80. package/dist/keys/server-account.d.ts +5 -9
  81. package/dist/keys/server-account.d.ts.map +1 -1
  82. package/dist/keys/server-account.js +1 -47
  83. package/dist/keys/server-account.js.map +1 -1
  84. package/dist/logger/index.d.ts +6 -2
  85. package/dist/logger/index.d.ts.map +1 -1
  86. package/dist/logger/index.js +2 -6
  87. package/dist/logger/index.js.map +1 -1
  88. package/dist/logging/access-log.d.ts +0 -1
  89. package/dist/logging/access-log.d.ts.map +1 -1
  90. package/dist/logging/access-log.js +1 -24
  91. package/dist/logging/access-log.js.map +1 -1
  92. package/dist/logging/access-reader.d.ts +0 -5
  93. package/dist/logging/access-reader.d.ts.map +1 -1
  94. package/dist/logging/access-reader.js +1 -49
  95. package/dist/logging/access-reader.js.map +1 -1
  96. package/dist/mcp/activity.d.ts +68 -0
  97. package/dist/mcp/activity.d.ts.map +1 -0
  98. package/dist/mcp/activity.js +111 -0
  99. package/dist/mcp/activity.js.map +1 -0
  100. package/dist/mcp/builder-registration.d.ts +24 -0
  101. package/dist/mcp/builder-registration.d.ts.map +1 -0
  102. package/dist/mcp/builder-registration.js +89 -0
  103. package/dist/mcp/builder-registration.js.map +1 -0
  104. package/dist/mcp/connection-api.d.ts +170 -0
  105. package/dist/mcp/connection-api.d.ts.map +1 -0
  106. package/dist/mcp/connection-api.js +379 -0
  107. package/dist/mcp/connection-api.js.map +1 -0
  108. package/dist/mcp/grantee.d.ts +52 -0
  109. package/dist/mcp/grantee.d.ts.map +1 -0
  110. package/dist/mcp/grantee.js +91 -0
  111. package/dist/mcp/grantee.js.map +1 -0
  112. package/dist/mcp/index.d.ts +10 -0
  113. package/dist/mcp/index.d.ts.map +1 -0
  114. package/dist/mcp/index.js +10 -0
  115. package/dist/mcp/index.js.map +1 -0
  116. package/dist/mcp/read-client.d.ts +136 -0
  117. package/dist/mcp/read-client.d.ts.map +1 -0
  118. package/dist/mcp/read-client.js +306 -0
  119. package/dist/mcp/read-client.js.map +1 -0
  120. package/dist/mcp/resources.d.ts +37 -0
  121. package/dist/mcp/resources.d.ts.map +1 -0
  122. package/dist/mcp/resources.js +114 -0
  123. package/dist/mcp/resources.js.map +1 -0
  124. package/dist/mcp/search/index.d.ts +34 -0
  125. package/dist/mcp/search/index.d.ts.map +1 -0
  126. package/dist/mcp/search/index.js +69 -0
  127. package/dist/mcp/search/index.js.map +1 -0
  128. package/dist/mcp/server.d.ts +53 -0
  129. package/dist/mcp/server.d.ts.map +1 -0
  130. package/dist/mcp/server.js +308 -0
  131. package/dist/mcp/server.js.map +1 -0
  132. package/dist/mcp/store.d.ts +13 -0
  133. package/dist/mcp/store.d.ts.map +1 -0
  134. package/dist/mcp/store.js +98 -0
  135. package/dist/mcp/store.js.map +1 -0
  136. package/dist/mcp/tools.d.ts +60 -0
  137. package/dist/mcp/tools.d.ts.map +1 -0
  138. package/dist/mcp/tools.js +1058 -0
  139. package/dist/mcp/tools.js.map +1 -0
  140. package/dist/mcp/types.d.ts +97 -0
  141. package/dist/mcp/types.d.ts.map +1 -0
  142. package/dist/mcp/types.js +24 -0
  143. package/dist/mcp/types.js.map +1 -0
  144. package/dist/payment/index.d.ts +2 -0
  145. package/dist/payment/index.d.ts.map +1 -0
  146. package/dist/payment/index.js +2 -0
  147. package/dist/payment/index.js.map +1 -0
  148. package/dist/payment/x402.d.ts +155 -0
  149. package/dist/payment/x402.d.ts.map +1 -0
  150. package/dist/payment/x402.js +402 -0
  151. package/dist/payment/x402.js.map +1 -0
  152. package/dist/policy/data-read.d.ts +15 -0
  153. package/dist/policy/data-read.d.ts.map +1 -0
  154. package/dist/policy/data-read.js +84 -0
  155. package/dist/policy/data-read.js.map +1 -0
  156. package/dist/policy/index.d.ts +2 -0
  157. package/dist/policy/index.d.ts.map +1 -0
  158. package/dist/policy/index.js +2 -0
  159. package/dist/policy/index.js.map +1 -0
  160. package/dist/ports/index.d.ts +95 -0
  161. package/dist/ports/index.d.ts.map +1 -0
  162. package/dist/ports/index.js +7 -0
  163. package/dist/ports/index.js.map +1 -0
  164. package/dist/schemas/server-config.d.ts +12 -0
  165. package/dist/schemas/server-config.d.ts.map +1 -1
  166. package/dist/schemas/server-config.js +34 -4
  167. package/dist/schemas/server-config.js.map +1 -1
  168. package/dist/signing/index.d.ts +0 -1
  169. package/dist/signing/index.d.ts.map +1 -1
  170. package/dist/signing/index.js +0 -1
  171. package/dist/signing/index.js.map +1 -1
  172. package/dist/signing/request-signer.d.ts +8 -2
  173. package/dist/signing/request-signer.d.ts.map +1 -1
  174. package/dist/signing/request-signer.js +5 -31
  175. package/dist/signing/request-signer.js.map +1 -1
  176. package/dist/signing/signer.d.ts +20 -2
  177. package/dist/signing/signer.d.ts.map +1 -1
  178. package/dist/signing/signer.js +23 -13
  179. package/dist/signing/signer.js.map +1 -1
  180. package/dist/storage/adapters/index.d.ts +2 -1
  181. package/dist/storage/adapters/index.d.ts.map +1 -1
  182. package/dist/storage/adapters/index.js +2 -1
  183. package/dist/storage/adapters/index.js.map +1 -1
  184. package/dist/storage/adapters/interface.d.ts +10 -0
  185. package/dist/storage/adapters/interface.d.ts.map +1 -1
  186. package/dist/storage/adapters/sdk.d.ts +23 -0
  187. package/dist/storage/adapters/sdk.d.ts.map +1 -0
  188. package/dist/storage/adapters/sdk.js +73 -0
  189. package/dist/storage/adapters/sdk.js.map +1 -0
  190. package/dist/storage/adapters/vana.d.ts +7 -30
  191. package/dist/storage/adapters/vana.d.ts.map +1 -1
  192. package/dist/storage/adapters/vana.js +37 -92
  193. package/dist/storage/adapters/vana.js.map +1 -1
  194. package/dist/storage/blocks/build.d.ts +20 -0
  195. package/dist/storage/blocks/build.d.ts.map +1 -0
  196. package/dist/storage/blocks/build.js +378 -0
  197. package/dist/storage/blocks/build.js.map +1 -0
  198. package/dist/storage/blocks/cursor.d.ts +33 -0
  199. package/dist/storage/blocks/cursor.d.ts.map +1 -0
  200. package/dist/storage/blocks/cursor.js +142 -0
  201. package/dist/storage/blocks/cursor.js.map +1 -0
  202. package/dist/storage/blocks/errors.d.ts +6 -0
  203. package/dist/storage/blocks/errors.d.ts.map +1 -0
  204. package/dist/storage/blocks/errors.js +9 -0
  205. package/dist/storage/blocks/errors.js.map +1 -0
  206. package/dist/storage/blocks/index.d.ts +5 -0
  207. package/dist/storage/blocks/index.d.ts.map +1 -0
  208. package/dist/storage/blocks/index.js +4 -0
  209. package/dist/storage/blocks/index.js.map +1 -0
  210. package/dist/storage/blocks/types.d.ts +49 -0
  211. package/dist/storage/blocks/types.d.ts.map +1 -0
  212. package/dist/storage/blocks/types.js +2 -0
  213. package/dist/{grants → storage/blocks}/types.js.map +1 -1
  214. package/dist/storage/hierarchy/index.d.ts +0 -1
  215. package/dist/storage/hierarchy/index.d.ts.map +1 -1
  216. package/dist/storage/hierarchy/index.js +0 -1
  217. package/dist/storage/hierarchy/index.js.map +1 -1
  218. package/dist/storage/hierarchy/manager.d.ts +0 -14
  219. package/dist/storage/hierarchy/manager.d.ts.map +1 -1
  220. package/dist/storage/hierarchy/manager.js +1 -59
  221. package/dist/storage/hierarchy/manager.js.map +1 -1
  222. package/dist/storage/hierarchy/paths.d.ts.map +1 -1
  223. package/dist/storage/hierarchy/paths.js +11 -4
  224. package/dist/storage/hierarchy/paths.js.map +1 -1
  225. package/dist/storage/index/index.d.ts +2 -3
  226. package/dist/storage/index/index.d.ts.map +1 -1
  227. package/dist/storage/index/index.js +1 -2
  228. package/dist/storage/index/index.js.map +1 -1
  229. package/dist/storage/index/manager.d.ts +26 -6
  230. package/dist/storage/index/manager.d.ts.map +1 -1
  231. package/dist/storage/index/manager.js +1 -131
  232. package/dist/storage/index/manager.js.map +1 -1
  233. package/dist/storage/index/schema.d.ts +1 -3
  234. package/dist/storage/index/schema.d.ts.map +1 -1
  235. package/dist/storage/index/schema.js +1 -26
  236. package/dist/storage/index/schema.js.map +1 -1
  237. package/dist/storage/index/types.d.ts +16 -0
  238. package/dist/storage/index/types.d.ts.map +1 -1
  239. package/dist/storage/preview.d.ts +12 -0
  240. package/dist/storage/preview.d.ts.map +1 -0
  241. package/dist/storage/preview.js +239 -0
  242. package/dist/storage/preview.js.map +1 -0
  243. package/dist/sync/cursor.d.ts +5 -7
  244. package/dist/sync/cursor.d.ts.map +1 -1
  245. package/dist/sync/cursor.js +1 -18
  246. package/dist/sync/cursor.js.map +1 -1
  247. package/dist/sync/engine/sync-manager.d.ts +17 -0
  248. package/dist/sync/engine/sync-manager.d.ts.map +1 -1
  249. package/dist/sync/engine/sync-manager.js +104 -43
  250. package/dist/sync/engine/sync-manager.js.map +1 -1
  251. package/dist/sync/index.d.ts +4 -2
  252. package/dist/sync/index.d.ts.map +1 -1
  253. package/dist/sync/index.js +1 -1
  254. package/dist/sync/index.js.map +1 -1
  255. package/dist/sync/issues.d.ts +55 -0
  256. package/dist/sync/issues.d.ts.map +1 -0
  257. package/dist/sync/issues.js +168 -0
  258. package/dist/sync/issues.js.map +1 -0
  259. package/dist/sync/retry-memory.d.ts +33 -0
  260. package/dist/sync/retry-memory.d.ts.map +1 -0
  261. package/dist/sync/retry-memory.js +68 -0
  262. package/dist/sync/retry-memory.js.map +1 -0
  263. package/dist/sync/types.d.ts +9 -24
  264. package/dist/sync/types.d.ts.map +1 -1
  265. package/dist/sync/workers/delete.d.ts +42 -0
  266. package/dist/sync/workers/delete.d.ts.map +1 -0
  267. package/dist/sync/workers/delete.js +40 -0
  268. package/dist/sync/workers/delete.js.map +1 -0
  269. package/dist/sync/workers/download.d.ts +64 -18
  270. package/dist/sync/workers/download.d.ts.map +1 -1
  271. package/dist/sync/workers/download.js +396 -54
  272. package/dist/sync/workers/download.js.map +1 -1
  273. package/dist/sync/workers/upload.d.ts +29 -18
  274. package/dist/sync/workers/upload.d.ts.map +1 -1
  275. package/dist/sync/workers/upload.js +163 -46
  276. package/dist/sync/workers/upload.js.map +1 -1
  277. package/dist/test-utils/wallet.d.ts +1 -0
  278. package/dist/test-utils/wallet.d.ts.map +1 -1
  279. package/dist/test-utils/wallet.js +9 -31
  280. package/dist/test-utils/wallet.js.map +1 -1
  281. package/package.json +51 -27
  282. package/dist/auth/web3-signed.d.ts +0 -48
  283. package/dist/auth/web3-signed.d.ts.map +0 -1
  284. package/dist/auth/web3-signed.js +0 -110
  285. package/dist/auth/web3-signed.js.map +0 -1
  286. package/dist/config/loader.d.ts +0 -8
  287. package/dist/config/loader.d.ts.map +0 -1
  288. package/dist/config/loader.js +0 -38
  289. package/dist/config/loader.js.map +0 -1
  290. package/dist/gateway/client.d.ts +0 -98
  291. package/dist/gateway/client.d.ts.map +0 -1
  292. package/dist/gateway/client.js +0 -171
  293. package/dist/gateway/client.js.map +0 -1
  294. package/dist/grants/eip712.d.ts +0 -28
  295. package/dist/grants/eip712.d.ts.map +0 -1
  296. package/dist/grants/eip712.js +0 -30
  297. package/dist/grants/eip712.js.map +0 -1
  298. package/dist/grants/types.d.ts +0 -29
  299. package/dist/grants/types.d.ts.map +0 -1
  300. package/dist/grants/types.js +0 -6
  301. package/dist/grants/verify.d.ts +0 -24
  302. package/dist/grants/verify.d.ts.map +0 -1
  303. package/dist/grants/verify.js +0 -71
  304. package/dist/grants/verify.js.map +0 -1
  305. package/dist/keys/derive.d.ts +0 -18
  306. package/dist/keys/derive.d.ts.map +0 -1
  307. package/dist/keys/derive.js +0 -43
  308. package/dist/keys/derive.js.map +0 -1
  309. package/dist/schemas/data-file.d.ts +0 -20
  310. package/dist/schemas/data-file.d.ts.map +0 -1
  311. package/dist/schemas/data-file.js +0 -23
  312. package/dist/schemas/data-file.js.map +0 -1
  313. package/dist/scopes/index.d.ts +0 -3
  314. package/dist/scopes/index.d.ts.map +0 -1
  315. package/dist/scopes/index.js +0 -3
  316. package/dist/scopes/index.js.map +0 -1
  317. package/dist/scopes/match.d.ts +0 -13
  318. package/dist/scopes/match.d.ts.map +0 -1
  319. package/dist/scopes/match.js +0 -23
  320. package/dist/scopes/match.js.map +0 -1
  321. package/dist/scopes/parse.d.ts +0 -12
  322. package/dist/scopes/parse.d.ts.map +0 -1
  323. package/dist/scopes/parse.js +0 -29
  324. package/dist/scopes/parse.js.map +0 -1
  325. package/dist/signing/eip712.d.ts +0 -69
  326. package/dist/signing/eip712.d.ts.map +0 -1
  327. package/dist/signing/eip712.js +0 -68
  328. package/dist/signing/eip712.js.map +0 -1
  329. package/dist/storage/encryption/decrypt.d.ts +0 -10
  330. package/dist/storage/encryption/decrypt.d.ts.map +0 -1
  331. package/dist/storage/encryption/decrypt.js +0 -19
  332. package/dist/storage/encryption/decrypt.js.map +0 -1
  333. package/dist/storage/encryption/encrypt.d.ts +0 -10
  334. package/dist/storage/encryption/encrypt.d.ts.map +0 -1
  335. package/dist/storage/encryption/encrypt.js +0 -19
  336. package/dist/storage/encryption/encrypt.js.map +0 -1
  337. package/dist/storage/encryption/index.d.ts +0 -3
  338. package/dist/storage/encryption/index.d.ts.map +0 -1
  339. package/dist/storage/encryption/index.js +0 -3
  340. package/dist/storage/encryption/index.js.map +0 -1
@@ -0,0 +1,774 @@
1
+ import { ProtocolError } from "../errors/catalog.js";
2
+ import { deleteDataScopeContract, ingestDataContract, ingestBinaryDataContract, listAccessLogsContract, listDataScopesContract, listDataVersionsContract, parseDataScopeContract, parseJsonObjectBody, readDataContract, syncFileContract, triggerSyncContract, getSyncStatusContract, configReadErrorContract, configWriteErrorContract, createGrantContract, listGrantsContract, oauthTokenContract, revokeGrantContract, validateServerConfigContract, verifyGrantContract, decodeBinaryEnvelope, isBinaryEnvelope, parseMetadataHeader, stringifyMetadataHeader, } from "../contracts/index.js";
3
+ import { buildChallenge, parsePaymentHeader, verifyPayment, } from "../payment/index.js";
4
+ function jsonResponse(body, init) {
5
+ const headers = new Headers(init?.headers);
6
+ headers.set("Content-Type", "application/json");
7
+ return new Response(JSON.stringify(body), { ...init, headers });
8
+ }
9
+ // X402 spec convention: payment responses are JSON, base64-encoded into a
10
+ // single header value. Returns undefined on encode failure so callers can
11
+ // just omit the header rather than fail the read. Uses btoa + TextEncoder
12
+ // to stay portable across Node and web runtimes.
13
+ function encodePaymentResponseHeader(body) {
14
+ try {
15
+ const json = JSON.stringify(body ?? null);
16
+ const bytes = new TextEncoder().encode(json);
17
+ let binary = "";
18
+ for (const b of bytes)
19
+ binary += String.fromCharCode(b);
20
+ return btoa(binary);
21
+ }
22
+ catch {
23
+ return undefined;
24
+ }
25
+ }
26
+ function contractResponse(result) {
27
+ return jsonResponse(result.body, { status: result.status });
28
+ }
29
+ function contractErrorResponse(err) {
30
+ return jsonResponse(err.body, { status: err.status });
31
+ }
32
+ function protocolErrorResponse(err) {
33
+ return jsonResponse(err.toJSON(), { status: err.code });
34
+ }
35
+ function errorResponse(status, errorCode, message) {
36
+ return jsonResponse({
37
+ error: {
38
+ code: status,
39
+ errorCode,
40
+ message,
41
+ },
42
+ }, { status });
43
+ }
44
+ function methodNotAllowed() {
45
+ return errorResponse(405, "METHOD_NOT_ALLOWED", "Method not allowed");
46
+ }
47
+ function notFound() {
48
+ return errorResponse(404, "NOT_FOUND", "Not found");
49
+ }
50
+ async function withApiErrors(handler) {
51
+ try {
52
+ return await handler();
53
+ }
54
+ catch (err) {
55
+ if (err instanceof ProtocolError) {
56
+ return protocolErrorResponse(err);
57
+ }
58
+ return errorResponse(500, "INTERNAL_ERROR", "Internal server error");
59
+ }
60
+ }
61
+ function normalizeLimit(value, fallback) {
62
+ if (value === null)
63
+ return fallback;
64
+ const parsed = Number(value);
65
+ return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
66
+ }
67
+ function stripBasePath(pathname, basePath) {
68
+ if (!basePath || basePath === "/")
69
+ return pathname;
70
+ if (pathname === basePath)
71
+ return "/";
72
+ if (pathname.startsWith(`${basePath}/`)) {
73
+ return pathname.slice(basePath.length);
74
+ }
75
+ return pathname;
76
+ }
77
+ function decodePathPart(value) {
78
+ return decodeURIComponent(value ?? "");
79
+ }
80
+ function selectedGrantId(request, url) {
81
+ return (url.searchParams.get("grantId") ??
82
+ request.headers.get("x-ps-grant-id") ??
83
+ undefined);
84
+ }
85
+ /**
86
+ * Run the X402 dispatch for one read:
87
+ *
88
+ * X-PAYMENT absent / malformed / fails local validation
89
+ * → challenge: fresh 402 with current fee + accessRecord
90
+ * X-PAYMENT valid, forward to gateway succeeds
91
+ * → ok: caller proceeds to read the data
92
+ * X-PAYMENT valid, gateway returns 4xx/5xx
93
+ * → gateway-error: relay the gateway's body verbatim so the builder
94
+ * can dispatch (insufficient balance vs. replay vs. race etc.)
95
+ *
96
+ * The gateway POST goes through `fetch` directly (not `gateway.payForOperation`)
97
+ * because the SDK client throws plain Errors and loses the body — which is
98
+ * exactly what we need to distinguish gateway 402 (insufficient balance) from
99
+ * 409 (replay) from 400 (amount mismatch).
100
+ */
101
+ async function handleX402Cycle(input) {
102
+ const { deps, gateway, gatewayConfig, gatewayUrl, builder, scope } = input;
103
+ // The gateway lowercases opId before EIP-712 recovery — we must do the
104
+ // same in the challenge so the builder signs over the canonical form.
105
+ const opIdLower = input.grantId.toLowerCase();
106
+ // Live grant — re-fetched every cycle. The fee.totalDue is a snapshot
107
+ // (SDK comment: "clients shouldn't cache"); paymentStatus may have just
108
+ // flipped if a concurrent payer paid the registration fee.
109
+ const grant = await gateway.getGrant(opIdLower);
110
+ if (!grant) {
111
+ return {
112
+ kind: "challenge",
113
+ body: {
114
+ x402Version: 1,
115
+ error: "PAYMENT_REQUIRED",
116
+ accepts: [],
117
+ },
118
+ };
119
+ }
120
+ // Bind the accessRecord to the entry being served, if it exists and has
121
+ // been registered with DPv2 yet.
122
+ const entryRow = deps.storage.findEntry({
123
+ scope,
124
+ fileId: input.fileIdParam,
125
+ at: input.atParam,
126
+ });
127
+ const entryForChallenge = entryRow
128
+ ? {
129
+ dataPointId: entryRow.dataPointId,
130
+ scope,
131
+ version: entryRow.version,
132
+ }
133
+ : undefined;
134
+ async function buildFreshChallenge() {
135
+ return buildChallenge({
136
+ builder,
137
+ grantId: opIdLower,
138
+ grant,
139
+ network: deps.network ?? `vana:${gatewayConfig.chainId}`,
140
+ gatewayConfig,
141
+ serverSigner: deps.serverSigner,
142
+ serverOwner: deps.serverOwner,
143
+ entry: entryForChallenge,
144
+ generateNonce: deps.generatePaymentNonce,
145
+ generateRecordIdFn: deps.generateRecordId,
146
+ now: deps.now,
147
+ });
148
+ }
149
+ const headerValue = input.request.headers.get("x-payment");
150
+ const parsed = parsePaymentHeader(headerValue);
151
+ if (!parsed) {
152
+ return { kind: "challenge", body: await buildFreshChallenge() };
153
+ }
154
+ if (!deps.serverAddress) {
155
+ // Can't validate accessRecord recovery without our own address; fail
156
+ // closed by reissuing a challenge.
157
+ deps.logger?.error?.({ scope, grantId: opIdLower }, "X402 enabled but serverAddress is not configured — cannot verify accessRecord");
158
+ return { kind: "challenge", body: await buildFreshChallenge() };
159
+ }
160
+ const verify = await verifyPayment({
161
+ builder,
162
+ grantId: opIdLower,
163
+ grant,
164
+ entry: entryForChallenge,
165
+ serverAddress: deps.serverAddress,
166
+ gatewayConfig,
167
+ serverOwner: deps.serverOwner,
168
+ payment: parsed,
169
+ });
170
+ if (!verify.ok) {
171
+ deps.logger?.info?.({ scope, grantId: opIdLower, reason: verify.reason }, "X402 payment verification failed; reissuing challenge");
172
+ return { kind: "challenge", body: await buildFreshChallenge() };
173
+ }
174
+ // Forward to gateway via direct fetch (preserves error body). The SDK
175
+ // client wraps the same endpoint but throws away the structured response
176
+ // on non-2xx, which is exactly the info we need to distinguish gateway
177
+ // 402 (insufficient balance) from 409 (replay / race) from 400 (mismatch).
178
+ const doFetch = deps.paymentFetch ?? fetch;
179
+ const body = {
180
+ payerAddress: verify.payment.payload.message.payerAddress,
181
+ opType: verify.payment.payload.message.opType,
182
+ opId: verify.payment.payload.message.opId,
183
+ asset: verify.payment.payload.message.asset,
184
+ amount: verify.payment.payload.message.amount,
185
+ paymentNonce: verify.payment.payload.message.paymentNonce,
186
+ };
187
+ if (verify.payment.payload.accessRecord) {
188
+ body["accessRecord"] = verify.payment.payload.accessRecord;
189
+ }
190
+ let gatewayRes;
191
+ try {
192
+ gatewayRes = await doFetch(`${gatewayUrl.replace(/\/+$/, "")}/v1/escrow/pay`, {
193
+ method: "POST",
194
+ headers: {
195
+ "Content-Type": "application/json",
196
+ Authorization: `Web3Signed ${verify.payment.payload.signature}`,
197
+ },
198
+ body: JSON.stringify(body),
199
+ });
200
+ }
201
+ catch (err) {
202
+ deps.logger?.error?.({
203
+ scope,
204
+ grantId: opIdLower,
205
+ error: err.message,
206
+ }, "X402 gateway forward threw; reissuing challenge");
207
+ return { kind: "challenge", body: await buildFreshChallenge() };
208
+ }
209
+ if (gatewayRes.ok) {
210
+ // Preserve the gateway's structured success body so the request
211
+ // handler can echo it back via the X-PAYMENT-RESPONSE header (canonical
212
+ // X402 convention). Empty/malformed body → propagate as null; the
213
+ // header still gets set so builders can tell payment succeeded.
214
+ let payResponseBody = null;
215
+ try {
216
+ payResponseBody = await gatewayRes.json();
217
+ }
218
+ catch {
219
+ // Body unreadable but status was 2xx — treat as success with no payload.
220
+ }
221
+ return { kind: "ok", payResponse: payResponseBody };
222
+ }
223
+ // Read the gateway's structured error body if there is one.
224
+ let errorBody;
225
+ try {
226
+ errorBody = await gatewayRes.json();
227
+ }
228
+ catch {
229
+ errorBody = { error: gatewayRes.statusText };
230
+ }
231
+ // 409 (replay or race) and 400 (amount mismatch / shape error) are
232
+ // recoverable with a fresh challenge — the builder re-signs with the
233
+ // new state and retries. 402 (insufficient balance) is relayed verbatim
234
+ // because the builder needs to fix their escrow, not their signature.
235
+ // 5xx is treated like 402-relayed since the personal server can't
236
+ // distinguish "transient" from "permanent" without more info.
237
+ if (gatewayRes.status === 409 || gatewayRes.status === 400) {
238
+ deps.logger?.info?.({ scope, grantId: opIdLower, status: gatewayRes.status, errorBody }, "Gateway rejected X-PAYMENT; reissuing X402 challenge");
239
+ return { kind: "challenge", body: await buildFreshChallenge() };
240
+ }
241
+ return { kind: "gateway-error", status: gatewayRes.status, body: errorBody };
242
+ }
243
+ function collectedAt(now) {
244
+ return now()
245
+ .toISOString()
246
+ .replace(/\.\d{3}Z$/, "Z");
247
+ }
248
+ /** True when the request body should be treated as a JSON object (the legacy
249
+ * path). Missing/blank Content-Type is treated as JSON for backward compat. */
250
+ function isJsonContentType(request) {
251
+ const ct = request.headers.get("content-type");
252
+ if (!ct)
253
+ return true;
254
+ return ct.toLowerCase().includes("application/json");
255
+ }
256
+ function binaryMimeType(request) {
257
+ const ct = request.headers.get("content-type");
258
+ if (!ct)
259
+ return "application/octet-stream";
260
+ // Strip any "; charset=..." / boundary parameters.
261
+ return ct.split(";")[0].trim() || "application/octet-stream";
262
+ }
263
+ /** Extract a filename from X-Filename or a Content-Disposition header. */
264
+ function binaryFilename(request) {
265
+ const explicit = request.headers.get("x-filename");
266
+ if (explicit)
267
+ return explicit;
268
+ const disposition = request.headers.get("content-disposition");
269
+ const match = disposition?.match(/filename\*?=(?:UTF-8'')?"?([^";]+)"?/i);
270
+ return match ? decodeURIComponent(match[1]) : undefined;
271
+ }
272
+ function notifyNewData(syncManager) {
273
+ if (!syncManager)
274
+ return;
275
+ if (syncManager.notifyNewData) {
276
+ syncManager.notifyNewData();
277
+ return;
278
+ }
279
+ void syncManager.trigger().catch(() => undefined);
280
+ }
281
+ function shouldReportReadFulfillment(grantId) {
282
+ return (grantId !== "unknown" && grantId !== "owner" && grantId !== "policy-bypass");
283
+ }
284
+ function warnReadFulfillmentReporterFailed(deps, event, err) {
285
+ deps.logger?.warn?.({
286
+ builder: event.builder,
287
+ error: err instanceof Error ? err.message : String(err),
288
+ grantId: event.grantId,
289
+ logId: event.logId,
290
+ scope: event.scope,
291
+ }, "Read fulfillment reporter failed");
292
+ }
293
+ export function reportPersonalServerReadFulfillment(deps, event) {
294
+ if (!deps.readFulfillmentReporter ||
295
+ !shouldReportReadFulfillment(event.grantId)) {
296
+ return;
297
+ }
298
+ try {
299
+ void Promise.resolve(deps.readFulfillmentReporter.report(event)).catch((err) => warnReadFulfillmentReporterFailed(deps, event, err));
300
+ }
301
+ catch (err) {
302
+ warnReadFulfillmentReporterFailed(deps, event, err);
303
+ }
304
+ }
305
+ export async function handlePersonalServerDataRequest(request, deps, options = {}) {
306
+ return withApiErrors(async () => {
307
+ const url = new URL(request.url);
308
+ const pathname = stripBasePath(url.pathname, options.basePath);
309
+ if (pathname === "/" || pathname === "") {
310
+ if (request.method !== "GET")
311
+ return methodNotAllowed();
312
+ await deps.auth.authorizeBuilderList(request);
313
+ const result = await listDataScopesContract({
314
+ storage: deps.storage,
315
+ scopePrefix: url.searchParams.get("scopePrefix") ?? undefined,
316
+ limit: normalizeLimit(url.searchParams.get("limit"), 20),
317
+ offset: normalizeLimit(url.searchParams.get("offset"), 0),
318
+ });
319
+ return jsonResponse(result.response);
320
+ }
321
+ const parts = pathname.split("/").filter(Boolean);
322
+ if (parts.length === 2 && parts[1] === "versions") {
323
+ if (request.method !== "GET")
324
+ return methodNotAllowed();
325
+ await deps.auth.authorizeBuilderList(request);
326
+ const result = listDataVersionsContract({
327
+ storage: deps.storage,
328
+ scopeParam: decodePathPart(parts[0]),
329
+ limit: normalizeLimit(url.searchParams.get("limit"), 20),
330
+ offset: normalizeLimit(url.searchParams.get("offset"), 0),
331
+ });
332
+ if (!result.ok)
333
+ return contractErrorResponse(result);
334
+ return jsonResponse(result.response);
335
+ }
336
+ if (parts.length !== 1)
337
+ return notFound();
338
+ const scopeParam = decodePathPart(parts[0]);
339
+ if (request.method === "GET") {
340
+ const scopeResult = parseDataScopeContract(scopeParam);
341
+ if (!scopeResult.ok)
342
+ return contractErrorResponse(scopeResult);
343
+ const selectedEntry = deps.storage.findEntry({
344
+ scope: scopeResult.scope,
345
+ fileId: url.searchParams.get("fileId") ?? undefined,
346
+ at: url.searchParams.get("at") ?? undefined,
347
+ });
348
+ // Holds the base64-encoded gateway payForOperation response body when
349
+ // X402 succeeds — emitted on the final read response as X-PAYMENT-RESPONSE.
350
+ let paymentResponseHeader;
351
+ const grantId = selectedGrantId(request, url);
352
+ const authResult = await deps.auth.authorizeBuilderRead({
353
+ request,
354
+ scope: scopeResult.scope,
355
+ grantId,
356
+ fileId: url.searchParams.get("fileId") ?? selectedEntry?.fileId ?? undefined,
357
+ });
358
+ // X402 payment dance for builder reads. Owner-exempt reads (the
359
+ // grantId sentinels "owner" / "policy-bypass") skip payment entirely
360
+ // since there's no payable op to attach the payment to.
361
+ //
362
+ // The grantId we pay against is the one verifyDataReadPolicy resolved
363
+ // from the Web3Signed payload (authoritative source) — not the URL /
364
+ // header hint, which may be absent when the payload carries it.
365
+ const isOwnerSignal = authResult?.grantId === "owner" ||
366
+ authResult?.grantId === "policy-bypass";
367
+ const builder = authResult?.builder;
368
+ const resolvedGrantId = !isOwnerSignal && authResult?.grantId ? authResult.grantId : undefined;
369
+ const willCharge = Boolean(deps.paymentEnabled &&
370
+ !isOwnerSignal &&
371
+ typeof builder === "string" &&
372
+ builder.startsWith("0x") &&
373
+ resolvedGrantId &&
374
+ deps.gateway &&
375
+ deps.gatewayConfig &&
376
+ deps.gatewayUrl);
377
+ // Diagnostic: makes it explicit why a read is/isn't charged. x402 has
378
+ // many "served free" branches (owner read, payment disabled, missing
379
+ // gateway deps); this surfaces the deciding inputs.
380
+ //
381
+ // Routing: when a logger is wired (the Node server always provides one),
382
+ // log the full detail through it at debug level so the operator's level
383
+ // controls + redaction apply and access metadata (scope/grantId/builder)
384
+ // doesn't unconditionally hit stdout. The browser PS-Lite runtime wires
385
+ // no logger; there we fall back to console but log ONLY the non-identifying
386
+ // decision booleans — never scope/grantId/builder.
387
+ if (deps.logger?.debug) {
388
+ deps.logger.debug({
389
+ scope: scopeResult.scope,
390
+ grantId: authResult?.grantId,
391
+ isOwnerSignal,
392
+ builder,
393
+ resolvedGrantId,
394
+ paymentEnabled: Boolean(deps.paymentEnabled),
395
+ hasGateway: Boolean(deps.gateway),
396
+ hasGatewayConfig: Boolean(deps.gatewayConfig),
397
+ hasGatewayUrl: Boolean(deps.gatewayUrl),
398
+ willCharge,
399
+ }, "[x402-gate] read");
400
+ }
401
+ else {
402
+ console.info("[x402-gate] read", {
403
+ isOwnerSignal,
404
+ paymentEnabled: Boolean(deps.paymentEnabled),
405
+ hasGateway: Boolean(deps.gateway),
406
+ hasGatewayConfig: Boolean(deps.gatewayConfig),
407
+ hasGatewayUrl: Boolean(deps.gatewayUrl),
408
+ willCharge,
409
+ });
410
+ }
411
+ // Keep the full condition inline (not `if (willCharge)`) so TS narrows
412
+ // deps.gateway/gatewayConfig/gatewayUrl to non-undefined in the block.
413
+ if (deps.paymentEnabled &&
414
+ !isOwnerSignal &&
415
+ typeof builder === "string" &&
416
+ builder.startsWith("0x") &&
417
+ resolvedGrantId &&
418
+ deps.gateway &&
419
+ deps.gatewayConfig &&
420
+ deps.gatewayUrl) {
421
+ const x402Result = await handleX402Cycle({
422
+ deps,
423
+ request,
424
+ scope: scopeResult.scope,
425
+ fileIdParam: url.searchParams.get("fileId") ?? undefined,
426
+ atParam: url.searchParams.get("at") ?? undefined,
427
+ grantId: resolvedGrantId,
428
+ builder: builder,
429
+ gateway: deps.gateway,
430
+ gatewayConfig: deps.gatewayConfig,
431
+ gatewayUrl: deps.gatewayUrl,
432
+ });
433
+ if (x402Result.kind === "challenge") {
434
+ return jsonResponse(x402Result.body, { status: 402 });
435
+ }
436
+ if (x402Result.kind === "gateway-error") {
437
+ // Relay the gateway's structured body verbatim so the builder can
438
+ // dispatch on it (insufficient balance vs. replay vs. race etc.).
439
+ return jsonResponse(x402Result.body, { status: x402Result.status });
440
+ }
441
+ // x402Result.kind === "ok" — payment accepted, proceed to read.
442
+ // The gateway's success body is forwarded back to the builder via
443
+ // X-PAYMENT-RESPONSE (canonical X402 convention) so callers can see
444
+ // breakdown / paidAt / paymentNonce without a second gateway round-trip.
445
+ paymentResponseHeader = encodePaymentResponseHeader(x402Result.payResponse);
446
+ }
447
+ const result = await readDataContract({
448
+ storage: deps.storage,
449
+ scopeParam: scopeResult.scope,
450
+ fileId: url.searchParams.get("fileId") ?? undefined,
451
+ at: url.searchParams.get("at") ?? undefined,
452
+ });
453
+ if (!result.ok)
454
+ return contractErrorResponse(result);
455
+ const logId = deps.createLogId?.() ?? crypto.randomUUID();
456
+ const timestamp = (deps.now ?? (() => new Date()))().toISOString();
457
+ const ipAddress = request.headers.get("x-forwarded-for") ??
458
+ request.headers.get("x-real-ip") ??
459
+ "unknown";
460
+ const userAgent = request.headers.get("user-agent") ?? "unknown";
461
+ const loggedGrantId = authResult?.grantId ?? grantId ?? "unknown";
462
+ const loggedBuilder = authResult?.builder ?? "unknown";
463
+ await deps.accessLogWriter.write({
464
+ logId,
465
+ grantId: loggedGrantId,
466
+ builder: loggedBuilder,
467
+ action: "read",
468
+ scope: scopeResult.scope,
469
+ timestamp,
470
+ ipAddress,
471
+ userAgent,
472
+ });
473
+ const reportReadFulfillment = () => {
474
+ if (!authResult?.grantId || !authResult.builder)
475
+ return;
476
+ reportPersonalServerReadFulfillment(deps, {
477
+ builder: authResult.builder,
478
+ fileId: url.searchParams.get("fileId") ??
479
+ selectedEntry?.fileId ??
480
+ undefined,
481
+ grantId: authResult.grantId,
482
+ ipAddress,
483
+ logId,
484
+ scope: scopeResult.scope,
485
+ servedAt: timestamp,
486
+ userAgent,
487
+ });
488
+ };
489
+ const headers = {};
490
+ if (paymentResponseHeader) {
491
+ headers["X-PAYMENT-RESPONSE"] = paymentResponseHeader;
492
+ }
493
+ const wantsRawContent = url.searchParams.get("content") === "raw";
494
+ // `?content=raw` streams the decoded bytes of a binary envelope with its
495
+ // original media type, so a builder can download the file directly. The
496
+ // X-PAYMENT-RESPONSE header (if any) rides along on the raw response too.
497
+ if (wantsRawContent) {
498
+ if (!isBinaryEnvelope(result.envelope)) {
499
+ return jsonResponse({
500
+ error: "NOT_BINARY_SCOPE",
501
+ message: `Scope "${scopeResult.scope}" does not expose raw binary content.`,
502
+ }, { status: 400, headers });
503
+ }
504
+ const decoded = decodeBinaryEnvelope(result.envelope);
505
+ headers["Content-Type"] = decoded.mimeType;
506
+ headers["Content-Length"] = String(decoded.bytes.length);
507
+ if (decoded.filename) {
508
+ headers["Content-Disposition"] =
509
+ `attachment; filename="${decoded.filename}"`;
510
+ }
511
+ if (decoded.metadata !== undefined) {
512
+ headers["X-Vana-Metadata"] = stringifyMetadataHeader(decoded.metadata);
513
+ }
514
+ const response = new Response(decoded.bytes, {
515
+ status: 200,
516
+ headers,
517
+ });
518
+ return response;
519
+ }
520
+ const response = jsonResponse(result.envelope, { headers });
521
+ reportReadFulfillment();
522
+ return response;
523
+ }
524
+ if (request.method === "POST") {
525
+ await deps.auth.authorizeOwner(request);
526
+ const scopeResult = parseDataScopeContract(scopeParam);
527
+ if (!scopeResult.ok)
528
+ return contractErrorResponse(scopeResult);
529
+ const collectedAtValue = collectedAt(deps.now ?? (() => new Date()));
530
+ const status = deps.syncManager ? "syncing" : "stored";
531
+ // Binary / unstructured data (e.g. a PDF): the body is raw bytes. DPv2
532
+ // data points are scope-addressed and carry no schemaId, so unstructured
533
+ // data needs no schema at all — we ingest it schemaless. (Structured JSON
534
+ // below still resolves a schema for validation/metadata.)
535
+ if (!isJsonContentType(request)) {
536
+ const bytes = new Uint8Array(await request.arrayBuffer());
537
+ const result = await ingestBinaryDataContract({
538
+ storage: deps.storage,
539
+ scopeParam: scopeResult.scope,
540
+ bytes,
541
+ mimeType: binaryMimeType(request),
542
+ filename: binaryFilename(request),
543
+ metadata: parseMetadataHeader(request.headers.get("x-vana-metadata")),
544
+ collectedAt: collectedAtValue,
545
+ status,
546
+ });
547
+ if (!result.ok)
548
+ return contractErrorResponse(result);
549
+ deps.logger?.info?.({
550
+ scope: scopeResult.scope,
551
+ collectedAt: collectedAtValue,
552
+ path: result.writeResult.relativePath,
553
+ mimeType: binaryMimeType(request),
554
+ sizeBytes: bytes.length,
555
+ }, "Binary data file ingested");
556
+ notifyNewData(deps.syncManager);
557
+ return jsonResponse(result.response, { status: 201 });
558
+ }
559
+ const parsed = await parseJsonObjectBody(request, "Request body must be valid JSON");
560
+ if (!parsed.ok)
561
+ return contractResponse(parsed.result);
562
+ // DPv2 is scope-addressed and the gateway records no schemas, so JSON
563
+ // ingest is schemaless too — no lookup, no schemaId/$schema stamped.
564
+ const result = await ingestDataContract({
565
+ storage: deps.storage,
566
+ scopeParam: scopeResult.scope,
567
+ body: parsed.body,
568
+ collectedAt: collectedAtValue,
569
+ status,
570
+ });
571
+ if (!result.ok)
572
+ return contractErrorResponse(result);
573
+ deps.logger?.info?.({
574
+ scope: scopeResult.scope,
575
+ collectedAt: collectedAtValue,
576
+ path: result.writeResult.relativePath,
577
+ }, "Data file ingested");
578
+ notifyNewData(deps.syncManager);
579
+ return jsonResponse(result.response, { status: 201 });
580
+ }
581
+ if (request.method === "DELETE") {
582
+ await deps.auth.authorizeOwner(request);
583
+ // Propagate the deletion to the authoritative stores (R2 blobs + gateway records) BEFORE the
584
+ // local delete — it reads the local index to find what to remove. Best-effort: a remote
585
+ // failure must not block the owner's local delete (and sync would otherwise resurrect it; the
586
+ // download-worker reconciliation is the backstop). Parse the scope first so an invalid scope
587
+ // still 400s without a wasted remote call.
588
+ const parsed = parseDataScopeContract(scopeParam);
589
+ if (parsed.ok && deps.syncManager?.deleteScopeRemote) {
590
+ try {
591
+ await deps.syncManager.deleteScopeRemote(parsed.scope);
592
+ }
593
+ catch (err) {
594
+ deps.logger?.info?.({ scope: scopeParam, error: err.message }, "Remote scope deletion failed; proceeding with local delete");
595
+ }
596
+ }
597
+ const result = await deleteDataScopeContract({
598
+ storage: deps.storage,
599
+ scopeParam,
600
+ });
601
+ if (!result.ok)
602
+ return contractErrorResponse(result);
603
+ deps.logger?.info?.({ scope: scopeParam, deletedCount: result.deletedCount }, "Scope deleted");
604
+ return new Response(null, { status: 204 });
605
+ }
606
+ return methodNotAllowed();
607
+ });
608
+ }
609
+ export async function handlePersonalServerAccessLogsRequest(request, deps, options = {}) {
610
+ return withApiErrors(async () => {
611
+ const url = new URL(request.url);
612
+ const pathname = stripBasePath(url.pathname, options.basePath);
613
+ if (pathname !== "/" && pathname !== "")
614
+ return notFound();
615
+ if (request.method !== "GET")
616
+ return methodNotAllowed();
617
+ await deps.auth.authorizeOwner(request);
618
+ return contractResponse(await listAccessLogsContract({
619
+ accessLogReader: deps.accessLogReader,
620
+ limit: url.searchParams.get("limit"),
621
+ offset: url.searchParams.get("offset"),
622
+ }));
623
+ });
624
+ }
625
+ export async function handlePersonalServerSyncRequest(request, deps, options = {}) {
626
+ return withApiErrors(async () => {
627
+ const url = new URL(request.url);
628
+ const pathname = stripBasePath(url.pathname, options.basePath);
629
+ if (pathname === "/trigger") {
630
+ if (request.method !== "POST")
631
+ return methodNotAllowed();
632
+ await deps.auth.authorizeOwner(request);
633
+ return contractResponse(await triggerSyncContract(deps.syncManager));
634
+ }
635
+ if (pathname === "/status") {
636
+ if (request.method !== "GET")
637
+ return methodNotAllowed();
638
+ await deps.auth.authorizeOwner(request);
639
+ return contractResponse(getSyncStatusContract(deps.syncManager));
640
+ }
641
+ if (pathname.startsWith("/file/")) {
642
+ if (request.method !== "POST")
643
+ return methodNotAllowed();
644
+ await deps.auth.authorizeOwner(request);
645
+ const fileId = decodeURIComponent(pathname.slice("/file/".length));
646
+ deps.logger?.info?.({ fileId }, "File sync requested, triggering full sync");
647
+ return contractResponse(await syncFileContract({
648
+ fileId,
649
+ syncManager: deps.syncManager,
650
+ }));
651
+ }
652
+ return notFound();
653
+ });
654
+ }
655
+ export async function handlePersonalServerGrantsRequest(request, deps, options = {}) {
656
+ return withApiErrors(async () => {
657
+ const url = new URL(request.url);
658
+ const pathname = stripBasePath(url.pathname, options.basePath);
659
+ if (pathname === "/" || pathname === "") {
660
+ await deps.auth.authorizeOwner(request);
661
+ if (!deps.gateway) {
662
+ return errorResponse(500, "SERVER_NOT_CONFIGURED", "Gateway is not configured");
663
+ }
664
+ if (request.method === "GET") {
665
+ return contractResponse(await listGrantsContract({
666
+ gateway: deps.gateway,
667
+ serverOwner: deps.serverOwner,
668
+ }));
669
+ }
670
+ if (request.method === "POST") {
671
+ const parsed = await parseJsonObjectBody(request);
672
+ if (!parsed.ok)
673
+ return contractResponse(parsed.result);
674
+ // Canary createGrant doesn't need a clock — grantVersion defaults
675
+ // to "1" and callers re-registering pass a strictly higher value.
676
+ return contractResponse(await createGrantContract({
677
+ gateway: deps.gateway,
678
+ serverOwner: deps.serverOwner,
679
+ serverSigner: deps.serverSigner,
680
+ body: parsed.body,
681
+ }));
682
+ }
683
+ return methodNotAllowed();
684
+ }
685
+ if (pathname === "/verify") {
686
+ if (request.method !== "POST")
687
+ return methodNotAllowed();
688
+ const parsed = await parseJsonObjectBody(request);
689
+ if (!parsed.ok)
690
+ return contractResponse(parsed.result);
691
+ return contractResponse(await verifyGrantContract({
692
+ body: parsed.body,
693
+ gatewayConfig: deps.gatewayConfig,
694
+ }));
695
+ }
696
+ if (pathname.startsWith("/")) {
697
+ if (request.method !== "DELETE")
698
+ return methodNotAllowed();
699
+ await deps.auth.authorizeOwner(request);
700
+ if (!deps.gateway) {
701
+ return errorResponse(500, "SERVER_NOT_CONFIGURED", "Gateway is not configured");
702
+ }
703
+ return contractResponse(await revokeGrantContract({
704
+ gateway: deps.gateway,
705
+ serverOwner: deps.serverOwner,
706
+ serverSigner: deps.serverSigner,
707
+ grantId: decodeURIComponent(pathname.slice(1)),
708
+ }));
709
+ }
710
+ return notFound();
711
+ });
712
+ }
713
+ export async function handlePersonalServerConfigRequest(request, deps) {
714
+ return withApiErrors(async () => {
715
+ await deps.auth.authorizeOwner(request);
716
+ if (request.method === "GET") {
717
+ try {
718
+ return jsonResponse(await deps.readConfig());
719
+ }
720
+ catch (err) {
721
+ const kind = err instanceof Error &&
722
+ "code" in err &&
723
+ err.code === "ENOENT"
724
+ ? "not-found"
725
+ : "read";
726
+ return contractResponse(configReadErrorContract(kind));
727
+ }
728
+ }
729
+ if (request.method === "PUT") {
730
+ const parsed = await parseJsonObjectBody(request);
731
+ if (!parsed.ok)
732
+ return contractResponse(parsed.result);
733
+ const result = validateServerConfigContract(parsed.body);
734
+ if (!result.ok)
735
+ return contractResponse(result);
736
+ try {
737
+ await deps.writeConfig(result.body.config);
738
+ }
739
+ catch {
740
+ return contractResponse(configWriteErrorContract());
741
+ }
742
+ return contractResponse(result);
743
+ }
744
+ return methodNotAllowed();
745
+ });
746
+ }
747
+ export async function handlePersonalServerOauthTokenRequest(request, deps) {
748
+ return withApiErrors(async () => {
749
+ if (request.method !== "POST")
750
+ return methodNotAllowed();
751
+ const contentType = request.headers.get("content-type") ?? "";
752
+ if (!contentType.includes("application/x-www-form-urlencoded")) {
753
+ return jsonResponse({
754
+ error: "invalid_request",
755
+ error_description: "Content-Type must be application/x-www-form-urlencoded",
756
+ }, { status: 400 });
757
+ }
758
+ const result = await oauthTokenContract({
759
+ body: new URLSearchParams(await request.text()),
760
+ authorizationHeader: request.headers.get("authorization"),
761
+ tokenStore: deps.tokenStore,
762
+ controlPlaneSecret: deps.controlPlaneSecret,
763
+ deviceSessions: deps.deviceSessions,
764
+ randomToken: deps.randomToken,
765
+ now: deps.now,
766
+ safeCompare: deps.safeCompare,
767
+ });
768
+ return jsonResponse(result.body, {
769
+ status: result.status,
770
+ headers: result.headers,
771
+ });
772
+ });
773
+ }
774
+ //# sourceMappingURL=index.js.map