@owlmeans/client-planning 0.1.18-rc.8 → 0.1.18

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.
Files changed (146) hide show
  1. package/README.md +94 -11
  2. package/agent-meta/manifest.json +62 -4
  3. package/agent-meta/skills/client-planning/SKILL.md +60 -78
  4. package/agent-meta/skills/client-planning/parts/aux-mirrors.md +61 -0
  5. package/agent-meta/skills/client-planning/parts/mirror.md +67 -0
  6. package/agent-meta/skills/client-planning/parts/validation.md +60 -0
  7. package/build/client.d.ts +1 -1
  8. package/build/client.d.ts.map +1 -1
  9. package/build/client.js +66 -14
  10. package/build/client.js.map +1 -1
  11. package/build/commits.d.ts +2 -13
  12. package/build/commits.d.ts.map +1 -1
  13. package/build/commits.js +190 -132
  14. package/build/commits.js.map +1 -1
  15. package/build/consts.d.ts +4 -0
  16. package/build/consts.d.ts.map +1 -1
  17. package/build/consts.js +10 -4
  18. package/build/consts.js.map +1 -1
  19. package/build/definitions.d.ts +13 -0
  20. package/build/definitions.d.ts.map +1 -0
  21. package/build/definitions.js +70 -0
  22. package/build/definitions.js.map +1 -0
  23. package/build/events/types.d.ts +34 -0
  24. package/build/events/types.d.ts.map +1 -0
  25. package/build/events/types.js +2 -0
  26. package/build/events/types.js.map +1 -0
  27. package/build/events.d.ts +10 -21
  28. package/build/events.d.ts.map +1 -1
  29. package/build/events.js +104 -102
  30. package/build/events.js.map +1 -1
  31. package/build/facade.d.ts.map +1 -1
  32. package/build/facade.js +86 -49
  33. package/build/facade.js.map +1 -1
  34. package/build/feed.d.ts +4 -4
  35. package/build/feed.d.ts.map +1 -1
  36. package/build/feed.js +50 -27
  37. package/build/feed.js.map +1 -1
  38. package/build/helper/types.d.ts +15 -0
  39. package/build/helper/types.d.ts.map +1 -0
  40. package/build/helper/types.js +2 -0
  41. package/build/helper/types.js.map +1 -0
  42. package/build/helper.d.ts +7 -7
  43. package/build/helper.d.ts.map +1 -1
  44. package/build/helper.js +16 -8
  45. package/build/helper.js.map +1 -1
  46. package/build/index.d.ts +11 -0
  47. package/build/index.d.ts.map +1 -1
  48. package/build/index.js +6 -0
  49. package/build/index.js.map +1 -1
  50. package/build/lifecycle/types.d.ts +27 -0
  51. package/build/lifecycle/types.d.ts.map +1 -0
  52. package/build/lifecycle/types.js +2 -0
  53. package/build/lifecycle/types.js.map +1 -0
  54. package/build/lifecycle.d.ts +3 -0
  55. package/build/lifecycle.d.ts.map +1 -0
  56. package/build/lifecycle.js +157 -0
  57. package/build/lifecycle.js.map +1 -0
  58. package/build/resource-feed.d.ts +5 -0
  59. package/build/resource-feed.d.ts.map +1 -0
  60. package/build/resource-feed.js +97 -0
  61. package/build/resource-feed.js.map +1 -0
  62. package/build/resources.d.ts +6 -0
  63. package/build/resources.d.ts.map +1 -0
  64. package/build/resources.js +93 -0
  65. package/build/resources.js.map +1 -0
  66. package/build/stores.d.ts +4 -11
  67. package/build/stores.d.ts.map +1 -1
  68. package/build/stores.js +11 -35
  69. package/build/stores.js.map +1 -1
  70. package/build/sync/types.d.ts +18 -0
  71. package/build/sync/types.d.ts.map +1 -0
  72. package/build/sync/types.js +2 -0
  73. package/build/sync/types.js.map +1 -0
  74. package/build/sync.d.ts +4 -0
  75. package/build/sync.d.ts.map +1 -0
  76. package/build/sync.js +26 -0
  77. package/build/sync.js.map +1 -0
  78. package/build/types.d.ts +48 -3
  79. package/build/types.d.ts.map +1 -1
  80. package/build/types.local.d.ts +13 -0
  81. package/build/types.local.d.ts.map +1 -0
  82. package/build/types.local.js +2 -0
  83. package/build/types.local.js.map +1 -0
  84. package/build/utils/record/types.d.ts +28 -0
  85. package/build/utils/record/types.d.ts.map +1 -0
  86. package/build/utils/record/types.js +2 -0
  87. package/build/utils/record/types.js.map +1 -0
  88. package/build/utils/record.d.ts +3 -24
  89. package/build/utils/record.d.ts.map +1 -1
  90. package/build/utils/record.js +64 -70
  91. package/build/utils/record.js.map +1 -1
  92. package/build/validation/consts.d.ts +23 -0
  93. package/build/validation/consts.d.ts.map +1 -0
  94. package/build/validation/consts.js +289 -0
  95. package/build/validation/consts.js.map +1 -0
  96. package/build/validation/types.d.ts +17 -0
  97. package/build/validation/types.d.ts.map +1 -0
  98. package/build/validation/types.js +2 -0
  99. package/build/validation/types.js.map +1 -0
  100. package/build/validation.d.ts +10 -0
  101. package/build/validation.d.ts.map +1 -0
  102. package/build/validation.js +171 -0
  103. package/build/validation.js.map +1 -0
  104. package/package.json +20 -18
  105. package/src/client.ts +58 -15
  106. package/src/commits.ts +152 -184
  107. package/src/consts.ts +12 -4
  108. package/src/definitions.ts +93 -0
  109. package/src/events/types.ts +35 -0
  110. package/src/events.ts +118 -108
  111. package/src/facade.ts +87 -55
  112. package/src/feed.ts +47 -28
  113. package/src/helper/types.ts +15 -0
  114. package/src/helper.ts +27 -10
  115. package/src/index.ts +12 -0
  116. package/src/lifecycle/types.ts +28 -0
  117. package/src/lifecycle.ts +123 -0
  118. package/src/resource-feed.ts +79 -0
  119. package/src/resources.ts +84 -0
  120. package/src/stores.ts +11 -40
  121. package/src/sync/types.ts +18 -0
  122. package/src/sync.ts +40 -0
  123. package/src/types.local.ts +14 -0
  124. package/src/types.ts +53 -5
  125. package/src/utils/record/types.ts +27 -0
  126. package/src/utils/record.ts +68 -71
  127. package/src/validation/consts.ts +289 -0
  128. package/src/validation/types.ts +18 -0
  129. package/src/validation.ts +140 -0
  130. package/tests/app.spec.ts +72 -0
  131. package/tests/context.ts +46 -15
  132. package/tests/csp.spec.ts +119 -0
  133. package/tests/definitions.spec.ts +74 -0
  134. package/tests/draft7.spec.ts +13 -0
  135. package/tests/events.spec.ts +13 -9
  136. package/tests/facade.spec.ts +2 -1
  137. package/tests/lifecycle/context.ts +29 -0
  138. package/tests/lifecycle-close.spec.ts +103 -0
  139. package/tests/lifecycle-commits.spec.ts +92 -0
  140. package/tests/lifecycle-definitions.spec.ts +28 -0
  141. package/tests/lifecycle-feeds.spec.ts +92 -0
  142. package/tests/lifecycle-sockets.spec.ts +76 -0
  143. package/tests/replies.spec.ts +81 -0
  144. package/tests/resources.spec.ts +116 -0
  145. package/tests/stores.spec.ts +13 -11
  146. package/tests/validation.spec.ts +127 -0
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":"AAUA,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,qBAoFF,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,9 +1,11 @@
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
+ import { makeRemoteDefinitions } from './definitions.js';
5
6
  import { makeRemoteFacade } from './facade.js';
