@knowlearning/agents 0.9.189 → 0.9.190

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.
@@ -28,12 +28,37 @@ export default function attachSynced(watchers, init) {
28
28
  if (syncRegistered) return
29
29
  syncRegistered = true
30
30
  if (!watchers[resolvedKey]) watchers[resolvedKey] = []
31
- watchers[resolvedKey].push(({ patch }) => {
31
+ watchers[resolvedKey].push(({ patch, state, ii }) => {
32
32
  if (!patch) return
33
- ctx.applyingExternalUpdate = true
34
- applyPatch(resolvedProxy, standardJSONPatch(patch))
35
- ctx.applyingExternalUpdate = false
36
- syncCallbacks.forEach(cb => cb(resolvedProxy, patch))
33
+ const pending = ctx.pendingInteractions?.size
34
+ ? Promise.all(ctx.pendingInteractions)
35
+ : Promise.resolve()
36
+ pending.then(() => {
37
+ const isOwn = ctx.ownInteractions?.has(ii)
38
+ const echoBatch = isOwn ? ctx.ownInteractionBatches?.get(ii) : null
39
+ if (isOwn) {
40
+ ctx.ownInteractions.delete(ii)
41
+ if (echoBatch) ctx.ownInteractionBatches.delete(ii)
42
+ } else {
43
+ ctx.applyingExternalUpdate = true
44
+ applyPatch(resolvedProxy, standardJSONPatch(patch))
45
+ ctx.applyingExternalUpdate = false
46
+ }
47
+ if (echoBatch) {
48
+ echoBatch.state = state
49
+ if (echoBatch.remaining !== undefined) echoBatch.remaining -= 1
50
+ if ((echoBatch.remaining === undefined || echoBatch.remaining === 0) && !echoBatch.resolved) {
51
+ echoBatch.resolved = true
52
+ syncCallbacks.forEach(cb => cb(echoBatch.state, patch))
53
+ echoBatch.resolve()
54
+ }
55
+ return
56
+ }
57
+ syncCallbacks.forEach(cb => cb(state, patch))
58
+ if (isOwn && ctx.echoResolvers?.length) {
59
+ ctx.echoResolvers.shift()()
60
+ }
61
+ })
37
62
  })
38
63
  }
39
64
 
@@ -47,6 +72,8 @@ export default function attachSynced(watchers, init) {
47
72
 
48
73
  promise.synced = function(callback) {
49
74
  shouldSync = true
75
+ ctx.syncActive = true
76
+ if (!ctx.echoResolvers) ctx.echoResolvers = []
50
77
  if (callback) syncCallbacks.push(callback)
51
78
  if (resolvedProxy) registerSyncWatcher()
52
79
  return promise
@@ -11,6 +11,7 @@ export default function EmbeddedAgent(postMessage) {
11
11
  const responses = {}
12
12
  const watchers = {}
13
13
  const sentUpdates = {}
14
+ const pendingEchoCallbacks = new Set()
14
15
 
15
16
  const [ watch, removeWatcher ] = watchImplementation({ metadata, state, watchers, synced, sentUpdates, environment })
16
17
 
@@ -108,12 +109,48 @@ export default function EmbeddedAgent(postMessage) {
108
109
  const d = !domain || domain === rootDomain ? '' : domain
109
110
  const u = !user || auth.user === user ? '' : user
110
111
  const key = isUUID(scope) ? scope : `${d}/${u}/${scope}`
112
+ if (!ctx.ownInteractions) ctx.ownInteractions = new Set()
113
+ if (!ctx.ownInteractionBatches) ctx.ownInteractionBatches = new Map()
114
+ if (!ctx.pendingInteractions) ctx.pendingInteractions = new Set()
115
+ if (!ctx.echoResolvers) ctx.echoResolvers = []
116
+ let currentEchoBatch = null
111
117
  const proxy = new PatchProxy(startState, patch => {
112
118
  // TODO: reject updates if user is not owner
113
119
  if (ctx.applyingExternalUpdate) return
114
120
  const activePatch = structuredClone(patch)
115
121
  activePatch.forEach(entry => entry.path.unshift('active'))
116
- interact(scope, activePatch)
122
+ let echoBatch
123
+ if (ctx.syncActive) {
124
+ if (!currentEchoBatch) {
125
+ let resolveEcho
126
+ const echoPromise = new Promise(r => resolveEcho = r)
127
+ echoBatch = currentEchoBatch = { remaining: 0, resolve: resolveEcho, resolved: false }
128
+ pendingEchoCallbacks.add(echoPromise)
129
+ echoPromise.then(() => pendingEchoCallbacks.delete(echoPromise))
130
+ Promise.resolve().then(() => { currentEchoBatch = null })
131
+ }
132
+ else echoBatch = currentEchoBatch
133
+ echoBatch.remaining += 1
134
+ }
135
+ const interactPromise = interact(scope, activePatch)
136
+ const p = interactPromise.then(
137
+ r => {
138
+ ctx.ownInteractions.add(r.ii)
139
+ if (echoBatch) ctx.ownInteractionBatches.set(r.ii, echoBatch)
140
+ ctx.pendingInteractions.delete(p)
141
+ },
142
+ () => {
143
+ if (echoBatch) {
144
+ echoBatch.remaining -= 1
145
+ if (echoBatch.remaining === 0 && !echoBatch.resolved) {
146
+ echoBatch.resolved = true
147
+ echoBatch.resolve()
148
+ }
149
+ }
150
+ ctx.pendingInteractions.delete(p)
151
+ }
152
+ )
153
+ ctx.pendingInteractions.add(p)
117
154
  })
118
155
  resolveSync(proxy, key)
119
156
  return proxy
@@ -213,7 +250,11 @@ export default function EmbeddedAgent(postMessage) {
213
250
  function logout() { return send({ type: 'logout' }) }
214
251
  function disconnect() { return send({ type: 'disconnect' }) }
215
252
  function reconnect() { return send({ type: 'reconnect' }) }
216
- function synced() { return send({ type: 'synced' }) }
253
+ async function synced() {
254
+ const echoSnapshot = [...pendingEchoCallbacks]
255
+ await send({ type: 'synced' })
256
+ if (echoSnapshot.length) await Promise.all(echoSnapshot)
257
+ }
217
258
  function close(info) { return send({ type: 'close', info }) }
218
259
  function guarantee(script, namespaces, context) { return send({ type: 'guarantee', script, namespaces, context }) }
219
260
  function response(id=lastRequestId) { return send({ type: 'response', id }) }
@@ -17,6 +17,7 @@ export default function Agent({ Connection, domain, token, sid, uuid, fetch, app
17
17
  const watchers = {}
18
18
  const keyToSubscriptionId = {}
19
19
  const lastInteractionResponse = {}
20
+ const pendingEchoCallbacks = new Set()
20
21
 
21
22
  log('INITIALIZING AGENT CONNECTION')
22
23
  const [
@@ -47,6 +48,7 @@ export default function Agent({ Connection, domain, token, sid, uuid, fetch, app
47
48
  interact,
48
49
  fetch,
49
50
  synced,
51
+ pendingEchoCallbacks,
50
52
  metadata,
51
53
  log
52
54
  }
@@ -204,6 +206,12 @@ export default function Agent({ Connection, domain, token, sid, uuid, fetch, app
204
206
  }
205
207
  }
206
208
 
209
+ async function syncedWithEchoCallbacks() {
210
+ const echoSnapshot = [...pendingEchoCallbacks]
211
+ await synced()
212
+ if (echoSnapshot.length) await Promise.all(echoSnapshot)
213
+ }
214
+
207
215
  function response() {
208
216
  return lastMessageResponse()
209
217
  }
@@ -224,7 +232,7 @@ export default function Agent({ Connection, domain, token, sid, uuid, fetch, app
224
232
  reset,
225
233
  metadata,
226
234
  query,
227
- synced,
235
+ synced: syncedWithEchoCallbacks,
228
236
  disconnect,
229
237
  reconnect,
230
238
  debug,
@@ -2,7 +2,7 @@ import { v4 as uuid, validate as isUUID } from 'uuid'
2
2
  import PatchProxy from '@knowlearning/patch-proxy'
3
3
  import attachSynced from '../attach-synced.js'
4
4
 
5
- export default function(scope='[]', user, domain, { keyToSubscriptionId, watchers, states, create, environment, lastMessageResponse, lastInteractionResponse, interact, log }) {
5
+ export default function(scope='[]', user, domain, { keyToSubscriptionId, watchers, states, create, environment, lastMessageResponse, lastInteractionResponse, interact, pendingEchoCallbacks, log }) {
6
6
  let resolveMetadataPromise
7
7
  let metadataPromise = new Promise(resolve => resolveMetadataPromise = resolve)
8
8
 
@@ -36,11 +36,43 @@ export default function(scope='[]', user, domain, { keyToSubscriptionId, watcher
36
36
  const active = data.active
37
37
  delete data.active
38
38
  resolveMetadataPromise(data)
39
+ if (!ctx.ownInteractions) ctx.ownInteractions = new Set()
40
+ if (!ctx.ownInteractionBatches) ctx.ownInteractionBatches = new Map()
41
+ if (!ctx.pendingInteractions) ctx.pendingInteractions = new Set()
42
+ if (!ctx.echoResolvers) ctx.echoResolvers = []
43
+ let currentEchoBatch = null
39
44
  const proxy = new PatchProxy(active || {}, patch => {
40
45
  if (ctx.applyingExternalUpdate) return
41
46
  const activePatch = structuredClone(patch)
42
47
  activePatch.forEach(entry => entry.path.unshift('active'))
43
- interact(scope, activePatch)
48
+ let echoBatch
49
+ if (ctx.syncActive && pendingEchoCallbacks) {
50
+ if (!currentEchoBatch) {
51
+ let resolveEcho
52
+ const echoPromise = new Promise(r => resolveEcho = r)
53
+ echoBatch = currentEchoBatch = { resolve: resolveEcho, resolved: false }
54
+ pendingEchoCallbacks.add(echoPromise)
55
+ echoPromise.then(() => pendingEchoCallbacks.delete(echoPromise))
56
+ Promise.resolve().then(() => { currentEchoBatch = null })
57
+ }
58
+ else echoBatch = currentEchoBatch
59
+ }
60
+ const interactPromise = interact(scope, activePatch)
61
+ const p = interactPromise.then(
62
+ r => {
63
+ ctx.ownInteractions.add(r.ii)
64
+ if (echoBatch) ctx.ownInteractionBatches.set(r.ii, echoBatch)
65
+ ctx.pendingInteractions.delete(p)
66
+ },
67
+ () => {
68
+ if (echoBatch && !echoBatch.resolved) {
69
+ echoBatch.resolved = true
70
+ echoBatch.resolve()
71
+ }
72
+ ctx.pendingInteractions.delete(p)
73
+ }
74
+ )
75
+ ctx.pendingInteractions.add(p)
44
76
  })
45
77
  resolveSync(proxy, qualifiedScope)
46
78
  resolveState(proxy)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knowlearning/agents",
3
- "version": "0.9.189",
3
+ "version": "0.9.190",
4
4
  "description": "API for embedding applications in KnowLearning systems.",
5
5
  "main": "node.js",
6
6
  "browser": "browser.js",