@owlmeans/server-planning 0.1.18-rc.12 → 0.1.18-rc.13
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 +21 -9
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/server-planning/SKILL.md +76 -14
- package/build/actions/execute.d.ts +10 -2
- package/build/actions/execute.d.ts.map +1 -1
- package/build/actions/execute.js +28 -4
- package/build/actions/execute.js.map +1 -1
- package/build/actions/schemas.d.ts +26 -3
- package/build/actions/schemas.d.ts.map +1 -1
- package/build/actions/schemas.js +63 -7
- package/build/actions/schemas.js.map +1 -1
- package/build/conformance/assert.d.ts +20 -0
- package/build/conformance/assert.d.ts.map +1 -0
- package/build/conformance/assert.js +54 -0
- package/build/conformance/assert.js.map +1 -0
- package/build/conformance/cases.d.ts +22 -0
- package/build/conformance/cases.d.ts.map +1 -0
- package/build/conformance/cases.js +392 -0
- package/build/conformance/cases.js.map +1 -0
- package/build/conformance/fixtures.d.ts +31 -0
- package/build/conformance/fixtures.d.ts.map +1 -0
- package/build/conformance/fixtures.js +147 -0
- package/build/conformance/fixtures.js.map +1 -0
- package/build/conformance/index.d.ts +10 -0
- package/build/conformance/index.d.ts.map +1 -0
- package/build/conformance/index.js +10 -0
- package/build/conformance/index.js.map +1 -0
- package/build/consts.d.ts +5 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +5 -0
- package/build/consts.js.map +1 -1
- package/build/definitions.d.ts +33 -0
- package/build/definitions.d.ts.map +1 -0
- package/build/definitions.js +211 -0
- package/build/definitions.js.map +1 -0
- package/build/executor/resolve.d.ts +16 -3
- package/build/executor/resolve.d.ts.map +1 -1
- package/build/executor/resolve.js +68 -11
- package/build/executor/resolve.js.map +1 -1
- package/build/executor/validate.d.ts.map +1 -1
- package/build/executor/validate.js +11 -11
- package/build/executor/validate.js.map +1 -1
- package/build/executor.d.ts.map +1 -1
- package/build/executor.js +2 -3
- package/build/executor.js.map +1 -1
- package/build/facade.d.ts +14 -1
- package/build/facade.d.ts.map +1 -1
- package/build/facade.js +92 -18
- package/build/facade.js.map +1 -1
- package/build/helper.d.ts +3 -2
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +7 -3
- package/build/helper.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/service.d.ts +10 -1
- package/build/service.d.ts.map +1 -1
- package/build/service.js +9 -0
- package/build/service.js.map +1 -1
- package/build/store/composite.d.ts +1 -0
- package/build/store/composite.d.ts.map +1 -1
- package/build/store/composite.js +3 -0
- package/build/store/composite.js.map +1 -1
- package/build/store/fold.d.ts +3 -1
- package/build/store/fold.d.ts.map +1 -1
- package/build/store/fold.js +8 -2
- package/build/store/fold.js.map +1 -1
- package/build/store/memory.d.ts +12 -0
- package/build/store/memory.d.ts.map +1 -1
- package/build/store/memory.js +0 -0
- package/build/store/memory.js.map +1 -1
- package/build/store/types.d.ts +15 -1
- package/build/store/types.d.ts.map +1 -1
- package/build/types.d.ts +37 -0
- package/build/types.d.ts.map +1 -1
- package/build/utils/scope.d.ts +28 -1
- package/build/utils/scope.d.ts.map +1 -1
- package/build/utils/scope.js +54 -15
- package/build/utils/scope.js.map +1 -1
- package/package.json +15 -8
- package/src/actions/execute.ts +36 -8
- package/src/actions/schemas.ts +81 -9
- package/src/conformance/assert.ts +59 -0
- package/src/conformance/cases.ts +483 -0
- package/src/conformance/fixtures.ts +168 -0
- package/src/conformance/index.ts +9 -0
- package/src/consts.ts +6 -0
- package/src/definitions.ts +282 -0
- package/src/executor/resolve.ts +84 -14
- package/src/executor/validate.ts +13 -11
- package/src/executor.ts +2 -3
- package/src/facade.ts +106 -19
- package/src/helper.ts +7 -3
- package/src/index.ts +1 -0
- package/src/service.ts +23 -1
- package/src/store/composite.ts +3 -0
- package/src/store/fold.ts +8 -2
- package/src/store/memory.ts +0 -0
- package/src/store/types.ts +16 -1
- package/src/types.ts +41 -0
- package/src/utils/scope.ts +75 -16
- package/tests/access.spec.ts +160 -0
- package/tests/conformance.spec.ts +47 -0
- package/tests/entrypoints.spec.ts +5 -0
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ the `@owlmeans/server-planning/store` subpath.
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
|
12
12
|
```sh
|
|
13
|
-
bun add @owlmeans/server-planning@^0.1.18-rc.
|
|
13
|
+
bun add @owlmeans/server-planning@^0.1.18-rc.13 @owlmeans/planning@^0.1.18-rc.11 ajv
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
## Concepts
|
|
@@ -105,15 +105,23 @@ const cards = await planningFor(ctx, req, { channel: 'web' }).cards.list({ paren
|
|
|
105
105
|
- Handlers: `servePlanningEntrypoints`, `planningFor`, `listSchemas`, `listCards`,
|
|
106
106
|
`summarizeCards`, `getCard`, `listCardTransitions`, `listCardSpecifications`, `getSpecification`,
|
|
107
107
|
`listSpecificationRevisions`, `listLinks`, `getTransition`, `executePlanning`, `wireExecution`,
|
|
108
|
-
`executeOptionsOf`, `getCommit`, `watchCommits`, `
|
|
109
|
-
|
|
110
|
-
`
|
|
108
|
+
`executeOptionsOf`, `assertExecutionGranted`, `getCommit`, `watchCommits`, `defineSchemas`,
|
|
109
|
+
`applySchemaRequest`, `PlanningHandlerOptions`, `PlanningScopeExtractor`, `PlanningAccessResolver`,
|
|
110
|
+
`PlanningAccess`, `PlanningAccessGrants`, `PlanningGrant`
|
|
111
|
+
- Scope: `scopeOf`, `accessScopeOf`, `actorOf`, `handlerFacade`, `handlerScopeOf`, `assertGranted`,
|
|
112
|
+
`planningServiceOf`, `concealed`, `assertScope`, `notFoundOf`, `clampSeconds`, `projectCriteriaOf`
|
|
113
|
+
- Data-defined schemas: `makeDefinitions`, `makeSchemaViews`, `SchemaViews`, `SchemaViewsOptions`
|
|
111
114
|
- Projection: `makeProjectionProcessor`, `planningQueueHooks`, `ProjectionOptions`
|
|
112
|
-
- Constants: `DEFAULT_ALIAS`, `MEMORY_STORE_ALIAS`, `DEFAULT_COMMIT_MEMORY`, `COMMIT_POLL_LADDER
|
|
115
|
+
- Constants: `DEFAULT_ALIAS`, `MEMORY_STORE_ALIAS`, `DEFAULT_COMMIT_MEMORY`, `COMMIT_POLL_LADDER`,
|
|
116
|
+
`DEFAULT_SCHEMA_VIEWS`
|
|
113
117
|
- `@owlmeans/server-planning/store`: `makeMemoryPlanningStore`, `foldPending`, `failPending`,
|
|
114
|
-
`revisionsFromLog`, `commitEventOf`, `makeCommitHub`, `makeCompositeStore`,
|
|
115
|
-
`BindablePlanningStore`, `CommitListener`, `CommitHub`, `CommitHubOptions`,
|
|
116
|
-
`FoldResult`, `MemoryPlanningStore`, `MemoryPlanningStoreOptions`, `StoreRoute`
|
|
118
|
+
`revisionsFromLog`, `commitEventOf`, `makeCommitHub`, `makeCompositeStore`, `wantsSpecifications`,
|
|
119
|
+
and the types `BindablePlanningStore`, `CommitListener`, `CommitHub`, `CommitHubOptions`,
|
|
120
|
+
`FoldOptions`, `FoldResult`, `MemoryPlanningStore`, `MemoryPlanningStoreOptions`, `StoreRoute`
|
|
121
|
+
- `@owlmeans/server-planning/conformance`: `planningConformance`, `conformanceCasesFor`,
|
|
122
|
+
`planningConformancePlugin`, `conformanceClock`, `ConformanceFailure`, `check`, `same`,
|
|
123
|
+
`sameSet`, `rejects`, the library fixtures (`LIBRARY`, `createBranch`, `createBook`, `transit`)
|
|
124
|
+
and the types `ConformanceCase`, `ConformanceSubject`, `ConformanceCapability`
|
|
117
125
|
|
|
118
126
|
## Common pitfalls
|
|
119
127
|
|
|
@@ -126,11 +134,15 @@ const cards = await planningFor(ctx, req, { channel: 'web' }).cards.list({ paren
|
|
|
126
134
|
- `expectSeq` compares against `head`; a stale one is `WorkcardConflict`, re-read and retry.
|
|
127
135
|
- Call `appendPlanningService` before any `ensurePlanningService`, or the appended host replaces
|
|
128
136
|
what was registered on the default one.
|
|
137
|
+
- `opts.access` makes the resolver the only source of the organization; a `grants` object refuses
|
|
138
|
+
every flag it leaves out.
|
|
139
|
+
- A durable store is not done until it passes `@owlmeans/server-planning/conformance`.
|
|
129
140
|
|
|
130
141
|
## Related packages
|
|
131
142
|
|
|
132
143
|
- `@owlmeans/planning` — records, flows, fold, query language, protocol tree, models
|
|
133
144
|
- `@owlmeans/client-planning` — the remote facade and state mirror
|
|
145
|
+
- `@owlmeans/planning-postgres` — the durable Postgres store
|
|
134
146
|
- `@owlmeans/server-job`, `@owlmeans/queue` — job feeds and the projection queue
|
|
135
147
|
|
|
136
148
|
<!-- owlmeans:agent-guidance:start -->
|
|
@@ -141,7 +153,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
141
153
|
your project's skill store (`.agents/skills/`):
|
|
142
154
|
|
|
143
155
|
```sh
|
|
144
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
156
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.41
|
|
145
157
|
```
|
|
146
158
|
|
|
147
159
|
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/server-planning",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.13",
|
|
5
|
+
"generatedAt": "2026-09-28T02:06:37.869Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -8,7 +8,7 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/server-planning
|
|
9
9
|
|
|
10
10
|
**Layer:** Server
|
|
11
|
-
**Install:** `"@owlmeans/server-planning": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/server-planning": "^0.1.18-rc.13"` in `dependencies` (`ajv` is a peer)
|
|
12
12
|
|
|
13
13
|
The general implementation of `@owlmeans/planning`: the planning service (a plugin host), the
|
|
14
14
|
scoped facade, the executor every write goes through, the in-memory reference store, the commit
|
|
@@ -24,13 +24,16 @@ reuses the `./store` subpath (`foldPending`, `makeCommitHub`) without pulling fa
|
|
|
24
24
|
| `makePlanningService(opts?, alias?)`, `planningServiceApi(opts, self)` | The service, and its body for a specialised service |
|
|
25
25
|
| `PlanningServiceOptions` | `{ store?, plugins?, schemas?, hooks?, ids?, now? }` |
|
|
26
26
|
| `makePluginRegistry`, `makeStoreFacade`, `executeTransition` | The pieces the service is made of |
|
|
27
|
-
| `servePlanningEntrypoints(protocols, opts?)` | One binding per protocol of a `makePlanningProtocols` tree |
|
|
27
|
+
| `servePlanningEntrypoints(protocols, opts?)` | One binding per protocol of a `makePlanningProtocols` tree (`schema.define` too when declared) |
|
|
28
28
|
| `planningFor(ctx, req, extra?)` | The request-scoped facade for a hand-written handler |
|
|
29
|
-
| `PlanningHandlerOptions` | `{ service?, event?, maxPoll?, scope?(req, ctx) }` |
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
29
|
+
| `PlanningHandlerOptions` | `{ service?, event?, maxPoll?, scope?(req, ctx), access?(req, ctx) }` |
|
|
30
|
+
| `PlanningAccessResolver`, `PlanningAccess`, `PlanningAccessGrants`, `PlanningGrant` | The optional access decision a hosting app supplies |
|
|
31
|
+
| `listCards` … `executePlanning`, `getCommit`, `watchCommits`, `listSchemas`, `defineSchemas`, `applySchemaRequest` | The handlers, to bind one by hand |
|
|
32
|
+
| `scopeOf(req, extra?)`, `accessScopeOf(req, access, extra?)`, `handlerScopeOf`, `assertGranted`, `actorOf(req)`, `concealed(run)`, `clampSeconds` | Scope and security helpers |
|
|
33
|
+
| `makeDefinitions(runtime, facade)`, `makeSchemaViews(opts)`, `projectCriteriaOf(projects)` | Data-defined schemas and the project narrowing |
|
|
32
34
|
| `makeProjectionProcessor(ctx, opts?)`, `planningQueueHooks(ctx, opts?)` | A generic projection job body and its `onJobDead` |
|
|
33
|
-
| `./store`: `makeMemoryPlanningStore`, `foldPending`, `failPending`, `revisionsFromLog`, `commitEventOf`, `makeCommitHub`, `makeCompositeStore` | What stores are built from |
|
|
35
|
+
| `./store`: `makeMemoryPlanningStore`, `foldPending`, `failPending`, `revisionsFromLog`, `commitEventOf`, `makeCommitHub`, `makeCompositeStore`, `wantsSpecifications` | What stores are built from |
|
|
36
|
+
| `./conformance`: `planningConformance`, `conformanceCasesFor`, `planningConformancePlugin`, `conformanceClock`, `ConformanceFailure` | The store conformance suite, runner-agnostic |
|
|
34
37
|
|
|
35
38
|
## Wiring
|
|
36
39
|
|
|
@@ -69,9 +72,15 @@ Nothing is appended before step 11 — every refusal leaves the log untouched.
|
|
|
69
72
|
2. **Scope** — `entityId` must be present.
|
|
70
73
|
3. **Idempotency** — a `key` already in the entity's log answers ITS receipt, before any
|
|
71
74
|
validation or middleware.
|
|
72
|
-
4. **Resolve** — the card (`WorkcardNotFound`; another entity's card is `PlanningScopeMismatch
|
|
73
|
-
its type and flow, every draft parent
|
|
74
|
-
`
|
|
75
|
+
4. **Resolve** — the card (`WorkcardNotFound`; another entity's card is `PlanningScopeMismatch`;
|
|
76
|
+
one outside the scope's `projects` is `WorkcardNotFound`), its type and flow, every draft parent
|
|
77
|
+
(`ParentNotFound` — loaded through the facade, so an invisible parent is absent) and the parent
|
|
78
|
+
project's `cardTypes`/`projectTypes` (`CardTypeNotAllowed`), the specification slot. Where the
|
|
79
|
+
default store holds data-defined schemas, the card's project is found first and everything
|
|
80
|
+
after reads that project's resolved layer (`Resolved.schemas`): a data-defined type of a
|
|
81
|
+
project with `scopedCardTypes` is admitted, a retired type creates nothing
|
|
82
|
+
(`UnknownWorkcardType('retired:…')`). Without the port `Resolved.schemas` IS the code registry
|
|
83
|
+
and the order is unchanged.
|
|
75
84
|
5. **`before` chain** — in plugin order; re-resolved once when it changed the card, type or parent.
|
|
76
85
|
6. **Validate** — shape (`planning:malformed:*`), the flow rule (`IllegalTransition`), immutables
|
|
77
86
|
(`planning:immutable:*`), merged `fields` (`FieldsInvalid`), labels (`LabelNotAllowed`), a moved
|
|
@@ -125,11 +134,15 @@ the store; `bind` replaces the listener, so a store has exactly one.
|
|
|
125
134
|
|
|
126
135
|
## The memory store
|
|
127
136
|
|
|
128
|
-
`makeMemoryPlanningStore({ sync?, ids?, now?, seed?, onCommitted?, alias? })`
|
|
137
|
+
`makeMemoryPlanningStore({ sync?, ids?, now?, seed?, onCommitted?, alias?, schemas? })`
|
|
129
138
|
|
|
130
139
|
- Cards and projects in one map, specifications in their own, relationships in a third; every read
|
|
131
140
|
goes through the `@owlmeans/resource` query engine. A card list includes specifications only when
|
|
132
|
-
the criteria asks for `kind: specification` or a `category
|
|
141
|
+
the criteria asks for `kind: specification` or a `category` (`wantsSpecifications`, the rule every
|
|
142
|
+
store routes by).
|
|
143
|
+
- `schemas: true` adds the data-defined schema port (records per layer, a revision per
|
|
144
|
+
organization, synchronous `watch`); off by default, so a plain memory store resolves everything
|
|
145
|
+
in code exactly as before.
|
|
133
146
|
- `sync` (default) folds, publishes and runs `committed` inside `project()`, serialized per card.
|
|
134
147
|
`sync: false` folds nothing until `flush(card?)` — a queued store's shape, for tests that need a
|
|
135
148
|
commit to stay pending.
|
|
@@ -155,7 +168,20 @@ the store; `bind` replaces the listener, so a store has exactly one.
|
|
|
155
168
|
no card to advance, and a store that refuses the advancing write stops the fold with `followUp`,
|
|
156
169
|
leaving the rest pending for a retry. `revisionsFromLog` replays past failed transitions the same
|
|
157
170
|
way.
|
|
171
|
+
- A transactional store passes `FoldOptions.unit` — it wraps each transition's writes (a
|
|
172
|
+
savepoint), so a write the database refuses is undone whole and the fold marks that transition
|
|
173
|
+
failed and goes past it. `foldPending` folds PAST a failure it produces; a row that is ALREADY
|
|
174
|
+
failed at the cursor, or a gap in the seqs, is the store's to step over before calling it
|
|
175
|
+
(`planning-postgres` does it in a prelude).
|
|
158
176
|
- `failPending` is what a dead projection job must do, or waiters hang to their timeout.
|
|
177
|
+
- The data-defined schema port (`schemas`) is optional; `makeCompositeStore` passes the default
|
|
178
|
+
store's through. A store that has it must purge a project's layer with the project.
|
|
179
|
+
- **Run the conformance suite.** `@owlmeans/server-planning/conformance` exports
|
|
180
|
+
`planningConformance` — named cases with no test runner inside (each throws
|
|
181
|
+
`ConformanceFailure`) — and `conformanceCasesFor(store)`, which drops the data-defined cases for a
|
|
182
|
+
store without the port. Boot a service with `planningConformancePlugin` and `conformanceClock()`
|
|
183
|
+
and hand each case `{ service, store, facade }`; every case works in its own organization, so one
|
|
184
|
+
boot serves them all.
|
|
159
185
|
- `makeCommitHub({ status, remember?, ladder? })` is the `CommitSource`: feed `publish` from the bus,
|
|
160
186
|
answer `status` from transition rows. `wait` subscribes first, polls once, then climbs the ladder.
|
|
161
187
|
- `revisionsFromLog(transitions, limit?)` answers `specs.revisions`.
|
|
@@ -180,24 +206,60 @@ mapping external records (`mappers`) belongs to that plugin's own store adapter.
|
|
|
180
206
|
|
|
181
207
|
## Scope and security
|
|
182
208
|
|
|
183
|
-
- `entityId` is `requireEntityKey(req)`; `opts.scope(req, ctx)` adds a
|
|
184
|
-
can never replace the entity. `actor` and a create's `createdBy` come
|
|
185
|
-
body.
|
|
209
|
+
- Without `opts.access`: `entityId` is `requireEntityKey(req)`; `opts.scope(req, ctx)` adds a
|
|
210
|
+
`channel` or `service` and can never replace the entity. `actor` and a create's `createdBy` come
|
|
211
|
+
from the request, never the body.
|
|
186
212
|
- Another entity's card, specification or transition answers `WorkcardNotFound` — the same as an
|
|
187
213
|
absent id — on reads and writes alike (`concealed`).
|
|
188
214
|
- Queries arrive in their wire form and are decoded with `decode*Query` before `criteriaOf`, which
|
|
189
215
|
always adds the scope's `entityId`.
|
|
190
216
|
|
|
217
|
+
### The access resolver
|
|
218
|
+
|
|
219
|
+
`opts.access: (req, ctx) => Promise<{ entityId, projects?, grants? }>` is the hosting app's
|
|
220
|
+
decision per request; a throw is the request's answer (an `AuthForbidden` answers 403).
|
|
221
|
+
|
|
222
|
+
- `entityId` is the resolver's — never the token's, never `opts.scope`'s.
|
|
223
|
+
- `projects` becomes `PlanningScope.projects`, which the server facade enforces everywhere: the
|
|
224
|
+
projects themselves and every card whose `parents` name one are visible (a specification
|
|
225
|
+
through its parent card); anything else reads as absent (`WorkcardNotFound`, `null`, an empty
|
|
226
|
+
list), a create under an invisible parent is `ParentNotFound` (the executor's own parent load),
|
|
227
|
+
lists, counts and summaries are AND'd with `projectCriteriaOf`, links and transitions are
|
|
228
|
+
filtered by `project` (and post-filtered, for a store that ignores the field), commit
|
|
229
|
+
subscriptions drop events outside the set and a status or wait refuses them.
|
|
230
|
+
- `grants` gates the stock handlers' writes: `createProjects` (a project create — `true` for the
|
|
231
|
+
root, a list names the parent projects), `deleteProjects` (the delete of a project card — a list
|
|
232
|
+
names the projects), `defineSchemas` (`schema.define` — `true` for the organization's layer, a
|
|
233
|
+
list names project layers). A `grants` object refuses every flag it leaves out with
|
|
234
|
+
`PlanningForbidden` (403); no `grants` gates nothing. In-process facade writes are not gated.
|
|
235
|
+
|
|
236
|
+
## Data-defined types and flows
|
|
237
|
+
|
|
238
|
+
Where the default store has the schema port, `runtime.schemasFor(entityId, project?)` answers the
|
|
239
|
+
resolved layer (`resolveScopedBundle` over the code bundle and the organization's and project's
|
|
240
|
+
records), cached per (organization, project) and keyed by the store's `revision()` and the plugin
|
|
241
|
+
registry's version — a write anywhere moves the revision the next lookup reads, so a cached layer is
|
|
242
|
+
never stale; a store's `watch` evicts early. Without the port it answers the code registry itself.
|
|
243
|
+
`makeDefinitions` gives the facade `definitions`: every write checks the project (a project card the
|
|
244
|
+
scope can see), the seal, the closed-form checks (a type's flows resolve in its layer, counting flows
|
|
245
|
+
written beside it), then the store's compare-and-set. `retire` of a flow refuses with `SchemaInUse`
|
|
246
|
+
while a live type of the layer — or of any project layer, for an organization record — still runs
|
|
247
|
+
it. `facade.model(card)` builds over the layer of the card's own project, and `schema.list` answers
|
|
248
|
+
the scoped bundle (`?project=`) where `definitions` exists, the code bundle otherwise.
|
|
249
|
+
|
|
191
250
|
## Testing
|
|
192
251
|
|
|
193
252
|
Category A. Build one real context with `makeBasicContext` + `appendPlanningService({ store:
|
|
194
253
|
makeMemoryPlanningStore({ now }), plugins: [fixtures] })`, and run a handler through
|
|
195
254
|
`handler.bind({ ref: { ctx } })(req, res)` with a request carrying `auth` and `entity`. A monotonic
|
|
196
255
|
`now` keeps log order deterministic; `sync: false` + `flush()` pins pending and hook-count cases.
|
|
256
|
+
`tests/conformance.spec.ts` runs the conformance suite against the memory store with and without
|
|
257
|
+
`schemas: true`; a durable store's own tests run the same cases against a real database.
|
|
197
258
|
|
|
198
259
|
## Related
|
|
199
260
|
|
|
200
261
|
- `planning` — records, flows, the fold, the query language, the protocol tree, the models
|
|
201
262
|
- `client-planning` — the remote facade and the state mirror that talk to these handlers
|
|
263
|
+
- `planning-postgres` — the durable Postgres store built from these pieces
|
|
202
264
|
- `server-job`, `queue` — the declare/serve/feed pattern and the projection queue
|
|
203
265
|
- `resource` — the criteria engine every memory read goes through
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { handlers } from '@owlmeans/server-api';
|
|
2
|
-
import type { ExecuteOptions, ExecuteRequest, PlanningProtocols, PlanningScope, TransitionExecution } from '@owlmeans/planning';
|
|
3
|
-
import type { Context, PlanningHandlerOptions } from '../types.js';
|
|
2
|
+
import type { ExecuteOptions, ExecuteRequest, PlanningFacade, PlanningProtocols, PlanningScope, TransitionExecution } from '@owlmeans/planning';
|
|
3
|
+
import type { Context, PlanningAccess, PlanningHandlerOptions } from '../types.js';
|
|
4
4
|
/**
|
|
5
5
|
* What a wire execution may say. `actor` is dropped — the scope names the writer — and a create's
|
|
6
6
|
* `createdBy` is the authenticated subject, whatever the body claimed.
|
|
@@ -8,6 +8,14 @@ import type { Context, PlanningHandlerOptions } from '../types.js';
|
|
|
8
8
|
export declare const wireExecution: (body: ExecuteRequest, scope: PlanningScope) => TransitionExecution;
|
|
9
9
|
/** `wait`/`timeout` as a server grants them: the timeout never holds a request past `maxPoll`. */
|
|
10
10
|
export declare const executeOptionsOf: (body: Pick<ExecuteRequest, 'wait' | 'timeout'>, maxPoll: number) => ExecuteOptions;
|
|
11
|
+
/**
|
|
12
|
+
* The grants a wire execution needs: `createProjects` for a project create (its primary parent,
|
|
13
|
+
* or the root), `deleteProjects` for the delete of a project card. A card this scope cannot see is
|
|
14
|
+
* left to the executor, which answers it as absent.
|
|
15
|
+
*
|
|
16
|
+
* @throws {PlanningForbidden}
|
|
17
|
+
*/
|
|
18
|
+
export declare const assertExecutionGranted: (facade: PlanningFacade, exec: TransitionExecution, access?: PlanningAccess) => Promise<void>;
|
|
11
19
|
/** The one write route. */
|
|
12
20
|
export declare const executePlanning: (protocol: PlanningProtocols['execute'], opts?: PlanningHandlerOptions) => ReturnType<ReturnType<typeof handlers<Context>>['request']>;
|
|
13
21
|
//# sourceMappingURL=execute.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/actions/execute.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/actions/execute.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAI/C,OAAO,KAAK,EACV,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,iBAAiB,EAAE,aAAa,EAAE,mBAAmB,EAEtG,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAGlF;;;GAGG;AACH,eAAO,MAAM,aAAa,SAAU,cAAc,SAAS,aAAa,KAAG,mBAQ1E,CAAA;AAED,kGAAkG;AAClG,eAAO,MAAM,gBAAgB,SAAU,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC,WAAW,MAAM,KAAG,cAOlG,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,WACzB,cAAc,QAAQ,mBAAmB,WAAW,cAAc,KACzE,OAAO,CAAC,IAAI,CAcd,CAAA;AAED,2BAA2B;AAC3B,eAAO,MAAM,eAAe,aAChB,iBAAiB,CAAC,SAAS,CAAC,SAAS,sBAAsB,KACpE,UAAU,CAAC,UAAU,CAAC,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAWxD,CAAA"}
|
package/build/actions/execute.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { handlers } from '@owlmeans/server-api';
|
|
2
|
-
import { DEFAULT_COMMIT_TIMEOUT, MAX_COMMIT_POLL, TransitionAction } from '@owlmeans/planning';
|
|
3
|
-
import { concealed,
|
|
2
|
+
import { DEFAULT_COMMIT_TIMEOUT, isProject, MAX_COMMIT_POLL, normalizeParents, TransitionAction, WorkcardKind, } from '@owlmeans/planning';
|
|
3
|
+
import { assertGranted, concealed, handlerScopeOf } from '../utils/index.js';
|
|
4
4
|
/**
|
|
5
5
|
* What a wire execution may say. `actor` is dropped — the scope names the writer — and a create's
|
|
6
6
|
* `createdBy` is the authenticated subject, whatever the body claimed.
|
|
@@ -23,11 +23,35 @@ export const executeOptionsOf = (body, maxPoll) => {
|
|
|
23
23
|
const asked = Number(body.timeout ?? DEFAULT_COMMIT_TIMEOUT);
|
|
24
24
|
return { wait: true, timeout: Math.min(Number.isFinite(asked) && asked > 0 ? asked : DEFAULT_COMMIT_TIMEOUT, ceiling) };
|
|
25
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* The grants a wire execution needs: `createProjects` for a project create (its primary parent,
|
|
28
|
+
* or the root), `deleteProjects` for the delete of a project card. A card this scope cannot see is
|
|
29
|
+
* left to the executor, which answers it as absent.
|
|
30
|
+
*
|
|
31
|
+
* @throws {PlanningForbidden}
|
|
32
|
+
*/
|
|
33
|
+
export const assertExecutionGranted = async (facade, exec, access) => {
|
|
34
|
+
if (access?.grants == null) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (exec.action === TransitionAction.Create && typeof exec.card === 'object' && exec.card?.kind === WorkcardKind.Project) {
|
|
38
|
+
assertGranted(access, 'createProjects', normalizeParents(exec.card.parent, exec.card.parents)[0]);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (exec.action === TransitionAction.Delete && typeof exec.card === 'string') {
|
|
42
|
+
const card = await facade.cards.load(exec.card);
|
|
43
|
+
if (card != null && isProject(card)) {
|
|
44
|
+
assertGranted(access, 'deleteProjects', card.id);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
26
48
|
/** The one write route. */
|
|
27
49
|
export const executePlanning = (protocol, opts) => handlers().request(protocol, async (req, ctx) => concealed(async () => {
|
|
28
|
-
const facade = await
|
|
50
|
+
const { facade, access } = await handlerScopeOf(ctx, req, opts);
|
|
29
51
|
const body = (req.body ?? {});
|
|
30
|
-
const
|
|
52
|
+
const exec = wireExecution(body, facade.scope);
|
|
53
|
+
await assertExecutionGranted(facade, exec, access);
|
|
54
|
+
const receipt = await facade.execute(exec, executeOptionsOf(body, opts?.maxPoll ?? MAX_COMMIT_POLL));
|
|
31
55
|
return receipt.card !== undefined
|
|
32
56
|
? { transition: receipt.transition, card: receipt.card }
|
|
33
57
|
: { transition: receipt.transition };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execute.js","sourceRoot":"","sources":["../../src/actions/execute.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAC/C,OAAO,
|
|
1
|
+
{"version":3,"file":"execute.js","sourceRoot":"","sources":["../../src/actions/execute.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import { handlers } from '@owlmeans/server-api';
|
|
2
|
-
import type { PlanningProtocols } from '@owlmeans/planning';
|
|
2
|
+
import type { PlanningDefinitions, PlanningProtocols, SchemaDefineReply, SchemaDefineRequest } from '@owlmeans/planning';
|
|
3
3
|
import type { Context, PlanningHandlerOptions } from '../types.js';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
type RequestHandler = ReturnType<ReturnType<typeof handlers<Context>>['request']>;
|
|
5
|
+
/**
|
|
6
|
+
* The schema bundle — what a client loads to answer `can()` with no round trip. Scoped like every
|
|
7
|
+
* other leaf (no organization, no bundle). Where the store holds data-defined schemas it is the
|
|
8
|
+
* resolved layer of the organization, or of the project the `project` query names (a project this
|
|
9
|
+
* scope can see); otherwise the service's code bundle.
|
|
10
|
+
*
|
|
11
|
+
* @throws {WorkcardNotFound} for a project this scope cannot see
|
|
12
|
+
*/
|
|
13
|
+
export declare const listSchemas: (protocol: PlanningProtocols['schema']['list'], opts?: PlanningHandlerOptions) => RequestHandler;
|
|
14
|
+
/**
|
|
15
|
+
* Apply one `schema.define` request to a facade's definitions: the declarations in the request's
|
|
16
|
+
* mode (flows before types), then the retirements — answering what was written and the layer as it
|
|
17
|
+
* resolves afterwards.
|
|
18
|
+
*/
|
|
19
|
+
export declare const applySchemaRequest: (definitions: PlanningDefinitions, request: SchemaDefineRequest) => Promise<SchemaDefineReply>;
|
|
20
|
+
/**
|
|
21
|
+
* The write of data-defined types and flows. Gated by `grants.defineSchemas` when the access
|
|
22
|
+
* resolver answers grants (the organization-wide layer needs `true`, a project layer its id).
|
|
23
|
+
*
|
|
24
|
+
* @throws {PlanningUnsupported} where the store holds no data-defined schemas
|
|
25
|
+
* @throws {PlanningForbidden | SchemaConflict | SchemaSealed | SchemaInvalid | SchemaInUse | WorkcardNotFound}
|
|
26
|
+
*/
|
|
27
|
+
export declare const defineSchemas: (protocol: NonNullable<PlanningProtocols['schema']['define']>, opts?: PlanningHandlerOptions) => RequestHandler;
|
|
28
|
+
export {};
|
|
6
29
|
//# sourceMappingURL=schemas.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/actions/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/actions/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAE/C,OAAO,KAAK,EACV,mBAAmB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAC/E,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAGlE,KAAK,cAAc,GAAG,UAAU,CAAC,UAAU,CAAC,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;AAKjF;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,aACZ,iBAAiB,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,sBAAsB,KAC3E,cAQE,CAAA;AAEL;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,gBAChB,mBAAmB,WAAW,mBAAmB,KAC7D,OAAO,CAAC,iBAAiB,CAyB3B,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,aACd,WAAW,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,SAAS,sBAAsB,KAC1F,cAUE,CAAA"}
|
package/build/actions/schemas.js
CHANGED
|
@@ -1,9 +1,65 @@
|
|
|
1
1
|
import { handlers } from '@owlmeans/server-api';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
import { PlanningUnsupported, SchemaWriteMode } from '@owlmeans/planning';
|
|
3
|
+
import { assertGranted, concealed, handlerScopeOf } from '../utils/index.js';
|
|
4
|
+
const text = (value) => value == null || value === '' ? undefined : `${Array.isArray(value) ? value[0] : value}`;
|
|
5
|
+
/**
|
|
6
|
+
* The schema bundle — what a client loads to answer `can()` with no round trip. Scoped like every
|
|
7
|
+
* other leaf (no organization, no bundle). Where the store holds data-defined schemas it is the
|
|
8
|
+
* resolved layer of the organization, or of the project the `project` query names (a project this
|
|
9
|
+
* scope can see); otherwise the service's code bundle.
|
|
10
|
+
*
|
|
11
|
+
* @throws {WorkcardNotFound} for a project this scope cannot see
|
|
12
|
+
*/
|
|
13
|
+
export const listSchemas = (protocol, opts) => handlers().request(protocol, async (req, ctx) => concealed(async () => {
|
|
14
|
+
const { facade } = await handlerScopeOf(ctx, req, opts);
|
|
15
|
+
if (facade.definitions == null) {
|
|
16
|
+
return facade.schemas.bundle();
|
|
17
|
+
}
|
|
18
|
+
return await facade.definitions.bundle(text(req.query?.project));
|
|
19
|
+
}));
|
|
20
|
+
/**
|
|
21
|
+
* Apply one `schema.define` request to a facade's definitions: the declarations in the request's
|
|
22
|
+
* mode (flows before types), then the retirements — answering what was written and the layer as it
|
|
23
|
+
* resolves afterwards.
|
|
24
|
+
*/
|
|
25
|
+
export const applySchemaRequest = async (definitions, request) => {
|
|
26
|
+
const layer = request.project != null ? { project: request.project } : {};
|
|
27
|
+
const declarations = { types: request.types ?? [], flows: request.flows ?? [] };
|
|
28
|
+
const records = [];
|
|
29
|
+
switch (request.mode ?? SchemaWriteMode.Define) {
|
|
30
|
+
case SchemaWriteMode.Put:
|
|
31
|
+
for (const flow of declarations.flows) {
|
|
32
|
+
records.push(await definitions.putFlow(flow, layer));
|
|
33
|
+
}
|
|
34
|
+
for (const type of declarations.types) {
|
|
35
|
+
records.push(await definitions.putType(type, layer));
|
|
36
|
+
}
|
|
37
|
+
break;
|
|
38
|
+
case SchemaWriteMode.Seed:
|
|
39
|
+
records.push(...await definitions.seed(declarations, layer));
|
|
40
|
+
break;
|
|
41
|
+
default:
|
|
42
|
+
records.push(...await definitions.define(declarations, layer));
|
|
43
|
+
}
|
|
44
|
+
for (const key of request.retire ?? []) {
|
|
45
|
+
records.push(await definitions.retire(key.kind, key.key, layer));
|
|
46
|
+
}
|
|
47
|
+
return { records, bundle: await definitions.bundle(request.project) };
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* The write of data-defined types and flows. Gated by `grants.defineSchemas` when the access
|
|
51
|
+
* resolver answers grants (the organization-wide layer needs `true`, a project layer its id).
|
|
52
|
+
*
|
|
53
|
+
* @throws {PlanningUnsupported} where the store holds no data-defined schemas
|
|
54
|
+
* @throws {PlanningForbidden | SchemaConflict | SchemaSealed | SchemaInvalid | SchemaInUse | WorkcardNotFound}
|
|
55
|
+
*/
|
|
56
|
+
export const defineSchemas = (protocol, opts) => handlers().request(protocol, async (req, ctx) => concealed(async () => {
|
|
57
|
+
const { facade, access } = await handlerScopeOf(ctx, req, opts);
|
|
58
|
+
const request = (req.body ?? {});
|
|
59
|
+
if (facade.definitions == null) {
|
|
60
|
+
throw new PlanningUnsupported('definitions');
|
|
61
|
+
}
|
|
62
|
+
assertGranted(access, 'defineSchemas', request.project);
|
|
63
|
+
return await applySchemaRequest(facade.definitions, request);
|
|
64
|
+
}));
|
|
9
65
|
//# sourceMappingURL=schemas.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/actions/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/actions/schemas.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** A conformance expectation that did not hold. Plain `Error`, so any test runner reports it. */
|
|
2
|
+
export declare class ConformanceFailure extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/** @throws {ConformanceFailure} */
|
|
6
|
+
export declare const check: (condition: unknown, message: string) => void;
|
|
7
|
+
/** Structural equality over JSON values. @throws {ConformanceFailure} */
|
|
8
|
+
export declare const same: (actual: unknown, expected: unknown, message: string) => void;
|
|
9
|
+
/** The same members in any order. @throws {ConformanceFailure} */
|
|
10
|
+
export declare const sameSet: (actual: readonly unknown[], expected: readonly unknown[], message: string) => void;
|
|
11
|
+
/**
|
|
12
|
+
* The promise rejects with an error of the class (matched by instance or by its registered type
|
|
13
|
+
* name, so a duplicate module copy counts). Answers the error.
|
|
14
|
+
*
|
|
15
|
+
* @throws {ConformanceFailure}
|
|
16
|
+
*/
|
|
17
|
+
export declare const rejects: (run: Promise<unknown> | (() => Promise<unknown>), expected: {
|
|
18
|
+
typeName: string;
|
|
19
|
+
}, message: string) => Promise<Error>;
|
|
20
|
+
//# sourceMappingURL=assert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert.d.ts","sourceRoot":"","sources":["../../src/conformance/assert.ts"],"names":[],"mappings":"AAEA,iGAAiG;AACjG,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAUD,mCAAmC;AACnC,eAAO,MAAM,KAAK,cAAe,OAAO,WAAW,MAAM,KAAG,IAI3D,CAAA;AAED,yEAAyE;AACzE,eAAO,MAAM,IAAI,WAAY,OAAO,YAAY,OAAO,WAAW,MAAM,KAAG,IAI1E,CAAA;AAED,kEAAkE;AAClE,eAAO,MAAM,OAAO,WAAY,SAAS,OAAO,EAAE,YAAY,SAAS,OAAO,EAAE,WAAW,MAAM,KAAG,IAGnG,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,OAAO,QACb,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,YAAY;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,WAAW,MAAM,KAChG,OAAO,CAAC,KAAK,CAYf,CAAA"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { sameValue } from '@owlmeans/planning';
|
|
2
|
+
/** A conformance expectation that did not hold. Plain `Error`, so any test runner reports it. */
|
|
3
|
+
export class ConformanceFailure extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(`planning conformance: ${message}`);
|
|
6
|
+
this.name = 'ConformanceFailure';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const show = (value) => {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.stringify(value);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return `${value}`;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
/** @throws {ConformanceFailure} */
|
|
18
|
+
export const check = (condition, message) => {
|
|
19
|
+
if (condition !== true) {
|
|
20
|
+
throw new ConformanceFailure(message);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
/** Structural equality over JSON values. @throws {ConformanceFailure} */
|
|
24
|
+
export const same = (actual, expected, message) => {
|
|
25
|
+
if (!sameValue(actual, expected)) {
|
|
26
|
+
throw new ConformanceFailure(`${message}: expected ${show(expected)}, got ${show(actual)}`);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
/** The same members in any order. @throws {ConformanceFailure} */
|
|
30
|
+
export const sameSet = (actual, expected, message) => {
|
|
31
|
+
const sort = (values) => [...values].map(show).sort();
|
|
32
|
+
same(sort(actual), sort(expected), message);
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The promise rejects with an error of the class (matched by instance or by its registered type
|
|
36
|
+
* name, so a duplicate module copy counts). Answers the error.
|
|
37
|
+
*
|
|
38
|
+
* @throws {ConformanceFailure}
|
|
39
|
+
*/
|
|
40
|
+
export const rejects = async (run, expected, message) => {
|
|
41
|
+
try {
|
|
42
|
+
await (typeof run === 'function' ? run() : run);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
const type = error?.type;
|
|
46
|
+
if (error instanceof expected
|
|
47
|
+
|| (typeof type === 'string' && type === expected.typeName)) {
|
|
48
|
+
return error;
|
|
49
|
+
}
|
|
50
|
+
throw new ConformanceFailure(`${message}: expected ${expected.typeName}, got ${show(error?.message ?? error)}`);
|
|
51
|
+
}
|
|
52
|
+
throw new ConformanceFailure(`${message}: expected ${expected.typeName}, nothing was thrown`);
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=assert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert.js","sourceRoot":"","sources":["../../src/conformance/assert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9C,iGAAiG;AACjG,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAe;QACzB,KAAK,CAAC,yBAAyB,OAAO,EAAE,CAAC,CAAA;QACzC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAA;IAClC,CAAC;CACF;AAED,MAAM,IAAI,GAAG,CAAC,KAAc,EAAU,EAAE;IACtC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,KAAK,EAAE,CAAA;IACnB,CAAC;AACH,CAAC,CAAA;AAED,mCAAmC;AACnC,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,SAAkB,EAAE,OAAe,EAAQ,EAAE;IACjE,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,MAAM,IAAI,kBAAkB,CAAC,OAAO,CAAC,CAAA;IACvC,CAAC;AACH,CAAC,CAAA;AAED,yEAAyE;AACzE,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,MAAe,EAAE,QAAiB,EAAE,OAAe,EAAQ,EAAE;IAChF,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,kBAAkB,CAAC,GAAG,OAAO,cAAc,IAAI,CAAC,QAAQ,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAC7F,CAAC;AACH,CAAC,CAAA;AAED,kEAAkE;AAClE,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,MAA0B,EAAE,QAA4B,EAAE,OAAe,EAAQ,EAAE;IACzG,MAAM,IAAI,GAAG,CAAC,MAA0B,EAAE,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;IACzE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAA;AAC7C,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,EAC1B,GAAgD,EAAE,QAA8B,EAAE,OAAe,EACjF,EAAE;IAClB,IAAI,CAAC;QACH,MAAM,CAAC,OAAO,GAAG,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACjD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,GAAI,KAAmC,EAAE,IAAI,CAAA;QACvD,IAAI,KAAK,YAAa,QAAkE;eACnF,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9D,OAAO,KAAc,CAAA;QACvB,CAAC;QACD,MAAM,IAAI,kBAAkB,CAAC,GAAG,OAAO,cAAc,QAAQ,CAAC,QAAQ,SAAS,IAAI,CAAE,KAAe,EAAE,OAAO,IAAI,KAAK,CAAC,EAAE,CAAC,CAAA;IAC5H,CAAC;IACD,MAAM,IAAI,kBAAkB,CAAC,GAAG,OAAO,cAAc,QAAQ,CAAC,QAAQ,sBAAsB,CAAC,CAAA;AAC/F,CAAC,CAAA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PlanningFacade, PlanningService, PlanningStore } from '@owlmeans/planning';
|
|
2
|
+
/** What a case runs against: a planning service over the store under test. */
|
|
3
|
+
export interface ConformanceSubject {
|
|
4
|
+
/** A service with `planningConformancePlugin` registered and a strictly increasing clock. */
|
|
5
|
+
service: PlanningService;
|
|
6
|
+
/** The store under test — the service's default store. */
|
|
7
|
+
store: PlanningStore;
|
|
8
|
+
/** A facade of one organization, acting as a signed-in person. */
|
|
9
|
+
facade: (entityId: string) => PlanningFacade;
|
|
10
|
+
}
|
|
11
|
+
/** A capability a store may implement; a case that needs one is skipped for a store without it. */
|
|
12
|
+
export type ConformanceCapability = 'schemas';
|
|
13
|
+
export interface ConformanceCase {
|
|
14
|
+
name: string;
|
|
15
|
+
needs?: ConformanceCapability[];
|
|
16
|
+
run: (subject: ConformanceSubject) => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/** Every conformance case, in order. */
|
|
19
|
+
export declare const planningConformance: readonly ConformanceCase[];
|
|
20
|
+
/** The cases a store qualifies for — a case needing `schemas` only where the store holds them. */
|
|
21
|
+
export declare const conformanceCasesFor: (store: PlanningStore) => ConformanceCase[];
|
|
22
|
+
//# sourceMappingURL=cases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cases.d.ts","sourceRoot":"","sources":["../../src/conformance/cases.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACG,cAAc,EAAE,eAAe,EAAE,aAAa,EAE5D,MAAM,oBAAoB,CAAA;AAI3B,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IACjC,6FAA6F;IAC7F,OAAO,EAAE,eAAe,CAAA;IACxB,0DAA0D;IAC1D,KAAK,EAAE,aAAa,CAAA;IACpB,kEAAkE;IAClE,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,cAAc,CAAA;CAC7C;AAED,mGAAmG;AACnG,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAAA;AAE7C,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,qBAAqB,EAAE,CAAA;IAC/B,GAAG,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACpD;AA+bD,wCAAwC;AACxC,eAAO,MAAM,mBAAmB,EAAE,SAAS,eAAe,EAAyB,CAAA;AAEnF,kGAAkG;AAClG,eAAO,MAAM,mBAAmB,UAAW,aAAa,KAAG,eAAe,EAC2C,CAAA"}
|