6
- import { planningStoresOf } from './stores.js';
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.
@@ -11,33 +13,60 @@ import { planningStoresOf } from './stores.js';
11
13
  * What only a server can do is refused rather than faked: a client runs no middleware and folds
12
14
  * nothing, so `store()`, `committed()` and a plugin carrying hooks or a store answer
13
15
  * `PlanningUnsupported`. A plugin carrying only schemas is accepted — it layers over the server's
14
- * bundle.
16
+ * bundle (its `schemas.app` merged over the served declarations, in plugin order).
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 = () => planningStoresOf(context);
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,
22
- });
23
- const layerPlugins = () => plugins.forEach(plugin => {
24
- plugin.schemas?.flows?.forEach(registry.registerFlow);
25
- plugin.schemas?.types?.forEach(registry.registerType);
34
+ socket: options.socket, poll: options.poll, timeout: options.timeout, stores, lifecycle,
26
35
  });
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
+ };
46
+ let loadedKey = scopeKey();
27
47
  let loaded = false;
28
48
  let loading = null;
29
49
  const loadSchemas = async (opts) => {
50
+ const key = scopeKey();
51
+ if (loadedKey !== key) {
52
+ loaded = false;
53
+ loading = null;
54
+ definitions?.invalidate();
55
+ loadedKey = key;
56
+ }
30
57
  if (loaded && opts?.force !== true) {
31
58
  return registry;
32
59
  }
33
60
  if (loading == null || opts?.force === true) {
34
- const attempt = (async () => {
35
- const bundle = await context.entrypoint(options.protocols.schema.list).call({ timeout: options.timeout });
61
+ const attempt = lifecycle.run(async (operation) => {
62
+ const bundle = await operation.wait(context.entrypoint(options.protocols.schema.list).call({ timeout: options.timeout, signal: operation.signal }));
63
+ operation.check();
36
64
  registry.load(bundle);
65
+ served = registry.app();
37
66
  layerPlugins();
38
67
  loaded = true;
39
68
  return registry;
40
- })();
69
+ });
41
70
  loading = attempt;
42
71
  // A failed load is forgotten, so the next caller asks again instead of inheriting the error.
43
72
  attempt.catch(() => {
@@ -48,19 +77,41 @@ export const makePlanningClientService = (context, options) => {
48
77
  }
49
78
  return await loading;
50
79
  };
80
+ // A write of data-defined schemas moves the organization's bundle too: the next `model()` reloads it.
81
+ const definitions = options.protocols.schema.define == null
82
+ ? undefined
83
+ : makeRemoteDefinitions(context, options.protocols, { lifecycle, timeout: options.timeout, onWrite: () => { loaded = false; loading = null; } });
51
84
  const facadeFor = (scope) => makeRemoteFacade(context, options.protocols, { ...options.scope, ...scope }, {
52
- commits, schemas: registry, loadSchemas, timeout: options.timeout, stores,
85
+ commits, schemas: registry, loadSchemas, scopeKey, lifecycle, timeout: options.timeout, stores,
86
+ ...(definitions != null ? { definitions } : {}),
53
87
  });
54
88
  let fallback = null;
89
+ lifecycle.onInvalidate(async () => {
90
+ const stopping = [...feeds];
91
+ feeds.clear();
92
+ loaded = false;
93
+ loading = null;
94
+ definitions?.invalidate();
95
+ registry.load({ version: 1, types: [], flows: [] });
96
+ served = undefined;
97
+ layerPlugins();
98
+ await Promise.all(stopping.map(async (feed) => await feed.stop()));
99
+ });
55
100
  // Lazy, like the server's host: `context.planning()` answers before the context initializes.
56
101
  return createLazyService(PLANNING_SERVICE, {
57
102
  schemas: registry,
103
+ lifecycle,
104
+ scopeKey,
105
+ registerFeed: feed => { feeds.add(feed); return () => { feeds.delete(feed); }; },
58
106
  commits,
59
107
  loadSchemas,
60
108
  use: plugin => {
61
109
  if (plugin.before != null || plugin.after != null || plugin.store != null || plugin.mintCode != null) {
62
110
  throw new PlanningUnsupported(`client:plugin:${plugin.name}`);
63
111
  }
112
+ if (plugin.schemas?.app != null) {
113
+ appSchemaHelper.assertDeclarations(plugin.schemas.app);
114
+ }
64
115
  const existing = plugins.findIndex(candidate => candidate.name === plugin.name);
65
116
  if (existing >= 0) {
66
117
  plugins.splice(existing, 1);
@@ -77,7 +128,8 @@ export const makePlanningClientService = (context, options) => {
77
128
  committed: async () => {
78
129
  throw new PlanningUnsupported('client:committed');
79
130
  },
80
- close: async () => await commits.close(),
131
+ close: lifecycle.close,
132
+ ...(definitions != null ? { definitions } : {}),
81
133
  });
82
134
  };
83
135
  /**
@@ -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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"}
@@ -1,17 +1,6 @@
1
1
  import type { BasicConfig, BasicContext } from '@owlmeans/context';
2
- import type { PlanningProtocols } from '@owlmeans/planning';
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
@@ -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;AAKlE,OAAO,KAAK,EAC+B,iBAAiB,EAC3D,MAAM,oBAAoB,CAAA;AAG3B,OAAO,KAAK,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA;AAuB/E;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GAAI,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,WAC5E,CAAC,aAAa,iBAAiB,SAAS,yBAAyB,KACzE,kBAuMF,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"}
package/build/commits.js CHANGED
@@ -1,205 +1,263 @@
1
- import { CommitFailed, CommitState, CommitTimeout, DEFAULT_COMMIT_POLL, DEFAULT_COMMIT_TIMEOUT, MAX_COMMIT_POLL, PLANNING_COMMIT_EVENT, PlanningError, WorkcardKind, } from '@owlmeans/planning';
1
+ import { logger } from '@owlmeans/log';
2
+ import { planningReplyHelper, CommitFailed, CommitState, CommitTimeout, DEFAULT_COMMIT_POLL, DEFAULT_COMMIT_TIMEOUT, MAX_COMMIT_POLL, PLANNING_COMMIT_EVENT, PlanningError, WorkcardKind } from '@owlmeans/planning';
2
3
  import { EARLY_POLL_LADDER, EARLY_POLL_MS, LONG_POLL_GRACE } from './consts.js';
3
- import { dropCard, putCard, putCommit } from './utils/record.js';
4
- const matches = (filter, event) => filter == null || ((filter.entityId == null || filter.entityId === event.entityId)
5
- && (filter.project == null || filter.project === event.project)
6
- && (filter.card == null || filter.card === event.card)
7
- && (filter.kind == null || filter.kind === event.kind));
8
- const delay = (ms) => {
9
- let handle;
10
- const promise = new Promise(resolve => { handle = setTimeout(resolve, Math.max(0, ms)); });
11
- return { promise, cancel: () => clearTimeout(handle) };
12
- };
13
- /**
14
- * The commit source of a remote facade: the `commit.get` long poll and, when the host injects a
15
- * socket opener, the `commit.events` feed.
16
- *
17
- * `wait` SUBSCRIBES FIRST and only then reads the status — a commit landing between a read and a
18
- * subscription would otherwise be missed until the deadline. It then long-polls in holds of
19
- * `min(poll, remaining)` seconds, so a client with no socket (a Node connector) still learns of a
20
- * commit within one round trip of it landing, and a client with one learns from whichever arrives
21
- * first. The deadline is a {@link CommitTimeout}; the transition stays pending — nothing is undone.
22
- *
23
- * ONE socket serves every subscription of the source; releasing a subscription never closes it.
24
- */
4
+ import { makePlanningClientLifecycle } from './lifecycle.js';
5
+ import { recordUtils } from './utils/record.js';
6
+ const log = logger('client-planning');
7
+ /** Subscribe before reading, then race the shared socket against bounded HTTP holds. */
25
8
  export const makeRemoteCommitSource = (context, protocols, opts) => {
9
+ const lifecycle = opts?.lifecycle ?? makePlanningClientLifecycle({ scopeKey: opts?.scopeKey });
26
10
  const poll = Math.max(0, Math.min(MAX_COMMIT_POLL, opts?.poll ?? DEFAULT_COMMIT_POLL));
27
11
  const subscriptions = new Set();
12
+ const operations = new Set();
28
13
  let opening = null;
14
+ const openingOf = () => opening;
29
15
  let open = null;
30
16
  let detach = null;
17
+ const capture = () => {
18
+ const captured = lifecycle.capture();
19
+ const operation = { ...captured, release: () => {
20
+ operations.delete(operation);
21
+ captured.release();
22
+ } };
23
+ operations.add(operation);
24
+ return operation;
25
+ };
26
+ const matches = (filter, event) => filter == null || ((filter.entityId == null || filter.entityId === event.entityId)
27
+ && (filter.project == null || filter.project === event.project)
28
+ && (filter.card == null || filter.card === event.card)
29
+ && (filter.kind == null || filter.kind === event.kind));
30
+ const delay = (ms) => {
31
+ let handle;
32
+ const promise = new Promise(resolve => { handle = setTimeout(resolve, Math.max(0, ms)); });
33
+ return { promise, cancel: () => clearTimeout(handle) };
34
+ };
31
35
  const dispatch = async (event) => {
32
36
  for (const subscription of [...subscriptions]) {
33
- if (!matches(subscription.filter, event)) {
37
+ if (!subscriptions.has(subscription) || !subscription.operation.active() || !matches(subscription.filter, event))
34
38
  continue;
35
- }
36
39
  try {
37
- await subscription.listener(event);
40
+ await subscription.operation.wait(Promise.resolve(subscription.listener(event)));
38
41
  }
39
- catch (e) {
40
- console.error('Planning commit listener error:', e);
42
+ catch (error) {
43
+ if (subscription.operation.active())
44
+ log.error('Planning commit listener failed', error);
41
45
  }
42
46
  }
43
47
  };
44
48
  const socket = async () => {
45
49
  const opener = opts?.socket;
46
- if (opener == null) {
50
+ if (opener == null)
47
51
  return null;
48
- }
49
- opening ??= (async () => {
52
+ lifecycle.key(); // Observe an identity change before reusing an open carrier.
53
+ if (open != null)
54
+ return open;
55
+ if (opening != null)
56
+ return await opening.promise;
57
+ const operation = capture();
58
+ const pending = (async () => {
50
59
  try {
51
- const connection = await opener(protocols.commit.events, { query: {} });
52
- if (connection == null) {
53
- // No socket right now (not signed in yet, say) — the next subscription asks again.
54
- opening = null;
55
- return null;
56
- }
57
- open = connection;
58
- detach = connection.observe(PLANNING_COMMIT_EVENT, async (message) => {
59
- await dispatch(message.payload);
60
+ const carrier = Promise.resolve().then(async () => {
61
+ operation.check();
62
+ return await opener(protocols.commit.events, { query: {}, signal: operation.signal });
63
+ }).then(async (connection) => {
64
+ if (connection == null)
65
+ return null;
66
+ if (!operation.active()) {
67
+ await connection.close();
68
+ return null;
69
+ }
70
+ open = connection;
71
+ detach = connection.observe(PLANNING_COMMIT_EVENT, async (message) => {
72
+ if (operation.active())
73
+ await dispatch(planningReplyHelper.hydrate(message.payload));
74
+ });
75
+ return connection;
60
76
  });
61
- return connection;
77
+ return await operation.wait(carrier);
62
78
  }
63
- catch (e) {
64
- opening = null;
65
- console.error('Planning commit socket error:', e);
79
+ catch (error) {
80
+ if (operation.active())
81
+ log.warn('Planning commit socket failed to open', error);
66
82
  return null;
67
83
  }
84
+ finally {
85
+ if (openingOf()?.operation === operation)
86
+ opening = null;
87
+ // Keep an open carrier's operation cancellable until close; it guards received frames.
88
+ if (open == null || !operation.active())
89
+ operation.release();
90
+ }
68
91
  })();
69
- return await opening;
92
+ opening = { operation, promise: pending };
93
+ return await pending;
70
94
  };
71
95
  const endpoint = () => context.entrypoint(protocols.commit.get);
72
- const status = async (transition) => await endpoint().call({
73
- params: { transition }, query: { wait: 0 }, timeout: opts?.timeout,
74
- });
75
- const hold = async (transition, wait, signal) => {
96
+ const status = async (transition, operation) => {
97
+ operation.check();
98
+ return planningReplyHelper.hydrate(await operation.wait(endpoint().call({
99
+ params: { transition }, query: { wait: 0 }, timeout: opts?.timeout, signal: operation.signal,
100
+ })));
101
+ };
102
+ const hold = async (transition, wait, signal, operation) => {
103
+ operation.check();
76
104
  try {
77
- return await endpoint().call({
105
+ return planningReplyHelper.hydrate(await operation.wait(endpoint().call({
78
106
  params: { transition }, query: { wait }, timeout: wait * 1000 + LONG_POLL_GRACE, signal,
79
- });
107
+ })));
80
108
  }
81
- catch (e) {
82
- if (signal.aborted || e instanceof PlanningError) {
83
- throw e;
84
- }
85
- // A dropped hold (a proxy closing an idle response) is not an answer about the commit: one
86
- // non-holding read replaces it, and a failure of THAT is reported.
87
- return await status(transition);
109
+ catch (error) {
110
+ operation.check();
111
+ if (signal.aborted || error instanceof PlanningError)
112
+ throw error;
113
+ // A dropped hold says nothing about the commit. Never retry it under another scope.
114
+ return await status(transition, operation);
88
115
  }
89
116
  };
90
- /** Fold a settled status into the mirror, when the host registered one. */
91
- const fold = async (transition, settled) => {
117
+ const answer = async (transition, settled, operation) => {
92
118
  const stores = opts?.stores?.();
93
- if (stores == null) {
94
- return;
95
- }
96
- const known = await stores.commits.load(transition);
97
- await putCommit(stores.commits, {
98
- id: transition, state: settled.state, at: settled.at, error: settled.error,
99
- });
100
- if (settled.state !== CommitState.Committed) {
101
- return;
102
- }
103
- if (settled.card != null) {
104
- await putCard(stores.cards, settled.card);
105
- }
106
- else if (settled.card === null && known?.card != null) {
107
- await dropCard(stores, known.card, known.kind === WorkcardKind.Project);
108
- }
109
- };
110
- const answer = async (transition, settled) => {
111
- await fold(transition, settled);
112
- if (settled.state === CommitState.Failed) {
119
+ if (stores != null)
120
+ await lifecycle.mutate(operation, async () => {
121
+ const known = await stores.commits.load(transition);
122
+ await recordUtils.putCommit(stores.commits, {
123
+ id: transition, state: settled.state, at: settled.at, error: settled.error,
124
+ });
125
+ if (settled.state === CommitState.Committed) {
126
+ if (settled.card != null)
127
+ await recordUtils.putCard(stores.cards, settled.card);
128
+ else if (settled.card === null && known?.card != null)
129
+ await recordUtils.dropCard(stores, known.card, known.kind === WorkcardKind.Project);
130
+ }
131
+ });
132
+ operation.check();
133
+ if (settled.state === CommitState.Failed)
113
134
  throw new CommitFailed(settled.error ?? transition);
114
- }
115
135
  return settled.card ?? null;
116
136
  };
117
- const subscribe = async (listener, filter) => {
118
- const subscription = { listener, filter };
137
+ const subscribe = async (listener, filter, signal) => {
138
+ const operation = capture();
139
+ const subscription = { listener, filter, operation };
119
140
  subscriptions.add(subscription);
120
- await socket();
121
- return () => { subscriptions.delete(subscription); };
141
+ const stop = () => {
142
+ subscriptions.delete(subscription);
143
+ operation.cancel();
144
+ operation.release();
145
+ signal?.removeEventListener('abort', stop);
146
+ };
147
+ signal?.addEventListener('abort', stop, { once: true });
148
+ if (signal?.aborted)
149
+ stop();
150
+ try {
151
+ await operation.wait(socket());
152
+ return stop;
153
+ }
154
+ catch (error) {
155
+ stop();
156
+ throw error;
157
+ }
158
+ };
159
+ const close = async () => {
160
+ for (const operation of operations) {
161
+ operation.cancel();
162
+ operation.release();
163
+ }
164
+ subscriptions.clear();
165
+ detach?.();
166
+ detach = null;
167
+ const connection = open;
168
+ open = null;
169
+ opening = null;
170
+ await Promise.all([connection?.close(), lifecycle.drain()]);
122
171
  };
123
- const source = {
124
- status,
172
+ lifecycle.onInvalidate(close);
173
+ return {
174
+ status: async (transition) => {
175
+ const operation = capture();
176
+ try {
177
+ return await status(transition, operation);
178
+ }
179
+ finally {
180
+ operation.release();
181
+ }
182
+ },
125
183
  subscribe,
126
184
  wait: async (transition, waitOpts) => {
127
- const deadline = Date.now() + (waitOpts?.timeout ?? DEFAULT_COMMIT_TIMEOUT);
128
- // Held in an object: a frame writes it from a callback, which flow analysis cannot see.
185
+ const operation = capture();
186
+ const duration = waitOpts?.timeout ?? DEFAULT_COMMIT_TIMEOUT;
187
+ const deadline = Date.now() + duration;
188
+ const deadlineTimer = setTimeout(() => operation.cancel(new CommitTimeout(transition)), Math.max(0, duration));
129
189
  const frame = { landed: null };
130
190
  let wake = () => { };
131
- const unsubscribe = await subscribe(event => {
132
- if (event.transition === transition && event.state !== CommitState.Pending) {
133
- frame.landed = event;
134
- wake();
135
- }
136
- });
137
- /** A frame says it settled; the status read carries the card when the frame does not. */
138
- const fromFrame = async (event) => event.record !== undefined
139
- ? await answer(transition, {
140
- transition, state: event.state, at: event.at, error: event.error, card: event.record,
141
- })
142
- : await answer(transition, await status(transition));
191
+ let unsubscribe;
143
192
  try {
144
- let current = await status(transition);
193
+ const subscribing = subscribe(event => {
194
+ if (operation.active() && event.transition === transition && event.state !== CommitState.Pending) {
195
+ frame.landed = event;
196
+ wake();
197
+ }
198
+ }, undefined, operation.signal);
199
+ // A cancelled waiter must also release a subscription whose opener completes later.
200
+ void subscribing.then(stop => { if (!operation.active())
201
+ stop(); }, () => { });
202
+ unsubscribe = await operation.wait(subscribing);
203
+ const fromFrame = async (event) => await answer(transition, event.record !== undefined
204
+ ? { transition, state: event.state, at: event.at, error: event.error, card: event.record }
205
+ : await status(transition, operation), operation);
206
+ let current = await status(transition, operation);
145
207
  let early = 0;
146
208
  while (current.state === CommitState.Pending) {
147
- if (frame.landed != null) {
209
+ operation.check();
210
+ if (frame.landed != null)
148
211
  return await fromFrame(frame.landed);
149
- }
150
212
  const remaining = deadline - Date.now();
151
- if (remaining <= 0) {
213
+ if (remaining <= 0)
152
214
  throw new CommitTimeout(transition);
153
- }
154
215
  const wait = Math.min(poll, Math.ceil(remaining / 1000));
155
216
  const abort = new AbortController();
217
+ const cancel = () => abort.abort(operation.signal.reason);
218
+ operation.signal.addEventListener('abort', cancel, { once: true });
156
219
  const timer = delay(remaining);
157
220
  const framed = new Promise(resolve => { wake = () => resolve('frame'); });
158
221
  const started = Date.now();
159
222
  try {
160
- const outcome = await Promise.race([
161
- hold(transition, wait, abort.signal),
162
- framed,
163
- timer.promise.then(() => 'deadline'),
164
- ]);
165
- if (outcome === 'frame' || outcome === 'deadline') {
223
+ const outcome = await operation.wait(Promise.race([
224
+ hold(transition, wait, abort.signal, operation), framed, timer.promise.then(() => 'deadline'),
225
+ ]));
226
+ if (outcome === 'frame' || outcome === 'deadline')
166
227
  continue;
167
- }
168
228
  current = outcome;
169
229
  }
170
230
  finally {
171
231
  abort.abort();
172
232
  timer.cancel();
173
233
  wake = () => { };
234
+ operation.signal.removeEventListener('abort', cancel);
174
235
  }
175
236
  if (current.state === CommitState.Pending && Date.now() - started < EARLY_POLL_MS) {
176
- // The server did not hold — pause on a ladder rather than spin until the deadline.
177
237
  const pause = delay(Math.min(EARLY_POLL_LADDER[Math.min(early, EARLY_POLL_LADDER.length - 1)], deadline - Date.now()));
178
- early += 1;
179
- await Promise.race([pause.promise, new Promise(resolve => { wake = resolve; })]);
180
- pause.cancel();
181
- wake = () => { };
238
+ early++;
239
+ try {
240
+ await operation.wait(Promise.race([pause.promise, new Promise(resolve => { wake = resolve; })]));
241
+ }
242
+ finally {
243
+ pause.cancel();
244
+ wake = () => { };
245
+ }
182
246
  }
183
- else {
247
+ else
184
248
  early = 0;
185
- }
186
249
  }
187
- return await answer(transition, current);
250
+ return await answer(transition, current, operation);
188
251
  }
189
252
  finally {
190
- unsubscribe();
253
+ clearTimeout(deadlineTimer);
254
+ unsubscribe?.();
255
+ operation.cancel();
256
+ operation.release();
191
257
  }
192
258
  },
193
- connected: () => open != null,
194
- close: async () => {
195
- detach?.();
196
- detach = null;
197
- const connection = open;
198
- open = null;
199
- opening = null;
200
- await connection?.close();
201
- },
259
+ connected: () => { lifecycle.key(); return open != null; },
260
+ close,
202
261
  };
203
- return source;
204
262
  };
205
263
  //# sourceMappingURL=commits.js.map