@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.
- package/LICENSE +15 -0
- package/README.md +294 -0
- package/dist/contribution-component.d.ts +82 -0
- package/dist/contribution-component.d.ts.map +1 -0
- package/dist/contribution-component.js +12 -0
- package/dist/contribution-component.js.map +1 -0
- package/dist/extension-outlet.d.ts +155 -0
- package/dist/extension-outlet.d.ts.map +1 -0
- package/dist/extension-outlet.js +229 -0
- package/dist/extension-outlet.js.map +1 -0
- package/dist/extension-registry-provider.d.ts +26 -0
- package/dist/extension-registry-provider.d.ts.map +1 -0
- package/dist/extension-registry-provider.js +82 -0
- package/dist/extension-registry-provider.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/adapter-configuration.d.ts +54 -0
- package/dist/internal/adapter-configuration.d.ts.map +1 -0
- package/dist/internal/adapter-configuration.js +89 -0
- package/dist/internal/adapter-configuration.js.map +1 -0
- package/dist/internal/adapter-context.d.ts +24 -0
- package/dist/internal/adapter-context.d.ts.map +1 -0
- package/dist/internal/adapter-context.js +23 -0
- package/dist/internal/adapter-context.js.map +1 -0
- package/dist/internal/adapter-registry-bridge.d.ts +58 -0
- package/dist/internal/adapter-registry-bridge.d.ts.map +1 -0
- package/dist/internal/adapter-registry-bridge.js +494 -0
- package/dist/internal/adapter-registry-bridge.js.map +1 -0
- package/dist/internal/adapter-registry-protocol.d.ts +53 -0
- package/dist/internal/adapter-registry-protocol.d.ts.map +1 -0
- package/dist/internal/adapter-registry-protocol.js +182 -0
- package/dist/internal/adapter-registry-protocol.js.map +1 -0
- package/dist/internal/adapter-scope.d.ts +87 -0
- package/dist/internal/adapter-scope.d.ts.map +1 -0
- package/dist/internal/adapter-scope.js +346 -0
- package/dist/internal/adapter-scope.js.map +1 -0
- package/dist/internal/implementation-classifier.d.ts +21 -0
- package/dist/internal/implementation-classifier.d.ts.map +1 -0
- package/dist/internal/implementation-classifier.js +39 -0
- package/dist/internal/implementation-classifier.js.map +1 -0
- package/dist/internal/managed-lazy-generation.d.ts +100 -0
- package/dist/internal/managed-lazy-generation.d.ts.map +1 -0
- package/dist/internal/managed-lazy-generation.js +419 -0
- package/dist/internal/managed-lazy-generation.js.map +1 -0
- package/dist/internal/outlet-commit-lease.d.ts +46 -0
- package/dist/internal/outlet-commit-lease.d.ts.map +1 -0
- package/dist/internal/outlet-commit-lease.js +137 -0
- package/dist/internal/outlet-commit-lease.js.map +1 -0
- package/dist/internal/outlet-error-pipeline.d.ts +122 -0
- package/dist/internal/outlet-error-pipeline.d.ts.map +1 -0
- package/dist/internal/outlet-error-pipeline.js +214 -0
- package/dist/internal/outlet-error-pipeline.js.map +1 -0
- package/dist/internal/outlet-input.d.ts +42 -0
- package/dist/internal/outlet-input.d.ts.map +1 -0
- package/dist/internal/outlet-input.js +86 -0
- package/dist/internal/outlet-input.js.map +1 -0
- package/dist/internal/outlet-membership-boundary.d.ts +93 -0
- package/dist/internal/outlet-membership-boundary.d.ts.map +1 -0
- package/dist/internal/outlet-membership-boundary.js +710 -0
- package/dist/internal/outlet-membership-boundary.js.map +1 -0
- package/dist/internal/outlet-membership.d.ts +31 -0
- package/dist/internal/outlet-membership.d.ts.map +1 -0
- package/dist/internal/outlet-membership.js +65 -0
- package/dist/internal/outlet-membership.js.map +1 -0
- package/dist/internal/outlet-projection.d.ts +102 -0
- package/dist/internal/outlet-projection.d.ts.map +1 -0
- package/dist/internal/outlet-projection.js +228 -0
- package/dist/internal/outlet-projection.js.map +1 -0
- package/dist/internal/outlet-reporter-boundary.d.ts +42 -0
- package/dist/internal/outlet-reporter-boundary.d.ts.map +1 -0
- package/dist/internal/outlet-reporter-boundary.js +57 -0
- package/dist/internal/outlet-reporter-boundary.js.map +1 -0
- package/dist/internal/public-input-capture.d.ts +13 -0
- package/dist/internal/public-input-capture.d.ts.map +1 -0
- package/dist/internal/public-input-capture.js +34 -0
- package/dist/internal/public-input-capture.js.map +1 -0
- package/dist/internal/resolved-contributions-query.d.ts +42 -0
- package/dist/internal/resolved-contributions-query.d.ts.map +1 -0
- package/dist/internal/resolved-contributions-query.js +203 -0
- package/dist/internal/resolved-contributions-query.js.map +1 -0
- package/dist/react-adapter-diagnostics.d.ts +199 -0
- package/dist/react-adapter-diagnostics.d.ts.map +1 -0
- package/dist/react-adapter-diagnostics.js +257 -0
- package/dist/react-adapter-diagnostics.js.map +1 -0
- package/dist/react-adapter-error.d.ts +35 -0
- package/dist/react-adapter-error.d.ts.map +1 -0
- package/dist/react-adapter-error.js +37 -0
- package/dist/react-adapter-error.js.map +1 -0
- package/dist/react-extension-point.d.ts +26 -0
- package/dist/react-extension-point.d.ts.map +1 -0
- package/dist/react-extension-point.js +100 -0
- package/dist/react-extension-point.js.map +1 -0
- package/dist/react-lazy-contribution.d.ts +40 -0
- package/dist/react-lazy-contribution.d.ts.map +1 -0
- package/dist/react-lazy-contribution.js +18 -0
- package/dist/react-lazy-contribution.js.map +1 -0
- package/dist/resolved-contributions.d.ts +90 -0
- package/dist/resolved-contributions.d.ts.map +1 -0
- package/dist/resolved-contributions.js +455 -0
- package/dist/resolved-contributions.js.map +1 -0
- package/package.json +47 -0
- package/src/contribution-component.ts +118 -0
- package/src/extension-outlet.ts +590 -0
- package/src/extension-registry-provider.ts +179 -0
- package/src/index.ts +72 -0
- package/src/internal/adapter-configuration.ts +181 -0
- package/src/internal/adapter-context.ts +43 -0
- package/src/internal/adapter-registry-bridge.ts +723 -0
- package/src/internal/adapter-registry-protocol.ts +365 -0
- package/src/internal/adapter-scope.ts +500 -0
- package/src/internal/implementation-classifier.ts +59 -0
- package/src/internal/managed-lazy-generation.ts +540 -0
- package/src/internal/outlet-commit-lease.ts +216 -0
- package/src/internal/outlet-error-pipeline.ts +447 -0
- package/src/internal/outlet-input.ts +264 -0
- package/src/internal/outlet-membership-boundary.ts +1048 -0
- package/src/internal/outlet-membership.ts +125 -0
- package/src/internal/outlet-projection.ts +511 -0
- package/src/internal/outlet-reporter-boundary.ts +105 -0
- package/src/internal/public-input-capture.ts +55 -0
- package/src/internal/resolved-contributions-query.ts +318 -0
- package/src/react-adapter-diagnostics.ts +457 -0
- package/src/react-adapter-error.ts +88 -0
- package/src/react-extension-point.ts +181 -0
- package/src/react-lazy-contribution.ts +66 -0
- package/src/resolved-contributions.ts +895 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import * as reactAdapterError from "../react-adapter-error.js";
|
|
2
|
+
|
|
3
|
+
/** Privátní compile-time identita opaque outlet tokenu. */
|
|
4
|
+
declare const outletInstanceTokenBrand: unique symbol;
|
|
5
|
+
|
|
6
|
+
/** Render-pure opaque identita jedné budoucí outlet instance. */
|
|
7
|
+
export interface OutletInstanceToken {
|
|
8
|
+
/** Privátní invariant bez runtime property nebo globálního brandu. */
|
|
9
|
+
readonly [outletInstanceTokenBrand]: "outlet-instance";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Druh commit-owned diagnostické identity. */
|
|
13
|
+
export type OutletCommitOwnerKind = "outlet" | "boundary";
|
|
14
|
+
|
|
15
|
+
/** Interní commit lease s přesnou epochovou autoritou. */
|
|
16
|
+
export interface OutletCommitLease {
|
|
17
|
+
/** Scope-local opaque identita bez veřejného formátového kontraktu. */
|
|
18
|
+
readonly id: string;
|
|
19
|
+
/** Monotonická scope epocha, ve které lease vznikla. */
|
|
20
|
+
readonly epoch: number;
|
|
21
|
+
/** Ověří current lease, owner cycle i owning scope epoch. */
|
|
22
|
+
readonly isCurrent: () => boolean;
|
|
23
|
+
/** Idempotentně ukončí právě tuto committed lease. */
|
|
24
|
+
readonly release: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Deterministický microtask seam pro Strict grace finalizaci. */
|
|
28
|
+
export type OutletCommitLeaseScheduler = (callback: () => void) => void;
|
|
29
|
+
|
|
30
|
+
/** Probe přesné scope epochy bez převzetí jejího lifecycle. */
|
|
31
|
+
export type OutletCommitEpochProbe = (epoch: number) => boolean;
|
|
32
|
+
|
|
33
|
+
/** Jedna autoritativní nebo již zneplatněná sada souběžných leases. */
|
|
34
|
+
interface OutletCommitOwnerCycle {
|
|
35
|
+
/** True pouze do owner release nebo epoch invalidace. */
|
|
36
|
+
authoritative: boolean;
|
|
37
|
+
/** Počet právě aktivních leases stejného owner tokenu. */
|
|
38
|
+
count: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Scope-local záznam jednoho commit-owned opaque tokenu. */
|
|
42
|
+
interface OutletCommitOwnerRecord {
|
|
43
|
+
/** Exact token používaný také jako WeakMap klíč. */
|
|
44
|
+
readonly token: object;
|
|
45
|
+
/** Rozlišuje outlet a membership boundary identitu. */
|
|
46
|
+
readonly kind: OutletCommitOwnerKind;
|
|
47
|
+
/** Scope-local diagnostická identita. */
|
|
48
|
+
readonly id: string;
|
|
49
|
+
/** Scope epocha, ve které je možné ID znovu získat. */
|
|
50
|
+
readonly epoch: number;
|
|
51
|
+
/** Právě autoritativní nebo grace owner cycle. */
|
|
52
|
+
cycle: OutletCommitOwnerCycle;
|
|
53
|
+
/** Zda záznam přijímá Strict reacquire před finalizací. */
|
|
54
|
+
state: "active" | "grace" | "retired";
|
|
55
|
+
/** Monotonický guard všech naplánovaných finalizerů. */
|
|
56
|
+
finalizeToken: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Vytvoří nový owner cycle bez zděděné autority starých leases. */
|
|
60
|
+
function createOwnerCycle(): OutletCommitOwnerCycle {
|
|
61
|
+
return { authoritative: true, count: 0 };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Vytvoří render-pure outlet token bez external identity nebo side effectu. */
|
|
65
|
+
export function createOutletInstanceToken(): OutletInstanceToken {
|
|
66
|
+
return Object.freeze({}) as OutletInstanceToken;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Scope-local allocator a Strict grace registr commit-owned diagnostických identit.
|
|
71
|
+
*/
|
|
72
|
+
export class OutletCommitLeaseRegistry {
|
|
73
|
+
/** Deterministický scheduler zachycený při vytvoření owning adapter scope. */
|
|
74
|
+
readonly #scheduleMicrotask: OutletCommitLeaseScheduler;
|
|
75
|
+
/** Token-indexovaný stav bez process-global nebo cross-scope historie. */
|
|
76
|
+
readonly #recordsByToken = new WeakMap<object, OutletCommitOwnerRecord>();
|
|
77
|
+
/** Iterovatelná množina pouze dosud nereleased scope records. */
|
|
78
|
+
readonly #records = new Set<OutletCommitOwnerRecord>();
|
|
79
|
+
/** Monotonický scope-local allocator obou druhů identit. */
|
|
80
|
+
#nextIdentity = 0;
|
|
81
|
+
|
|
82
|
+
/** Zachytí jediný scheduler seam pro celý scope lifetime. */
|
|
83
|
+
public constructor(scheduleMicrotask: OutletCommitLeaseScheduler) {
|
|
84
|
+
this.#scheduleMicrotask = scheduleMicrotask;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Vrátí počet dosud nefinalizovaných owner records pro lifecycle testy. */
|
|
88
|
+
public readRetainedOwnerCount(): number {
|
|
89
|
+
return this.#records.size;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Přijme jednu committed lease; grace reacquire zachová existující ID.
|
|
94
|
+
*/
|
|
95
|
+
public acquire(
|
|
96
|
+
epoch: number,
|
|
97
|
+
kind: OutletCommitOwnerKind,
|
|
98
|
+
token: object,
|
|
99
|
+
isEpochCurrent: OutletCommitEpochProbe,
|
|
100
|
+
): OutletCommitLease {
|
|
101
|
+
if (!Number.isSafeInteger(epoch) || epoch < 1) {
|
|
102
|
+
throw new reactAdapterError.ReactAdapterError(
|
|
103
|
+
"REACT_ADAPTER_DISPOSED",
|
|
104
|
+
"A commit lease requires an active adapter scope epoch.",
|
|
105
|
+
{ details: { field: "scope.epoch", phase: "lifecycle" } },
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let record = this.#recordsByToken.get(token);
|
|
110
|
+
if (record !== undefined
|
|
111
|
+
&& (record.kind !== kind || record.epoch !== epoch || record.state === "retired")) {
|
|
112
|
+
this.#retireRecord(record);
|
|
113
|
+
record = undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (record === undefined) {
|
|
117
|
+
this.#nextIdentity += 1;
|
|
118
|
+
record = {
|
|
119
|
+
token,
|
|
120
|
+
kind,
|
|
121
|
+
id: `react-${kind}:${this.#nextIdentity}`,
|
|
122
|
+
epoch,
|
|
123
|
+
cycle: createOwnerCycle(),
|
|
124
|
+
state: "active",
|
|
125
|
+
finalizeToken: 0,
|
|
126
|
+
};
|
|
127
|
+
this.#recordsByToken.set(token, record);
|
|
128
|
+
this.#records.add(record);
|
|
129
|
+
} else if (record.state === "grace") {
|
|
130
|
+
record.finalizeToken += 1;
|
|
131
|
+
record.cycle = createOwnerCycle();
|
|
132
|
+
record.state = "active";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const currentRecord = record;
|
|
136
|
+
const cycle = currentRecord.cycle;
|
|
137
|
+
cycle.count += 1;
|
|
138
|
+
let active = true;
|
|
139
|
+
const registry = this;
|
|
140
|
+
|
|
141
|
+
/** Ověří přesnou lease bez reaktivace stale owner cycle. */
|
|
142
|
+
function isCurrent(): boolean {
|
|
143
|
+
return active
|
|
144
|
+
&& currentRecord.state === "active"
|
|
145
|
+
&& currentRecord.cycle === cycle
|
|
146
|
+
&& cycle.authoritative
|
|
147
|
+
&& isEpochCurrent(epoch);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Idempotentně odebere právě jednu lease a případně zahájí grace. */
|
|
151
|
+
function release(): void {
|
|
152
|
+
if (!active) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
active = false;
|
|
156
|
+
cycle.count -= 1;
|
|
157
|
+
if (currentRecord.state !== "active"
|
|
158
|
+
|| currentRecord.cycle !== cycle
|
|
159
|
+
|| cycle.count !== 0) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
registry.#beginRecordGrace(currentRecord);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return Object.freeze({ id: currentRecord.id, epoch, isCurrent, release });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Synchronně zneplatní všechny capability jedné scope epochy před cleanupem. */
|
|
169
|
+
public invalidateEpoch(epoch: number): void {
|
|
170
|
+
for (const record of [...this.#records]) {
|
|
171
|
+
if (record.epoch === epoch && record.state === "active") {
|
|
172
|
+
this.#beginRecordGrace(record);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Terminálně odstraní všechny owner records jedné retired scope epochy. */
|
|
178
|
+
public retireEpoch(epoch: number): void {
|
|
179
|
+
for (const record of [...this.#records]) {
|
|
180
|
+
if (record.epoch === epoch) {
|
|
181
|
+
this.#retireRecord(record);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Zneplatní current cycle a naplánuje tokenově chráněný finalizer. */
|
|
187
|
+
#beginRecordGrace(record: OutletCommitOwnerRecord): void {
|
|
188
|
+
if (record.state !== "active") {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
record.cycle.authoritative = false;
|
|
192
|
+
record.state = "grace";
|
|
193
|
+
record.finalizeToken += 1;
|
|
194
|
+
const finalizeToken = record.finalizeToken;
|
|
195
|
+
this.#scheduleMicrotask((): void => {
|
|
196
|
+
if (record.state !== "grace" || record.finalizeToken !== finalizeToken) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
this.#retireRecord(record);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Odstraní record z iterovatelné i weak token projekce. */
|
|
204
|
+
#retireRecord(record: OutletCommitOwnerRecord): void {
|
|
205
|
+
if (record.state === "retired") {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
record.cycle.authoritative = false;
|
|
209
|
+
record.state = "retired";
|
|
210
|
+
record.finalizeToken += 1;
|
|
211
|
+
this.#records.delete(record);
|
|
212
|
+
if (this.#recordsByToken.get(record.token) === record) {
|
|
213
|
+
this.#recordsByToken.delete(record.token);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import type * as contribKitCore from "@primafuture/contrib-kit-core";
|
|
2
|
+
|
|
3
|
+
import * as adapterConfiguration from "./adapter-configuration.js";
|
|
4
|
+
import * as outletInput from "./outlet-input.js";
|
|
5
|
+
import type * as contributionComponent from "../contribution-component.js";
|
|
6
|
+
import * as reactAdapterDiagnostics from "../react-adapter-diagnostics.js";
|
|
7
|
+
import type * as reactAdapterError from "../react-adapter-error.js";
|
|
8
|
+
import type * as reactExtensionPoint from "../react-extension-point.js";
|
|
9
|
+
|
|
10
|
+
/** Frozen capability doručená outlet `onError` callbacku. */
|
|
11
|
+
export interface OutletContributionErrorEmission {
|
|
12
|
+
/** Rozlišuje contribution failure od budoucí resolve větve. */
|
|
13
|
+
readonly kind: "contribution";
|
|
14
|
+
/** Tentýž frozen primary event použitý sinkem a policy. */
|
|
15
|
+
readonly error: reactAdapterDiagnostics.ReactAdapterErrorEvent;
|
|
16
|
+
/** Generation-bound recovery command bez registry capability. */
|
|
17
|
+
readonly retry: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Autoritativní výsledek committed contribution error pipeline. */
|
|
21
|
+
export type OutletPrimaryErrorDecision =
|
|
22
|
+
| {
|
|
23
|
+
/** Starý attempt byl reentrantně zneplatněn a nic dalšího nesmí publikovat. */
|
|
24
|
+
readonly kind: "stale";
|
|
25
|
+
}
|
|
26
|
+
| {
|
|
27
|
+
/** Membership zůstane skrytá při zachování logical placementu sourozenců. */
|
|
28
|
+
readonly kind: "hide";
|
|
29
|
+
/** Frozen primary event dostupný internímu boundary state. */
|
|
30
|
+
readonly error: reactAdapterDiagnostics.ReactAdapterErrorEvent;
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
/** Host fallback se smí renderovat v oddělené reporter boundary. */
|
|
34
|
+
readonly kind: "renderFallback";
|
|
35
|
+
/** Frozen primary event dostupný fallback render state. */
|
|
36
|
+
readonly error: reactAdapterDiagnostics.ReactAdapterErrorEvent;
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
/** Přesný primary cause se má v příštím renderu eskalovat hostu. */
|
|
40
|
+
readonly kind: "escalate";
|
|
41
|
+
/** Frozen primary event zůstává diagnostickou evidencí. */
|
|
42
|
+
readonly error: reactAdapterDiagnostics.ReactAdapterErrorEvent;
|
|
43
|
+
/** Exact captured cause; nikdy structured wrapper. */
|
|
44
|
+
readonly cause: unknown;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Společná metadata jedné committed membership failure. */
|
|
48
|
+
export interface OutletErrorIdentity<
|
|
49
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
50
|
+
> {
|
|
51
|
+
/** Opaque commit-owned boundary identita. */
|
|
52
|
+
readonly boundaryId: string;
|
|
53
|
+
/** Exact outlet context bez automatické diagnostické publikace. */
|
|
54
|
+
readonly context: contribKitCore.ExtensionPointContext<Point>;
|
|
55
|
+
/** Bezpečný public contribution view. */
|
|
56
|
+
readonly contribution: contributionComponent.ExtensionContributionView<
|
|
57
|
+
contribKitCore.ExtensionPointData<Point>
|
|
58
|
+
>;
|
|
59
|
+
/** Opaque commit-owned outlet identita. */
|
|
60
|
+
readonly outletId: string;
|
|
61
|
+
/** Exact canonical React point. */
|
|
62
|
+
readonly point: Point;
|
|
63
|
+
/** Opaque Core activation identita dostupná pouze boundary pipeline. */
|
|
64
|
+
readonly registrationId: string;
|
|
65
|
+
/** Monotonická membership retry generation. */
|
|
66
|
+
readonly retryGeneration: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Vstup primary contribution failure pipeline po React commit pointu. */
|
|
70
|
+
export interface OutletPrimaryErrorPipelineInput<
|
|
71
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
72
|
+
> extends OutletErrorIdentity<Point> {
|
|
73
|
+
/** Stabilní adapter error code zdroje failure. */
|
|
74
|
+
readonly code: reactAdapterError.ReactAdapterErrorCode;
|
|
75
|
+
/** Effective scope/outlet diagnostic konfigurace. */
|
|
76
|
+
readonly configuration: adapterConfiguration.AdapterScopeConfiguration;
|
|
77
|
+
/** Přesný cizí nebo bezpečný syntetický cause. */
|
|
78
|
+
readonly cause: unknown;
|
|
79
|
+
/** Zda host poskytl oddělený contribution fallback renderer. */
|
|
80
|
+
readonly hasFallback: boolean;
|
|
81
|
+
/** `false` vypne contribution isolation bez volání policy. */
|
|
82
|
+
readonly isolateErrors: boolean;
|
|
83
|
+
/** Current authority checkpoint po každém user-observable kroku. */
|
|
84
|
+
readonly isCurrent: () => boolean;
|
|
85
|
+
/** Optional guarded outlet callback zachycený read-once. */
|
|
86
|
+
readonly onError: outletInput.CapturedOutletCallback<
|
|
87
|
+
OutletContributionErrorEmission,
|
|
88
|
+
unknown
|
|
89
|
+
> | undefined;
|
|
90
|
+
/** Přesná adapter phase zdroje primary failure. */
|
|
91
|
+
readonly phase: reactAdapterError.ReactAdapterErrorPhase;
|
|
92
|
+
/** Normalizovaný React component stack, existuje-li. */
|
|
93
|
+
readonly reactComponentStack?: string;
|
|
94
|
+
/** Generation-bound retry command. */
|
|
95
|
+
readonly retry: () => void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Vstup sink-only fallback reporteru bez policy nebo callback recursion. */
|
|
99
|
+
export interface OutletFallbackErrorInput<
|
|
100
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
101
|
+
> extends OutletErrorIdentity<Point> {
|
|
102
|
+
/** Effective diagnostic konfigurace owning outletu. */
|
|
103
|
+
readonly configuration: adapterConfiguration.AdapterScopeConfiguration;
|
|
104
|
+
/** Exact fallback render cause eskalovaný hostu. */
|
|
105
|
+
readonly cause: unknown;
|
|
106
|
+
/** Ověří committed boundary/outlet autoritu před publikací. */
|
|
107
|
+
readonly isCurrent: () => boolean;
|
|
108
|
+
/** Normalizovaný React component stack, existuje-li. */
|
|
109
|
+
readonly reactComponentStack?: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Frozen committed event jednoho accepted retry. */
|
|
113
|
+
export interface OutletRetryEvent {
|
|
114
|
+
/** Opaque committed boundary identita. */
|
|
115
|
+
readonly boundaryId: string;
|
|
116
|
+
/** Veřejná contribution identita bez dat. */
|
|
117
|
+
readonly contributionId: string;
|
|
118
|
+
/** Opaque committed outlet identita. */
|
|
119
|
+
readonly outletId: string;
|
|
120
|
+
/** Veřejná point identita. */
|
|
121
|
+
readonly pointId: string;
|
|
122
|
+
/** Opaque Core activation identita. */
|
|
123
|
+
readonly registrationId: string;
|
|
124
|
+
/** Nově committed retry generation. */
|
|
125
|
+
readonly retryGeneration: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Vstup guarded onRetry callbacku po commitu nové generation. */
|
|
129
|
+
export interface OutletRetryCallbackInput<
|
|
130
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
131
|
+
> extends OutletErrorIdentity<Point> {
|
|
132
|
+
/** Effective sink pro případ callback failure. */
|
|
133
|
+
readonly configuration: adapterConfiguration.AdapterScopeConfiguration;
|
|
134
|
+
/** Current generation authority. */
|
|
135
|
+
readonly isCurrent: () => boolean;
|
|
136
|
+
/** Optional read-once outlet callback. */
|
|
137
|
+
readonly onRetry: outletInput.CapturedOutletCallback<OutletRetryEvent, unknown> | undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Vytvoří bezpečná contribution metadata bez čtení business data hodnoty. */
|
|
141
|
+
function createProjectedContributionMetadata<
|
|
142
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
143
|
+
>(
|
|
144
|
+
identity: OutletErrorIdentity<Point>,
|
|
145
|
+
): reactAdapterDiagnostics.ReadonlyContributionMetadata {
|
|
146
|
+
const contribution = identity.contribution;
|
|
147
|
+
return Object.freeze({
|
|
148
|
+
registrationId: identity.registrationId,
|
|
149
|
+
contributionId: contribution.contributionId,
|
|
150
|
+
...(contribution.contributorId === undefined
|
|
151
|
+
? {}
|
|
152
|
+
: { contributorId: contribution.contributorId }),
|
|
153
|
+
...(contribution.label === undefined ? {} : { label: contribution.label }),
|
|
154
|
+
...(contribution.localName === undefined
|
|
155
|
+
? {}
|
|
156
|
+
: { localName: contribution.localName }),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Vytvoří secondary event sdílející pouze bezpečné identity primary failure. */
|
|
161
|
+
function createSecondaryErrorEvent<
|
|
162
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
163
|
+
>(
|
|
164
|
+
input: OutletErrorIdentity<Point>,
|
|
165
|
+
code: reactAdapterError.ReactAdapterErrorCode,
|
|
166
|
+
phase: reactAdapterError.ReactAdapterErrorPhase,
|
|
167
|
+
cause: unknown,
|
|
168
|
+
): reactAdapterDiagnostics.ReactAdapterErrorEvent {
|
|
169
|
+
const point = reactAdapterDiagnostics.createPointMetadata(input.point);
|
|
170
|
+
return reactAdapterDiagnostics.createReactAdapterErrorEvent({
|
|
171
|
+
boundaryId: input.boundaryId,
|
|
172
|
+
code,
|
|
173
|
+
cause,
|
|
174
|
+
contribution: createProjectedContributionMetadata(input),
|
|
175
|
+
outletId: input.outletId,
|
|
176
|
+
phase,
|
|
177
|
+
...(point === undefined ? {} : { point }),
|
|
178
|
+
retryGeneration: input.retryGeneration,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Publikuje secondary diagnostiku bez callback nebo policy recursion. */
|
|
183
|
+
function reportSecondaryFailure<
|
|
184
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
185
|
+
>(
|
|
186
|
+
input: OutletErrorIdentity<Point> & {
|
|
187
|
+
readonly configuration: adapterConfiguration.AdapterScopeConfiguration;
|
|
188
|
+
},
|
|
189
|
+
code: reactAdapterError.ReactAdapterErrorCode,
|
|
190
|
+
phase: reactAdapterError.ReactAdapterErrorPhase,
|
|
191
|
+
cause: unknown,
|
|
192
|
+
): void {
|
|
193
|
+
const error = createSecondaryErrorEvent(input, code, phase, cause);
|
|
194
|
+
reactAdapterDiagnostics.reportReactAdapterDiagnostic(
|
|
195
|
+
reactAdapterDiagnostics.createErrorDiagnostic(error),
|
|
196
|
+
input.configuration,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Bezpečně spotřebuje callback return a reportuje nejvýše jednu rejection. */
|
|
201
|
+
function consumeCallbackResult<
|
|
202
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
203
|
+
>(
|
|
204
|
+
result: unknown,
|
|
205
|
+
input: OutletErrorIdentity<Point> & {
|
|
206
|
+
readonly configuration: adapterConfiguration.AdapterScopeConfiguration;
|
|
207
|
+
readonly isCurrent: () => boolean;
|
|
208
|
+
},
|
|
209
|
+
): void {
|
|
210
|
+
let reported = false;
|
|
211
|
+
/** Přeloží právě jednu current callback failure na sink-only diagnostiku. */
|
|
212
|
+
function reportCallbackFailure(cause: unknown): void {
|
|
213
|
+
if (reported || !input.isCurrent()) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
reported = true;
|
|
217
|
+
reportSecondaryFailure(input, "REACT_CALLBACK_FAILED", "callback", cause);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
void Promise.resolve(result).catch(reportCallbackFailure);
|
|
222
|
+
} catch (cause: unknown) {
|
|
223
|
+
reportCallbackFailure(cause);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Zavolá guarded outlet callback s undefined receiverem a bezpečnou finalitou. */
|
|
228
|
+
function callGuardedOutletCallback<
|
|
229
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
230
|
+
Argument,
|
|
231
|
+
>(
|
|
232
|
+
callback: outletInput.CapturedOutletCallback<Argument, unknown>,
|
|
233
|
+
argument: Argument,
|
|
234
|
+
input: OutletErrorIdentity<Point> & {
|
|
235
|
+
readonly configuration: adapterConfiguration.AdapterScopeConfiguration;
|
|
236
|
+
readonly isCurrent: () => boolean;
|
|
237
|
+
},
|
|
238
|
+
): void {
|
|
239
|
+
let result: unknown;
|
|
240
|
+
try {
|
|
241
|
+
result = outletInput.callCapturedOutletCallback(callback, argument);
|
|
242
|
+
} catch (cause: unknown) {
|
|
243
|
+
if (input.isCurrent()) {
|
|
244
|
+
reportSecondaryFailure(input, "REACT_CALLBACK_FAILED", "callback", cause);
|
|
245
|
+
}
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
consumeCallbackResult(result, input);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Pure-synchronně projektuje context a případné selhání publikuje před primary. */
|
|
252
|
+
function projectPrimaryContext<
|
|
253
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
254
|
+
>(
|
|
255
|
+
input: OutletErrorIdentity<Point> & {
|
|
256
|
+
readonly configuration: adapterConfiguration.AdapterScopeConfiguration;
|
|
257
|
+
readonly isCurrent: () => boolean;
|
|
258
|
+
},
|
|
259
|
+
): { readonly hasProjection: boolean; readonly projection?: unknown } {
|
|
260
|
+
const projector = input.configuration.projectContext;
|
|
261
|
+
if (projector === undefined) {
|
|
262
|
+
return Object.freeze({ hasProjection: false });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
const projection = Reflect.apply(projector, undefined, [input.context]) as unknown;
|
|
267
|
+
return Object.freeze({ hasProjection: true, projection });
|
|
268
|
+
} catch (cause: unknown) {
|
|
269
|
+
if (input.isCurrent()) {
|
|
270
|
+
reportSecondaryFailure(
|
|
271
|
+
input,
|
|
272
|
+
"REACT_CONTEXT_PROJECTION_FAILED",
|
|
273
|
+
"projection",
|
|
274
|
+
cause,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return Object.freeze({ hasProjection: false });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Vytvoří primary event po jediné pure context projection. */
|
|
282
|
+
function createPrimaryErrorEvent<
|
|
283
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
284
|
+
>(
|
|
285
|
+
input: OutletPrimaryErrorPipelineInput<Point>,
|
|
286
|
+
projection: { readonly hasProjection: boolean; readonly projection?: unknown },
|
|
287
|
+
): reactAdapterDiagnostics.ReactAdapterErrorEvent {
|
|
288
|
+
const point = reactAdapterDiagnostics.createPointMetadata(input.point);
|
|
289
|
+
return reactAdapterDiagnostics.createReactAdapterErrorEvent({
|
|
290
|
+
boundaryId: input.boundaryId,
|
|
291
|
+
code: input.code,
|
|
292
|
+
cause: input.cause,
|
|
293
|
+
contribution: createProjectedContributionMetadata(input),
|
|
294
|
+
outletId: input.outletId,
|
|
295
|
+
phase: input.phase,
|
|
296
|
+
...(point === undefined ? {} : { point }),
|
|
297
|
+
...(input.reactComponentStack === undefined
|
|
298
|
+
? {}
|
|
299
|
+
: { reactComponentStack: input.reactComponentStack }),
|
|
300
|
+
retryGeneration: input.retryGeneration,
|
|
301
|
+
...(projection.hasProjection
|
|
302
|
+
? { contextProjection: projection.projection, hasContextProjection: true }
|
|
303
|
+
: {}),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Vyhodnotí captured nebo package-default contribution policy. */
|
|
308
|
+
function evaluateErrorPolicy<
|
|
309
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
310
|
+
>(
|
|
311
|
+
input: OutletPrimaryErrorPipelineInput<Point>,
|
|
312
|
+
error: reactAdapterDiagnostics.ReactAdapterErrorEvent,
|
|
313
|
+
): reactAdapterDiagnostics.ReactErrorAction | undefined {
|
|
314
|
+
const policy = input.configuration.errorPolicy;
|
|
315
|
+
if (policy === undefined) {
|
|
316
|
+
return input.hasFallback ? "renderFallback" : "hide";
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
let action: unknown;
|
|
320
|
+
try {
|
|
321
|
+
action = Reflect.apply(policy, undefined, [error]);
|
|
322
|
+
} catch (cause: unknown) {
|
|
323
|
+
reportSecondaryFailure(input, "REACT_ERROR_POLICY_FAILED", "policy", cause);
|
|
324
|
+
return undefined;
|
|
325
|
+
}
|
|
326
|
+
if (action !== "hide" && action !== "renderFallback" && action !== "escalate") {
|
|
327
|
+
reportSecondaryFailure(
|
|
328
|
+
input,
|
|
329
|
+
"REACT_ERROR_POLICY_FAILED",
|
|
330
|
+
"policy",
|
|
331
|
+
new TypeError("React error policy returned an unsupported action."),
|
|
332
|
+
);
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
return action;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Spustí přesně seřazenou committed contribution error pipeline. */
|
|
339
|
+
export function runOutletPrimaryErrorPipeline<
|
|
340
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
341
|
+
>(
|
|
342
|
+
input: OutletPrimaryErrorPipelineInput<Point>,
|
|
343
|
+
): OutletPrimaryErrorDecision {
|
|
344
|
+
if (!input.isCurrent()) {
|
|
345
|
+
return Object.freeze({ kind: "stale" });
|
|
346
|
+
}
|
|
347
|
+
const projection = projectPrimaryContext(input);
|
|
348
|
+
if (!input.isCurrent()) {
|
|
349
|
+
return Object.freeze({ kind: "stale" });
|
|
350
|
+
}
|
|
351
|
+
const error = createPrimaryErrorEvent(input, projection);
|
|
352
|
+
reactAdapterDiagnostics.reportReactAdapterDiagnostic(
|
|
353
|
+
reactAdapterDiagnostics.createErrorDiagnostic(error),
|
|
354
|
+
input.configuration,
|
|
355
|
+
);
|
|
356
|
+
if (!input.isCurrent()) {
|
|
357
|
+
return Object.freeze({ kind: "stale" });
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (input.onError !== undefined) {
|
|
361
|
+
const emission: OutletContributionErrorEmission = Object.freeze({
|
|
362
|
+
kind: "contribution",
|
|
363
|
+
error,
|
|
364
|
+
retry: input.retry,
|
|
365
|
+
});
|
|
366
|
+
callGuardedOutletCallback(input.onError, emission, input);
|
|
367
|
+
if (!input.isCurrent()) {
|
|
368
|
+
return Object.freeze({ kind: "stale" });
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (!input.isolateErrors) {
|
|
373
|
+
return Object.freeze({ kind: "escalate", error, cause: input.cause });
|
|
374
|
+
}
|
|
375
|
+
const action = evaluateErrorPolicy(input, error);
|
|
376
|
+
if (!input.isCurrent()) {
|
|
377
|
+
return Object.freeze({ kind: "stale" });
|
|
378
|
+
}
|
|
379
|
+
if (action === undefined || action === "escalate") {
|
|
380
|
+
return Object.freeze({ kind: "escalate", error, cause: input.cause });
|
|
381
|
+
}
|
|
382
|
+
if (action === "renderFallback") {
|
|
383
|
+
return input.hasFallback
|
|
384
|
+
? Object.freeze({ kind: "renderFallback", error })
|
|
385
|
+
: Object.freeze({ kind: "escalate", error, cause: input.cause });
|
|
386
|
+
}
|
|
387
|
+
return Object.freeze({ kind: "hide", error });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Reportuje fallback failure bez onError/policy recursion a vrátí exact cause. */
|
|
391
|
+
export function reportOutletFallbackError<
|
|
392
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
393
|
+
>(
|
|
394
|
+
input: OutletFallbackErrorInput<Point>,
|
|
395
|
+
): unknown {
|
|
396
|
+
if (!input.isCurrent()) {
|
|
397
|
+
return input.cause;
|
|
398
|
+
}
|
|
399
|
+
const point = reactAdapterDiagnostics.createPointMetadata(input.point);
|
|
400
|
+
const event = reactAdapterDiagnostics.createReactAdapterErrorEvent({
|
|
401
|
+
boundaryId: input.boundaryId,
|
|
402
|
+
code: "REACT_RENDER_FAILED",
|
|
403
|
+
cause: input.cause,
|
|
404
|
+
contribution: createProjectedContributionMetadata(input),
|
|
405
|
+
outletId: input.outletId,
|
|
406
|
+
phase: "fallback",
|
|
407
|
+
...(point === undefined ? {} : { point }),
|
|
408
|
+
...(input.reactComponentStack === undefined
|
|
409
|
+
? {}
|
|
410
|
+
: { reactComponentStack: input.reactComponentStack }),
|
|
411
|
+
retryGeneration: input.retryGeneration,
|
|
412
|
+
});
|
|
413
|
+
reactAdapterDiagnostics.reportReactAdapterDiagnostic(
|
|
414
|
+
reactAdapterDiagnostics.createErrorDiagnostic(event),
|
|
415
|
+
input.configuration,
|
|
416
|
+
);
|
|
417
|
+
return input.cause;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** Doručí committed retry event nejvýše jednou volajícímu boundary lifecycle. */
|
|
421
|
+
export function dispatchOutletRetryCallback<
|
|
422
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
423
|
+
>(
|
|
424
|
+
input: OutletRetryCallbackInput<Point>,
|
|
425
|
+
): void {
|
|
426
|
+
if (input.onRetry === undefined || !input.isCurrent()) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const event: OutletRetryEvent = Object.freeze({
|
|
430
|
+
boundaryId: input.boundaryId,
|
|
431
|
+
contributionId: input.contribution.contributionId,
|
|
432
|
+
outletId: input.outletId,
|
|
433
|
+
pointId: input.point.pointId,
|
|
434
|
+
registrationId: input.registrationId,
|
|
435
|
+
retryGeneration: input.retryGeneration,
|
|
436
|
+
});
|
|
437
|
+
callGuardedOutletCallback(input.onRetry, event, input);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** Normalizuje React component stack bez synthetic prázdného pole. */
|
|
441
|
+
export function normalizeReactComponentStack(value: unknown): string | undefined {
|
|
442
|
+
if (typeof value !== "string") {
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
const normalized = value.replaceAll("\r\n", "\n").trim();
|
|
446
|
+
return normalized.length === 0 ? undefined : normalized;
|
|
447
|
+
}
|