@knowlearning/agents 0.9.187 → 0.9.189

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.
@@ -0,0 +1,56 @@
1
+ import { applyPatch } from 'fast-json-patch'
2
+ import { standardJSONPatch } from '@knowlearning/patch-proxy'
3
+
4
+ // Wraps a state promise with a .synced(callback?) method that applies external
5
+ // patches to the live proxy in-place.
6
+ //
7
+ // Usage:
8
+ // const promise = attachSynced(watchers, (ctx, resolve) => {
9
+ // const p = new Promise(async (...) => {
10
+ // const proxy = new PatchProxy(data, patch => {
11
+ // if (ctx.applyingExternalUpdate) return
12
+ // // ... interact
13
+ // })
14
+ // resolve(proxy, qualifiedKey)
15
+ // resolvePromise(proxy)
16
+ // })
17
+ // return p
18
+ // })
19
+ export default function attachSynced(watchers, init) {
20
+ const ctx = { applyingExternalUpdate: false }
21
+ let shouldSync = false
22
+ let syncCallbacks = []
23
+ let syncRegistered = false
24
+ let resolvedProxy = null
25
+ let resolvedKey = null
26
+
27
+ function registerSyncWatcher() {
28
+ if (syncRegistered) return
29
+ syncRegistered = true
30
+ if (!watchers[resolvedKey]) watchers[resolvedKey] = []
31
+ watchers[resolvedKey].push(({ patch }) => {
32
+ if (!patch) return
33
+ ctx.applyingExternalUpdate = true
34
+ applyPatch(resolvedProxy, standardJSONPatch(patch))
35
+ ctx.applyingExternalUpdate = false
36
+ syncCallbacks.forEach(cb => cb(resolvedProxy, patch))
37
+ })
38
+ }
39
+
40
+ function resolve(proxy, key) {
41
+ resolvedProxy = proxy
42
+ resolvedKey = key
43
+ if (shouldSync) registerSyncWatcher()
44
+ }
45
+
46
+ const promise = init(ctx, resolve)
47
+
48
+ promise.synced = function(callback) {
49
+ shouldSync = true
50
+ if (callback) syncCallbacks.push(callback)
51
+ if (resolvedProxy) registerSyncWatcher()
52
+ return promise
53
+ }
54
+
55
+ return promise
56
+ }
@@ -1,6 +1,8 @@
1
1
  import { validate as isUUID, v1 as uuid } from 'uuid'
2
2
  import PatchProxy from '@knowlearning/patch-proxy'
3
+ import attachSynced from './attach-synced.js'
3
4
  import watchImplementation from './watch.js'
5
+ import sync from './sync.js'
4
6
 
