@meshline/sdk 0.0.0-stage → 0.1.0-alpha.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 (338) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +43 -2
  3. package/dist/channels/repository.d.ts +73 -0
  4. package/dist/channels/repository.d.ts.map +1 -0
  5. package/dist/channels/repository.js +367 -0
  6. package/dist/channels/repository.js.map +1 -0
  7. package/dist/client/account-operations.d.ts +41 -0
  8. package/dist/client/account-operations.d.ts.map +1 -0
  9. package/dist/client/account-operations.js +304 -0
  10. package/dist/client/account-operations.js.map +1 -0
  11. package/dist/client/conversations.d.ts +36 -0
  12. package/dist/client/conversations.d.ts.map +1 -0
  13. package/dist/client/conversations.js +144 -0
  14. package/dist/client/conversations.js.map +1 -0
  15. package/dist/client.d.ts +53 -0
  16. package/dist/client.d.ts.map +1 -0
  17. package/dist/client.js +162 -0
  18. package/dist/client.js.map +1 -0
  19. package/dist/components/account.d.ts +38 -0
  20. package/dist/components/account.d.ts.map +1 -0
  21. package/dist/components/account.js +243 -0
  22. package/dist/components/account.js.map +1 -0
  23. package/dist/components/channel.d.ts +78 -0
  24. package/dist/components/channel.d.ts.map +1 -0
  25. package/dist/components/channel.js +475 -0
  26. package/dist/components/channel.js.map +1 -0
  27. package/dist/components/component.d.ts +49 -0
  28. package/dist/components/component.d.ts.map +1 -0
  29. package/dist/components/component.js +194 -0
  30. package/dist/components/component.js.map +1 -0
  31. package/dist/components/device.d.ts +60 -0
  32. package/dist/components/device.d.ts.map +1 -0
  33. package/dist/components/device.js +648 -0
  34. package/dist/components/device.js.map +1 -0
  35. package/dist/components/group.d.ts +141 -0
  36. package/dist/components/group.d.ts.map +1 -0
  37. package/dist/components/group.js +933 -0
  38. package/dist/components/group.js.map +1 -0
  39. package/dist/components/message.d.ts +63 -0
  40. package/dist/components/message.d.ts.map +1 -0
  41. package/dist/components/message.js +439 -0
  42. package/dist/components/message.js.map +1 -0
  43. package/dist/components/profile.d.ts +37 -0
  44. package/dist/components/profile.d.ts.map +1 -0
  45. package/dist/components/profile.js +232 -0
  46. package/dist/components/profile.js.map +1 -0
  47. package/dist/crypto/groups.d.ts +56 -0
  48. package/dist/crypto/groups.d.ts.map +1 -0
  49. package/dist/crypto/groups.js +163 -0
  50. package/dist/crypto/groups.js.map +1 -0
  51. package/dist/crypto/messages.d.ts +52 -0
  52. package/dist/crypto/messages.d.ts.map +1 -0
  53. package/dist/crypto/messages.js +180 -0
  54. package/dist/crypto/messages.js.map +1 -0
  55. package/dist/crypto/primitives.d.ts +30 -0
  56. package/dist/crypto/primitives.d.ts.map +1 -0
  57. package/dist/crypto/primitives.js +102 -0
  58. package/dist/crypto/primitives.js.map +1 -0
  59. package/dist/errors.d.ts +11 -0
  60. package/dist/errors.d.ts.map +1 -0
  61. package/dist/errors.js +14 -0
  62. package/dist/errors.js.map +1 -0
  63. package/dist/groups/account-sync.d.ts +28 -0
  64. package/dist/groups/account-sync.d.ts.map +1 -0
  65. package/dist/groups/account-sync.js +235 -0
  66. package/dist/groups/account-sync.js.map +1 -0
  67. package/dist/groups/messages.d.ts +39 -0
  68. package/dist/groups/messages.d.ts.map +1 -0
  69. package/dist/groups/messages.js +141 -0
  70. package/dist/groups/messages.js.map +1 -0
  71. package/dist/groups/operations.d.ts +57 -0
  72. package/dist/groups/operations.d.ts.map +1 -0
  73. package/dist/groups/operations.js +225 -0
  74. package/dist/groups/operations.js.map +1 -0
  75. package/dist/groups/repository.d.ts +56 -0
  76. package/dist/groups/repository.d.ts.map +1 -0
  77. package/dist/groups/repository.js +254 -0
  78. package/dist/groups/repository.js.map +1 -0
  79. package/dist/groups/secrets.d.ts +67 -0
  80. package/dist/groups/secrets.d.ts.map +1 -0
  81. package/dist/groups/secrets.js +452 -0
  82. package/dist/groups/secrets.js.map +1 -0
  83. package/dist/groups/state.d.ts +39 -0
  84. package/dist/groups/state.d.ts.map +1 -0
  85. package/dist/groups/state.js +201 -0
  86. package/dist/groups/state.js.map +1 -0
  87. package/dist/identity/identifiers.d.ts +9 -0
  88. package/dist/identity/identifiers.d.ts.map +1 -0
  89. package/dist/identity/identifiers.js +37 -0
  90. package/dist/identity/identifiers.js.map +1 -0
  91. package/dist/identity/neo.d.ts +11 -0
  92. package/dist/identity/neo.d.ts.map +1 -0
  93. package/dist/identity/neo.js +77 -0
  94. package/dist/identity/neo.js.map +1 -0
  95. package/dist/index.d.ts +53 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +46 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/interactions.d.ts +31 -0
  100. package/dist/interactions.d.ts.map +1 -0
  101. package/dist/interactions.js +2 -0
  102. package/dist/interactions.js.map +1 -0
  103. package/dist/messages/account-sender.d.ts +9 -0
  104. package/dist/messages/account-sender.d.ts.map +1 -0
  105. package/dist/messages/account-sender.js +46 -0
  106. package/dist/messages/account-sender.js.map +1 -0
  107. package/dist/messages/contacts.d.ts +103 -0
  108. package/dist/messages/contacts.d.ts.map +1 -0
  109. package/dist/messages/contacts.js +525 -0
  110. package/dist/messages/contacts.js.map +1 -0
  111. package/dist/messages/outbox.d.ts +66 -0
  112. package/dist/messages/outbox.d.ts.map +1 -0
  113. package/dist/messages/outbox.js +167 -0
  114. package/dist/messages/outbox.js.map +1 -0
  115. package/dist/messages/receiver.d.ts +35 -0
  116. package/dist/messages/receiver.d.ts.map +1 -0
  117. package/dist/messages/receiver.js +113 -0
  118. package/dist/messages/receiver.js.map +1 -0
  119. package/dist/messages/repository.d.ts +89 -0
  120. package/dist/messages/repository.d.ts.map +1 -0
  121. package/dist/messages/repository.js +295 -0
  122. package/dist/messages/repository.js.map +1 -0
  123. package/dist/models/authentication.d.ts +37 -0
  124. package/dist/models/authentication.d.ts.map +1 -0
  125. package/dist/models/authentication.js +53 -0
  126. package/dist/models/authentication.js.map +1 -0
  127. package/dist/models/channels.d.ts +141 -0
  128. package/dist/models/channels.d.ts.map +1 -0
  129. package/dist/models/channels.js +258 -0
  130. package/dist/models/channels.js.map +1 -0
  131. package/dist/models/contacts.d.ts +68 -0
  132. package/dist/models/contacts.d.ts.map +1 -0
  133. package/dist/models/contacts.js +186 -0
  134. package/dist/models/contacts.js.map +1 -0
  135. package/dist/models/content.d.ts +37 -0
  136. package/dist/models/content.d.ts.map +1 -0
  137. package/dist/models/content.js +137 -0
  138. package/dist/models/content.js.map +1 -0
  139. package/dist/models/device-state.d.ts +8 -0
  140. package/dist/models/device-state.d.ts.map +1 -0
  141. package/dist/models/device-state.js +22 -0
  142. package/dist/models/device-state.js.map +1 -0
  143. package/dist/models/group-admission.d.ts +111 -0
  144. package/dist/models/group-admission.d.ts.map +1 -0
  145. package/dist/models/group-admission.js +153 -0
  146. package/dist/models/group-admission.js.map +1 -0
  147. package/dist/models/group-keys.d.ts +30 -0
  148. package/dist/models/group-keys.d.ts.map +1 -0
  149. package/dist/models/group-keys.js +52 -0
  150. package/dist/models/group-keys.js.map +1 -0
  151. package/dist/models/group-management.d.ts +190 -0
  152. package/dist/models/group-management.d.ts.map +1 -0
  153. package/dist/models/group-management.js +263 -0
  154. package/dist/models/group-management.js.map +1 -0
  155. package/dist/models/groups.d.ts +81 -0
  156. package/dist/models/groups.d.ts.map +1 -0
  157. package/dist/models/groups.js +127 -0
  158. package/dist/models/groups.js.map +1 -0
  159. package/dist/models/identity.d.ts +47 -0
  160. package/dist/models/identity.d.ts.map +1 -0
  161. package/dist/models/identity.js +136 -0
  162. package/dist/models/identity.js.map +1 -0
  163. package/dist/models/messages.d.ts +104 -0
  164. package/dist/models/messages.d.ts.map +1 -0
  165. package/dist/models/messages.js +168 -0
  166. package/dist/models/messages.js.map +1 -0
  167. package/dist/models/profile.d.ts +24 -0
  168. package/dist/models/profile.d.ts.map +1 -0
  169. package/dist/models/profile.js +47 -0
  170. package/dist/models/profile.js.map +1 -0
  171. package/dist/models/relay.d.ts +39 -0
  172. package/dist/models/relay.d.ts.map +1 -0
  173. package/dist/models/relay.js +160 -0
  174. package/dist/models/relay.js.map +1 -0
  175. package/dist/protocol/codec.d.ts +36 -0
  176. package/dist/protocol/codec.d.ts.map +1 -0
  177. package/dist/protocol/codec.js +144 -0
  178. package/dist/protocol/codec.js.map +1 -0
  179. package/dist/protocol/context.d.ts +14 -0
  180. package/dist/protocol/context.d.ts.map +1 -0
  181. package/dist/protocol/context.js +43 -0
  182. package/dist/protocol/context.js.map +1 -0
  183. package/dist/protocol/encoding.d.ts +15 -0
  184. package/dist/protocol/encoding.d.ts.map +1 -0
  185. package/dist/protocol/encoding.js +133 -0
  186. package/dist/protocol/encoding.js.map +1 -0
  187. package/dist/protocol/json.d.ts +16 -0
  188. package/dist/protocol/json.d.ts.map +1 -0
  189. package/dist/protocol/json.js +172 -0
  190. package/dist/protocol/json.js.map +1 -0
  191. package/dist/protocol/validation.d.ts +4 -0
  192. package/dist/protocol/validation.d.ts.map +1 -0
  193. package/dist/protocol/validation.js +18 -0
  194. package/dist/protocol/validation.js.map +1 -0
  195. package/dist/runtime/abort.d.ts +6 -0
  196. package/dist/runtime/abort.d.ts.map +1 -0
  197. package/dist/runtime/abort.js +32 -0
  198. package/dist/runtime/abort.js.map +1 -0
  199. package/dist/runtime/async-gate.d.ts +6 -0
  200. package/dist/runtime/async-gate.d.ts.map +1 -0
  201. package/dist/runtime/async-gate.js +15 -0
  202. package/dist/runtime/async-gate.js.map +1 -0
  203. package/dist/runtime/async-pulse.d.ts +7 -0
  204. package/dist/runtime/async-pulse.d.ts.map +1 -0
  205. package/dist/runtime/async-pulse.js +23 -0
  206. package/dist/runtime/async-pulse.js.map +1 -0
  207. package/dist/runtime/clock.d.ts +16 -0
  208. package/dist/runtime/clock.d.ts.map +1 -0
  209. package/dist/runtime/clock.js +62 -0
  210. package/dist/runtime/clock.js.map +1 -0
  211. package/dist/runtime/events.d.ts +11 -0
  212. package/dist/runtime/events.d.ts.map +1 -0
  213. package/dist/runtime/events.js +45 -0
  214. package/dist/runtime/events.js.map +1 -0
  215. package/dist/runtime/url.d.ts +3 -0
  216. package/dist/runtime/url.d.ts.map +1 -0
  217. package/dist/runtime/url.js +23 -0
  218. package/dist/runtime/url.js.map +1 -0
  219. package/dist/storage/signed-request.d.ts +14 -0
  220. package/dist/storage/signed-request.d.ts.map +1 -0
  221. package/dist/storage/signed-request.js +26 -0
  222. package/dist/storage/signed-request.js.map +1 -0
  223. package/dist/storage/store.d.ts +58 -0
  224. package/dist/storage/store.d.ts.map +1 -0
  225. package/dist/storage/store.js +52 -0
  226. package/dist/storage/store.js.map +1 -0
  227. package/dist/storage/transaction.d.ts +7 -0
  228. package/dist/storage/transaction.d.ts.map +1 -0
  229. package/dist/storage/transaction.js +22 -0
  230. package/dist/storage/transaction.js.map +1 -0
  231. package/dist/transport/client.d.ts +61 -0
  232. package/dist/transport/client.d.ts.map +1 -0
  233. package/dist/transport/client.js +392 -0
  234. package/dist/transport/client.js.map +1 -0
  235. package/dist/transport/discovery.d.ts +19 -0
  236. package/dist/transport/discovery.d.ts.map +1 -0
  237. package/dist/transport/discovery.js +75 -0
  238. package/dist/transport/discovery.js.map +1 -0
  239. package/dist/transport/endpoint.d.ts +6 -0
  240. package/dist/transport/endpoint.d.ts.map +1 -0
  241. package/dist/transport/endpoint.js +35 -0
  242. package/dist/transport/endpoint.js.map +1 -0
  243. package/dist/transport/http.d.ts +52 -0
  244. package/dist/transport/http.d.ts.map +1 -0
  245. package/dist/transport/http.js +128 -0
  246. package/dist/transport/http.js.map +1 -0
  247. package/dist/transport/pool.d.ts +31 -0
  248. package/dist/transport/pool.d.ts.map +1 -0
  249. package/dist/transport/pool.js +137 -0
  250. package/dist/transport/pool.js.map +1 -0
  251. package/dist/transport/relay-error.d.ts +23 -0
  252. package/dist/transport/relay-error.d.ts.map +1 -0
  253. package/dist/transport/relay-error.js +47 -0
  254. package/dist/transport/relay-error.js.map +1 -0
  255. package/dist/transport/rpc.d.ts +31 -0
  256. package/dist/transport/rpc.d.ts.map +1 -0
  257. package/dist/transport/rpc.js +56 -0
  258. package/dist/transport/rpc.js.map +1 -0
  259. package/dist/transport/session.d.ts +49 -0
  260. package/dist/transport/session.d.ts.map +1 -0
  261. package/dist/transport/session.js +169 -0
  262. package/dist/transport/session.js.map +1 -0
  263. package/dist/transport/socket.d.ts +55 -0
  264. package/dist/transport/socket.d.ts.map +1 -0
  265. package/dist/transport/socket.js +266 -0
  266. package/dist/transport/socket.js.map +1 -0
  267. package/dist/transport/subscription.d.ts +36 -0
  268. package/dist/transport/subscription.d.ts.map +1 -0
  269. package/dist/transport/subscription.js +169 -0
  270. package/dist/transport/subscription.js.map +1 -0
  271. package/package.json +35 -4
  272. package/src/channels/repository.ts +244 -0
  273. package/src/client/account-operations.ts +183 -0
  274. package/src/client/conversations.ts +95 -0
  275. package/src/client.ts +108 -0
  276. package/src/components/account.ts +206 -0
  277. package/src/components/channel.ts +253 -0
  278. package/src/components/component.ts +151 -0
  279. package/src/components/device.ts +455 -0
  280. package/src/components/group.ts +546 -0
  281. package/src/components/message.ts +253 -0
  282. package/src/components/profile.ts +179 -0
  283. package/src/crypto/groups.ts +107 -0
  284. package/src/crypto/messages.ts +129 -0
  285. package/src/crypto/primitives.ts +103 -0
  286. package/src/errors.ts +13 -0
  287. package/src/groups/account-sync.ts +144 -0
  288. package/src/groups/messages.ts +89 -0
  289. package/src/groups/operations.ts +145 -0
  290. package/src/groups/repository.ts +161 -0
  291. package/src/groups/secrets.ts +256 -0
  292. package/src/groups/state.ts +138 -0
  293. package/src/identity/identifiers.ts +41 -0
  294. package/src/identity/neo.ts +72 -0
  295. package/src/index.ts +52 -0
  296. package/src/interactions.ts +35 -0
  297. package/src/messages/account-sender.ts +33 -0
  298. package/src/messages/contacts.ts +379 -0
  299. package/src/messages/outbox.ts +152 -0
  300. package/src/messages/receiver.ts +86 -0
  301. package/src/messages/repository.ts +230 -0
  302. package/src/models/authentication.ts +63 -0
  303. package/src/models/channels.ts +155 -0
  304. package/src/models/contacts.ts +141 -0
  305. package/src/models/content.ts +105 -0
  306. package/src/models/device-state.ts +18 -0
  307. package/src/models/group-admission.ts +110 -0
  308. package/src/models/group-keys.ts +34 -0
  309. package/src/models/group-management.ts +182 -0
  310. package/src/models/groups.ts +100 -0
  311. package/src/models/identity.ts +164 -0
  312. package/src/models/messages.ts +139 -0
  313. package/src/models/profile.ts +46 -0
  314. package/src/models/relay.ts +151 -0
  315. package/src/protocol/codec.ts +144 -0
  316. package/src/protocol/context.ts +39 -0
  317. package/src/protocol/encoding.ts +106 -0
  318. package/src/protocol/json.ts +140 -0
  319. package/src/protocol/validation.ts +13 -0
  320. package/src/runtime/abort.ts +27 -0
  321. package/src/runtime/async-gate.ts +16 -0
  322. package/src/runtime/async-pulse.ts +20 -0
  323. package/src/runtime/clock.ts +63 -0
  324. package/src/runtime/events.ts +31 -0
  325. package/src/runtime/url.ts +17 -0
  326. package/src/storage/signed-request.ts +26 -0
  327. package/src/storage/store.ts +90 -0
  328. package/src/storage/transaction.ts +17 -0
  329. package/src/transport/client.ts +328 -0
  330. package/src/transport/discovery.ts +64 -0
  331. package/src/transport/endpoint.ts +32 -0
  332. package/src/transport/http.ts +129 -0
  333. package/src/transport/pool.ts +115 -0
  334. package/src/transport/relay-error.ts +46 -0
  335. package/src/transport/rpc.ts +56 -0
  336. package/src/transport/session.ts +154 -0
  337. package/src/transport/socket.ts +248 -0
  338. package/src/transport/subscription.ts +117 -0
