@owlmeans/client-planning 0.1.18-rc.37 → 0.1.18-rc.38

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 CHANGED
@@ -9,7 +9,7 @@ planning service runs in-process.
9
9
  ## Installation
10
10
 
11
11
  ```bash
12
- bun add @owlmeans/client-planning@^0.1.18-rc.37
12
+ bun add @owlmeans/client-planning@^0.1.18-rc.38
13
13
  ```
14
14
 
15
15
  ## Concepts
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 3,
3
3
  "package": "@owlmeans/client-planning",
4
- "version": "0.1.18-rc.37",
5
- "generatedAt": "2026-10-20T23:19:58.628Z",
4
+ "version": "0.1.18-rc.38",
5
+ "generatedAt": "2026-10-24T21:07:16.901Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -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.37"` in `dependencies`
11
+ **Install:** `"@owlmeans/client-planning": "^0.1.18-rc.38"` 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
@@ -65,14 +65,18 @@ appendPlanningStores(context) // a browser mirror; a Node client usually
65
65
  from the credential. The execution's own `actor` is never sent.
66
66
  - A client runs no middleware and folds nothing: `store()`, `committed()` and a plugin carrying
67
67
  `before`/`after`/`store`/`mintCode` answer `PlanningUnsupported`. `use()` accepts a schemas-only
68
- plugin, layered over the server's bundle.
68
+ plugin, layered over the server's bundle; its `schemas.app` merges over the served declarations
69
+ (`bundle.app`) in plugin order, and a malformed one is refused (`SchemaInvalid`) before anything
70
+ changes.
69
71
  - `timeout` (ms) bounds every call that is not a long poll; a long poll's deadline is its own hold
70
72
  plus `LONG_POLL_GRACE`.
71
73
 
72
74
  ### The schema bundle
73
75
 
74
76
  `model()` needs the flows and types (`can()`, `available()`, `statusOf()` read the registry with no
75
- round trip). The bundle is fetched in the background once the context is **Ready** and a failure
77
+ round trip). The bundle also carries the application's declarations (`registry.app()`), so
78
+ `registry.fieldsSchema(type)` / `validator(type)` include its base card fields and a definition's
79
+ `app` bag reads back as written. The bundle is fetched in the background once the context is **Ready** and a failure
76
80
  there is swallowed — a browser that is not signed in yet cannot read it, and a context must never
77
81
  fail or wait over it. `model()` and `service.loadSchemas()` load it on first use regardless, and a
78
82
  failed load is retried by the next caller. `schemas: false` skips the background load.
@@ -11,8 +11,10 @@ metadata:
11
11
 
12
12
  ## Browser field validation under CSP
13
13
 
14
- Bind `makePlanningFieldValidator(type.fields)` to the exact definition returned by the resolved
15
- organization or project bundle. Its `validateFields(fields)` returns `{ valid, errors }`; errors
14
+ Bind `makePlanningFieldValidator(registry.fieldsSchema(type))` to the registry of the resolved
15
+ organization or project bundle — the type's own `fields` plus the application's base card fields
16
+ (`planning` → types part); with no base fields it IS `type.fields`. Passing `type.fields` alone
17
+ skips the base fields the server still enforces. Its `validateFields(fields)` returns `{ valid, errors }`; errors
16
18
  carry decoded `path`, `keyword`, `schemaPath` and a bounded value-free `message`. Construct one JSON
17
19
  fields object, validate it and send that same object. No coercion, defaults or property removal
18
20
  occurs, and the schema is left unchanged. Preserve nested money objects and calendar date strings.
package/build/client.d.ts CHANGED
@@ -6,7 +6,7 @@ import type { Config, Context, PlanningClientOptions, PlanningClientService, Wit
6
6
  * What only a server can do is refused rather than faked: a client runs no middleware and folds
7
7
  * nothing, so `store()`, `committed()` and a plugin carrying hooks or a store answer
8
8
  * `PlanningUnsupported`. A plugin carrying only schemas is accepted — it layers over the server's
9
- * bundle.
9
+ * bundle (its `schemas.app` merged over the served declarations, in plugin order).
10
10
  */
11
11
  export declare const makePlanningClientService: <C extends Config, T extends Context<C>>(context: T, options: PlanningClientOptions) => PlanningClientService;
12
12
  /**
@@ -1 +1 @@
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"}
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,qBA6HF,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
@@ -1,6 +1,6 @@
1
1
  import { bindAll } from '@owlmeans/client-entrypoint';
2
2
  import { createLazyService, MiddlewareStage, MiddlewareType } from '@owlmeans/context';
3
- import { DEFAULT_PLUGIN_ORDER, makeSchemaRegistry, PLANNING_SERVICE, PlanningUnsupported, } from '@owlmeans/planning';
3
+ import { appSchemaHelper, DEFAULT_PLUGIN_ORDER, makeSchemaRegistry, PLANNING_SERVICE, PlanningUnsupported, } from '@owlmeans/planning';
4
4
  import { makeRemoteCommitSource } from './commits.js';
5
5
  import { makeRemoteDefinitions } from './definitions.js';
6
6
  import { makeRemoteFacade } from './facade.js';
@@ -13,7 +13,7 @@ import { makePlanningClientLifecycle } from './lifecycle.js';
13
13
  * What only a server can do is refused rather than faked: a client runs no middleware and folds
14
14
  * nothing, so `store()`, `committed()` and a plugin carrying hooks or a store answer
15
15
  * `PlanningUnsupported`. A plugin carrying only schemas is accepted — it layers over the server's
16
- * bundle.
16
+ * bundle (its `schemas.app` merged over the served declarations, in plugin order).
17
17
  */
18
18
  export const makePlanningClientService = (context, options) => {
19
19
  const feeds = new Set();
@@ -33,11 +33,16 @@ export const makePlanningClientService = (context, options) => {
33
33
  const commits = makeRemoteCommitSource(context, options.protocols, {
34
34
  socket: options.socket, poll: options.poll, timeout: options.timeout, stores, lifecycle,
35
35
  });
36
- const layerPlugins = () => plugins.forEach(plugin => {
37
- plugin.schemas?.assigneeTypes?.forEach(registry.registerAssigneeType);
38
- plugin.schemas?.flows?.forEach(registry.registerFlow);
39
- plugin.schemas?.types?.forEach(registry.registerType);
40
- });
36
+ // The application's declarations the server served — what every client plugin's `app` merges over.
37
+ let served;
38
+ const layerPlugins = () => {
39
+ registry.registerApp(appSchemaHelper.merge([served, ...plugins.map(plugin => plugin.schemas?.app)]));
40
+ plugins.forEach(plugin => {
41
+ plugin.schemas?.assigneeTypes?.forEach(registry.registerAssigneeType);
42
+ plugin.schemas?.flows?.forEach(registry.registerFlow);
43
+ plugin.schemas?.types?.forEach(registry.registerType);
44
+ });
45
+ };
41
46
  let loadedKey = scopeKey();
42
47
  let loaded = false;
43
48
  let loading = null;
@@ -57,6 +62,7 @@ export const makePlanningClientService = (context, options) => {
57
62
  const bundle = await operation.wait(context.entrypoint(options.protocols.schema.list).call({ timeout: options.timeout, signal: operation.signal }));
58
63
  operation.check();
59
64
  registry.load(bundle);
65
+ served = registry.app();
60
66
  layerPlugins();
61
67
  loaded = true;
62
68
  return registry;
@@ -87,6 +93,7 @@ export const makePlanningClientService = (context, options) => {
87
93
  loading = null;
88
94
  definitions?.invalidate();
89
95
  registry.load({ version: 1, types: [], flows: [] });
96
+ served = undefined;
90
97
  layerPlugins();
91
98
  await Promise.all(stopping.map(async (feed) => await feed.stop()));
92
99
  });
@@ -102,6 +109,9 @@ export const makePlanningClientService = (context, options) => {
102
109
  if (plugin.before != null || plugin.after != null || plugin.store != null || plugin.mintCode != null) {
103
110
  throw new PlanningUnsupported(`client:plugin:${plugin.name}`);
104
111
  }
112
+ if (plugin.schemas?.app != null) {
113
+ appSchemaHelper.assertDeclarations(plugin.schemas.app);
114
+ }
105
115
  const existing = plugins.findIndex(candidate => candidate.name === plugin.name);
106
116
  if (existing >= 0) {
107
117
  plugins.splice(existing, 1);
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/client-planning",
3
- "version": "0.1.18-rc.37",
3
+ "version": "0.1.18-rc.38",
4
4
  "license": "MIT",
5
5
  "description": "The client half of OwlMeans planning — the remote planning facade, the state mirror and its commit fold, and the subscribe-then-poll commit wait. React-free: it runs in a browser and in a Node client alike.",
6
6
  "type": "module",
@@ -30,7 +30,7 @@
30
30
  "@owlmeans/context": "^0.1.18-rc.35",
31
31
  "@owlmeans/entrypoint": "^0.1.18-rc.42",
32
32
  "@owlmeans/error": "^0.1.18-rc.39",
33
- "@owlmeans/planning": "^0.1.18-rc.29",
33
+ "@owlmeans/planning": "^0.1.18-rc.30",
34
34
  "@owlmeans/resource": "^0.1.18-rc.41",
35
35
  "@owlmeans/log": "^0.1.18-rc.4",
36
36
  "@owlmeans/route": "^0.1.18-rc.36",
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "@owlmeans/dep-config": "workspace:*",
46
- "@owlmeans/server-planning": "^0.1.18-rc.35",
46
+ "@owlmeans/server-planning": "^0.1.18-rc.36",
47
47
  "@owlmeans/test": "^0.1.18-rc.38",
48
48
  "@types/bun": "^1.4.2",
49
49
  "ajv": "^8.20.0",
package/src/client.ts CHANGED
@@ -2,9 +2,9 @@ import { bindAll } from '@owlmeans/client-entrypoint'
2
2
  import { createLazyService, MiddlewareStage, MiddlewareType } from '@owlmeans/context'
3
3
  import type { EntrypointTree } from '@owlmeans/entrypoint'
4
4
  import {
5
- DEFAULT_PLUGIN_ORDER, makeSchemaRegistry, PLANNING_SERVICE, PlanningUnsupported,
5
+ appSchemaHelper, DEFAULT_PLUGIN_ORDER, makeSchemaRegistry, PLANNING_SERVICE, PlanningUnsupported,
6
6
  } from '@owlmeans/planning'
7
- import type { PlanningFacade, PlanningPlugin, PlanningSchemaRegistry } from '@owlmeans/planning'
7
+ import type { PlanningAppDeclarations, PlanningFacade, PlanningPlugin, PlanningSchemaRegistry } from '@owlmeans/planning'
8
8
  import { makeRemoteCommitSource } from './commits.js'
9
9
  import { makeRemoteDefinitions } from './definitions.js'
10
10
  import { makeRemoteFacade } from './facade.js'
@@ -21,7 +21,7 @@ import type {
21
21
  * What only a server can do is refused rather than faked: a client runs no middleware and folds
22
22
  * nothing, so `store()`, `committed()` and a plugin carrying hooks or a store answer
23
23
  * `PlanningUnsupported`. A plugin carrying only schemas is accepted — it layers over the server's
24
- * bundle.
24
+ * bundle (its `schemas.app` merged over the served declarations, in plugin order).
25
25
  */
26
26
  export const makePlanningClientService = <C extends Config, T extends Context<C>>(
27
27
  context: T, options: PlanningClientOptions
@@ -43,11 +43,16 @@ export const makePlanningClientService = <C extends Config, T extends Context<C>
43
43
  socket: options.socket, poll: options.poll, timeout: options.timeout, stores, lifecycle,
44
44
  })
45
45
 
46
- const layerPlugins = (): void => plugins.forEach(plugin => {
47
- plugin.schemas?.assigneeTypes?.forEach(registry.registerAssigneeType)
48
- plugin.schemas?.flows?.forEach(registry.registerFlow)
49
- plugin.schemas?.types?.forEach(registry.registerType)
50
- })
46
+ // The application's declarations the server served — what every client plugin's `app` merges over.
47
+ let served: PlanningAppDeclarations | undefined
48
+ const layerPlugins = (): void => {
49
+ registry.registerApp(appSchemaHelper.merge([served, ...plugins.map(plugin => plugin.schemas?.app)]))
50
+ plugins.forEach(plugin => {
51
+ plugin.schemas?.assigneeTypes?.forEach(registry.registerAssigneeType)
52
+ plugin.schemas?.flows?.forEach(registry.registerFlow)
53
+ plugin.schemas?.types?.forEach(registry.registerType)
54
+ })
55
+ }
51
56
 
52
57
  let loadedKey = scopeKey()
53
58
  let loaded = false
@@ -64,6 +69,7 @@ export const makePlanningClientService = <C extends Config, T extends Context<C>
64
69
  const bundle = await operation.wait(context.entrypoint(options.protocols.schema.list).call({ timeout: options.timeout, signal: operation.signal }))
65
70
  operation.check()
66
71
  registry.load(bundle)
72
+ served = registry.app()
67
73
  layerPlugins()
68
74
  loaded = true
69
75
 
@@ -97,7 +103,7 @@ export const makePlanningClientService = <C extends Config, T extends Context<C>
97
103
  const stopping = [...feeds]
98
104
  feeds.clear()
99
105
  loaded = false; loading = null; definitions?.invalidate()
100
- registry.load({ version: 1, types: [], flows: [] }); layerPlugins()
106
+ registry.load({ version: 1, types: [], flows: [] }); served = undefined; layerPlugins()
101
107
  await Promise.all(stopping.map(async feed => await feed.stop()))
102
108
  })
103
109
 
@@ -116,6 +122,9 @@ export const makePlanningClientService = <C extends Config, T extends Context<C>
116
122
  if (plugin.before != null || plugin.after != null || plugin.store != null || plugin.mintCode != null) {
117
123
  throw new PlanningUnsupported(`client:plugin:${plugin.name}`)
118
124
  }
125
+ if (plugin.schemas?.app != null) {
126
+ appSchemaHelper.assertDeclarations(plugin.schemas.app)
127
+ }
119
128
  const existing = plugins.findIndex(candidate => candidate.name === plugin.name)
120
129
  if (existing >= 0) {
121
130
  plugins.splice(existing, 1)
@@ -0,0 +1,72 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { appSchemaHelper, FieldsInvalid, SchemaInvalid, TransitionAction, WorkcardKind } from '@owlmeans/planning'
3
+ import type { PlanningAppDeclarations, PlanningService, WorkcardTypeSchema } from '@owlmeans/planning'
4
+ import { makePlanningFieldValidator } from '../src/index.js'
5
+ import { makeSuite, STORY, STORY_FLOW } from './context.js'
6
+ import type { Suite } from './context.js'
7
+
8
+ /** A choir: every type says what it is for, every card may carry a part. */
9
+ const APP: PlanningAppDeclarations = {
10
+ type: { properties: { description: { type: 'string' } } },
11
+ card: { properties: { part: { type: 'string', enum: ['soprano', 'alto', 'tenor', 'bass'] } } },
12
+ }
13
+ /** What a service serves: the declarations merged (normalized with `type: 'object'`). */
14
+ const SERVED = appSchemaHelper.merge([APP])
15
+
16
+ const SECTIONAL: WorkcardTypeSchema = {
17
+ type: 'test:sectional', kind: WorkcardKind.Card, version: 1, flows: [STORY_FLOW], specifications: [],
18
+ fields: { type: 'object', additionalProperties: false },
19
+ }
20
+
21
+ const declared = async (opts: Parameters<typeof makeSuite>[0] = {}): Promise<Suite> => {
22
+ const suite = await makeSuite(opts)
23
+ ;(suite.server as unknown as { planning: () => PlanningService }).planning().use({ name: 'choir', schemas: { app: APP } })
24
+
25
+ return suite
26
+ }
27
+
28
+ describe('@owlmeans/client-planning — application declarations', () => {
29
+ test('the served bundle carries them: the registry validates base fields, a form validator takes the same schema', async () => {
30
+ const suite = await declared()
31
+ const registry = await suite.client.planning().loadSchemas()
32
+
33
+ expect(registry.app()).toEqual(SERVED!)
34
+ expect(registry.validator(STORY)({ area: 'stage', part: 'alto' })).toBe(true)
35
+ expect(registry.validator(STORY)({ area: 'stage', part: 'drums' })).toBe(false)
36
+ const form = makePlanningFieldValidator(registry.fieldsSchema(STORY))
37
+ expect(form.validateFields({ area: 'stage', part: 'alto' }).valid).toBe(true)
38
+ expect(form.validateFields({ area: 'stage', part: 'drums' }).valid).toBe(false)
39
+ expect(form.validateFields({ area: 'stage', colour: 'red' }).valid).toBe(false)
40
+
41
+ const project = await suite.project('Spring concert')
42
+ await expect(suite.planning.execute({
43
+ card: { kind: WorkcardKind.Card, type: STORY, parent: project.id!, title: 'Warm-up', fields: { part: 'drums' } },
44
+ action: TransitionAction.Create,
45
+ }, { wait: true })).rejects.toBeInstanceOf(FieldsInvalid)
46
+ })
47
+
48
+ test('a schemas-only client plugin merges over the served declarations; a malformed one is refused', async () => {
49
+ const suite = await declared()
50
+ const service = suite.client.planning()
51
+ await service.loadSchemas()
52
+
53
+ service.use({ name: 'stage', schemas: { app: { card: { properties: { row: { type: 'number' } } } } } })
54
+ expect(Object.keys(service.schemas.app()?.card?.properties ?? {})).toEqual(['part', 'row'])
55
+ expect(() => service.use({ name: 'broken', schemas: { app: { card: { properties: { 'a.b': {} } } } } })).toThrow(SchemaInvalid)
56
+ expect(service.plugins().map(plugin => plugin.name)).toEqual(['stage'])
57
+ service.use({ name: 'stage' })
58
+ expect(Object.keys(service.schemas.app()?.card?.properties ?? {})).toEqual(['part'])
59
+ })
60
+
61
+ test('definitions carry a type\'s app bag over the wire; a refused bag crosses as SchemaInvalid', async () => {
62
+ const suite = await declared({ definitions: true })
63
+ const definitions = suite.planning.definitions!
64
+
65
+ await definitions.define({ types: [{ ...SECTIONAL, app: { description: 'Small groups' } }] })
66
+ const layer = await definitions.registry()
67
+ expect((layer.type(SECTIONAL.type) as WorkcardTypeSchema).app).toEqual({ description: 'Small groups' })
68
+ expect(layer.app()).toEqual(SERVED!)
69
+ expect(layer.validator(SECTIONAL.type)({ part: 'tenor' })).toBe(true)
70
+ await expect(definitions.putType({ ...SECTIONAL, version: 2, app: { conductor: 'me' } })).rejects.toBeInstanceOf(SchemaInvalid)
71
+ })
72
+ })