@primafuture/contrib-kit-react 1.0.0

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 (128) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +294 -0
  3. package/dist/contribution-component.d.ts +82 -0
  4. package/dist/contribution-component.d.ts.map +1 -0
  5. package/dist/contribution-component.js +12 -0
  6. package/dist/contribution-component.js.map +1 -0
  7. package/dist/extension-outlet.d.ts +155 -0
  8. package/dist/extension-outlet.d.ts.map +1 -0
  9. package/dist/extension-outlet.js +229 -0
  10. package/dist/extension-outlet.js.map +1 -0
  11. package/dist/extension-registry-provider.d.ts +26 -0
  12. package/dist/extension-registry-provider.d.ts.map +1 -0
  13. package/dist/extension-registry-provider.js +82 -0
  14. package/dist/extension-registry-provider.js.map +1 -0
  15. package/dist/index.d.ts +22 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +14 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/internal/adapter-configuration.d.ts +54 -0
  20. package/dist/internal/adapter-configuration.d.ts.map +1 -0
  21. package/dist/internal/adapter-configuration.js +89 -0
  22. package/dist/internal/adapter-configuration.js.map +1 -0
  23. package/dist/internal/adapter-context.d.ts +24 -0
  24. package/dist/internal/adapter-context.d.ts.map +1 -0
  25. package/dist/internal/adapter-context.js +23 -0
  26. package/dist/internal/adapter-context.js.map +1 -0
  27. package/dist/internal/adapter-registry-bridge.d.ts +58 -0
  28. package/dist/internal/adapter-registry-bridge.d.ts.map +1 -0
  29. package/dist/internal/adapter-registry-bridge.js +494 -0
  30. package/dist/internal/adapter-registry-bridge.js.map +1 -0
  31. package/dist/internal/adapter-registry-protocol.d.ts +53 -0
  32. package/dist/internal/adapter-registry-protocol.d.ts.map +1 -0
  33. package/dist/internal/adapter-registry-protocol.js +182 -0
  34. package/dist/internal/adapter-registry-protocol.js.map +1 -0
  35. package/dist/internal/adapter-scope.d.ts +87 -0
  36. package/dist/internal/adapter-scope.d.ts.map +1 -0
  37. package/dist/internal/adapter-scope.js +346 -0
  38. package/dist/internal/adapter-scope.js.map +1 -0
  39. package/dist/internal/implementation-classifier.d.ts +21 -0
  40. package/dist/internal/implementation-classifier.d.ts.map +1 -0
  41. package/dist/internal/implementation-classifier.js +39 -0
  42. package/dist/internal/implementation-classifier.js.map +1 -0
  43. package/dist/internal/managed-lazy-generation.d.ts +100 -0
  44. package/dist/internal/managed-lazy-generation.d.ts.map +1 -0
  45. package/dist/internal/managed-lazy-generation.js +419 -0
  46. package/dist/internal/managed-lazy-generation.js.map +1 -0
  47. package/dist/internal/outlet-commit-lease.d.ts +46 -0
  48. package/dist/internal/outlet-commit-lease.d.ts.map +1 -0
  49. package/dist/internal/outlet-commit-lease.js +137 -0
  50. package/dist/internal/outlet-commit-lease.js.map +1 -0
  51. package/dist/internal/outlet-error-pipeline.d.ts +122 -0
  52. package/dist/internal/outlet-error-pipeline.d.ts.map +1 -0
  53. package/dist/internal/outlet-error-pipeline.js +214 -0
  54. package/dist/internal/outlet-error-pipeline.js.map +1 -0
  55. package/dist/internal/outlet-input.d.ts +42 -0
  56. package/dist/internal/outlet-input.d.ts.map +1 -0
  57. package/dist/internal/outlet-input.js +86 -0
  58. package/dist/internal/outlet-input.js.map +1 -0
  59. package/dist/internal/outlet-membership-boundary.d.ts +93 -0
  60. package/dist/internal/outlet-membership-boundary.d.ts.map +1 -0
  61. package/dist/internal/outlet-membership-boundary.js +710 -0
  62. package/dist/internal/outlet-membership-boundary.js.map +1 -0
  63. package/dist/internal/outlet-membership.d.ts +31 -0
  64. package/dist/internal/outlet-membership.d.ts.map +1 -0
  65. package/dist/internal/outlet-membership.js +65 -0
  66. package/dist/internal/outlet-membership.js.map +1 -0
  67. package/dist/internal/outlet-projection.d.ts +102 -0
  68. package/dist/internal/outlet-projection.d.ts.map +1 -0
  69. package/dist/internal/outlet-projection.js +228 -0
  70. package/dist/internal/outlet-projection.js.map +1 -0
  71. package/dist/internal/outlet-reporter-boundary.d.ts +42 -0
  72. package/dist/internal/outlet-reporter-boundary.d.ts.map +1 -0
  73. package/dist/internal/outlet-reporter-boundary.js +57 -0
  74. package/dist/internal/outlet-reporter-boundary.js.map +1 -0
  75. package/dist/internal/public-input-capture.d.ts +13 -0
  76. package/dist/internal/public-input-capture.d.ts.map +1 -0
  77. package/dist/internal/public-input-capture.js +34 -0
  78. package/dist/internal/public-input-capture.js.map +1 -0
  79. package/dist/internal/resolved-contributions-query.d.ts +42 -0
  80. package/dist/internal/resolved-contributions-query.d.ts.map +1 -0
  81. package/dist/internal/resolved-contributions-query.js +203 -0
  82. package/dist/internal/resolved-contributions-query.js.map +1 -0
  83. package/dist/react-adapter-diagnostics.d.ts +199 -0
  84. package/dist/react-adapter-diagnostics.d.ts.map +1 -0
  85. package/dist/react-adapter-diagnostics.js +257 -0
  86. package/dist/react-adapter-diagnostics.js.map +1 -0
  87. package/dist/react-adapter-error.d.ts +35 -0
  88. package/dist/react-adapter-error.d.ts.map +1 -0
  89. package/dist/react-adapter-error.js +37 -0
  90. package/dist/react-adapter-error.js.map +1 -0
  91. package/dist/react-extension-point.d.ts +26 -0
  92. package/dist/react-extension-point.d.ts.map +1 -0
  93. package/dist/react-extension-point.js +100 -0
  94. package/dist/react-extension-point.js.map +1 -0
  95. package/dist/react-lazy-contribution.d.ts +40 -0
  96. package/dist/react-lazy-contribution.d.ts.map +1 -0
  97. package/dist/react-lazy-contribution.js +18 -0
  98. package/dist/react-lazy-contribution.js.map +1 -0
  99. package/dist/resolved-contributions.d.ts +90 -0
  100. package/dist/resolved-contributions.d.ts.map +1 -0
  101. package/dist/resolved-contributions.js +455 -0
  102. package/dist/resolved-contributions.js.map +1 -0
  103. package/package.json +47 -0
  104. package/src/contribution-component.ts +118 -0
  105. package/src/extension-outlet.ts +590 -0
  106. package/src/extension-registry-provider.ts +179 -0
  107. package/src/index.ts +72 -0
  108. package/src/internal/adapter-configuration.ts +181 -0
  109. package/src/internal/adapter-context.ts +43 -0
  110. package/src/internal/adapter-registry-bridge.ts +723 -0
  111. package/src/internal/adapter-registry-protocol.ts +365 -0
  112. package/src/internal/adapter-scope.ts +500 -0
  113. package/src/internal/implementation-classifier.ts +59 -0
  114. package/src/internal/managed-lazy-generation.ts +540 -0
  115. package/src/internal/outlet-commit-lease.ts +216 -0
  116. package/src/internal/outlet-error-pipeline.ts +447 -0
  117. package/src/internal/outlet-input.ts +264 -0
  118. package/src/internal/outlet-membership-boundary.ts +1048 -0
  119. package/src/internal/outlet-membership.ts +125 -0
  120. package/src/internal/outlet-projection.ts +511 -0
  121. package/src/internal/outlet-reporter-boundary.ts +105 -0
  122. package/src/internal/public-input-capture.ts +55 -0
  123. package/src/internal/resolved-contributions-query.ts +318 -0
  124. package/src/react-adapter-diagnostics.ts +457 -0
  125. package/src/react-adapter-error.ts +88 -0
  126. package/src/react-extension-point.ts +181 -0
  127. package/src/react-lazy-contribution.ts +66 -0
  128. package/src/resolved-contributions.ts +895 -0