@@ -0,0 +1,140 @@
1
+ import { ProtocolError } from '../errors.js';
2
+ import { decodeUtf8, encodeUtf8, validateUnicode } from './encoding.js';
3
+
4
+ export type JsonValue = null | boolean | number | string | JsonValue[] | JsonObject;
5
+ export interface JsonObject { [name: string]: JsonValue }
6
+ export const maximumJsonDepth = 16;
7
+
8
+ /** Checks the exact safe-integer domain used by protocol JSON. */
9
+ export function requireSafeInteger(value: unknown, minimum = -Number.MAX_SAFE_INTEGER): asserts value is number {
10
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || Object.is(value, -0) || value < minimum)
11
+ throw new ProtocolError('invalid_integer', 'Expected a safe integer in the allowed range.');
12
+ }
13
+
14
+ /** Parses protocol JSON before duplicate names or numeric spelling can be lost. */
15
+ export function parseJson(input: string | Uint8Array): JsonValue {
16
+ const source = typeof input === 'string' ? input : decodeUtf8(input);
17
+ validateUnicode(source);
18
+ let position = 0;
19
+ const fail = (message: string): never => { throw new ProtocolError('invalid_json', `${message} At offset ${position}.`); };
20
+ const whitespace = (): void => { while (/[\x20\t\r\n]/.test(source[position] ?? '') && position < source.length) position++; };
21
+
22
+ function string(): string {
23
+ const start = position++;
24
+ while (position < source.length) {
25
+ const character = source[position++]!;
26
+ if (character === '"') {
27
+ let result: string;
28
+ try { result = JSON.parse(source.slice(start, position)) as string; }
29
+ catch (cause) { throw new ProtocolError('invalid_json', 'Invalid JSON string.', { cause }); }
30
+ validateUnicode(result);
31
+ return result;
32
+ }
33
+ if (character === '\\') {
34
+ const escape = source[position++];
35
+ if (escape === 'u') {
36
+ if (!/^[0-9a-fA-F]{4}$/.test(source.slice(position, position + 4))) fail('Invalid Unicode escape.');
37
+ position += 4;
38
+ } else if (escape === undefined || !'"\\/bfnrt'.includes(escape)) fail('Invalid string escape.');
39
+ } else if (character.charCodeAt(0) < 32) fail('Unescaped control character.');
40
+ }
41
+ return fail('Unterminated string.');
42
+ }
43
+
44
+ function value(depth: number): JsonValue {
45
+ if (depth > maximumJsonDepth) fail('JSON depth exceeds 16.');
46
+ whitespace();
47
+ const character = source[position];
48
+ if (character === '"') return string();
49
+ if (character === '{') {
50
+ position++;
51
+ const result = Object.create(null) as JsonObject;
52
+ whitespace();
53
+ if (source[position] === '}') { position++; return result; }
54
+ while (true) {
55
+ whitespace();
56
+ if (source[position] !== '"') fail('Expected an object property name.');
57
+ const name = string();
58
+ if (Object.hasOwn(result, name)) fail('Duplicate property name.');
59
+ whitespace();
60
+ if (source[position++] !== ':') fail('Expected a colon.');
61
+ result[name] = value(depth + 1);
62
+ whitespace();
63
+ const separator = source[position++];
64
+ if (separator === '}') return result;
65
+ if (separator !== ',') fail('Expected an object separator.');
66
+ }
67
+ }
68
+ if (character === '[') {
69
+ position++;
70
+ const result: JsonValue[] = [];
71
+ whitespace();
72
+ if (source[position] === ']') { position++; return result; }
73
+ while (true) {
74
+ result.push(value(depth + 1));
75
+ whitespace();
76
+ const separator = source[position++];
77
+ if (separator === ']') return result;
78
+ if (separator !== ',') fail('Expected an array separator.');
79
+ }
80
+ }
81
+ for (const [literal, result] of [['true', true], ['false', false], ['null', null]] as const) {
82
+ if (source.startsWith(literal, position)) { position += literal.length; return result; }
83
+ }
84
+ const start = position;
85
+ while (position < source.length && /[0-9eE+.\-]/.test(source[position]!)) position++;
86
+ const token = source.slice(start, position);
87
+ if (!/^-?(0|[1-9][0-9]*)$/.test(token) || token === '-0') fail('Expected a shortest decimal integer.');
88
+ const result = Number(token);
89
+ requireSafeInteger(result);
90
+ return result;
91
+ }
92
+
93
+ const result = value(0);
94
+ whitespace();
95
+ if (position !== source.length) fail('Trailing non-whitespace data.');
96
+ return result;
97
+ }
98
+
99
+ /** Encodes only JSON data, preserving unknown properties and UTF-16 key ordering. */
100
+ export function canonicalJson(value: JsonValue): string {
101
+ function encode(input: JsonValue, depth: number): string {
102
+ if (depth > maximumJsonDepth) throw new ProtocolError('invalid_json', 'JSON depth exceeds 16.');
103
+ if (input === null) return 'null';
104
+ if (typeof input === 'string') { validateUnicode(input); return JSON.stringify(input); }
105
+ if (typeof input === 'boolean') return input ? 'true' : 'false';
106
+ if (typeof input === 'number') { requireSafeInteger(input); return String(input); }
107
+ if (Array.isArray(input)) {
108
+ const parts: string[] = [];
109
+ for (let index = 0; index < input.length; index++) {
110
+ if (!Object.hasOwn(input, index)) throw new ProtocolError('invalid_json', 'Sparse arrays are not JSON data.');
111
+ parts.push(encode(input[index]!, depth + 1));
112
+ }
113
+ return `[${parts.join(',')}]`;
114
+ }
115
+ if (typeof input !== 'object' || ![Object.prototype, null].includes(Object.getPrototypeOf(input) as object | null))
116
+ throw new ProtocolError('invalid_json', 'Expected plain JSON data.');
117
+ if (Object.getOwnPropertySymbols(input).length !== 0)
118
+ throw new ProtocolError('invalid_json', 'Symbol properties are not JSON data.');
119
+ const properties = Object.keys(input).sort().map(name => {
120
+ validateUnicode(name);
121
+ const descriptor = Object.getOwnPropertyDescriptor(input, name)!;
122
+ if (!('value' in descriptor)) throw new ProtocolError('invalid_json', 'JSON properties cannot be accessors.');
123
+ return `${JSON.stringify(name)}:${encode(descriptor.value as JsonValue, depth + 1)}`;
124
+ });
125
+ return `{${properties.join(',')}}`;
126
+ }
127
+ return encode(value, 0);
128
+ }
129
+
130
+ /** Encodes canonical protocol JSON as strict UTF-8. */
131
+ export function canonicalBytes(value: JsonValue): Uint8Array {
132
+ return encodeUtf8(canonicalJson(value));
133
+ }
134
+
135
+ /** Requires a JSON object; arrays and null do not qualify. */
136
+ export function requireObject(value: JsonValue): JsonObject {
137
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
138
+ throw new ProtocolError('invalid_object', 'Expected a JSON object.');
139
+ return value;
140
+ }
@@ -0,0 +1,13 @@
1
+ import { ProtocolError } from '../errors.js';
2
+ import { canonicalJson, type JsonObject, type JsonValue } from './json.js';
3
+
4
+ /** Complete business objects do not permit null, including extensions, unless their schema explicitly says otherwise. */
5
+ export function validateCompleteObject(value: JsonObject): void {
6
+ canonicalJson(value);
7
+ function visit(node: JsonValue): void {
8
+ if (node === null) throw new ProtocolError('invalid_null', 'Null is not permitted in this complete protocol object.');
9
+ if (Array.isArray(node)) for (const item of node) visit(item);
10
+ else if (typeof node === 'object') for (const item of Object.values(node)) visit(item);
11
+ }
12
+ visit(value);
13
+ }
@@ -0,0 +1,27 @@
1
+ const fallbackReasons = new WeakMap<AbortSignal, DOMException>();
2
+ const aborted = () => new DOMException('The operation was aborted.', 'AbortError');
3
+
4
+ /** Legacy caller signals cannot expose a reason their runtime already discarded. */
5
+ export function abortReason(signal: AbortSignal): unknown {
6
+ if (signal.reason !== undefined) return signal.reason;
7
+ let reason = fallbackReasons.get(signal);
8
+ if (!reason) { reason = aborted(); fallbackReasons.set(signal, reason); }
9
+ return reason;
10
+ }
11
+
12
+ /** Keep the platform's real signal and synchronous abort event, adding reason
13
+ * only to SDK-owned legacy signals. Globals and caller-owned signals are untouched. */
14
+ export function createAbortController(): AbortController {
15
+ const controller = new AbortController();
16
+ if ('reason' in controller.signal) return controller;
17
+ let reason: unknown;
18
+ Object.defineProperty(controller.signal, 'reason', { get: () => reason });
19
+ return {
20
+ signal: controller.signal,
21
+ abort(value?: unknown) {
22
+ if (controller.signal.aborted) return;
23
+ reason = value === undefined ? aborted() : value;
24
+ controller.abort();
25
+ },
26
+ };
27
+ }
@@ -0,0 +1,16 @@
1
+ import { throwIfAborted } from './clock.js';
2
+
3
+ /** Serializes state changes without poisoning subsequent operations after a failure. */
4
+ export class AsyncGate {
5
+ private tail: Promise<void> = Promise.resolve();
6
+
7
+ run<T>(operation: () => T | Promise<T>, signal?: AbortSignal): Promise<T> {
8
+ const result = this.tail.then(() => {
9
+ throwIfAborted(signal);
10
+ return operation();
11
+ });
12
+ // Callers receive the original rejection; only the queue tail is recovered.
13
+ this.tail = result.then(() => undefined, () => undefined);
14
+ return result;
15
+ }
16
+ }
@@ -0,0 +1,20 @@
1
+ import { throwIfAborted } from './clock.js';
2
+ import { abortReason } from './abort.js';
3
+
4
+ /** One consumer, coalesced wakeups. Pulses received during processing remain pending. */
5
+ export class AsyncPulse {
6
+ #pending = false;
7
+ #wake: (() => void) | undefined;
8
+ pulse(): void { this.#pending = true; this.#wake?.(); }
9
+ async wait(signal: AbortSignal): Promise<void> {
10
+ throwIfAborted(signal);
11
+ if (this.#wake) throw new Error('AsyncPulse supports only one consumer.');
12
+ if (!this.#pending) await new Promise<void>((resolve, reject) => {
13
+ const cleanup = (): void => { this.#wake = undefined; signal.removeEventListener('abort', aborted); };
14
+ const aborted = (): void => { cleanup(); reject(abortReason(signal)); };
15
+ this.#wake = () => { cleanup(); resolve(); };
16
+ signal.addEventListener('abort', aborted, { once: true });
17
+ });
18
+ throwIfAborted(signal); this.#pending = false;
19
+ }
20
+ }
@@ -0,0 +1,63 @@
1
+ import { abortReason, createAbortController } from './abort.js';
2
+
3
+ /** Wall time signs timestamps; monotonic time measures session and backoff lifetimes. */
4
+ export interface RuntimeClock {
5
+ nowSeconds(): number;
6
+ monotonicMilliseconds(): number;
7
+ delay(milliseconds: number, signal?: AbortSignal): Promise<void>;
8
+ }
9
+
10
+ export function throwIfAborted(signal?: AbortSignal): void {
11
+ if (signal?.aborted) throw abortReason(signal);
12
+ }
13
+
14
+ export const systemClock: RuntimeClock = {
15
+ nowSeconds: () => Math.floor(Date.now() / 1000),
16
+ monotonicMilliseconds: () => performance.now(),
17
+ delay(milliseconds, signal) {
18
+ throwIfAborted(signal);
19
+ if (!Number.isFinite(milliseconds) || milliseconds < 0 || milliseconds > 60_000)
20
+ throw new RangeError('An individual delay must be between zero and 60000 milliseconds.');
21
+ return new Promise<void>((resolve, reject) => {
22
+ const onAbort = (): void => {
23
+ clearTimeout(timer);
24
+ signal?.removeEventListener('abort', onAbort);
25
+ reject(abortReason(signal!));
26
+ };
27
+ const timer = setTimeout(() => { signal?.removeEventListener('abort', onAbort); resolve(); }, milliseconds);
28
+ signal?.addEventListener('abort', onAbort, { once: true });
29
+ });
30
+ },
31
+ };
32
+
33
+ /** A caller can abandon a shared operation without canceling other callers' work. */
34
+ export function awaitWithSignal<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {
35
+ throwIfAborted(signal);
36
+ if (!signal) return promise;
37
+ return new Promise<T>((resolve, reject) => {
38
+ const onAbort = (): void => { signal.removeEventListener('abort', onAbort); reject(abortReason(signal)); };
39
+ signal.addEventListener('abort', onAbort, { once: true });
40
+ promise.then(value => { signal.removeEventListener('abort', onAbort); resolve(value); }, error => {
41
+ signal.removeEventListener('abort', onAbort); reject(error);
42
+ });
43
+ });
44
+ }
45
+
46
+ /** Explicit cleanup avoids accumulating listeners or request deadline timers. */
47
+ export function abortScope(signals: readonly (AbortSignal | undefined)[], timeoutMilliseconds?: number): { signal: AbortSignal; dispose(): void } {
48
+ const controller = createAbortController();
49
+ const listeners = new Map<AbortSignal, () => void>();
50
+ for (const signal of signals) {
51
+ if (!signal || listeners.has(signal)) continue;
52
+ const onAbort = (): void => controller.abort(abortReason(signal));
53
+ if (signal.aborted) onAbort();
54
+ else { signal.addEventListener('abort', onAbort, { once: true }); listeners.set(signal, onAbort); }
55
+ }
56
+ const timer = timeoutMilliseconds === undefined ? undefined : setTimeout(() => {
57
+ controller.abort(new DOMException('The relay request timed out.', 'TimeoutError'));
58
+ }, timeoutMilliseconds);
59
+ return {
60
+ signal: controller.signal,
61
+ dispose() { clearTimeout(timer); for (const [signal, listener] of listeners) signal.removeEventListener('abort', listener); listeners.clear(); },
62
+ };
63
+ }
@@ -0,0 +1,31 @@
1
+ export type EventListener<T> = (value: T) => void | Promise<void>;
2
+
3
+ /** Typed subscriptions with awaited dispatch or nonblocking notifications. */
4
+ export class EventHub<Events extends object> {
5
+ readonly #listeners = new Map<keyof Events, Set<EventListener<never>>>();
6
+ on<K extends keyof Events>(event: K, listener: EventListener<Events[K]>): () => void {
7
+ let listeners = this.#listeners.get(event);
8
+ if (!listeners) { listeners = new Set(); this.#listeners.set(event, listeners); }
9
+ listeners.add(listener as EventListener<never>);
10
+ return () => { listeners.delete(listener as EventListener<never>); };
11
+ }
12
+ async emit<K extends keyof Events>(event: K, value: Events[K]): Promise<void> {
13
+ const failures: unknown[] = [];
14
+ for (const listener of [...(this.#listeners.get(event) ?? [])]) {
15
+ try { await listener(value as never); }
16
+ catch (error) { failures.push(error); }
17
+ }
18
+ if (failures.length !== 0) throw new AggregateError(failures, `A ${String(event)} event handler failed.`);
19
+ }
20
+ /** Invoke every subscriber immediately; application async work never delays other subscribers or the publisher. */
21
+ notify<K extends keyof Events>(event: K, value: Events[K], failed: (error: AggregateError) => void): void {
22
+ const failures: unknown[] = [];
23
+ const report = (errors: unknown[]): void => { if (errors.length) failed(new AggregateError(errors, `A ${String(event)} event handler failed.`)); };
24
+ for (const listener of [...(this.#listeners.get(event) ?? [])]) {
25
+ try { const result = listener(value as never); if (result) void Promise.resolve(result).catch(error => report([error])); }
26
+ catch (error) { failures.push(error); }
27
+ }
28
+ report(failures);
29
+ }
30
+ clear(): void { this.#listeners.clear(); }
31
+ }
@@ -0,0 +1,17 @@
1
+ import { toASCII } from 'tr46';
2
+
3
+ /** Normalize domain names before the platform parser, including Expo's ASCII-only URL. */
4
+ export function parseAbsoluteUrl(input: string): URL {
5
+ const match = /^([a-z][a-z0-9+.-]*:\/\/)([^/?#]*)([\s\S]*)$/i.exec(input);
6
+ if (!match) return new URL(input);
7
+ const authority = match[2]!; const at = authority.lastIndexOf('@');
8
+ const credentials = authority.slice(0, at + 1); const address = authority.slice(at + 1);
9
+ if (!address || address.startsWith('[')) return new URL(input); // IPv6 belongs to the URL parser.
10
+ const colon = address.lastIndexOf(':'); const port = colon === -1 ? '' : address.slice(colon);
11
+ const host = decodeURIComponent(colon === -1 ? address : address.slice(0, colon));
12
+ const ascii = toASCII(host, { checkBidi: true, checkJoiners: true, checkHyphens: false,
13
+ transitionalProcessing: false, useSTD3ASCIIRules: false, verifyDNSLength: false });
14
+ // Prevent escaped or mapped separators from changing authority boundaries.
15
+ if (!ascii || /[\u0000-\u0020\u007f#%/:<>?@[\\\]^|]/.test(ascii)) throw new TypeError('Invalid international domain name.');
16
+ return new URL(match[1]! + credentials + ascii + port + match[3]!);
17
+ }
@@ -0,0 +1,26 @@
1
+ import { ProtocolError } from '../errors.js';
2
+ import { validateRelayId } from '../identity/neo.js';
3
+ import { canonicalJson, requireObject, requireSafeInteger, type JsonObject } from '../protocol/json.js';
4
+ import type { MeshlineStore, RecordKey, StoreMutation } from './store.js';
5
+ import { updateStore } from './transaction.js';
6
+
7
+ export interface SignedRequest { readonly relayId: string; readonly revision: number; readonly document: JsonObject; readonly pending: boolean }
8
+ export const requestKey = (method: string): RecordKey => ({ collection: 'signed_requests', key: method });
9
+ export function readSignedRequest(value: JsonObject | undefined): SignedRequest | undefined {
10
+ if (value === undefined) return undefined;
11
+ if (typeof value.relayId !== 'string' || typeof value.pending !== 'boolean' || value.document === undefined)
12
+ throw new ProtocolError('invalid_storage', 'Invalid persisted signed request.');
13
+ validateRelayId(value.relayId); requireSafeInteger(value.revision, 0);
14
+ return { relayId: value.relayId, revision: value.revision, document: requireObject(value.document), pending: value.pending };
15
+ }
16
+ export function putSignedRequest(method: string, value: SignedRequest): StoreMutation {
17
+ return { ...requestKey(method), kind: 'put', value: { ...value } };
18
+ }
19
+ /** Never clear a newer request installed by a different component/process. No caller cancellation. */
20
+ export async function finishSignedRequest(store: MeshlineStore, method: string, expected: SignedRequest): Promise<void> {
21
+ await updateStore(store, [requestKey(method)], snapshot => {
22
+ const current = readSignedRequest(snapshot.sets[0]![0]?.value);
23
+ const matches = current?.pending && current.relayId === expected.relayId && canonicalJson(current.document) === canonicalJson(expected.document);
24
+ return { mutations: matches ? [putSignedRequest(method, { ...current, pending: false })] : [], result: undefined };
25
+ });
26
+ }
@@ -0,0 +1,90 @@
1
+ import { ProtocolError } from '../errors.js';
2
+ import { validateAccountId } from '../identity/neo.js';
3
+ import { NetworkContext } from '../protocol/context.js';
4
+ import { canonicalJson, parseJson, requireObject, requireSafeInteger, type JsonObject } from '../protocol/json.js';
5
+
6
+ /** A storage instance belongs to one network/account and its persisted local device. */
7
+ export interface StorageBinding { readonly context: string; readonly accountId: string }
8
+
9
+ export interface RecordKey { readonly collection: string; readonly key: string }
10
+ export interface StoredRecord extends RecordKey { readonly revision: number; readonly value: JsonObject }
11
+
12
+ /** Keys are canonical ASCII SDK identifiers or sortable indexes, never user-facing text. */
13
+ export interface RecordQuery {
14
+ readonly collection: string;
15
+ readonly key?: string;
16
+ readonly prefix?: string;
17
+ readonly reverse?: boolean;
18
+ }
19
+
20
+ export type StoreMutation = (RecordKey & { readonly kind: 'put'; readonly value: JsonObject })
21
+ | (RecordKey & { readonly kind: 'delete' });
22
+
23
+ export interface StoreSnapshot { readonly version: number; readonly sets: readonly (readonly StoredRecord[])[] }
24
+
25
+ /** Fixed at open time. An empty batch marks completion; dispose releases the snapshot. */
26
+ export interface QueryReader<T> {
27
+ readNext(count: number, signal?: AbortSignal): Promise<readonly T[]>;
28
+ dispose(): Promise<void>;
29
+ }
30
+
31
+ /**
32
+ * Atomic compare-and-commit storage. Computation, signing, and network I/O happen
33
+ * outside database transactions. A conflicting commit must be recomputed from a
34
+ * new read; it must never blindly replay an external business request.
35
+ */
36
+ export interface MeshlineStore {
37
+ migrate(signal?: AbortSignal): Promise<void>;
38
+ initialize(binding: StorageBinding, signal?: AbortSignal): Promise<void>;
39
+ read(queries: readonly RecordQuery[], signal?: AbortSignal): Promise<StoreSnapshot>;
40
+ commit(expectedVersion: number, mutations: readonly StoreMutation[], signal?: AbortSignal): Promise<number>;
41
+ openQuery(query: RecordQuery, signal?: AbortSignal): Promise<QueryReader<StoredRecord>>;
42
+ dispose(): Promise<void>;
43
+ }
44
+
45
+ export function bindingJson(binding: StorageBinding): string {
46
+ NetworkContext.parse(binding.context);
47
+ validateAccountId(binding.accountId);
48
+ return canonicalJson({ context: binding.context, accountId: binding.accountId });
49
+ }
50
+
51
+ export function validateRecordKey(value: RecordKey): void {
52
+ if (typeof value.collection !== 'string' || !/^[a-z][a-z0-9_.-]{0,63}$/.test(value.collection) || /[^a-z0-9_.-]/.test(value.collection))
53
+ throw new ProtocolError('invalid_storage_key', 'Collection must be a lowercase SDK identifier.');
54
+ if (typeof value.key !== 'string' || value.key.length > 4096 || /[^\x20-\x7e]/.test(value.key))
55
+ throw new ProtocolError('invalid_storage_key', 'Record keys must be at most 4096 printable ASCII characters.');
56
+ }
57
+
58
+ export function validateRecordQuery(query: RecordQuery): void {
59
+ if (query.key !== undefined && query.prefix !== undefined)
60
+ throw new ProtocolError('invalid_storage_query', 'Specify either key or prefix.');
61
+ validateRecordKey({ collection: query.collection, key: query.key ?? query.prefix ?? '' });
62
+ if (query.reverse !== undefined && typeof query.reverse !== 'boolean') throw new ProtocolError('invalid_storage_query', 'reverse must be boolean.');
63
+ }
64
+
65
+ /** Copies and validates a commit before an adapter opens its write transaction. */
66
+ export function prepareCommit(expectedVersion: number, mutations: readonly StoreMutation[]): readonly StoreMutation[] {
67
+ requireSafeInteger(expectedVersion, 0);
68
+ if (expectedVersion === Number.MAX_SAFE_INTEGER) throw new ProtocolError('revision_exhausted', 'Storage revision is exhausted.');
69
+ if (mutations.length === 0) throw new ProtocolError('empty_commit', 'A commit requires at least one mutation.');
70
+ const keys = new Set<string>();
71
+ return mutations.map(mutation => {
72
+ validateRecordKey(mutation);
73
+ const id = mutation.collection + '\0' + mutation.key;
74
+ if (keys.has(id)) throw new ProtocolError('duplicate_mutation', 'A record may be changed only once per commit.');
75
+ keys.add(id);
76
+ if (mutation.kind === 'put') return { ...mutation, value: requireObject(parseJson(canonicalJson(mutation.value))) };
77
+ if (mutation.kind === 'delete') return { ...mutation };
78
+ throw new ProtocolError('invalid_mutation', 'Unknown store mutation.');
79
+ });
80
+ }
81
+
82
+ export function requireBatchCount(count: number): void { requireSafeInteger(count, 1); }
83
+
84
+ /** Preserves fixed snapshot semantics while projecting stored rows into domain results. */
85
+ export function mapQueryReader<T, U>(reader: QueryReader<T>, project: (value: T) => U): QueryReader<U> {
86
+ return {
87
+ async readNext(count, signal) { return (await reader.readNext(count, signal)).map(project); },
88
+ dispose() { return reader.dispose(); },
89
+ };
90
+ }
@@ -0,0 +1,17 @@
1
+ import { StateConflictError } from '../errors.js';
2
+ import { throwIfAborted } from '../runtime/clock.js';
3
+ import type { MeshlineStore, RecordQuery, StoreMutation, StoreSnapshot } from './store.js';
4
+
5
+ /** Recomputes local changes on CAS contention. The planner must be synchronous and side-effect free. */
6
+ export async function updateStore<T>(store: MeshlineStore, queries: readonly RecordQuery[],
7
+ plan: (snapshot: StoreSnapshot) => { readonly mutations: readonly StoreMutation[]; readonly result: T }, signal?: AbortSignal): Promise<T> {
8
+ for (let attempt = 0; attempt < 16; attempt++) {
9
+ throwIfAborted(signal);
10
+ const snapshot = await store.read(queries, signal);
11
+ const update = plan(snapshot);
12
+ if (!update.mutations.length) return update.result;
13
+ try { await store.commit(snapshot.version, update.mutations, signal); return update.result; }
14
+ catch (error) { if (!(error instanceof StateConflictError) || attempt === 15) throw error; }
15
+ }
16
+ throw new StateConflictError('Storage remained busy.');
17
+ }