5
7
  export default function EmbeddedAgent(postMessage) {
6
8
  let messageIndex = 0
@@ -95,18 +97,27 @@ export default function EmbeddedAgent(postMessage) {
95
97
  return send({ type: 'patch', root, scopes })
96
98
  }
97
99
 
98
- async function state(scope, user, domain) {
99
- if (scope === undefined) {
100
- const { context } = await environment()
101
- scope = JSON.stringify(context)
102
- }
103
- const startState = await send({ type: 'state', scope, user, domain })
104
- return new PatchProxy(startState, patch => {
105
- // TODO: reject updates if user is not owner
106
- const activePatch = structuredClone(patch)
107
- activePatch.forEach(entry => entry.path.unshift('active'))
108
- interact(scope, activePatch)
109
- })
100
+ function state(scope, user, domain) {
101
+ return attachSynced(watchers, (ctx, resolveSync) => (async () => {
102
+ if (scope === undefined) {
103
+ const { context } = await environment()
104
+ scope = JSON.stringify(context)
105
+ }
106
+ const startState = await send({ type: 'state', scope, user, domain })
107
+ const { auth, domain: rootDomain } = await environment()
108
+ const d = !domain || domain === rootDomain ? '' : domain
109
+ const u = !user || auth.user === user ? '' : user
110
+ const key = isUUID(scope) ? scope : `${d}/${u}/${scope}`
111
+ const proxy = new PatchProxy(startState, patch => {
112
+ // TODO: reject updates if user is not owner
113
+ if (ctx.applyingExternalUpdate) return
114
+ const activePatch = structuredClone(patch)
115
+ activePatch.forEach(entry => entry.path.unshift('active'))
116
+ interact(scope, activePatch)
117
+ })
118
+ resolveSync(proxy, key)
119
+ return proxy
120
+ })())
110
121
  }
111
122
 
112
123
  function reset(scope) {
@@ -227,6 +238,7 @@ export default function EmbeddedAgent(postMessage) {
227
238
  synced,
228
239
  close,
229
240
  response,
241
+ sync,
230
242
  query
231
243
  }
232
244
  }
@@ -4,6 +4,7 @@ import messageQueue from './message-queue.js'
4
4
  import stateImplementation from './state.js'
5
5
  import watchImplementation from '../watch.js'
6
6
  import downloadImplementation from '../download.js'
7
+ import sync from '../sync.js'
7
8
 
8
9
  // TODO: consider using something better than name as mechanism
9
10
  // for resoling default scope in context
@@ -229,6 +230,7 @@ export default function Agent({ Connection, domain, token, sid, uuid, fetch, app
229
230
  debug,
230
231
  guarantee,
231
232
  response,
233
+ sync,
232
234
  on
233
235
  }
234
236
  }
@@ -1,11 +1,12 @@
1
1
  import { v4 as uuid, validate as isUUID } from 'uuid'
2
2
  import PatchProxy from '@knowlearning/patch-proxy'
3
+ import attachSynced from '../attach-synced.js'
3
4
 
4
5
  export default function(scope='[]', user, domain, { keyToSubscriptionId, watchers, states, create, environment, lastMessageResponse, lastInteractionResponse, interact, log }) {
5
6
  let resolveMetadataPromise
6
7
  let metadataPromise = new Promise(resolve => resolveMetadataPromise = resolve)
7
8
 
8
- const statePromise = new Promise(async (resolveState, rejectState) => {
9
+ const statePromise = attachSynced(watchers, (ctx, resolveSync) => new Promise(async (resolveState, rejectState) => {
9
10
  const { auth: { user: u }, domain: d, session } = await environment()
10
11
 
11
12
  const qualifiedScope = isUUID(scope) ? scope : `${!domain || domain === d ? '' : domain}/${!user || user === u ? '' : user}/${scope}`
@@ -35,17 +36,20 @@ export default function(scope='[]', user, domain, { keyToSubscriptionId, watcher
35
36
  const active = data.active
36
37
  delete data.active
37
38
  resolveMetadataPromise(data)
38
- resolveState(new PatchProxy(active || {}, patch => {
39
+ const proxy = new PatchProxy(active || {}, patch => {
40
+ if (ctx.applyingExternalUpdate) return
39
41
  const activePatch = structuredClone(patch)
40
42
  activePatch.forEach(entry => entry.path.unshift('active'))
41
43
  interact(scope, activePatch)
42
- }))
44
+ })
45
+ resolveSync(proxy, qualifiedScope)
46
+ resolveState(proxy)
43
47
  }
44
48
  catch (error) {
45
49
  rejectState(error)
46
50
  }
47
- })
51
+ }))
48
52
 
49
53
  statePromise.metadata = metadataPromise
50
54
  return statePromise
51
- }
55
+ }
package/agents/sync.js ADDED
@@ -0,0 +1,7 @@
1
+ import { compare, applyPatch } from 'fast-json-patch'
2
+
3
+ export default function sync(state, target) {
4
+ const patch = compare(state, target)
5
+ applyPatch(state, patch, true, true)
6
+ return { patch }
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knowlearning/agents",
3
- "version": "0.9.187",
3
+ "version": "0.9.189",
4
4
  "description": "API for embedding applications in KnowLearning systems.",
5
5
  "main": "node.js",
6
6
  "browser": "browser.js",
package/types.d.ts CHANGED
@@ -27,11 +27,16 @@ export interface AgentUploadInfo {
27
27
  accept?: string;
28
28
  }
29
29
 
30
+ export interface SyncedStatePromise extends Promise<object> {
31
+ synced(callback?: (state: object, patch: object[]) => void): SyncedStatePromise;
32
+ metadata: Promise<object>;
33
+ }
34
+
30
35
  export interface Agent {
31
36
  login(provider: string): void;
32
37
  logout(): void;
33
38
  uuid(): string;
34
- state(id: string, user?: string, domain?: string): Promise<object>;
39
+ state(id: string, user?: string, domain?: string): SyncedStatePromise;
35
40
  metadata(id: string, user?: string, domain?: string): Promise<object>;
36
41
  watch(id: string, callback: (update: { state: object }) => void, user?: string, domain?: string): void;
37
42
  upload(info?: AgentUploadInfo): Promise<string>;