@owlmeans/client-planning 0.1.18-rc.3 → 0.1.18-rc.30
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.
- package/README.md +94 -11
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/client-planning/SKILL.md +148 -36
- package/build/client.d.ts.map +1 -1
- package/build/client.js +50 -8
- package/build/client.js.map +1 -1
- package/build/commits.d.ts +2 -13
- package/build/commits.d.ts.map +1 -1
- package/build/commits.js +190 -132
- package/build/commits.js.map +1 -1
- package/build/consts.d.ts +4 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +10 -4
- package/build/consts.js.map +1 -1
- package/build/definitions.d.ts +13 -0
- package/build/definitions.d.ts.map +1 -0
- package/build/definitions.js +70 -0
- package/build/definitions.js.map +1 -0
- package/build/events/types.d.ts +34 -0
- package/build/events/types.d.ts.map +1 -0
- package/build/events/types.js +2 -0
- package/build/events/types.js.map +1 -0
- package/build/events.d.ts +10 -21
- package/build/events.d.ts.map +1 -1
- package/build/events.js +104 -102
- package/build/events.js.map +1 -1
- package/build/facade.d.ts.map +1 -1
- package/build/facade.js +86 -49
- package/build/facade.js.map +1 -1
- package/build/feed.d.ts +4 -4
- package/build/feed.d.ts.map +1 -1
- package/build/feed.js +50 -27
- package/build/feed.js.map +1 -1
- package/build/helper/types.d.ts +15 -0
- package/build/helper/types.d.ts.map +1 -0
- package/build/helper/types.js +2 -0
- package/build/helper/types.js.map +1 -0
- package/build/helper.d.ts +7 -7
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +16 -8
- package/build/helper.js.map +1 -1
- package/build/index.d.ts +11 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +6 -0
- package/build/index.js.map +1 -1
- package/build/lifecycle/types.d.ts +27 -0
- package/build/lifecycle/types.d.ts.map +1 -0
- package/build/lifecycle/types.js +2 -0
- package/build/lifecycle/types.js.map +1 -0
- package/build/lifecycle.d.ts +3 -0
- package/build/lifecycle.d.ts.map +1 -0
- package/build/lifecycle.js +157 -0
- package/build/lifecycle.js.map +1 -0
- package/build/resource-feed.d.ts +5 -0
- package/build/resource-feed.d.ts.map +1 -0
- package/build/resource-feed.js +96 -0
- package/build/resource-feed.js.map +1 -0
- package/build/resources.d.ts +6 -0
- package/build/resources.d.ts.map +1 -0
- package/build/resources.js +93 -0
- package/build/resources.js.map +1 -0
- package/build/stores.d.ts +4 -11
- package/build/stores.d.ts.map +1 -1
- package/build/stores.js +11 -35
- package/build/stores.js.map +1 -1
- package/build/sync/types.d.ts +18 -0
- package/build/sync/types.d.ts.map +1 -0
- package/build/sync/types.js +2 -0
- package/build/sync/types.js.map +1 -0
- package/build/sync.d.ts +4 -0
- package/build/sync.d.ts.map +1 -0
- package/build/sync.js +26 -0
- package/build/sync.js.map +1 -0
- package/build/types.d.ts +48 -3
- package/build/types.d.ts.map +1 -1
- package/build/types.local.d.ts +13 -0
- package/build/types.local.d.ts.map +1 -0
- package/build/types.local.js +2 -0
- package/build/types.local.js.map +1 -0
- package/build/utils/record/types.d.ts +28 -0
- package/build/utils/record/types.d.ts.map +1 -0
- package/build/utils/record/types.js +2 -0
- package/build/utils/record/types.js.map +1 -0
- package/build/utils/record.d.ts +3 -24
- package/build/utils/record.d.ts.map +1 -1
- package/build/utils/record.js +64 -70
- package/build/utils/record.js.map +1 -1
- package/build/validation/consts.d.ts +16 -0
- package/build/validation/consts.d.ts.map +1 -0
- package/build/validation/consts.js +42 -0
- package/build/validation/consts.js.map +1 -0
- package/build/validation/types.d.ts +17 -0
- package/build/validation/types.d.ts.map +1 -0
- package/build/validation/types.js +2 -0
- package/build/validation/types.js.map +1 -0
- package/build/validation.d.ts +10 -0
- package/build/validation.d.ts.map +1 -0
- package/build/validation.js +172 -0
- package/build/validation.js.map +1 -0
- package/package.json +18 -16
- package/src/client.ts +42 -8
- package/src/commits.ts +152 -184
- package/src/consts.ts +12 -4
- package/src/definitions.ts +93 -0
- package/src/events/types.ts +35 -0
- package/src/events.ts +118 -108
- package/src/facade.ts +87 -55
- package/src/feed.ts +47 -28
- package/src/helper/types.ts +15 -0
- package/src/helper.ts +27 -10
- package/src/index.ts +12 -0
- package/src/lifecycle/types.ts +28 -0
- package/src/lifecycle.ts +123 -0
- package/src/resource-feed.ts +78 -0
- package/src/resources.ts +84 -0
- package/src/stores.ts +11 -40
- package/src/sync/types.ts +18 -0
- package/src/sync.ts +40 -0
- package/src/types.local.ts +14 -0
- package/src/types.ts +53 -5
- package/src/utils/record/types.ts +27 -0
- package/src/utils/record.ts +68 -71
- package/src/validation/consts.ts +41 -0
- package/src/validation/types.ts +19 -0
- package/src/validation.ts +141 -0
- package/tests/context.ts +46 -15
- package/tests/definitions.spec.ts +74 -0
- package/tests/events.spec.ts +13 -9
- package/tests/facade.spec.ts +2 -1
- package/tests/lifecycle/context.ts +29 -0
- package/tests/lifecycle-close.spec.ts +103 -0
- package/tests/lifecycle-commits.spec.ts +92 -0
- package/tests/lifecycle-definitions.spec.ts +28 -0
- package/tests/lifecycle-feeds.spec.ts +92 -0
- package/tests/lifecycle-sockets.spec.ts +76 -0
- package/tests/replies.spec.ts +81 -0
- package/tests/resources.spec.ts +90 -0
- package/tests/stores.spec.ts +13 -11
- package/tests/validation.spec.ts +127 -0
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# @owlmeans/client-planning
|
|
2
2
|
|
|
3
3
|
The client half of OwlMeans planning: a `PlanningFacade` over the protocol tree a server mounted
|
|
4
|
-
with [`@owlmeans/server-planning`](../server-planning),
|
|
5
|
-
commits, and a commit wait that subscribes first and then long-polls. It is React-free, so the same
|
|
4
|
+
with [`@owlmeans/server-planning`](../server-planning), optional state mirrors of cards, links,
|
|
5
|
+
commits, assignees, teams, comments and mentions, and a commit wait that subscribes first and then long-polls. It is React-free, so the same
|
|
6
6
|
code runs in a browser app and in a Node client. Use `@owlmeans/server-planning` instead wherever the
|
|
7
7
|
planning service runs in-process.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
bun add @owlmeans/client-planning@^0.1.18-rc.
|
|
12
|
+
bun add @owlmeans/client-planning@^0.1.18-rc.29
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## Concepts
|
|
@@ -17,14 +17,46 @@ bun add @owlmeans/client-planning@^0.1.18-rc.3
|
|
|
17
17
|
- **Remote facade** — the same `PlanningFacade` interface the server answers; every method is one
|
|
18
18
|
entrypoint call, and `modelOf(record, facade)` models behave identically on both sides.
|
|
19
19
|
- **One id space** — projects, cards and specifications share ONE card store; a list reaches it
|
|
20
|
-
through `syncCards(store, items, where)`, never `replace()`.
|
|
20
|
+
through `syncHelper.syncCards(store, items, where)`, never `replace()`.
|
|
21
21
|
- **Commit** — `execute()` returns a receipt for a pending transition; `receipt.committed()` /
|
|
22
22
|
`commits.wait()` resolve with the folded card, or throw `CommitTimeout` / `CommitFailed`.
|
|
23
23
|
- **Socket seam** — the commit socket is opened by an injected `PlanningSocketOpener`; without one
|
|
24
24
|
the client long-polls.
|
|
25
|
-
- **`head > seq`** — a card with a transition allocated and not yet folded; `applyReceipt` marks it
|
|
25
|
+
- **`head > seq`** — a card with a transition allocated and not yet folded; the mirror's `applyReceipt` marks it
|
|
26
26
|
the moment the server answers.
|
|
27
27
|
|
|
28
|
+
## Forms under a strict Content Security Policy
|
|
29
|
+
|
|
30
|
+
Use the exact resolved card or assignee type's field schema:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { makePlanningFieldValidator } from '@owlmeans/client-planning'
|
|
34
|
+
|
|
35
|
+
const form = makePlanningFieldValidator(resolvedType.fields)
|
|
36
|
+
const checked = form.validateFields(fields)
|
|
37
|
+
if (checked.valid) {
|
|
38
|
+
// Submit this same fields object through the stock planning facade.
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The runtime interpreter supports dynamic organization and project definitions without `eval` or
|
|
43
|
+
`new Function`. It preserves the schema and payload, nested objects, required fields, enum/const,
|
|
44
|
+
dates, references, compositions and AJV nullable semantics; string formats use native
|
|
45
|
+
`ajv-formats` full-mode predicates. Errors contain field `path`, `keyword`, `schemaPath` and a
|
|
46
|
+
bounded message without submitted values. The server's authoritative AJV validates every write.
|
|
47
|
+
|
|
48
|
+
The schema contract is Draft 7 plus `$defs` and `nullable`, with native AJV reference-sibling
|
|
49
|
+
semantics. References must resolve within the supplied schema. Malformed schemas, missing
|
|
50
|
+
references, same-instance reference cycles, other drafts/custom validation keywords, unknown or
|
|
51
|
+
numeric formats, format-limit extensions and `multipleOf` throw `SchemaInvalid` at preparation.
|
|
52
|
+
The interpreter's floating tolerance and native AJV's multiple semantics differ, so that keyword
|
|
53
|
+
is explicitly refused. JSON fields cannot contain dot/dollar keys or non-JSON values. Standard
|
|
54
|
+
annotations and `x-` metadata remain annotations. At most 64 validation errors are returned.
|
|
55
|
+
|
|
56
|
+
Surface preparation errors and keep save disabled. Do not remove constraints, choose another
|
|
57
|
+
schema or relax CSP. Registry `validator` / `assigneeValidator` compile with native AJV and need
|
|
58
|
+
code generation; browser forms under strict CSP use this API instead.
|
|
59
|
+
|
|
28
60
|
## Usage
|
|
29
61
|
|
|
30
62
|
Wire the client into a context with the server's own tree:
|
|
@@ -36,9 +68,14 @@ import { appendPlanningClient, appendPlanningStores } from '@owlmeans/client-pla
|
|
|
36
68
|
const planningProtocols = makePlanningProtocols({
|
|
37
69
|
base: { alias: 'app:api:planning', path: '/planning', service: API },
|
|
38
70
|
guards: DEFAULT_GUARD,
|
|
71
|
+
resources: true,
|
|
72
|
+
definitions: true,
|
|
39
73
|
})
|
|
40
74
|
|
|
41
|
-
appendPlanningClient(context, {
|
|
75
|
+
appendPlanningClient(context, {
|
|
76
|
+
protocols: planningProtocols, poll: 20,
|
|
77
|
+
scopeKey: () => `${currentEntitySlug}:${currentSessionId}`,
|
|
78
|
+
})
|
|
42
79
|
appendPlanningStores(context)
|
|
43
80
|
```
|
|
44
81
|
|
|
@@ -77,18 +114,61 @@ Give a browser its commit socket through the host's own opener:
|
|
|
77
114
|
```typescript
|
|
78
115
|
appendPlanningClient(context, {
|
|
79
116
|
protocols: planningProtocols,
|
|
80
|
-
socket: async (protocol, request) => await ws(context.entrypoint(protocol), request),
|
|
117
|
+
socket: async (protocol, request) => await socketClientHelper.ws(context.entrypoint(protocol), request),
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Auxiliary resources and session lifecycle
|
|
122
|
+
|
|
123
|
+
The remote facade supports the same `assignees`, `teams`, `comments` and `mentions` methods as
|
|
124
|
+
the server, including compare-and-set writes, canonical team membership/project attachment,
|
|
125
|
+
effective project assignees and mention-cache rebuilding. `definitions` supports assignee types
|
|
126
|
+
when the server mounts writable schemas. Relationships include endpoint resource kinds and named
|
|
127
|
+
inverse views; models resolve schemas through their nearest primary project.
|
|
128
|
+
|
|
129
|
+
`appendPlanningStores` registers four auxiliary mirrors beside cards, links and commits. Reads
|
|
130
|
+
and writes through the facade update those mirrors without replacing newer versions. A resource
|
|
131
|
+
feed refreshes the requested subsets through the stock API:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { makePlanningResourceFeed } from '@owlmeans/client-planning'
|
|
135
|
+
|
|
136
|
+
const resources = makePlanningResourceFeed(context, {
|
|
137
|
+
assignees: { retired: false, size: 0 }, teams: { size: 0 },
|
|
138
|
+
comments: { card: cardId, size: 0 }, mentions: { card: cardId, size: 0 },
|
|
139
|
+
refresh: 5_000,
|
|
81
140
|
})
|
|
141
|
+
await resources.ready
|
|
142
|
+
// Read context.planningStores().assignees, teams, comments and mentions.
|
|
143
|
+
await resources.stop()
|
|
82
144
|
```
|
|
83
145
|
|
|
146
|
+
`scopeKey` identifies the current organization slug and authenticated session. A changed key
|
|
147
|
+
cancels old requests and feeds, drains local mutations and clears mirrors; capture a new facade
|
|
148
|
+
and feed for the new session. `await context.planning().close()` releases feeds, subscriptions,
|
|
149
|
+
commit waits and sockets, including carriers that open late. Stopped requests cannot repopulate
|
|
150
|
+
cleared mirrors, even when a transport ignores cancellation.
|
|
151
|
+
|
|
152
|
+
The server derives authorization scope from credentials. Wire records omit native organization
|
|
153
|
+
`entityId`; the client hydrates empty advisory metadata for the shared record shape. Never use
|
|
154
|
+
that placeholder to authorize a request, and never send an organization record id on the wire.
|
|
155
|
+
|
|
84
156
|
## API
|
|
85
157
|
|
|
86
158
|
- `appendPlanningClient(context, options)`, `makePlanningClientService(context, options)`
|
|
87
159
|
- `makeRemoteFacade(context, protocols, scope, opts)`, `makeRemoteCommitSource(context, protocols, opts?)`
|
|
88
|
-
- `appendPlanningStores(context, aliases?)
|
|
89
|
-
- `
|
|
160
|
+
- `appendPlanningStores(context, aliases?)`
|
|
161
|
+
- `syncHelper` — `.syncCards(store, items, where?, opts?)`, `.syncLinks(store, items, where?, opts?)`
|
|
162
|
+
- `planningMirrorOf(stores)` (`makePlanningMirror`) — `.applyCommitEvent(event, facade?)`,
|
|
163
|
+
`.applyReceipt(view)`, `.applyCards(cards)`
|
|
90
164
|
- `makePlanningFeed(context, opts?)`
|
|
91
|
-
- `
|
|
165
|
+
- `makePlanningResourceFeed(context, opts?)`, `PlanningResourceFeedOptions`
|
|
166
|
+
- `makePlanningFieldValidator(schema)`, `PlanningFieldValidator`, `PlanningFieldValidation`, `PlanningFieldError`
|
|
167
|
+
- `makePlanningClientLifecycle`, `PlanningClientLifecycle`; service `close()` and `scopeKey`
|
|
168
|
+
- `planningContextOf(context)` (`makePlanningContextHelper`) — `.facade(scope?)`, `.model(card, scope?)`,
|
|
169
|
+
`.stores()`
|
|
170
|
+
- The former plain functions (`syncCards`, `applyCards`, `planningOf`, …) remain as deprecated
|
|
171
|
+
delegates.
|
|
92
172
|
- `CARDS`, `LINKS`, `COMMITS`, `DEFAULT_STORE_ALIASES`, `LONG_POLL_GRACE`, `EARLY_POLL_LADDER`, `EARLY_POLL_MS`
|
|
93
173
|
- Types: `PlanningClientOptions`, `PlanningClientService`, `WithPlanningClient`,
|
|
94
174
|
`PlanningSocketOpener`, `RemoteCommitSource`, `RemoteCommitSourceOptions`, `RemoteFacadeOptions`,
|
|
@@ -103,6 +183,9 @@ appendPlanningClient(context, {
|
|
|
103
183
|
- `CommitTimeout` leaves the transition pending — re-wait or re-read, do not re-execute without a `key`.
|
|
104
184
|
- A client refuses `store()`, `committed()` and plugins carrying hooks with `PlanningUnsupported`.
|
|
105
185
|
- Mount one feed per screen; every other reader uses the store.
|
|
186
|
+
- Auxiliary updates/removals require `{ version: record.version }`. Their `fields` replace the
|
|
187
|
+
previous object; card `changes.fields` merge.
|
|
188
|
+
- After a scope/session change, acquire new facades and feeds. Old ones are deliberately stale.
|
|
106
189
|
|
|
107
190
|
## Related packages
|
|
108
191
|
|
|
@@ -119,7 +202,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
119
202
|
your project's skill store (`.agents/skills/`):
|
|
120
203
|
|
|
121
204
|
```sh
|
|
122
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
205
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.53
|
|
123
206
|
```
|
|
124
207
|
|
|
125
208
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/client-planning",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-
|
|
4
|
+
"version": "0.1.18-rc.30",
|
|
5
|
+
"generatedAt": "2026-10-10T00:34:44.458Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: client-planning
|
|
3
|
-
description: How to use @owlmeans/client-planning — appendPlanningClient for the remote planning facade, appendPlanningStores and
|
|
3
|
+
description: How to use @owlmeans/client-planning — appendPlanningClient for the remote planning facade, appendPlanningStores and planningMirrorOf for the state mirror, makePlanningFeed, commits.wait (subscribe then long-poll), and the models that are the same objects the server uses. Auto-invoked when reading planning data in a browser or a Node client, mounting the commit feed, or wiring optimistic transitions.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,7 +8,7 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/client-planning
|
|
9
9
|
|
|
10
10
|
**Layer:** Client
|
|
11
|
-
**Install:** `"@owlmeans/client-planning": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/client-planning": "^0.1.18-rc.30"` in `dependencies`
|
|
12
12
|
|
|
13
13
|
The client half of OwlMeans planning. It answers the `PlanningFacade` interface of
|
|
14
14
|
`@owlmeans/planning` over the protocol tree a server mounted with `@owlmeans/server-planning`, keeps
|
|
@@ -24,13 +24,15 @@ CLI). React hooks over the mirror are `useStoreModel` / `useStoreList` from `@ow
|
|
|
24
24
|
| `makePlanningClientService(context, options)` | The service itself, for a host that registers it on its own |
|
|
25
25
|
| `makeRemoteFacade(context, protocols, scope, opts)` | One facade — every method is one entrypoint call |
|
|
26
26
|
| `makeRemoteCommitSource(context, protocols, opts?)` | `status` / `subscribe` / `wait` over `commit.get` and `commit.events` |
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
27
|
+
| `makeRemoteDefinitions(context, protocols, opts?)` | Data-defined types and flows over a tree declared with `definitions: true` |
|
|
28
|
+
| `appendPlanningStores(context, aliases?)` / `planningContextOf(context).stores()` | The state mirror, and a lookup that answers `null` without one |
|
|
29
|
+
| `syncHelper.syncCards(store, items, where?, opts?)` / `syncHelper.syncLinks(...)` | Make the mirror agree with a list WITHIN a scope |
|
|
30
|
+
| `planningMirrorOf(stores).applyCommitEvent(event, facade?)` / `.applyReceipt(view)` / `.applyCards(cards)` | The folds |
|
|
30
31
|
| `makePlanningFeed(context, opts?)` | Subscribe, seed, fold, refresh — `{ connected, seeded, error, ready, refresh, stop }` |
|
|
31
|
-
| `
|
|
32
|
+
| `planningContextOf(context).facade(scope?)` / `.model(card, scope?)` | The facade / a model with the schemas loaded |
|
|
33
|
+
| `makePlanningFieldValidator(schema)` | CSP-safe validation of an exact served field schema; `{ validateFields }` |
|
|
32
34
|
| `CARDS`, `LINKS`, `COMMITS` | Store aliases (`planning-card-state`, `planning-link-state`, `planning-commit-state`) |
|
|
33
|
-
| Types | `PlanningClientOptions`, `PlanningClientService`, `WithPlanningClient`, `PlanningSocketOpener`, `RemoteCommitSource`, `PlanningStores`, `PlanningStoreAliases`, `WithPlanningStores`, `PlanningCommitRecord`, `PlanningFeed`, `PlanningFeedOptions`, `PlanningFeedState`, `SyncOptions` |
|
|
35
|
+
| Types | `PlanningClientOptions`, `PlanningClientService`, `WithPlanningClient`, `PlanningSocketOpener`, `RemoteCommitSource`, `RemoteDefinitions`, `RemoteDefinitionsOptions`, `PlanningStores`, `PlanningStoreAliases`, `WithPlanningStores`, `PlanningCommitRecord`, `PlanningFeed`, `PlanningFeedOptions`, `PlanningFeedState`, `SyncOptions`, `PlanningContextHelper`, `PlanningMirror`, `SyncHelper` |
|
|
34
36
|
|
|
35
37
|
## Wiring
|
|
36
38
|
|
|
@@ -59,8 +61,8 @@ appendPlanningStores(context) // a browser mirror; a Node client usually
|
|
|
59
61
|
- `bind: false` when the host already bound the tree.
|
|
60
62
|
- The service answers under `PLANNING_SERVICE`, the alias the server's service uses, so
|
|
61
63
|
`context.planning().for(scope)` is the same call in a handler and in a screen. The scope is
|
|
62
|
-
**advisory
|
|
63
|
-
`actor` is never sent.
|
|
64
|
+
**advisory** — it changes nothing the server decides: the server takes the entity and the actor
|
|
65
|
+
from the credential. The execution's own `actor` is never sent.
|
|
64
66
|
- A client runs no middleware and folds nothing: `store()`, `committed()` and a plugin carrying
|
|
65
67
|
`before`/`after`/`store`/`mintCode` answer `PlanningUnsupported`. `use()` accepts a schemas-only
|
|
66
68
|
plugin, layered over the server's bundle.
|
|
@@ -75,20 +77,78 @@ there is swallowed — a browser that is not signed in yet cannot read it, and a
|
|
|
75
77
|
fail or wait over it. `model()` and `service.loadSchemas()` load it on first use regardless, and a
|
|
76
78
|
failed load is retried by the next caller. `schemas: false` skips the background load.
|
|
77
79
|
|
|
80
|
+
### Data-defined types and flows
|
|
81
|
+
|
|
82
|
+
A tree declared with `definitions: true` (the server's store holds data-defined schemas) gives the
|
|
83
|
+
service and every facade `definitions`: `registry(project?)` / `bundle(project?)` read
|
|
84
|
+
`schema.list?project=` and are cached per project; `putType`/`putFlow` (compare-and-set on the
|
|
85
|
+
declaration's `version`), `define`, `seed` and `retire` go through `schema.define` and drop the
|
|
86
|
+
whole cache — an organization-wide write reaches every project's layer — and make the next
|
|
87
|
+
`model()` reload the service's own bundle. `records` is the server's alone (`PlanningUnsupported`).
|
|
88
|
+
`model(card)` of a card resolves its type in its project's layer (a project's own id, a card's
|
|
89
|
+
nearest primary project ancestor); a specification's type is always code's. A tree without `definitions` has none of this.
|
|
90
|
+
|
|
91
|
+
## Browser field validation under CSP
|
|
92
|
+
|
|
93
|
+
Bind `makePlanningFieldValidator(type.fields)` to the exact definition returned by the resolved
|
|
94
|
+
organization or project bundle. Its `validateFields(fields)` returns `{ valid, errors }`; errors
|
|
95
|
+
carry decoded `path`, `keyword`, `schemaPath` and a bounded value-free `message`. Construct one JSON
|
|
96
|
+
fields object, validate it and send that same object. No coercion, defaults or property removal
|
|
97
|
+
occurs, and the schema is left unchanged. Preserve nested money objects and calendar date strings.
|
|
98
|
+
The native server validates every write again.
|
|
99
|
+
|
|
100
|
+
The validator interprets schemas with `@cfworker/json-schema`, with no `eval`, `new Function` or
|
|
101
|
+
network reference loading. Do not call `makeSchemaRegistry(...).validator()` or
|
|
102
|
+
`.assigneeValidator()` in a browser with strict `script-src`: those remain native AJV compilers.
|
|
103
|
+
Static AJV standalone compilation is suitable only when the schema cannot change after building;
|
|
104
|
+
organization and project definitions require the runtime interpreter.
|
|
105
|
+
|
|
106
|
+
The supported contract is Draft 7's object/array/scalar, required, enum/const, ranges, string
|
|
107
|
+
length/pattern, dependencies, boolean, composition and conditional keywords; `$defs`, local/nested
|
|
108
|
+
`$id` references and AJV's `nullable` are also supported. `$ref` sibling constraints are enforced as
|
|
109
|
+
by native planning AJV. String formats use the same `ajv-formats` full-mode predicates, including
|
|
110
|
+
real calendar dates and timezone-aware times. `nullable` adds null to the declared type while
|
|
111
|
+
leaving enum and const constraints intact. All references must resolve inside the supplied field
|
|
112
|
+
schema; productive recursive schemas are supported, same-instance reference cycles are refused.
|
|
113
|
+
Plain JSON values and field names without dot or dollar characters are required.
|
|
114
|
+
|
|
115
|
+
Preparation throws `SchemaInvalid` for malformed schemas, unresolved references, unsupported
|
|
116
|
+
drafts/keywords/formats, numeric formats, `formatMinimum`/`formatMaximum` extensions and
|
|
117
|
+
`multipleOf`. The latter is refused because the interpreter's epsilon and native AJV's quotient
|
|
118
|
+
semantics disagree for fractional and very large numbers. Standard annotation keywords and `x-`
|
|
119
|
+
annotations do not add validation. Never discard a refused constraint, invent a substitute schema,
|
|
120
|
+
enable `unsafe-eval` or proceed with a ready form after preparation fails. Show the error and retain
|
|
121
|
+
disabled save controls. Validation reports at most 64 errors, with paths bounded to 512 characters.
|
|
122
|
+
|
|
123
|
+
## Mounting in a target
|
|
124
|
+
|
|
125
|
+
The web half of the target mount (`planning` → Mounting in a target).
|
|
126
|
+
|
|
127
|
+
- `appendPlanningClient(context, { protocols, bind: false, schemas: false })` — the target binds
|
|
128
|
+
its whole api tree itself, and a signed-out visitor may read nothing, so the bundle loads on
|
|
129
|
+
first use — plus `appendPlanningStores(context)`. No `socket`: the target serves no commit socket,
|
|
130
|
+
so `commits.wait` long-polls.
|
|
131
|
+
- Screens read the mirror with `useStoreList` / `useStoreModel` (`@owlmeans/client`), never a
|
|
132
|
+
fetched array kept in component state; lists reach the store through `syncHelper.syncCards` or
|
|
133
|
+
`makePlanningFeed` (with `refresh`), NEVER `replace()` — the one card store holds every kind.
|
|
134
|
+
- Every write is `facade.execute(...)` with a `key` (a retried click or a double submit answers
|
|
135
|
+
the first receipt) and `{ wait: true }` where the screen shows the result.
|
|
136
|
+
- Never call a route with an empty id; a picker offers no archived parent (`intrinsic: closed`).
|
|
137
|
+
|
|
78
138
|
## Reading
|
|
79
139
|
|
|
80
140
|
```typescript
|
|
81
141
|
const planning = context.planning().for()
|
|
82
142
|
|
|
83
|
-
const
|
|
84
|
-
const board = await planning.cards.summary([
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
143
|
+
const tools = await planning.cards.list({ parent: shedId, type: 'shed:tool', sort: ['order'], size: 0 })
|
|
144
|
+
const board = await planning.cards.summary([shedId])
|
|
145
|
+
const rules = await planning.specifications.current(shedId, 'house-rules')
|
|
146
|
+
const drill = await planning.model(drillId)
|
|
147
|
+
drill.available().map(rule => rule.name)
|
|
88
148
|
```
|
|
89
149
|
|
|
90
150
|
- The facade takes the rich `WorkcardQuery`; it encodes the scalar wire shape itself
|
|
91
|
-
(`encodeWorkcardQuery` and its twins). Never pass a wire shape to the facade.
|
|
151
|
+
(`wireHelper.encodeWorkcardQuery` and its twins). Never pass a wire shape to the facade.
|
|
92
152
|
- `cards.load(id)` answers `null` for a missing card — and for another entity's card, which the
|
|
93
153
|
server deliberately reports the same way.
|
|
94
154
|
- `cards.count(query)` is a one-row list read for its `total`; the tree declares no count route.
|
|
@@ -102,13 +162,14 @@ story.available().map(rule => rule.name)
|
|
|
102
162
|
share an id space on the server, so they share one store here: a store per kind lets a card list
|
|
103
163
|
that reloads drop the projects beside it.
|
|
104
164
|
|
|
105
|
-
That is also why a list reaches the store through **`syncCards(store, items, where)`,
|
|
106
|
-
`replace()`**: every card given is written, every card matching `where` that the list does not
|
|
107
|
-
is dropped, and everything outside `where` is left alone. An unchanged card is not rewritten, so a
|
|
165
|
+
That is also why a list reaches the store through **`syncHelper.syncCards(store, items, where)`,
|
|
166
|
+
never `replace()`**: every card given is written, every card matching `where` that the list does not
|
|
167
|
+
name is dropped, and everything outside `where` is left alone. An unchanged card is not rewritten, so a
|
|
108
168
|
periodic re-seed wakes no subscriber. `opts.keep` protects ids a commit wrote while the list was in
|
|
109
169
|
flight.
|
|
110
170
|
|
|
111
|
-
The fold rules, shared by `syncCards`, `applyCards
|
|
171
|
+
The fold rules, shared by `syncHelper.syncCards`, the mirror's `applyCards` and `applyCommitEvent`, and
|
|
172
|
+
`commits.wait`:
|
|
112
173
|
|
|
113
174
|
- an OLDER `seq` never overwrites a newer record — frames and list answers race;
|
|
114
175
|
- a committed `delete` removes the row and every link touching it; a project's delete also drops
|
|
@@ -124,8 +185,8 @@ The fold rules, shared by `syncCards`, `applyCards`, `applyCommitEvent` and `com
|
|
|
124
185
|
|
|
125
186
|
```typescript
|
|
126
187
|
const feed = makePlanningFeed(context, {
|
|
127
|
-
query: { parent:
|
|
128
|
-
filter: { project:
|
|
188
|
+
query: { parent: shedId, type: 'shed:tool' },
|
|
189
|
+
filter: { project: shedId },
|
|
129
190
|
refresh: 15_000,
|
|
130
191
|
onChange: state => setState(state),
|
|
131
192
|
})
|
|
@@ -135,11 +196,11 @@ await feed.stop() // stops folding; the shared socket stays open
|
|
|
135
196
|
```
|
|
136
197
|
|
|
137
198
|
1. Subscribes to commits FIRST (the socket, when an opener exists).
|
|
138
|
-
2. Seeds: `cards.list` (unpaged unless the query pages) → `syncCards` over `where`
|
|
139
|
-
(`criteriaOf(query)` by default). A PAGED seed only writes — a page cannot say what
|
|
140
|
-
exist.
|
|
141
|
-
3. Folds every frame with `applyCommitEvent`, and re-seeds every `refresh`
|
|
142
|
-
backstop to a socket that can drop frames.
|
|
199
|
+
2. Seeds: `cards.list` (unpaged unless the query pages) → `syncHelper.syncCards` over `where`
|
|
200
|
+
(`queryHelper.criteriaOf(query)` by default). A PAGED seed only writes — a page cannot say what
|
|
201
|
+
does not exist.
|
|
202
|
+
3. Folds every frame with `planningMirrorOf(stores).applyCommitEvent`, and re-seeds every `refresh`
|
|
203
|
+
ms — the authoritative backstop to a socket that can drop frames.
|
|
143
204
|
|
|
144
205
|
`connected` reports whether a socket was open when the feed subscribed; `seeded` tells "nothing
|
|
145
206
|
there" from "not loaded yet". A React hook wraps the feed in an effect and stops it on unmount.
|
|
@@ -181,22 +242,18 @@ A Node client passes no opener and relies on the long poll.
|
|
|
181
242
|
|
|
182
243
|
## Optimistic writes and `head > seq`
|
|
183
244
|
|
|
184
|
-
`seq` is the last transition folded into a card, `head` the highest allocated.
|
|
185
|
-
(run by `execute` whenever stores are registered) raises the stored card's `head` to
|
|
186
|
-
transition's `seq`, so `model.pending()` is true before any frame arrives; the commit's fold
|
|
187
|
-
`seq` up to it. The mirror only ever grows `head`, so a list fetched before the append does not
|
|
188
|
-
clear the marker. A model's
|
|
189
|
-
gets `WorkcardConflict` — reload and retry.
|
|
245
|
+
`seq` is the last transition folded into a card, `head` the highest allocated. The mirror's
|
|
246
|
+
`applyReceipt` (run by `execute` whenever stores are registered) raises the stored card's `head` to
|
|
247
|
+
the new transition's `seq`, so `model.pending()` is true before any frame arrives; the commit's fold
|
|
248
|
+
brings `seq` up to it. The mirror only ever grows `head`, so a list fetched before the append does not
|
|
249
|
+
clear the marker. A model's `expectSeq` default and its `WorkcardConflict` are `planning` → Models.
|
|
190
250
|
|
|
191
251
|
## Gotchas
|
|
192
252
|
|
|
193
253
|
- Mount a feed once per screen; read the store everywhere else.
|
|
194
|
-
- Do not `replace()` the card store — it holds every kind. Use `syncCards` with a `where`.
|
|
195
254
|
- A refusal crosses the hop as its class (`IllegalTransition`, `WorkcardConflict`,
|
|
196
255
|
`FieldsInvalid`, a plugin's own class) — branch on `instanceof`, never on message text.
|
|
197
256
|
- `CommitTimeout` is not a failure of the transition; re-wait or re-read the card.
|
|
198
|
-
- The scope passed to `for()` changes nothing the server decides; it exists so code written against
|
|
199
|
-
`PlanningFacade` reads the same on both sides.
|
|
200
257
|
|
|
201
258
|
## Depends On
|
|
202
259
|
|
|
@@ -204,9 +261,64 @@ gets `WorkcardConflict` — reload and retry.
|
|
|
204
261
|
- `@owlmeans/client-entrypoint` — `bindAll`, the typed `call()`
|
|
205
262
|
- `@owlmeans/state` — the mirror; `@owlmeans/socket` — `Connection`
|
|
206
263
|
|
|
264
|
+
## Auxiliary mirrors and authenticated scope lifecycle
|
|
265
|
+
|
|
266
|
+
With `resources: true` in the shared protocol tree, every remote facade exposes the complete
|
|
267
|
+
assignee/team/comment/mention API, including versioned writes, team membership and project
|
|
268
|
+
attachment. `appendPlanningStores` registers separate stock typed mirrors for all four resources.
|
|
269
|
+
`makePlanningResourceFeed` seeds and polls selected resource queries; use it with the stock stores
|
|
270
|
+
and `useStoreList` / `useStoreModel`. Auxiliary writes update the mirror only after success.
|
|
271
|
+
Comments use trusted server authors; mentions are read-only derived cache commands.
|
|
272
|
+
|
|
273
|
+
Supply `PlanningClientOptions.scopeKey` from authenticated organization entity slug, profile/user
|
|
274
|
+
id, session id and authorization revision; return undefined when signed out. Before switching
|
|
275
|
+
organization or signing out, **await** `service.close()` before adopting the credential. It cancels
|
|
276
|
+
requests and commit waits, invalidates receipts (including already-settled receipts), stops every
|
|
277
|
+
registered feed, removes socket subscriptions, closes the carrier and rejects late socket openers.
|
|
278
|
+
A stale operation answers `client:scope-changed`; cancellation never rolls back an accepted server
|
|
279
|
+
write. A new subscription opens a fresh carrier after close.
|
|
280
|
+
|
|
281
|
+
Close drains admitted **local** mirror writes before clearing every stock store and schema cache.
|
|
282
|
+
New calls during an explicit close are refused, including hosts without `scopeKey`. Concurrent
|
|
283
|
+
closes share the boundary. If clearing fails, close rejects and fresh work stays blocked; retain
|
|
284
|
+
the old credential and retry close explicitly. An observed identity change also cancels old work
|
|
285
|
+
and queues clearing before the new identity's mirror writes. Reload schemas and create fresh feeds
|
|
286
|
+
after credential adoption. Feed query scope alone is insufficient for this lifecycle.
|
|
287
|
+
|
|
288
|
+
The service's `lifecycle` coordinates stock facades, receipts, feeds and schema writes. A custom
|
|
289
|
+
mirror integration captures an operation with `lifecycle.run`, performs remote reads through
|
|
290
|
+
`operation.wait` with `operation.signal`, then uses `lifecycle.mutate(operation, write)` for local
|
|
291
|
+
writes; never hold or nest that queue over a request, socket opener or another queued mutation.
|
|
292
|
+
`planningMirrorOf(stores)` accepts `{ lifecycle, operation }` as its optional last argument and
|
|
293
|
+
fetches missing frame records/links before entering the queue. Direct mirror/sync helpers without
|
|
294
|
+
that option are local primitives: their caller owns isolation and cleanup.
|
|
295
|
+
|
|
296
|
+
Planning protocols return `PlanningReply<T>` without storage organization identifiers. Stock
|
|
297
|
+
facades hydrate records, receipts, schema scopes and socket frames with `entityId: ''` only to
|
|
298
|
+
preserve the shared facade shape. That value is an advisory placeholder, never an organization
|
|
299
|
+
identity or a credential. Use `scopeKey` and the authenticated organization entity slug for
|
|
300
|
+
identity changes and access isolation; native server records keep their verified stable scope.
|
|
301
|
+
|
|
302
|
+
Resource feed reconciliation snapshots mirror versions before requesting a list. Only unchanged
|
|
303
|
+
snapshot rows absent from that response are removed, so a new local write or a newer version
|
|
304
|
+
survives an older in-flight poll. Stopped feeds cannot publish. Track custom feeds through
|
|
305
|
+
`registerFeed` when their lifecycle must end with the planning service. Card feeds use the same
|
|
306
|
+
identity fence and stop registration; component cleanup awaits `stop()` when it must observe a
|
|
307
|
+
completed boundary. Stop cancels unresolved seeds and drains admitted local changes without waiting
|
|
308
|
+
for the network; neither late callbacks nor authoritative deletion passes may publish afterward.
|
|
309
|
+
A schema write from an old generation cannot invalidate a newer generation's cached registry.
|
|
310
|
+
|
|
311
|
+
|
|
207
312
|
## Related
|
|
208
313
|
|
|
209
314
|
- `planning` — records, flows, the fold, the models
|
|
210
315
|
- `server-planning` — the handlers this addresses, the executor, the commit hub
|
|
211
316
|
- `client-job` — the same seed-then-fold shape for safe application-job views
|
|
212
|
-
- `state` — `syncCards` is built on its criteria engine and `purge`
|
|
317
|
+
- `state` — `syncHelper.syncCards` is built on its criteria engine and `purge`
|
|
318
|
+
|
|
319
|
+
## External docs
|
|
320
|
+
|
|
321
|
+
- https://ajv.js.org/security.html#content-security-policy — runtime AJV requires unsafe-eval; standalone build compilation preserves strict CSP for static schemas.
|
|
322
|
+
- https://www.npmjs.com/package/@cfworker/json-schema/v/4.1.1 — the interpreter supports dynamic schemas without code generation; select an explicit vocabulary and check its known limitations (4.1.1).
|
|
323
|
+
- https://github.com/ajv-validator/ajv-formats#full-and-fast-validation-modes — full-mode predicates validate formats completely and are reusable through the plugin's `get` API (3.0.1).
|
|
324
|
+
- https://ajv.js.org/json-schema.html#nullable-openapi — nullable expands the declared type without adding null to enum or const.
|
package/build/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,MAAM,EAAE,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,kBAAkB,EAClF,MAAM,YAAY,CAAA;AAEnB;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,WACrE,CAAC,WAAW,qBAAqB,KACzC,qBAoHF,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,WAChE,CAAC,WAAW,qBAAqB,KACzC,CAAC,GAAG,kBA0BN,CAAA"}
|
package/build/client.js
CHANGED
|
@@ -2,8 +2,10 @@ import { bindAll } from '@owlmeans/client-entrypoint';
|
|
|
2
2
|
import { createLazyService, MiddlewareStage, MiddlewareType } from '@owlmeans/context';
|
|
3
3
|
import { DEFAULT_PLUGIN_ORDER, makeSchemaRegistry, PLANNING_SERVICE, PlanningUnsupported, } from '@owlmeans/planning';
|
|
4
4
|
import { makeRemoteCommitSource } from './commits.js';
|
|
5
|
+
import { makeRemoteDefinitions } from './definitions.js';
|
|
5
6
|
import { makeRemoteFacade } from './facade.js';
|
|
6
|
-
import {
|
|
7
|
+
import { planningContextOf } from './helper.js';
|
|
8
|
+
import { makePlanningClientLifecycle } from './lifecycle.js';
|
|
7
9
|
/**
|
|
8
10
|
* The client planning service: a remote facade factory under the SAME alias the server registers
|
|
9
11
|
* its service under, so `context.planning().for(scope)` is one call on both sides.
|
|
@@ -14,30 +16,51 @@ import { planningStoresOf } from './stores.js';
|
|
|
14
16
|
* bundle.
|
|
15
17
|
*/
|
|
16
18
|
export const makePlanningClientService = (context, options) => {
|
|
19
|
+
const feeds = new Set();
|
|
17
20
|
const registry = makeSchemaRegistry();
|
|
18
21
|
const plugins = [];
|
|
19
|
-
const stores = () =>
|
|
22
|
+
const stores = () => planningContextOf(context).stores();
|
|
23
|
+
const lifecycle = makePlanningClientLifecycle({ scopeKey: options.scopeKey, clear: async () => {
|
|
24
|
+
const mirrors = stores();
|
|
25
|
+
if (mirrors != null)
|
|
26
|
+
for (const resource of Object.values(mirrors)) {
|
|
27
|
+
const rows = await resource.list({}, { size: 0 });
|
|
28
|
+
for (const row of rows.items)
|
|
29
|
+
await resource.delete(row.id);
|
|
30
|
+
}
|
|
31
|
+
} });
|
|
32
|
+
const scopeKey = lifecycle.key;
|
|
20
33
|
const commits = makeRemoteCommitSource(context, options.protocols, {
|
|
21
|
-
socket: options.socket, poll: options.poll, timeout: options.timeout, stores,
|
|
34
|
+
socket: options.socket, poll: options.poll, timeout: options.timeout, stores, lifecycle,
|
|
22
35
|
});
|
|
23
36
|
const layerPlugins = () => plugins.forEach(plugin => {
|
|
37
|
+
plugin.schemas?.assigneeTypes?.forEach(registry.registerAssigneeType);
|
|
24
38
|
plugin.schemas?.flows?.forEach(registry.registerFlow);
|
|
25
39
|
plugin.schemas?.types?.forEach(registry.registerType);
|
|
26
40
|
});
|
|
41
|
+
let loadedKey = scopeKey();
|
|
27
42
|
let loaded = false;
|
|
28
43
|
let loading = null;
|
|
29
44
|
const loadSchemas = async (opts) => {
|
|
45
|
+
const key = scopeKey();
|
|
46
|
+
if (loadedKey !== key) {
|
|
47
|
+
loaded = false;
|
|
48
|
+
loading = null;
|
|
49
|
+
definitions?.invalidate();
|
|
50
|
+
loadedKey = key;
|
|
51
|
+
}
|
|
30
52
|
if (loaded && opts?.force !== true) {
|
|
31
53
|
return registry;
|
|
32
54
|
}
|
|
33
55
|
if (loading == null || opts?.force === true) {
|
|
34
|
-
const attempt = (async () => {
|
|
35
|
-
const bundle = await context.entrypoint(options.protocols.schema.list).call({ timeout: options.timeout });
|
|
56
|
+
const attempt = lifecycle.run(async (operation) => {
|
|
57
|
+
const bundle = await operation.wait(context.entrypoint(options.protocols.schema.list).call({ timeout: options.timeout, signal: operation.signal }));
|
|
58
|
+
operation.check();
|
|
36
59
|
registry.load(bundle);
|
|
37
60
|
layerPlugins();
|
|
38
61
|
loaded = true;
|
|
39
62
|
return registry;
|
|
40
|
-
})
|
|
63
|
+
});
|
|
41
64
|
loading = attempt;
|
|
42
65
|
// A failed load is forgotten, so the next caller asks again instead of inheriting the error.
|
|
43
66
|
attempt.catch(() => {
|
|
@@ -48,13 +71,31 @@ export const makePlanningClientService = (context, options) => {
|
|
|
48
71
|
}
|
|
49
72
|
return await loading;
|
|
50
73
|
};
|
|
74
|
+
// A write of data-defined schemas moves the organization's bundle too: the next `model()` reloads it.
|
|
75
|
+
const definitions = options.protocols.schema.define == null
|
|
76
|
+
? undefined
|
|
77
|
+
: makeRemoteDefinitions(context, options.protocols, { lifecycle, timeout: options.timeout, onWrite: () => { loaded = false; loading = null; } });
|
|
51
78
|
const facadeFor = (scope) => makeRemoteFacade(context, options.protocols, { ...options.scope, ...scope }, {
|
|
52
|
-
commits, schemas: registry, loadSchemas, timeout: options.timeout, stores,
|
|
79
|
+
commits, schemas: registry, loadSchemas, scopeKey, lifecycle, timeout: options.timeout, stores,
|
|
80
|
+
...(definitions != null ? { definitions } : {}),
|
|
53
81
|
});
|
|
54
82
|
let fallback = null;
|
|
83
|
+
lifecycle.onInvalidate(async () => {
|
|
84
|
+
const stopping = [...feeds];
|
|
85
|
+
feeds.clear();
|
|
86
|
+
loaded = false;
|
|
87
|
+
loading = null;
|
|
88
|
+
definitions?.invalidate();
|
|
89
|
+
registry.load({ version: 1, types: [], flows: [] });
|
|
90
|
+
layerPlugins();
|
|
91
|
+
await Promise.all(stopping.map(async (feed) => await feed.stop()));
|
|
92
|
+
});
|
|
55
93
|
// Lazy, like the server's host: `context.planning()` answers before the context initializes.
|
|
56
94
|
return createLazyService(PLANNING_SERVICE, {
|
|
57
95
|
schemas: registry,
|
|
96
|
+
lifecycle,
|
|
97
|
+
scopeKey,
|
|
98
|
+
registerFeed: feed => { feeds.add(feed); return () => { feeds.delete(feed); }; },
|
|
58
99
|
commits,
|
|
59
100
|
loadSchemas,
|
|
60
101
|
use: plugin => {
|
|
@@ -77,7 +118,8 @@ export const makePlanningClientService = (context, options) => {
|
|
|
77
118
|
committed: async () => {
|
|
78
119
|
throw new PlanningUnsupported('client:committed');
|
|
79
120
|
},
|
|
80
|
-
close:
|
|
121
|
+
close: lifecycle.close,
|
|
122
|
+
...(definitions != null ? { definitions } : {}),
|
|
81
123
|
});
|
|
82
124
|
};
|
|
83
125
|
/**
|
package/build/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEtF,OAAO,EACL,oBAAoB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,mBAAmB,GAChF,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/build/commits.d.ts
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import type { BasicConfig, BasicContext } from '@owlmeans/context';
|
|
2
|
-
import type
|
|
2
|
+
import { type PlanningProtocols } from '@owlmeans/planning';
|
|
3
3
|
import type { RemoteCommitSource, RemoteCommitSourceOptions } from './types.js';
|
|
4
|
-
/**
|
|
5
|
-
* The commit source of a remote facade: the `commit.get` long poll and, when the host injects a
|
|
6
|
-
* socket opener, the `commit.events` feed.
|
|
7
|
-
*
|
|
8
|
-
* `wait` SUBSCRIBES FIRST and only then reads the status — a commit landing between a read and a
|
|
9
|
-
* subscription would otherwise be missed until the deadline. It then long-polls in holds of
|
|
10
|
-
* `min(poll, remaining)` seconds, so a client with no socket (a Node connector) still learns of a
|
|
11
|
-
* commit within one round trip of it landing, and a client with one learns from whichever arrives
|
|
12
|
-
* first. The deadline is a {@link CommitTimeout}; the transition stays pending — nothing is undone.
|
|
13
|
-
*
|
|
14
|
-
* ONE socket serves every subscription of the source; releasing a subscription never closes it.
|
|
15
|
-
*/
|
|
4
|
+
/** Subscribe before reading, then race the shared socket against bounded HTTP holds. */
|
|
16
5
|
export declare const makeRemoteCommitSource: <C extends BasicConfig, T extends BasicContext<C>>(context: T, protocols: PlanningProtocols, opts?: RemoteCommitSourceOptions) => RemoteCommitSource;
|
|
17
6
|
//# sourceMappingURL=commits.d.ts.map
|
package/build/commits.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commits.d.ts","sourceRoot":"","sources":["../src/commits.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"commits.d.ts","sourceRoot":"","sources":["../src/commits.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,EAA2O,KAAK,iBAAiB,EAAmC,MAAM,oBAAoB,CAAA;AAKrU,OAAO,KAAK,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA;AAM/E,wFAAwF;AACxF,eAAO,MAAM,sBAAsB,GAAI,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,WAC5E,CAAC,aAAa,iBAAiB,SAAS,yBAAyB,KACzE,kBAsMF,CAAA"}
|