@agentshouse/mdruntime 0.1.0-cd3486bd400ed → 0.1.0-cd37f80239af0

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 (201) hide show
  1. package/README.md +21 -9
  2. package/dist/adapter.d.ts +149 -46
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/attached-model.d.ts +5 -1
  5. package/dist/attached-model.d.ts.map +1 -1
  6. package/dist/attached-model.js +38 -4
  7. package/dist/attempt.d.ts.map +1 -1
  8. package/dist/attempt.js +14 -1
  9. package/dist/binary.d.ts +18 -0
  10. package/dist/binary.d.ts.map +1 -0
  11. package/dist/binary.js +8 -0
  12. package/dist/bootstrap.d.ts +27 -11
  13. package/dist/bootstrap.d.ts.map +1 -1
  14. package/dist/bootstrap.js +184 -84
  15. package/dist/chain.d.ts +11 -0
  16. package/dist/chain.d.ts.map +1 -0
  17. package/dist/chain.js +64 -0
  18. package/dist/change-set.d.ts +9 -14
  19. package/dist/change-set.d.ts.map +1 -1
  20. package/dist/change-set.js +11 -62
  21. package/dist/change.d.ts +41 -0
  22. package/dist/change.d.ts.map +1 -1
  23. package/dist/change.js +34 -0
  24. package/dist/client.d.ts +31 -13
  25. package/dist/client.d.ts.map +1 -1
  26. package/dist/client.js +103 -7
  27. package/dist/collection.d.ts +10 -7
  28. package/dist/collection.d.ts.map +1 -1
  29. package/dist/collection.js +38 -79
  30. package/dist/compile.d.ts +16 -0
  31. package/dist/compile.d.ts.map +1 -0
  32. package/dist/compile.js +786 -0
  33. package/dist/connection.d.ts +50 -16
  34. package/dist/connection.d.ts.map +1 -1
  35. package/dist/connection.js +3786 -931
  36. package/dist/conversation.d.ts +37 -16
  37. package/dist/conversation.d.ts.map +1 -1
  38. package/dist/conversation.js +700 -257
  39. package/dist/digest.d.ts +7 -0
  40. package/dist/digest.d.ts.map +1 -0
  41. package/dist/digest.js +113 -0
  42. package/dist/durability.d.ts +4 -1
  43. package/dist/durability.d.ts.map +1 -1
  44. package/dist/durability.js +5 -0
  45. package/dist/entry.d.ts +20 -0
  46. package/dist/entry.d.ts.map +1 -0
  47. package/dist/entry.js +151 -0
  48. package/dist/fence.d.ts +6 -0
  49. package/dist/fence.d.ts.map +1 -0
  50. package/dist/fence.js +10 -0
  51. package/dist/file.d.ts +2 -19
  52. package/dist/file.d.ts.map +1 -1
  53. package/dist/file.js +7 -122
  54. package/dist/fulltext.d.ts +27 -0
  55. package/dist/fulltext.d.ts.map +1 -0
  56. package/dist/fulltext.js +370 -0
  57. package/dist/host.d.ts +13 -3
  58. package/dist/host.d.ts.map +1 -1
  59. package/dist/host.js +130 -17
  60. package/dist/identity.d.ts +2 -0
  61. package/dist/identity.d.ts.map +1 -1
  62. package/dist/identity.js +6 -0
  63. package/dist/index.d.ts +17 -9
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +7 -0
  66. package/dist/live.d.ts +22 -0
  67. package/dist/live.d.ts.map +1 -0
  68. package/dist/live.js +123 -0
  69. package/dist/media.d.ts +8 -0
  70. package/dist/media.d.ts.map +1 -0
  71. package/dist/media.js +218 -0
  72. package/dist/memory.d.ts +17 -7
  73. package/dist/memory.d.ts.map +1 -1
  74. package/dist/memory.js +231 -91
  75. package/dist/messages.d.ts +28 -0
  76. package/dist/messages.d.ts.map +1 -0
  77. package/dist/messages.js +90 -0
  78. package/dist/model.d.ts +9 -2
  79. package/dist/model.d.ts.map +1 -1
  80. package/dist/model.js +8 -2
  81. package/dist/name.d.ts +4 -0
  82. package/dist/name.d.ts.map +1 -0
  83. package/dist/name.js +122 -0
  84. package/dist/observation.d.ts +20 -2
  85. package/dist/observation.d.ts.map +1 -1
  86. package/dist/observation.js +113 -12
  87. package/dist/opened.d.ts +22 -0
  88. package/dist/opened.d.ts.map +1 -0
  89. package/dist/opened.js +107 -0
  90. package/dist/outcome.d.ts +52 -4
  91. package/dist/outcome.d.ts.map +1 -1
  92. package/dist/outcome.js +46 -0
  93. package/dist/overlay.d.ts +15 -5
  94. package/dist/overlay.d.ts.map +1 -1
  95. package/dist/overlay.js +129 -177
  96. package/dist/paths.d.ts +1 -0
  97. package/dist/paths.d.ts.map +1 -1
  98. package/dist/paths.js +4 -0
  99. package/dist/query.d.ts +68 -4
  100. package/dist/query.d.ts.map +1 -1
  101. package/dist/query.js +48 -7
  102. package/dist/reach.d.ts +18 -4
  103. package/dist/reach.d.ts.map +1 -1
  104. package/dist/reach.js +224 -55
  105. package/dist/react.d.ts +9 -6
  106. package/dist/react.d.ts.map +1 -1
  107. package/dist/react.js +7 -17
  108. package/dist/readiness.d.ts +52 -0
  109. package/dist/readiness.d.ts.map +1 -0
  110. package/dist/readiness.js +27 -0
  111. package/dist/recovery.d.ts +26 -0
  112. package/dist/recovery.d.ts.map +1 -0
  113. package/dist/recovery.js +224 -0
  114. package/dist/refresh.d.ts +17 -14
  115. package/dist/refresh.d.ts.map +1 -1
  116. package/dist/refresh.js +136 -56
  117. package/dist/replica.d.ts +58 -33
  118. package/dist/replica.d.ts.map +1 -1
  119. package/dist/replica.js +307 -159
  120. package/dist/room-draft.d.ts +51 -4
  121. package/dist/room-draft.d.ts.map +1 -1
  122. package/dist/room-draft.js +210 -18
  123. package/dist/rows.d.ts +37 -35
  124. package/dist/rows.d.ts.map +1 -1
  125. package/dist/rows.js +173 -102
  126. package/dist/save.d.ts +70 -3
  127. package/dist/save.d.ts.map +1 -1
  128. package/dist/save.js +805 -147
  129. package/dist/search.d.ts +39 -28
  130. package/dist/search.d.ts.map +1 -1
  131. package/dist/search.js +37 -105
  132. package/dist/seed.d.ts +16 -0
  133. package/dist/seed.d.ts.map +1 -0
  134. package/dist/seed.js +75 -0
  135. package/dist/shared.d.ts +15 -0
  136. package/dist/shared.d.ts.map +1 -0
  137. package/dist/shared.js +421 -0
  138. package/dist/status.d.ts +5 -0
  139. package/dist/status.d.ts.map +1 -1
  140. package/dist/status.js +3 -1
  141. package/dist/store.d.ts +107 -80
  142. package/dist/store.d.ts.map +1 -1
  143. package/dist/store.js +325 -461
  144. package/dist/stored.d.ts +130 -0
  145. package/dist/stored.d.ts.map +1 -0
  146. package/dist/stored.js +560 -0
  147. package/dist/tables.d.ts +325 -0
  148. package/dist/tables.d.ts.map +1 -0
  149. package/dist/tables.js +578 -0
  150. package/dist/transfer.d.ts +0 -5
  151. package/dist/transfer.d.ts.map +1 -1
  152. package/dist/turn.d.ts +9 -0
  153. package/dist/turn.d.ts.map +1 -0
  154. package/dist/turn.js +75 -0
  155. package/dist/validation.d.ts +29 -0
  156. package/dist/validation.d.ts.map +1 -0
  157. package/dist/validation.js +101 -0
  158. package/dist/windows.d.ts +22 -0
  159. package/dist/windows.d.ts.map +1 -0
  160. package/dist/windows.js +89 -0
  161. package/dist/wire.d.ts +165 -30
  162. package/dist/wire.d.ts.map +1 -1
  163. package/dist/wire.js +56 -29
  164. package/dist/words.d.ts +19 -0
  165. package/dist/words.d.ts.map +1 -0
  166. package/dist/words.js +156 -0
  167. package/package.json +26 -15
  168. package/AGENTS.md +0 -290
  169. package/dist/attachment.d.ts +0 -3
  170. package/dist/attachment.d.ts.map +0 -1
  171. package/dist/attachment.js +0 -8
  172. package/dist/chat/acts.d.ts +0 -34
  173. package/dist/chat/acts.d.ts.map +0 -1
  174. package/dist/chat/acts.js +0 -36
  175. package/dist/chat/messages.d.ts +0 -21
  176. package/dist/chat/messages.d.ts.map +0 -1
  177. package/dist/chat/messages.js +0 -151
  178. package/dist/chat/overlay.d.ts +0 -31
  179. package/dist/chat/overlay.d.ts.map +0 -1
  180. package/dist/chat/overlay.js +0 -97
  181. package/dist/chat/parts.d.ts +0 -57
  182. package/dist/chat/parts.d.ts.map +0 -1
  183. package/dist/chat/parts.js +0 -119
  184. package/dist/chat/process.d.ts +0 -20
  185. package/dist/chat/process.d.ts.map +0 -1
  186. package/dist/chat/process.js +0 -32
  187. package/dist/chat/provider.d.ts +0 -57
  188. package/dist/chat/provider.d.ts.map +0 -1
  189. package/dist/chat/provider.js +0 -637
  190. package/dist/chat/questions.d.ts +0 -31
  191. package/dist/chat/questions.d.ts.map +0 -1
  192. package/dist/chat/questions.js +0 -111
  193. package/dist/chat/read.d.ts +0 -5
  194. package/dist/chat/read.d.ts.map +0 -1
  195. package/dist/chat/read.js +0 -13
  196. package/dist/chat.d.ts +0 -7
  197. package/dist/chat.d.ts.map +0 -1
  198. package/dist/chat.js +0 -2
  199. package/dist/read-side.d.ts +0 -18
  200. package/dist/read-side.d.ts.map +0 -1
  201. package/dist/read-side.js +0 -433