@@ -0,0 +1,723 @@
1
+ import type * as contribKitCore from "@primafuture/contrib-kit-core";
2
+
3
+ import * as adapterRegistryProtocol from "./adapter-registry-protocol.js";
4
+ import * as reactAdapterError from "../react-adapter-error.js";
5
+
6
+ /** Listener tvar kompatibilní s React external-store subscribe callbackem. */
7
+ export type AdapterExternalStoreListener = () => unknown;
8
+
9
+ /** Immutable point-specific snapshot token vytvořený pouze mimo render. */
10
+ export interface AdapterPointSnapshotToken {
11
+ /** Rozlišuje committed point cell od pre-subscribe primitive. */
12
+ readonly kind: "point";
13
+ /** Bridge-local monotonní změna bez veřejného identity kontraktu. */
14
+ readonly serial: number;
15
+ }
16
+
17
+ /** Hybridní client snapshot před a po committed point bindu. */
18
+ export type AdapterExternalStoreSnapshot =
19
+ | adapterRegistryProtocol.AdapterRegistrySnapshotToken
20
+ | AdapterPointSnapshotToken
21
+ | "contrib-kit-react:server";
22
+
23
+ /** Constant logical SSR/hydration sentinel bez request-local identity. */
24
+ export const ADAPTER_SERVER_SNAPSHOT = "contrib-kit-react:server";
25
+
26
+ /** Interní host lifecycle oddělující bridge od konkrétního React hooku. */
27
+ export interface AdapterRegistryBridgeHost {
28
+ /** Ověří current scope epoch po každém reentrantním Core seam. */
29
+ readonly checkpoint: () => void;
30
+ /** Povolí ordinary last-consumer detach pouze během aktivní scope epochy. */
31
+ readonly canFinalizeDetach: () => boolean;
32
+ /** Deterministicky naplánuje nejbližší microtask finalizaci. */
33
+ readonly scheduleMicrotask: (callback: () => void) => void;
34
+ /** Best-effort seam pro committed lifecycle diagnostiku. */
35
+ readonly reportLifecycleFailure: (error: reactAdapterError.ReactAdapterError) => void;
36
+ /** Odstraní fyzicky detached bridge z owning epoch mapy. */
37
+ readonly onDetached: (bridge: AdapterRegistryBridge) => void;
38
+ }
39
+
40
+ /** Mutable committed cell jedné exact canonical point reference. */
41
+ interface AdapterPointCell {
42
+ /** Přesná canonical point capability. */
43
+ readonly point: contribKitCore.AnyExtensionPoint;
44
+ /** Insertion-ordered committed React listeners. */
45
+ readonly listeners: Map<AdapterExternalStoreListener, number>;
46
+ /** Poslední immutable token viditelný všem consumerům pointu. */
47
+ token: AdapterPointSnapshotToken;
48
+ }
49
+
50
+ /** Opaque idempotentní membership jednoho committed subscribe callu. */
51
+ interface AdapterBridgeConsumerRecord {
52
+ /** Určuje, zda cleanup ještě smí změnit ownership. */
53
+ active: boolean;
54
+ /** Point cell sdílený dalšími consumery stejného pointu. */
55
+ readonly cell: AdapterPointCell;
56
+ /** Přesný listener předaný React external-store contractem. */
57
+ readonly listener: AdapterExternalStoreListener;
58
+ }
59
+
60
+ /** Validovaný immutable event snapshot před jakoukoli bridge mutací. */
61
+ interface CapturedRegistryEvent {
62
+ /** Exact public Core event kind. */
63
+ readonly kind: contribKitCore.RegistryEventKind;
64
+ /** Canonical point references v public committed pořadí. */
65
+ readonly affectedPoints: readonly contribKitCore.AnyExtensionPoint[];
66
+ }
67
+
68
+ /** Public Core event kinds přijímané strukturálním subscriberem. */
69
+ const registryEventKinds: ReadonlySet<contribKitCore.RegistryEventKind> = new Set([
70
+ "pointRegistered",
71
+ "pointPolicyUpdated",
72
+ "pointDefinitionRefreshed",
73
+ "contributionActivated",
74
+ "contributionDisposed",
75
+ "contributionReplaced",
76
+ "pointClosed",
77
+ "registryDisposing",
78
+ "registryDisposed",
79
+ ]);
80
+
81
+ /**
82
+ * Vytvoří stabilní lifecycle chybu bez raw registry, eventu nebo listeneru.
83
+ */
84
+ function createBridgeError(
85
+ code: "REACT_REGISTRY_INVALID" | "REACT_REGISTRY_UNAVAILABLE" | "REACT_CALLBACK_FAILED",
86
+ message: string,
87
+ field: string,
88
+ cause?: unknown,
89
+ preserveCause = false,
90
+ ): reactAdapterError.ReactAdapterError {
91
+ return new reactAdapterError.ReactAdapterError(
92
+ code,
93
+ message,
94
+ preserveCause
95
+ ? { cause, details: { field, phase: "lifecycle" } }
96
+ : { details: { field, phase: "lifecycle" } },
97
+ );
98
+ }
99
+
100
+ /**
101
+ * Pohltí sync throw, throwing thenable i async rejection cleanup callbacku.
102
+ */
103
+ function consumeCleanupResult(
104
+ callback: () => unknown,
105
+ reportFailure: (cause: unknown) => void,
106
+ ): void {
107
+ let result: unknown;
108
+ try {
109
+ result = callback();
110
+ } catch (cause: unknown) {
111
+ reportFailure(cause);
112
+ return;
113
+ }
114
+
115
+ try {
116
+ void Promise.resolve(result).catch(reportFailure);
117
+ } catch (cause: unknown) {
118
+ reportFailure(cause);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Bezpečně doručí external-store listener bez úniku host failure.
124
+ */
125
+ function notifyListener(
126
+ listener: AdapterExternalStoreListener,
127
+ reportFailure: (cause: unknown) => void,
128
+ ): void {
129
+ consumeCleanupResult(listener, reportFailure);
130
+ }
131
+
132
+ /**
133
+ * Přečte jedno event pole právě jednou se zachovaným returned receiverem.
134
+ */
135
+ function readEventProperty(event: object, property: PropertyKey): unknown {
136
+ try {
137
+ return Reflect.get(event, property, event);
138
+ } catch (cause: unknown) {
139
+ throw createBridgeError(
140
+ "REACT_REGISTRY_INVALID",
141
+ "Unable to read a committed registry event field.",
142
+ `registryEvent.${String(property)}`,
143
+ cause,
144
+ true,
145
+ );
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Přečte jeden array index bez spreadu nebo iterator user code.
151
+ */
152
+ function readArrayIndex(values: readonly unknown[], index: number, field: string): unknown {
153
+ try {
154
+ return Reflect.get(values, index, values);
155
+ } catch (cause: unknown) {
156
+ throw createBridgeError(
157
+ "REACT_REGISTRY_INVALID",
158
+ "Unable to read a committed registry event array entry.",
159
+ `${field}[${index}]`,
160
+ cause,
161
+ true,
162
+ );
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Zachytí a plně validuje event dříve, než změní jediný point token.
168
+ */
169
+ function captureRegistryEvent(value: unknown): CapturedRegistryEvent {
170
+ if (typeof value !== "object" || value === null) {
171
+ throw createBridgeError(
172
+ "REACT_REGISTRY_INVALID",
173
+ "Committed registry event must be a non-null object.",
174
+ "registryEvent",
175
+ );
176
+ }
177
+
178
+ const revision = readEventProperty(value, "revision");
179
+ const kind = readEventProperty(value, "kind");
180
+ const affectedPoints = readEventProperty(value, "affectedPoints");
181
+ const pointRevisions = readEventProperty(value, "pointRevisions");
182
+ if (typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 1) {
183
+ throw createBridgeError(
184
+ "REACT_REGISTRY_INVALID",
185
+ "Committed registry event revision is invalid.",
186
+ "registryEvent.revision",
187
+ );
188
+ }
189
+ if (typeof kind !== "string" || !registryEventKinds.has(kind as contribKitCore.RegistryEventKind)) {
190
+ throw createBridgeError(
191
+ "REACT_REGISTRY_INVALID",
192
+ "Committed registry event kind is invalid.",
193
+ "registryEvent.kind",
194
+ );
195
+ }
196
+ if (!Array.isArray(affectedPoints) || !Array.isArray(pointRevisions)) {
197
+ throw createBridgeError(
198
+ "REACT_REGISTRY_INVALID",
199
+ "Committed registry event point collections must be arrays.",
200
+ "registryEvent.affectedPoints",
201
+ );
202
+ }
203
+ if (affectedPoints.length !== pointRevisions.length) {
204
+ throw createBridgeError(
205
+ "REACT_REGISTRY_INVALID",
206
+ "Committed registry event point collections have different lengths.",
207
+ "registryEvent.pointRevisions",
208
+ );
209
+ }
210
+
211
+ const capturedPoints: contribKitCore.AnyExtensionPoint[] = [];
212
+ for (let index = 0; index < affectedPoints.length; index += 1) {
213
+ const point = readArrayIndex(affectedPoints, index, "registryEvent.affectedPoints");
214
+ const entry = readArrayIndex(pointRevisions, index, "registryEvent.pointRevisions");
215
+ if (typeof point !== "object" || point === null
216
+ || typeof entry !== "object" || entry === null) {
217
+ throw createBridgeError(
218
+ "REACT_REGISTRY_INVALID",
219
+ "Committed registry event point entry is invalid.",
220
+ `registryEvent.pointRevisions[${index}]`,
221
+ );
222
+ }
223
+ const entryPoint = readEventProperty(entry, "point");
224
+ const pointRevision = readEventProperty(entry, "pointRevision");
225
+ if (entryPoint !== point
226
+ || typeof pointRevision !== "number"
227
+ || !Number.isSafeInteger(pointRevision)
228
+ || pointRevision < 1) {
229
+ throw createBridgeError(
230
+ "REACT_REGISTRY_INVALID",
231
+ "Committed registry event point revision is invalid.",
232
+ `registryEvent.pointRevisions[${index}]`,
233
+ );
234
+ }
235
+ capturedPoints.push(point as contribKitCore.AnyExtensionPoint);
236
+ }
237
+
238
+ return Object.freeze({
239
+ kind: kind as contribKitCore.RegistryEventKind,
240
+ affectedPoints: Object.freeze(capturedPoints),
241
+ });
242
+ }
243
+
244
+ /**
245
+ * Scope-owned multiplexer jednoho Core subscriberu a exact point cells.
246
+ */
247
+ export class AdapterRegistryBridge {
248
+ /** First committed read-once registry protocol držený do detach. */
249
+ readonly #protocol: adapterRegistryProtocol.AdapterRegistryProtocolSnapshot;
250
+ /** Owning epoch callbacks a deterministic scheduler. */
251
+ readonly #host: AdapterRegistryBridgeHost;
252
+ /** Weak lookup canonical pointu bez process-global retention. */
253
+ readonly #cellsByPoint = new WeakMap<object, AdapterPointCell>();
254
+ /** Enumerovatelný bounded set pouze pro global invalidaci a detach. */
255
+ readonly #cells = new Set<AdapterPointCell>();
256
+ /** Všechny committed consumer records současné epochy. */
257
+ readonly #consumers = new Set<AdapterBridgeConsumerRecord>();
258
+ /** Zachycený Core unsubscribe callback právě aktivního subscriberu. */
259
+ #unsubscribe: (() => unknown) | undefined;
260
+ /** Zabrání reentrantnímu druhému fyzickému subscribe pokusu. */
261
+ #subscribing = false;
262
+ /** Terminální bridge detach po skutečném grace drainu. */
263
+ #detached = false;
264
+ /** Monotónní token rušící stale detach microtask. */
265
+ #detachGeneration = 0;
266
+ /** Monotónní serial immutable point snapshotů. */
267
+ #snapshotSerial = 0;
268
+ /** První malformed/setup fault sdílený všemi point cells. */
269
+ #fault: reactAdapterError.ReactAdapterError | undefined;
270
+ /** Poslední validní registry lifecycle stav z Core eventů. */
271
+ #registryState: contribKitCore.ExtensionRegistryState = "active";
272
+
273
+ /** Vytvoří lazy bridge bez subscriberu, cellu nebo microtasku. */
274
+ public constructor(
275
+ protocol: adapterRegistryProtocol.AdapterRegistryProtocolSnapshot,
276
+ host: AdapterRegistryBridgeHost,
277
+ ) {
278
+ this.#protocol = protocol;
279
+ this.#host = host;
280
+ }
281
+
282
+ /** Vrátí přesnou raw registry identity používanou owning mapou. */
283
+ public readRawRegistry(): object {
284
+ return this.#protocol.rawRegistry;
285
+ }
286
+
287
+ /** Vrátí první stabilní fault pro následnou hook error projekci. */
288
+ public readFault(): reactAdapterError.ReactAdapterError | undefined {
289
+ return this.#fault;
290
+ }
291
+
292
+ /** Vrátí poslední validní registry-wide lifecycle stav. */
293
+ public readRegistryState(): contribKitCore.ExtensionRegistryState {
294
+ return this.#registryState;
295
+ }
296
+
297
+ /** Pure lookup již committed point cellu bez vytvoření cache state. */
298
+ public peekSnapshot(
299
+ point: contribKitCore.AnyExtensionPoint,
300
+ ): AdapterPointSnapshotToken | undefined {
301
+ return this.#cellsByPoint.get(point)?.token;
302
+ }
303
+
304
+ /**
305
+ * Commit-time acquire jednoho React listeneru se subscription rollbackem.
306
+ */
307
+ public subscribe(
308
+ point: contribKitCore.AnyExtensionPoint,
309
+ listener: AdapterExternalStoreListener,
310
+ ): contribKitCore.Unsubscribe {
311
+ if (typeof listener !== "function") {
312
+ throw new reactAdapterError.ReactAdapterError(
313
+ "REACT_INVALID_ARGUMENT",
314
+ "External-store listener must be callable.",
315
+ { details: { field: "listener", phase: "lifecycle" } },
316
+ );
317
+ }
318
+ if (this.#detached) {
319
+ return function releaseDetachedSubscribe(): void {
320
+ // Terminálně detached bridge stale subscribe znovu neaktivuje.
321
+ };
322
+ }
323
+
324
+ this.#host.checkpoint();
325
+ this.#cancelPendingDetach();
326
+ if (this.#fault !== undefined || this.#registryState !== "active") {
327
+ return this.#notifyRejectedSubscribe(listener);
328
+ }
329
+
330
+ const cell = this.#getOrCreateCell(point);
331
+ const record: AdapterBridgeConsumerRecord = {
332
+ active: true,
333
+ cell,
334
+ listener,
335
+ };
336
+ this.#consumers.add(record);
337
+ cell.listeners.set(listener, (cell.listeners.get(listener) ?? 0) + 1);
338
+
339
+ if (this.#unsubscribe === undefined && !this.#establishSubscription()) {
340
+ this.#removeConsumer(record);
341
+ return function releaseRejectedProvisionalSubscribe(): void {
342
+ // Fault už provisional listener invalidoval; stale scope jej nedoručuje.
343
+ };
344
+ }
345
+
346
+ try {
347
+ this.#host.checkpoint();
348
+ } catch {
349
+ this.#removeConsumer(record);
350
+ this.#scheduleDetachIfEmpty();
351
+ return function releaseStaleSubscribe(): void {
352
+ // Reentrant scope invalidace již provisional membership odstranila.
353
+ };
354
+ }
355
+
356
+ this.#notifyOne(listener);
357
+ const bridge = this;
358
+ /** Idempotentně uvolní právě tuto committed membership. */
359
+ function releaseConsumer(): void {
360
+ bridge.#releaseConsumer(record);
361
+ }
362
+
363
+ return releaseConsumer;
364
+ }
365
+
366
+ /**
367
+ * Scope cleanup okamžitě revokuje listeners a odloží fyzický unsubscribe.
368
+ */
369
+ public prepareEpochDeactivation(): void {
370
+ if (this.#detached) {
371
+ return;
372
+ }
373
+ for (const record of [...this.#consumers]) {
374
+ this.#removeConsumer(record);
375
+ }
376
+ this.#scheduleDetach();
377
+ }
378
+
379
+ /** Zruší Strict grace detach před vznikem nového consumeru stejné epochy. */
380
+ public reactivateEpoch(): void {
381
+ if (!this.#detached) {
382
+ this.#cancelPendingDetach();
383
+ }
384
+ }
385
+
386
+ /** Terminálně odpojí bridge při generation switchi bez grace. */
387
+ public forceDetach(): void {
388
+ if (this.#detached) {
389
+ return;
390
+ }
391
+ this.#detachGeneration += 1;
392
+ for (const record of [...this.#consumers]) {
393
+ this.#removeConsumer(record);
394
+ }
395
+ this.#finalizeDetach();
396
+ }
397
+
398
+ /** Vytvoří immutable point token pouze v commit-time cestě. */
399
+ #createPointToken(): AdapterPointSnapshotToken {
400
+ this.#snapshotSerial += 1;
401
+ return Object.freeze({ kind: "point", serial: this.#snapshotSerial });
402
+ }
403
+
404
+ /** Vrátí existující cell nebo commit-time vytvoří bounded nový cell. */
405
+ #getOrCreateCell(point: contribKitCore.AnyExtensionPoint): AdapterPointCell {
406
+ const existing = this.#cellsByPoint.get(point);
407
+ if (existing !== undefined) {
408
+ return existing;
409
+ }
410
+
411
+ const created: AdapterPointCell = {
412
+ point,
413
+ listeners: new Map(),
414
+ token: this.#createPointToken(),
415
+ };
416
+ this.#cellsByPoint.set(point, created);
417
+ this.#cells.add(created);
418
+ return created;
419
+ }
420
+
421
+ /** Připojí jediný Core subscriber a atomicky přijme jeho cleanup capability. */
422
+ #establishSubscription(): boolean {
423
+ if (this.#unsubscribe !== undefined) {
424
+ return true;
425
+ }
426
+ if (this.#subscribing) {
427
+ this.#commitFault(createBridgeError(
428
+ "REACT_REGISTRY_INVALID",
429
+ "Registry subscribe reentered before completion.",
430
+ "registry.subscribe",
431
+ ));
432
+ return false;
433
+ }
434
+
435
+ this.#subscribing = true;
436
+ let candidate: unknown;
437
+ let accepted = false;
438
+ try {
439
+ /** Přeloží jeden Core event do no-throw atomic bridge invalidace. */
440
+ const handleRegistryEvent = (event: contribKitCore.RegistryCommittedEvent): void => {
441
+ this.#handleRegistryEvent(event);
442
+ };
443
+ candidate = adapterRegistryProtocol.subscribeAdapterRegistry(
444
+ this.#protocol,
445
+ handleRegistryEvent,
446
+ );
447
+ if (typeof candidate !== "function") {
448
+ throw createBridgeError(
449
+ "REACT_REGISTRY_INVALID",
450
+ "Registry subscribe must return a callable unsubscribe.",
451
+ "registry.subscribe",
452
+ );
453
+ }
454
+
455
+ this.#host.checkpoint();
456
+ adapterRegistryProtocol.requireAdapterRegistryAvailable(
457
+ this.#protocol,
458
+ "lifecycle",
459
+ this.#host.checkpoint,
460
+ );
461
+ if (this.#fault !== undefined) {
462
+ throw this.#fault;
463
+ }
464
+ this.#unsubscribe = candidate as () => unknown;
465
+ accepted = true;
466
+ return true;
467
+ } catch (cause: unknown) {
468
+ if (typeof candidate === "function" && !accepted) {
469
+ this.#consumeUnsubscribe(candidate as () => unknown);
470
+ }
471
+ const classified = this.#classifySubscribeFailure(cause);
472
+ if (classified !== undefined) {
473
+ this.#commitFault(classified);
474
+ }
475
+ return false;
476
+ } finally {
477
+ this.#subscribing = false;
478
+ }
479
+ }
480
+
481
+ /** Zachová lifecycle precedence po throwing nebo reentrantním subscribe seamu. */
482
+ #classifySubscribeFailure(
483
+ cause: unknown,
484
+ ): reactAdapterError.ReactAdapterError | undefined {
485
+ try {
486
+ this.#host.checkpoint();
487
+ } catch {
488
+ return undefined;
489
+ }
490
+
491
+ try {
492
+ adapterRegistryProtocol.requireAdapterRegistryAvailable(
493
+ this.#protocol,
494
+ "lifecycle",
495
+ this.#host.checkpoint,
496
+ );
497
+ } catch (availabilityError: unknown) {
498
+ if (availabilityError instanceof reactAdapterError.ReactAdapterError) {
499
+ return availabilityError;
500
+ }
501
+ return createBridgeError(
502
+ "REACT_REGISTRY_UNAVAILABLE",
503
+ "Registry became unavailable during subscribe.",
504
+ "registry.state",
505
+ availabilityError,
506
+ true,
507
+ );
508
+ }
509
+
510
+ if (cause instanceof reactAdapterError.ReactAdapterError) {
511
+ return cause;
512
+ }
513
+ return createBridgeError(
514
+ "REACT_REGISTRY_INVALID",
515
+ "Registry subscribe failed.",
516
+ "registry.subscribe",
517
+ cause,
518
+ true,
519
+ );
520
+ }
521
+
522
+ /** No-throw subscriber boundary s atomic capture-before-commit chováním. */
523
+ #handleRegistryEvent(value: unknown): void {
524
+ if (this.#detached || this.#fault !== undefined) {
525
+ return;
526
+ }
527
+
528
+ let event: CapturedRegistryEvent;
529
+ try {
530
+ event = captureRegistryEvent(value);
531
+ } catch (error: unknown) {
532
+ this.#commitFault(error instanceof reactAdapterError.ReactAdapterError
533
+ ? error
534
+ : createBridgeError(
535
+ "REACT_REGISTRY_INVALID",
536
+ "Committed registry event capture failed.",
537
+ "registryEvent",
538
+ error,
539
+ true,
540
+ ));
541
+ return;
542
+ }
543
+
544
+ if (event.kind === "registryDisposing" || event.kind === "registryDisposed") {
545
+ this.#registryState = event.kind === "registryDisposing" ? "disposing" : "disposed";
546
+ for (const cell of this.#cells) {
547
+ this.#bumpCell(cell);
548
+ }
549
+ if (event.kind === "registryDisposed") {
550
+ this.#detachPhysicalSubscription();
551
+ }
552
+ return;
553
+ }
554
+
555
+ const affected = new Set(event.affectedPoints);
556
+ for (const cell of this.#cells) {
557
+ if (affected.has(cell.point)) {
558
+ this.#bumpCell(cell);
559
+ }
560
+ }
561
+ }
562
+
563
+ /** Atomicky faultne bridge, posune všechny cells a odpojí event autoritu. */
564
+ #commitFault(error: reactAdapterError.ReactAdapterError): void {
565
+ if (this.#fault !== undefined || this.#detached) {
566
+ return;
567
+ }
568
+ this.#fault = error;
569
+ for (const cell of this.#cells) {
570
+ this.#bumpCell(cell);
571
+ }
572
+ this.#detachPhysicalSubscription();
573
+ this.#reportLifecycleFailure(error);
574
+ }
575
+
576
+ /** Posune jeden cell token a doručí snapshot současných listeners. */
577
+ #bumpCell(cell: AdapterPointCell): void {
578
+ cell.token = this.#createPointToken();
579
+ for (const listener of [...cell.listeners.keys()]) {
580
+ this.#notifyOne(listener);
581
+ }
582
+ }
583
+
584
+ /** Bezpečně doručí jeden listener a izoluje jeho případné selhání. */
585
+ #notifyOne(listener: AdapterExternalStoreListener): void {
586
+ notifyListener(listener, (cause: unknown): void => {
587
+ this.#reportLifecycleFailure(createBridgeError(
588
+ "REACT_CALLBACK_FAILED",
589
+ "External-store listener failed.",
590
+ "listener",
591
+ cause,
592
+ true,
593
+ ));
594
+ });
595
+ }
596
+
597
+ /** Vrátí stable no-op cleanup a synchronně vynutí snapshot recheck. */
598
+ #notifyRejectedSubscribe(listener: AdapterExternalStoreListener): contribKitCore.Unsubscribe {
599
+ this.#notifyOne(listener);
600
+ return function releaseRejectedSubscribe(): void {
601
+ // Rejected membership nikdy nezískala bridge ownership.
602
+ };
603
+ }
604
+
605
+ /** Idempotentně uvolní jeden committed consumer record. */
606
+ #releaseConsumer(record: AdapterBridgeConsumerRecord): void {
607
+ if (!record.active) {
608
+ return;
609
+ }
610
+ this.#removeConsumer(record);
611
+ this.#scheduleDetachIfEmpty();
612
+ }
613
+
614
+ /** Odstraní membership bez spuštění detach policy. */
615
+ #removeConsumer(record: AdapterBridgeConsumerRecord): void {
616
+ if (!record.active) {
617
+ return;
618
+ }
619
+ record.active = false;
620
+ this.#consumers.delete(record);
621
+ const listenerCount = record.cell.listeners.get(record.listener);
622
+ if (listenerCount === 1) {
623
+ record.cell.listeners.delete(record.listener);
624
+ } else if (listenerCount !== undefined) {
625
+ record.cell.listeners.set(record.listener, listenerCount - 1);
626
+ }
627
+ if (record.cell.listeners.size === 0) {
628
+ this.#cells.delete(record.cell);
629
+ this.#cellsByPoint.delete(record.cell.point);
630
+ }
631
+ }
632
+
633
+ /** Naplánuje bridge detach pouze po skutečně posledním consumeru. */
634
+ #scheduleDetachIfEmpty(): void {
635
+ if (this.#consumers.size === 0) {
636
+ this.#scheduleDetach();
637
+ }
638
+ }
639
+
640
+ /** Naplánuje tokenově guarded microtask fyzického detach. */
641
+ #scheduleDetach(): void {
642
+ if (this.#detached) {
643
+ return;
644
+ }
645
+ this.#detachGeneration += 1;
646
+ const generation = this.#detachGeneration;
647
+ this.#host.scheduleMicrotask((): void => {
648
+ if (this.#detached
649
+ || generation !== this.#detachGeneration
650
+ || this.#consumers.size !== 0
651
+ || !this.#host.canFinalizeDetach()) {
652
+ return;
653
+ }
654
+ this.#finalizeDetach();
655
+ });
656
+ }
657
+
658
+ /** Zruší stale last-release nebo epoch-deactivation drain. */
659
+ #cancelPendingDetach(): void {
660
+ this.#detachGeneration += 1;
661
+ }
662
+
663
+ /** Terminálně vyčistí bridge a oznámí owning epoch mapě detach. */
664
+ #finalizeDetach(): void {
665
+ if (this.#detached) {
666
+ return;
667
+ }
668
+ this.#detached = true;
669
+ this.#detachPhysicalSubscription();
670
+ for (const cell of this.#cells) {
671
+ this.#cellsByPoint.delete(cell.point);
672
+ cell.listeners.clear();
673
+ }
674
+ this.#cells.clear();
675
+ this.#consumers.clear();
676
+ try {
677
+ this.#host.onDetached(this);
678
+ } catch (cause: unknown) {
679
+ this.#reportLifecycleFailure(createBridgeError(
680
+ "REACT_CALLBACK_FAILED",
681
+ "Bridge detach callback failed.",
682
+ "bridge.onDetached",
683
+ cause,
684
+ true,
685
+ ));
686
+ }
687
+ }
688
+
689
+ /** Vyjme Core unsubscribe před best-effort voláním s undefined receiverem. */
690
+ #detachPhysicalSubscription(): void {
691
+ const unsubscribe = this.#unsubscribe;
692
+ if (unsubscribe === undefined) {
693
+ return;
694
+ }
695
+ this.#unsubscribe = undefined;
696
+ this.#consumeUnsubscribe(unsubscribe);
697
+ }
698
+
699
+ /** Spotřebuje všechny cleanup outcomes bez druhého lifecycle outcome. */
700
+ #consumeUnsubscribe(unsubscribe: () => unknown): void {
701
+ consumeCleanupResult(
702
+ (): unknown => Reflect.apply(unsubscribe, undefined, []),
703
+ (cause: unknown): void => {
704
+ this.#reportLifecycleFailure(createBridgeError(
705
+ "REACT_CALLBACK_FAILED",
706
+ "Registry unsubscribe failed.",
707
+ "registry.unsubscribe",
708
+ cause,
709
+ true,
710
+ ));
711
+ },
712
+ );
713
+ }
714
+
715
+ /** Izoluje lifecycle reporter jako poslední best-effort hranici. */
716
+ #reportLifecycleFailure(error: reactAdapterError.ReactAdapterError): void {
717
+ try {
718
+ this.#host.reportLifecycleFailure(error);
719
+ } catch {
720
+ // Diagnostic seam nemá business ani lifecycle autoritu.
721
+ }
722
+ }
723
+ }