package/dist/memory.js CHANGED
@@ -1,7 +1,11 @@
1
1
  import { sameChange } from './overlay.js';
2
+ const ATTACHMENTS = 'attachments';
3
+ function lastName(path) {
4
+ return path.slice(path.lastIndexOf('/') + 1);
5
+ }
2
6
  const FIXTURE_RETENTION = {
3
7
  history: { kind: 'duration', seconds: 1 },
4
- outcome: 1,
8
+ outcome: 604_800,
5
9
  refreshHorizon: 1,
6
10
  };
7
11
  function declaredRetention(value) {
@@ -15,12 +19,7 @@ function declaredRetention(value) {
15
19
  }
16
20
  const MODEL_ABSENT = { kind: 'model-unavailable', reason: 'not-readable' };
17
21
  function modelsOf(room) {
18
- return {
19
- collection: room.model ?? MODEL_ABSENT,
20
- ...(room.typedSelection === undefined
21
- ? {}
22
- : { selection: room.selectionModel ?? MODEL_ABSENT }),
23
- };
22
+ return { collection: room.model ?? MODEL_ABSENT };
24
23
  }
25
24
  function changedModels(room, from) {
26
25
  const now = modelsOf(room);
@@ -53,12 +52,16 @@ export function createMemoryAdapter(options = {}) {
53
52
  const listeners = new Set();
54
53
  const modeObservers = new Set();
55
54
  const scripted = new Map();
56
- const unsealed = new Map();
57
- const sealed = new Set();
55
+ const sentTurns = new Map();
58
56
  const historyPageSize = options.historyPageSize;
59
- let held = [];
57
+ const attached = new Map();
58
+ const linked = new WeakMap();
59
+ const carried = new Map();
60
60
  let expiring = [];
61
+ let openings = 0;
62
+ const declaredRooms = new Set();
61
63
  let replicated = [];
64
+ let heldCursor = null;
62
65
  let issued = 0;
63
66
  function scriptConversations(entries) {
64
67
  scripted.clear();
@@ -86,6 +89,19 @@ export function createMemoryAdapter(options = {}) {
86
89
  const wanted = ordinals.get(`${locator}\u0000${target}`);
87
90
  return held !== undefined && wanted !== undefined && held >= wanted;
88
91
  }
92
+ function heldBlob(room, path) {
93
+ return attached.get(`${room.locator}\u0000${path}`) ?? room.blobs?.[path];
94
+ }
95
+ function freePath(room, name) {
96
+ const dot = name.lastIndexOf('.');
97
+ const stem = dot > 0 ? name.slice(0, dot) : name;
98
+ const extension = dot > 0 ? name.slice(dot) : '';
99
+ for (let count = 1;; count += 1) {
100
+ const path = `${ATTACHMENTS}/${count === 1 ? stem : `${stem}-${count}`}${extension}`;
101
+ if (heldBlob(room, path) === undefined && room.documents?.[path] === undefined)
102
+ return path;
103
+ }
104
+ }
89
105
  function retain(entries) {
90
106
  for (const room of entries) {
91
107
  note(room.locator, room);
@@ -114,30 +130,44 @@ export function createMemoryAdapter(options = {}) {
114
130
  const size = pageSize ?? paths.length;
115
131
  const taken = paths.slice(from, from + size);
116
132
  const next = from + taken.length;
117
- const targets = opening ? (room.binaryTargets ?? []) : [];
133
+ const targets = opening ? Object.keys(room.binaryFiles ?? {}) : [];
118
134
  return {
119
135
  kind: 'bootstrap',
120
136
  roomVersion: room.roomVersion,
121
137
  ...(opening ? { models: modelsOf(room) } : {}),
122
138
  documents: named(room.documents ?? {}, taken),
123
- revisions: named(room.revisions ?? {}, taken),
139
+ revisions: named(room.revisions ?? {}, [...taken, ...targets]),
140
+ ...(room.fileIds === undefined
141
+ ? {}
142
+ : { fileIds: named(room.fileIds, [...taken, ...targets]) }),
124
143
  changes: named(room.changes ?? {}, [...taken, ...targets]),
125
- binaryTargets: targets,
126
- ...(opening && room.attachments !== undefined ? { attachments: room.attachments } : {}),
144
+ binaryFiles: named(room.binaryFiles ?? {}, targets),
145
+ ...(opening && room.folders !== undefined ? { folders: room.folders } : {}),
127
146
  ...(next < paths.length ? { pageToken: taken.at(-1) } : {}),
128
147
  };
129
148
  }
130
- function advancement(room, from, at) {
149
+ function foldersMoved(room, from) {
150
+ const now = new Set(room.folders ?? []);
151
+ const before = new Set(from.folders ?? []);
152
+ const folders = [...now].filter((folder) => !before.has(folder)).sort();
153
+ const folderRemovals = [...before].filter((folder) => !now.has(folder)).sort();
154
+ return {
155
+ ...(folders.length === 0 ? {} : { folders }),
156
+ ...(folderRemovals.length === 0 ? {} : { folderRemovals }),
157
+ };
158
+ }
159
+ function advancement(room, from, at, whole = false) {
131
160
  const current = room.documents ?? {};
132
161
  const previous = from.documents ?? {};
133
- const currentTargets = new Set(room.binaryTargets ?? []);
134
- const previousTargets = new Set(from.binaryTargets ?? []);
162
+ const currentTargets = new Set(Object.keys(room.binaryFiles ?? {}));
163
+ const previousTargets = new Set(Object.keys(from.binaryFiles ?? {}));
135
164
  const headMoved = (path) => room.revisions?.[path] !== from.revisions?.[path] ||
165
+ room.fileIds?.[path] !== from.fileIds?.[path] ||
136
166
  !sameChange(room.changes?.[path], from.changes?.[path]);
137
167
  const changed = Object.keys(current)
138
168
  .filter((path) => current[path] !== previous[path] || headMoved(path))
139
169
  .sort();
140
- const size = pageSize ?? changed.length;
170
+ const size = whole ? changed.length : (pageSize ?? changed.length);
141
171
  const taken = changed.slice(at, at + size);
142
172
  const next = at + taken.length;
143
173
  const opening = at === 0;
@@ -147,27 +177,48 @@ export function createMemoryAdapter(options = {}) {
147
177
  const targets = opening
148
178
  ? [...currentTargets].filter((target) => !previousTargets.has(target) || headMoved(target))
149
179
  : [];
150
- const attachments = room.attachments ?? [];
151
- const declared = opening && JSON.stringify(attachments) !== JSON.stringify(from.attachments ?? []);
152
180
  const models = opening ? changedModels(room, from) : undefined;
153
181
  return {
154
182
  kind: 'net-advancement',
155
183
  roomVersion: room.roomVersion,
156
184
  ...(models === undefined ? {} : { models }),
157
185
  documents: named(current, taken),
158
- revisions: named(room.revisions ?? {}, taken),
186
+ revisions: named(room.revisions ?? {}, [...taken, ...targets]),
187
+ ...(room.fileIds === undefined
188
+ ? {}
189
+ : { fileIds: named(room.fileIds, [...taken, ...targets]) }),
159
190
  changes: named(room.changes ?? {}, [...taken, ...targets]),
160
- binaryTargets: targets,
191
+ binaryFiles: named(room.binaryFiles ?? {}, targets),
192
+ ...(opening ? foldersMoved(room, from) : {}),
161
193
  removals: opening
162
194
  ? [
163
195
  ...Object.keys(previous).filter((path) => !(path in current) && !currentTargets.has(path)),
164
196
  ...[...previousTargets].filter((target) => !currentTargets.has(target) && !(target in current)),
165
197
  ].sort()
166
198
  : [],
167
- ...(declared ? { attachments } : {}),
168
199
  ...(pageToken === undefined ? {} : { pageToken }),
169
200
  };
170
201
  }
202
+ function carry(locator) {
203
+ const at = carried.get(locator);
204
+ const current = rooms.find((entry) => entry.locator === locator);
205
+ if (at === undefined || current === undefined || current.roomVersion === at)
206
+ return;
207
+ const from = served.get(locator)?.get(at);
208
+ carried.set(locator, current.roomVersion);
209
+ if (from === undefined)
210
+ return;
211
+ const page = advancement(current, from, 0, true);
212
+ for (const observer of observers) {
213
+ observer({
214
+ kind: 'room-change',
215
+ locator,
216
+ from: at,
217
+ roomVersion: current.roomVersion,
218
+ read: async () => page,
219
+ });
220
+ }
221
+ }
171
222
  function pageOf(conversation, after) {
172
223
  const messages = conversation.messages ?? [];
173
224
  const first = typeof after === 'string' ? messages.findIndex((message) => message.position > after) : 0;
@@ -182,16 +233,13 @@ export function createMemoryAdapter(options = {}) {
182
233
  : {}),
183
234
  };
184
235
  }
185
- function historiesOf(input) {
186
- const asked = input?.conversations;
187
- if (!Array.isArray(asked))
188
- return { kind: 'rejected', code: 'invalid_request', fields: {}, retryable: false };
236
+ function historiesOf(request) {
189
237
  const histories = [];
190
- for (const entry of asked) {
191
- const conversation = typeof entry.conversation === 'string' ? scripted.get(entry.conversation) : undefined;
238
+ for (const entry of request.conversations) {
239
+ const conversation = scripted.get(entry.conversation);
192
240
  if (conversation === undefined)
193
241
  continue;
194
- const outcome = conversation.outcomes?.['conversation.history'];
242
+ const outcome = conversation.outcomes?.history;
195
243
  if (outcome !== undefined)
196
244
  return outcome;
197
245
  histories.push(pageOf(conversation, entry.after));
@@ -202,15 +250,13 @@ export function createMemoryAdapter(options = {}) {
202
250
  function conversationAction(request) {
203
251
  const name = request.name;
204
252
  const input = (request.input ?? {});
205
- if (name === 'conversation.history')
206
- return historiesOf(request.input);
207
- if (name === 'conversation.create') {
253
+ if (name === 'conversations/create') {
208
254
  issued += 1;
209
255
  const conversation = `conversation-${issued}`;
210
256
  scripted.set(conversation, { conversation });
211
- return { kind: 'action', result: { conversation } };
257
+ return { kind: 'action', result: { conversation_id: conversation } };
212
258
  }
213
- const named = typeof input.conversation === 'string' ? input.conversation : '';
259
+ const named = typeof input.conversation_id === 'string' ? input.conversation_id : '';
214
260
  const conversation = scripted.get(named);
215
261
  if (conversation === undefined) {
216
262
  return {
@@ -223,28 +269,28 @@ export function createMemoryAdapter(options = {}) {
223
269
  const outcome = conversation.outcomes?.[name];
224
270
  if (outcome !== undefined)
225
271
  return outcome;
226
- if (name === 'conversation.send') {
272
+ if (name === 'conversations/send') {
273
+ const operation = typeof input.operation_id === 'string' ? input.operation_id : null;
274
+ const answered = operation === null ? undefined : sentTurns.get(operation);
275
+ if (answered !== undefined)
276
+ return { kind: 'action', result: { message_id: answered } };
227
277
  issued += 1;
228
278
  const turn = `turn-${issued}`;
229
- unsealed.set(turn, named);
230
- return { kind: 'action', result: { turn } };
279
+ if (operation !== null)
280
+ sentTurns.set(operation, turn);
281
+ return { kind: 'action', result: { message_id: turn } };
231
282
  }
232
- if (name === 'conversation.cancel') {
233
- const turn = typeof input.turn === 'string' ? input.turn : '';
234
- if (sealed.has(turn) || !unsealed.has(turn)) {
235
- return { kind: 'rejected', code: 'turn-sealed', fields: { turn }, retryable: false };
236
- }
237
- unsealed.delete(turn);
238
- return { kind: 'action', result: { turn, cancelled: true } };
239
- }
240
- if (name === 'conversation.delete')
283
+ if (name === 'conversations/delete')
241
284
  scripted.delete(named);
242
- return { kind: 'action', result: { conversation: named } };
285
+ return { kind: 'action', result: { conversation_id: named } };
243
286
  }
244
287
  function announceList() {
245
- const entries = [...scripted.values()].flatMap(({ conversation, listed }) => listed === undefined ? [] : [{ conversation, ...listed }]);
246
- for (const listener of listeners)
247
- listener({ kind: 'list', entries });
288
+ const entries = [...scripted.values()].flatMap(({ conversation, listed, messages = [] }) => listed === undefined
289
+ ? []
290
+ : [{ conversation, ...listed, newest: messages.at(-1)?.position ?? null }]);
291
+ for (const listener of listeners) {
292
+ listener({ kind: 'list', entries, whole: true, more: false, cursor: null });
293
+ }
248
294
  }
249
295
  function discovery() {
250
296
  return {
@@ -256,7 +302,41 @@ export function createMemoryAdapter(options = {}) {
256
302
  ...(options.member === undefined ? {} : { member: options.member }),
257
303
  };
258
304
  }
305
+ function discovered() {
306
+ return resolveScript(scripts.discover, discovery, undefined).then((answer) => {
307
+ if (answer.kind === 'discovery')
308
+ for (const room of answer.rooms)
309
+ note(room.locator, room);
310
+ return answer;
311
+ });
312
+ }
313
+ function opened(notify) {
314
+ const opening = ++openings;
315
+ notify({ kind: 'opened' });
316
+ void discovered()
317
+ .catch(() => ({ kind: 'transport-uncertainty' }))
318
+ .then((answer) => {
319
+ if (opening !== openings)
320
+ return;
321
+ if (answer.kind !== 'discovery') {
322
+ openings += 1;
323
+ carried.clear();
324
+ const revoked = answer.kind === 'revoked-authorization';
325
+ notify({ kind: 'closed', revoked, lost: !revoked });
326
+ return;
327
+ }
328
+ declaredRooms.clear();
329
+ for (const room of answer.rooms)
330
+ declaredRooms.add(room.locator);
331
+ notify({ kind: 'discovered', discovery: answer });
332
+ });
333
+ }
334
+ function emitAll(event) {
335
+ for (const observer of observers)
336
+ observer(event);
337
+ }
259
338
  return {
339
+ ...(options.roomTypes === undefined ? {} : { roomTypes: options.roomTypes }),
260
340
  setRooms(next) {
261
341
  rooms = [...next];
262
342
  retain(rooms);
@@ -265,26 +345,37 @@ export function createMemoryAdapter(options = {}) {
265
345
  scriptConversations(next);
266
346
  },
267
347
  open() {
348
+ carried.clear();
268
349
  announceList();
269
- for (const observer of observers)
270
- observer({ kind: 'opened' });
350
+ opened(emitAll);
271
351
  },
272
- close(revoked = false) {
273
- for (const observer of observers)
274
- observer({ kind: 'closed', revoked });
352
+ close({ revoked = false, lost = !revoked } = {}) {
353
+ openings += 1;
354
+ carried.clear();
355
+ emitAll({ kind: 'closed', revoked, lost });
275
356
  },
276
357
  expire(locators) {
277
358
  expiring = [...locators];
278
359
  },
279
- notifyRoomVersion({ locator, roomVersion }) {
280
- for (const observer of observers)
281
- observer({ kind: 'room-version', locator, roomVersion });
360
+ notifyRoomVersion({ locator }) {
361
+ carry(locator);
362
+ },
363
+ notifyAuthorityChanged(locators) {
364
+ const named = new Set(locators ?? [...declaredRooms, ...rooms.map((room) => room.locator)]);
365
+ const listed = rooms.filter((room) => named.has(room.locator));
366
+ const left = [...named].filter((locator) => !listed.some((room) => room.locator === locator));
367
+ for (const room of listed)
368
+ declaredRooms.add(room.locator);
369
+ for (const locator of left)
370
+ declaredRooms.delete(locator);
371
+ emitAll({
372
+ kind: 'authority-changed',
373
+ rooms: listed.map(declared),
374
+ left,
375
+ ...(options.member === undefined ? {} : { member: options.member }),
376
+ });
282
377
  },
283
378
  notifyConversation(event) {
284
- if (event.kind === 'accepted-message') {
285
- sealed.add(event.message.turn);
286
- unsealed.delete(event.message.turn);
287
- }
288
379
  for (const listener of listeners)
289
380
  listener(event);
290
381
  },
@@ -293,21 +384,31 @@ export function createMemoryAdapter(options = {}) {
293
384
  observer(mode);
294
385
  },
295
386
  heldRooms() {
296
- return held;
387
+ return [...carried.keys()].sort();
297
388
  },
298
389
  replicatedConversations() {
299
390
  return replicated;
300
391
  },
392
+ heldConversationCursor() {
393
+ return heldCursor;
394
+ },
301
395
  conversations: {
302
396
  replicate(next) {
303
397
  replicated = [...next];
304
398
  },
399
+ hold(cursor) {
400
+ heldCursor = cursor;
401
+ },
305
402
  observe(notify) {
306
403
  listeners.add(notify);
307
404
  return () => {
308
405
  listeners.delete(notify);
309
406
  };
310
407
  },
408
+ history: (request) => Promise.resolve(historiesOf(request)),
409
+ ...(options.agents === undefined
410
+ ? {}
411
+ : { agents: () => Promise.resolve(structuredClone(options.agents)) }),
311
412
  },
312
413
  serviceMode: {
313
414
  observe(notify) {
@@ -319,29 +420,29 @@ export function createMemoryAdapter(options = {}) {
319
420
  },
320
421
  roomStream: {
321
422
  hold(asked) {
322
- held = asked.map((room) => room.locator);
323
- const expired = expiring.filter((locator) => held.includes(locator));
324
- expiring = [];
325
- const moved = asked.flatMap((room) => {
423
+ const named = asked.map((room) => room.locator);
424
+ const unknown = asked.flatMap((room) => {
326
425
  const current = rooms.find((entry) => entry.locator === room.locator);
327
426
  return current === undefined ||
328
- expired.includes(room.locator) ||
329
- current.roomVersion === room.roomVersion
427
+ current.roomVersion === room.roomVersion ||
428
+ served.get(room.locator)?.has(room.roomVersion) === true
330
429
  ? []
331
- : [{ locator: room.locator, roomVersion: current.roomVersion }];
430
+ : [room.locator];
332
431
  });
432
+ const expired = [
433
+ ...new Set([...expiring.filter((locator) => named.includes(locator)), ...unknown]),
434
+ ];
435
+ expiring = expiring.filter((locator) => !named.includes(locator));
333
436
  queueMicrotask(() => {
334
- for (const observer of observers) {
335
- for (const room of moved)
336
- observer({ kind: 'room-version', ...room });
337
- observer({ kind: 'opening', expired });
437
+ for (const room of asked) {
438
+ if (expired.includes(room.locator))
439
+ carried.delete(room.locator);
440
+ else {
441
+ carried.set(room.locator, room.roomVersion);
442
+ carry(room.locator);
443
+ }
338
444
  }
339
- });
340
- },
341
- reopen() {
342
- queueMicrotask(() => {
343
- for (const observer of observers)
344
- observer({ kind: 'closed', revoked: false });
445
+ emitAll({ kind: 'caught-up', expired });
345
446
  });
346
447
  },
347
448
  observe(notify) {
@@ -350,11 +451,14 @@ export function createMemoryAdapter(options = {}) {
350
451
  if (!observers.has(notify))
351
452
  return;
352
453
  if (options.unopened === true) {
353
- notify({ kind: 'closed', revoked: false });
454
+ notify({ kind: 'closed', revoked: false, lost: true });
354
455
  return;
355
456
  }
356
457
  announceList();
357
- notify({ kind: 'opened' });
458
+ opened((event) => {
459
+ if (observers.has(notify))
460
+ notify(event);
461
+ });
358
462
  });
359
463
  return () => {
360
464
  observers.delete(notify);
@@ -371,12 +475,7 @@ export function createMemoryAdapter(options = {}) {
371
475
  return resolveScript(scripts.authorize, () => ({}), undefined);
372
476
  },
373
477
  async discover(_authorization) {
374
- return resolveScript(scripts.discover, discovery, undefined).then((answer) => {
375
- if (answer.kind === 'discovery')
376
- for (const room of answer.rooms)
377
- note(room.locator, room);
378
- return answer;
379
- });
478
+ return discovered();
380
479
  },
381
480
  reached,
382
481
  follows(locator, roomVersion, next) {
@@ -433,7 +532,13 @@ export function createMemoryAdapter(options = {}) {
433
532
  retryable: false,
434
533
  affected: {},
435
534
  };
436
- }, request).then((answer) => note(request.locator, answer));
535
+ }, request).then((answer) => {
536
+ const destination = request.changeBatch.between?.destination.room;
537
+ if (answer.kind === 'accepted' && answer.destination !== undefined && destination) {
538
+ note(destination, answer.destination);
539
+ }
540
+ return note(request.locator, answer);
541
+ });
437
542
  },
438
543
  async history(_authorization, request) {
439
544
  return resolveScript(scripts.history, () => {
@@ -447,9 +552,44 @@ export function createMemoryAdapter(options = {}) {
447
552
  };
448
553
  }, request);
449
554
  },
555
+ async binary(_authorization, request) {
556
+ return resolveScript(scripts.binary, () => {
557
+ const room = rooms.find((entry) => entry.locator === request.locator);
558
+ if (room === undefined)
559
+ return { kind: 'room-not-readable', locator: request.locator };
560
+ const blob = heldBlob(room, request.path);
561
+ return blob === undefined
562
+ ? { kind: 'rejected', code: 'file_not_found', fields: {}, retryable: false }
563
+ : { kind: 'binary', body: blob.stream(), mediaType: blob.type };
564
+ }, request);
565
+ },
566
+ async link(_authorization, request) {
567
+ return resolveScript(scripts.link, () => {
568
+ const room = rooms.find((entry) => entry.locator === request.locator);
569
+ if (room === undefined)
570
+ return { kind: 'room-not-readable', locator: request.locator };
571
+ const blob = heldBlob(room, request.path);
572
+ if (blob === undefined) {
573
+ return { kind: 'rejected', code: 'file_not_found', fields: {}, retryable: false };
574
+ }
575
+ const url = linked.get(blob) ?? URL.createObjectURL(blob);
576
+ linked.set(blob, url);
577
+ return { kind: 'link', url, name: lastName(request.path), mediaType: blob.type };
578
+ }, request);
579
+ },
580
+ async attach(_authorization, request) {
581
+ return resolveScript(scripts.attach, () => {
582
+ const room = rooms.find((entry) => entry.locator === request.locator);
583
+ if (room === undefined)
584
+ return { kind: 'room-not-readable', locator: request.locator };
585
+ const path = freePath(room, request.name);
586
+ attached.set(`${room.locator}\u0000${path}`, request.blob);
587
+ return { kind: 'attached', path, revision: `attached-${attached.size}` };
588
+ }, request);
589
+ },
450
590
  async action(_authorization, request) {
451
591
  return resolveScript(scripts.action, () => {
452
- if (request.name.startsWith('conversation.'))
592
+ if (request.name.startsWith('conversations/'))
453
593
  return conversationAction(request);
454
594
  return { kind: 'rejected', code: 'unscripted-action', fields: {}, retryable: false };
455
595
  }, request).then((answer) => {
@@ -0,0 +1,28 @@
1
+ import type { AcceptedMessage } from './adapter.js';
2
+ import type { Connection } from './tables.js';
3
+ export declare const MESSAGE_TABLES: readonly string[];
4
+ export type HeldConversation = {
5
+ readonly conversation: string;
6
+ readonly read: string | null | undefined;
7
+ readonly partial: boolean;
8
+ };
9
+ export type MessageWindow = {
10
+ readonly messages: AcceptedMessage[];
11
+ readonly more: boolean;
12
+ };
13
+ export type ConversationWrite = {
14
+ readonly conversation: string;
15
+ readonly messages: readonly AcceptedMessage[];
16
+ readonly read?: {
17
+ readonly at: string | null;
18
+ readonly partial: boolean;
19
+ };
20
+ };
21
+ export declare function heldConversations(connection: Connection): HeldConversation[];
22
+ export declare function keptMessages(connection: Connection, { conversation, messages, read }: ConversationWrite): null;
23
+ export declare function messageWindow(connection: Connection, conversation: string, limit: number, before: readonly [string, string] | null): MessageWindow;
24
+ export declare function messageCount(connection: Connection, conversation: string): number;
25
+ export declare function turnArrived(connection: Connection, conversation: string, turn: string): boolean;
26
+ export declare function droppedMessages(connection: Connection, conversations: readonly string[]): void;
27
+ export declare function forgottenMessages(connection: Connection): void;
28
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,aAAa,CAAC;AAE1D,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAM3C,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,CAAC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAE7F,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IAC9C,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;CAC3E,CAAC;AAMF,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,gBAAgB,EAAE,CAmB5E;AAED,wBAAgB,YAAY,CAC1B,UAAU,EAAE,UAAU,EACtB,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,iBAAiB,GAClD,IAAI,CAoCN;AAYD,wBAAgB,aAAa,CAC3B,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,GACvC,aAAa,CAUf;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAMjF;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAO/F;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAW9F;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAG9D"}
@@ -0,0 +1,90 @@
1
+ import { indexMessage, unindexMessages } from './fulltext.js';
2
+ export const MESSAGE_TABLES = [
3
+ 'CREATE TABLE conversation_reads (conversation TEXT PRIMARY KEY, read TEXT, partial INTEGER NOT NULL) WITHOUT ROWID',
4
+ 'CREATE TABLE conversation_messages (id INTEGER PRIMARY KEY, conversation TEXT NOT NULL, identity TEXT NOT NULL, turn TEXT NOT NULL, position TEXT NOT NULL, message TEXT NOT NULL, text TEXT NOT NULL)',
5
+ 'CREATE UNIQUE INDEX conversation_messages_identity ON conversation_messages (conversation, identity)',
6
+ 'CREATE INDEX conversation_messages_position ON conversation_messages (conversation, position, identity)',
7
+ 'CREATE INDEX conversation_messages_turn ON conversation_messages (conversation, turn)',
8
+ ];
9
+ function text(value) {
10
+ return typeof value === 'string' ? value : String(value);
11
+ }
12
+ export function heldConversations(connection) {
13
+ const held = new Map();
14
+ for (const row of connection.rows('SELECT conversation, read, partial FROM conversation_reads')) {
15
+ held.set(text(row.conversation), {
16
+ conversation: text(row.conversation),
17
+ read: row.read === null ? null : text(row.read),
18
+ partial: Number(row.partial) === 1,
19
+ });
20
+ }
21
+ for (const row of connection.rows('SELECT DISTINCT conversation FROM conversation_messages WHERE conversation NOT IN (SELECT conversation FROM conversation_reads)')) {
22
+ held.set(text(row.conversation), {
23
+ conversation: text(row.conversation),
24
+ read: undefined,
25
+ partial: true,
26
+ });
27
+ }
28
+ return [...held.values()];
29
+ }
30
+ export function keptMessages(connection, { conversation, messages, read }) {
31
+ for (const message of messages) {
32
+ const [held] = connection.rows('SELECT id FROM conversation_messages WHERE conversation = ? AND identity = ?', [conversation, message.identity]);
33
+ const record = [
34
+ message.turn,
35
+ message.position,
36
+ JSON.stringify(message.message ?? null),
37
+ message.text,
38
+ ];
39
+ let id;
40
+ if (held === undefined) {
41
+ connection.run('INSERT INTO conversation_messages (conversation, identity, turn, position, message, text) VALUES (?, ?, ?, ?, ?, ?)', [conversation, message.identity, ...record]);
42
+ id = Number(connection.rows('SELECT last_insert_rowid() AS id')[0].id);
43
+ }
44
+ else {
45
+ id = Number(held.id);
46
+ unindexMessages(connection, [id]);
47
+ connection.run('UPDATE conversation_messages SET turn = ?, position = ?, message = ?, text = ? WHERE id = ?', [...record, id]);
48
+ }
49
+ indexMessage(connection, id, message.text);
50
+ }
51
+ if (read !== undefined) {
52
+ connection.run('INSERT OR REPLACE INTO conversation_reads (conversation, read, partial) VALUES (?, ?, ?)', [conversation, read.at, read.partial ? 1 : 0]);
53
+ }
54
+ return null;
55
+ }
56
+ function accepted(row) {
57
+ return {
58
+ identity: text(row.identity),
59
+ turn: text(row.turn),
60
+ position: text(row.position),
61
+ text: text(row.text),
62
+ message: JSON.parse(text(row.message)),
63
+ };
64
+ }
65
+ export function messageWindow(connection, conversation, limit, before) {
66
+ const rows = connection.rows(`SELECT identity, turn, position, message, text FROM conversation_messages WHERE conversation = ?${before === null ? '' : ' AND (position < ? OR (position = ? AND identity < ?))'} ORDER BY position DESC, identity DESC LIMIT ?`, before === null
67
+ ? [conversation, limit + 1]
68
+ : [conversation, before[0], before[0], before[1], limit + 1]);
69
+ return { messages: rows.slice(0, limit).map(accepted).reverse(), more: rows.length > limit };
70
+ }
71
+ export function messageCount(connection, conversation) {
72
+ const [counted] = connection.rows('SELECT count(*) AS held FROM conversation_messages WHERE conversation = ?', [conversation]);
73
+ return Number(counted?.held ?? 0);
74
+ }
75
+ export function turnArrived(connection, conversation, turn) {
76
+ return (connection.rows('SELECT 1 FROM conversation_messages WHERE conversation = ? AND turn = ? LIMIT 1', [conversation, turn]).length > 0);
77
+ }
78
+ export function droppedMessages(connection, conversations) {
79
+ for (const conversation of conversations) {
80
+ unindexMessages(connection, connection
81
+ .rows('SELECT id FROM conversation_messages WHERE conversation = ?', [conversation])
82
+ .map((row) => Number(row.id)));
83
+ connection.run('DELETE FROM conversation_messages WHERE conversation = ?', [conversation]);
84
+ connection.run('DELETE FROM conversation_reads WHERE conversation = ?', [conversation]);
85
+ }
86
+ }
87
+ export function forgottenMessages(connection) {
88
+ connection.run('DELETE FROM conversation_messages');
89
+ connection.run('DELETE FROM conversation_reads');
90
+ }
package/dist/model.d.ts CHANGED
@@ -28,13 +28,19 @@ export type ModelState = {
28
28
  verifiedWith: ModelIdentity;
29
29
  types: VerifiedModel['types'];
30
30
  };
31
- export type TypesResult = {
31
+ export type StoredModel = Extract<ModelState, {
32
32
  status: 'unavailable';
33
+ }> | Omit<Extract<ModelState, {
34
+ status: 'current' | 'cached';
35
+ }>, 'types'>;
36
+ export type TypesResult = {
37
+ status: Open<'unavailable'>;
33
38
  reason: ModelUnavailableReason;
39
+ types: null;
34
40
  schema?: string | null;
35
41
  revision?: string | null;
36
42
  } | {
37
- status: 'current' | 'cached';
43
+ status: Open<'current' | 'cached'>;
38
44
  identity: string;
39
45
  version: string;
40
46
  verifiedWith: ModelIdentity;
@@ -50,6 +56,7 @@ export type ValidationReceipt = {
50
56
  export declare const NO_MODEL: ModelState;
51
57
  export declare function definitionOf(state: ModelState, type: string | undefined): Definition | undefined;
52
58
  export declare function modelTypes(state: ModelState): TypesResult;
59
+ export declare function storedModel(state: ModelState): StoredModel;
53
60
  export declare function cached(state: ModelState): ModelState;
54
61
  export declare function sameModel(left: ModelIdentity, right: ModelIdentity): boolean;
55
62
  export declare function referenceField(facts: ValueFacts | undefined): boolean;