@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,125 @@
|
|
|
1
|
+
import * as reactAdapterError from "../react-adapter-error.js";
|
|
2
|
+
import type * as reactExtensionPoint from "../react-extension-point.js";
|
|
3
|
+
import type * as outletProjection from "./outlet-projection.js";
|
|
4
|
+
|
|
5
|
+
/** Privátní compile-time identita opaque membership tokenu. */
|
|
6
|
+
declare const outletMembershipTokenBrand: unique symbol;
|
|
7
|
+
|
|
8
|
+
/** Render-pure opaque identita jedné outlet membership. */
|
|
9
|
+
export interface OutletMembershipToken {
|
|
10
|
+
/** Privátní invariant bez runtime property nebo globálního brandu. */
|
|
11
|
+
readonly [outletMembershipTokenBrand]: "outlet-membership";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Jeden frozen membership candidate s přiděleným lokálním tokenem. */
|
|
15
|
+
export interface OutletMembershipEntry<
|
|
16
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
17
|
+
> {
|
|
18
|
+
/** Opaque token zachovaný pouze pro retained registry/registration tuple. */
|
|
19
|
+
readonly token: OutletMembershipToken;
|
|
20
|
+
/** Aktuální frozen render seed daného outlet snapshotu. */
|
|
21
|
+
readonly seed: outletProjection.OutletProjectedMembershipSeed<Point>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Immutable čistý návrh jednoho outlet membership snapshotu. */
|
|
25
|
+
export interface OutletMembershipSnapshot<
|
|
26
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
27
|
+
> {
|
|
28
|
+
/** Exact raw registry reference jako první osa identity. */
|
|
29
|
+
readonly rawRegistry: object;
|
|
30
|
+
/** Atomický projection snapshot včetně placement-free větví. */
|
|
31
|
+
readonly projection: outletProjection.OutletProjectionSnapshot<Point>;
|
|
32
|
+
/** Ordered memberships; empty a resolve-failed projection používají prázdnou tuple. */
|
|
33
|
+
readonly memberships: readonly OutletMembershipEntry<Point>[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Sdílená frozen prázdná membership tuple bez runtime historie. */
|
|
37
|
+
const EMPTY_OUTLET_MEMBERSHIPS: readonly [] = Object.freeze([]);
|
|
38
|
+
|
|
39
|
+
/** Vytvoří lokální opaque token bez external identity nebo side effectu. */
|
|
40
|
+
function createOutletMembershipToken(): OutletMembershipToken {
|
|
41
|
+
return Object.freeze({}) as OutletMembershipToken;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Ověří raw registry identity bez čtení jejích public polí. */
|
|
45
|
+
function requireRawRegistry(value: unknown): object {
|
|
46
|
+
if (typeof value !== "object" || value === null) {
|
|
47
|
+
throw new reactAdapterError.ReactAdapterError(
|
|
48
|
+
"REACT_REGISTRY_INVALID",
|
|
49
|
+
"The outlet registry identity must be a non-null object.",
|
|
50
|
+
{ details: { field: "registry", phase: "configuration" } },
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Zachytí reusable tokeny před vytvořením nového proposal snapshotu. */
|
|
57
|
+
function collectRetainedTokens<
|
|
58
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
59
|
+
>(
|
|
60
|
+
previous: OutletMembershipSnapshot<Point> | undefined,
|
|
61
|
+
rawRegistry: object,
|
|
62
|
+
): ReadonlyMap<string, OutletMembershipToken> {
|
|
63
|
+
const tokens = new Map<string, OutletMembershipToken>();
|
|
64
|
+
if (previous === undefined || previous.rawRegistry !== rawRegistry) {
|
|
65
|
+
return tokens;
|
|
66
|
+
}
|
|
67
|
+
for (const membership of previous.memberships) {
|
|
68
|
+
tokens.set(membership.seed.registrationId, membership.token);
|
|
69
|
+
}
|
|
70
|
+
return tokens;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Vytvoří atomický membership proposal bez zápisu do předchozího snapshotu.
|
|
75
|
+
*/
|
|
76
|
+
export function reconcileOutletMemberships<
|
|
77
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
78
|
+
>(
|
|
79
|
+
previous: OutletMembershipSnapshot<Point> | undefined,
|
|
80
|
+
rawRegistryValue: unknown,
|
|
81
|
+
projection: outletProjection.OutletProjectionSnapshot<Point>,
|
|
82
|
+
): OutletMembershipSnapshot<Point> {
|
|
83
|
+
const rawRegistry = requireRawRegistry(rawRegistryValue);
|
|
84
|
+
if (projection.kind !== "memberships") {
|
|
85
|
+
return Object.freeze({
|
|
86
|
+
rawRegistry,
|
|
87
|
+
projection,
|
|
88
|
+
memberships: EMPTY_OUTLET_MEMBERSHIPS,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const registrationIds = new Set<string>();
|
|
93
|
+
for (const seed of projection.memberships) {
|
|
94
|
+
if (registrationIds.has(seed.registrationId)) {
|
|
95
|
+
throw new reactAdapterError.ReactAdapterError(
|
|
96
|
+
"REACT_RESOLVE_FAILED",
|
|
97
|
+
"The projected outlet collection contains duplicate registration identities.",
|
|
98
|
+
{
|
|
99
|
+
details: {
|
|
100
|
+
field: "projection.memberships.registrationId",
|
|
101
|
+
phase: "resolve",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
registrationIds.add(seed.registrationId);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const retainedTokens = collectRetainedTokens(previous, rawRegistry);
|
|
110
|
+
const memberships = projection.memberships.map(
|
|
111
|
+
/** Připojí nový render seed k retained nebo čerstvému opaque tokenu. */
|
|
112
|
+
function createMembershipEntry(seed): OutletMembershipEntry<Point> {
|
|
113
|
+
return Object.freeze({
|
|
114
|
+
token: retainedTokens.get(seed.registrationId) ?? createOutletMembershipToken(),
|
|
115
|
+
seed,
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return Object.freeze({
|
|
121
|
+
rawRegistry,
|
|
122
|
+
projection,
|
|
123
|
+
memberships: Object.freeze(memberships),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import type * as contribKitCore from "@primafuture/contrib-kit-core";
|
|
2
|
+
|
|
3
|
+
import * as implementationClassifier from "./implementation-classifier.js";
|
|
4
|
+
import * as publicInputCapture from "./public-input-capture.js";
|
|
5
|
+
import type * as contributionComponent from "../contribution-component.js";
|
|
6
|
+
import * as reactAdapterError from "../react-adapter-error.js";
|
|
7
|
+
import type * as reactAdapterDiagnostics from "../react-adapter-diagnostics.js";
|
|
8
|
+
import type * as reactExtensionPoint from "../react-extension-point.js";
|
|
9
|
+
import type * as resolvedContributions from "../resolved-contributions.js";
|
|
10
|
+
|
|
11
|
+
/** Společný frozen render vstup jedné projected membership. */
|
|
12
|
+
export interface OutletContributionRenderSeed<
|
|
13
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
14
|
+
> {
|
|
15
|
+
/** Exact context jednoho outlet resolution snapshotu. */
|
|
16
|
+
readonly context: contribKitCore.ExtensionPointContext<Point>;
|
|
17
|
+
/** Bezpečný public contribution view bez runtime identity. */
|
|
18
|
+
readonly contribution: contributionComponent.ExtensionContributionView<
|
|
19
|
+
contribKitCore.ExtensionPointData<Point>
|
|
20
|
+
>;
|
|
21
|
+
/** Outlet-local logical placement odvozený z celé final kolekce. */
|
|
22
|
+
readonly placement: contributionComponent.ResolvedContributionPlacement;
|
|
23
|
+
/** Exact canonical point společný všem memberships snapshotu. */
|
|
24
|
+
readonly point: Point;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Frozen ready seed eager contribution. */
|
|
28
|
+
export interface OutletReadyMembershipSeed<
|
|
29
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
30
|
+
> {
|
|
31
|
+
/** Rozlišuje eager ready větev od loading a failure. */
|
|
32
|
+
readonly kind: "ready";
|
|
33
|
+
/** Opaque Core registration identity dostupná pouze reconcileru. */
|
|
34
|
+
readonly registrationId: string;
|
|
35
|
+
/** Frozen ready render state včetně component props. */
|
|
36
|
+
readonly renderState: OutletContributionRenderSeed<Point> & {
|
|
37
|
+
/** Exact eager React component reference. */
|
|
38
|
+
readonly component: contributionComponent.ReactContributionComponent<
|
|
39
|
+
contribKitCore.ExtensionPointContext<Point>,
|
|
40
|
+
contribKitCore.ExtensionPointData<Point>
|
|
41
|
+
>;
|
|
42
|
+
/** Jediné tři frozen props budoucího default rendereru. */
|
|
43
|
+
readonly componentProps: contributionComponent.ExtensionContributionProps<
|
|
44
|
+
contribKitCore.ExtensionPointContext<Point>,
|
|
45
|
+
contribKitCore.ExtensionPointData<Point>
|
|
46
|
+
>;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Frozen loading seed managed-lazy contribution bez spuštěného loaderu. */
|
|
51
|
+
export interface OutletLoadingMembershipSeed<
|
|
52
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
53
|
+
> {
|
|
54
|
+
/** Rozlišuje managed-lazy loading větev. */
|
|
55
|
+
readonly kind: "loading";
|
|
56
|
+
/** Opaque Core registration identity dostupná pouze reconcileru. */
|
|
57
|
+
readonly registrationId: string;
|
|
58
|
+
/** Zachycená loader reference bez invocation nebo controlleru. */
|
|
59
|
+
readonly load: implementationClassifier.CapturedReactLazyLoader;
|
|
60
|
+
/** Frozen loading render state bez raw implementation. */
|
|
61
|
+
readonly renderState: OutletContributionRenderSeed<Point>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Frozen provisional failure seed neplatné contribution implementation. */
|
|
65
|
+
export interface OutletFailedMembershipSeed<
|
|
66
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
67
|
+
> {
|
|
68
|
+
/** Rozlišuje per-membership classifier failure. */
|
|
69
|
+
readonly kind: "failed";
|
|
70
|
+
/** Opaque Core registration identity dostupná pouze reconcileru. */
|
|
71
|
+
readonly registrationId: string;
|
|
72
|
+
/** Stabilní adapter error připravená pro příští boundary checkpoint. */
|
|
73
|
+
readonly error: reactAdapterError.ReactAdapterError;
|
|
74
|
+
/** Frozen contribution render state bez raw implementation. */
|
|
75
|
+
readonly renderState: OutletContributionRenderSeed<Point>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Jeden interní projected membership seed před přidělením identity tokenu. */
|
|
79
|
+
export type OutletProjectedMembershipSeed<
|
|
80
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
81
|
+
> =
|
|
82
|
+
| OutletReadyMembershipSeed<Point>
|
|
83
|
+
| OutletLoadingMembershipSeed<Point>
|
|
84
|
+
| OutletFailedMembershipSeed<Point>;
|
|
85
|
+
|
|
86
|
+
/** Frozen placement-free stav úspěšného prázdného outletu. */
|
|
87
|
+
export interface OutletEmptyProjection<
|
|
88
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
89
|
+
> {
|
|
90
|
+
/** Rozlišuje empty stav od resolve failure a memberships. */
|
|
91
|
+
readonly kind: "empty";
|
|
92
|
+
/** Připravený budoucí render state bez placementu. */
|
|
93
|
+
readonly renderState: {
|
|
94
|
+
/** Exact context úspěšného prázdného query. */
|
|
95
|
+
readonly context: contribKitCore.ExtensionPointContext<Point>;
|
|
96
|
+
/** Exact canonical point. */
|
|
97
|
+
readonly point: Point;
|
|
98
|
+
/** Přesná úspěšná empty klasifikace hooku. */
|
|
99
|
+
readonly reason: resolvedContributions.ExtensionOutletEmptyReason;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Frozen placement-free stav outlet resolve failure. */
|
|
104
|
+
export interface OutletResolveFailureProjection<
|
|
105
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
106
|
+
> {
|
|
107
|
+
/** Rozlišuje resolve failure od empty stavu a memberships. */
|
|
108
|
+
readonly kind: "resolveFailed";
|
|
109
|
+
/** Připravený budoucí render state bez placementu. */
|
|
110
|
+
readonly renderState: {
|
|
111
|
+
/** Exact context neúspěšného query. */
|
|
112
|
+
readonly context: contribKitCore.ExtensionPointContext<Point>;
|
|
113
|
+
/** Frozen public error event z resolved hooku. */
|
|
114
|
+
readonly error: reactAdapterDiagnostics.ReactAdapterErrorEvent;
|
|
115
|
+
/** Exact canonical point. */
|
|
116
|
+
readonly point: Point;
|
|
117
|
+
/** Stabilní hook refresh capability. */
|
|
118
|
+
readonly refresh: () => void;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Frozen neprázdný ordered snapshot všech projected memberships. */
|
|
123
|
+
export interface OutletResolvedProjection<
|
|
124
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
125
|
+
> {
|
|
126
|
+
/** Rozlišuje neprázdný membership snapshot. */
|
|
127
|
+
readonly kind: "memberships";
|
|
128
|
+
/** Immutable seeds ve finálním Core resolved pořadí. */
|
|
129
|
+
readonly memberships: readonly OutletProjectedMembershipSeed<Point>[];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Atomický outlet projection candidate bez committed identity nebo resource. */
|
|
133
|
+
export type OutletProjectionSnapshot<
|
|
134
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
135
|
+
> =
|
|
136
|
+
| OutletEmptyProjection<Point>
|
|
137
|
+
| OutletResolveFailureProjection<Point>
|
|
138
|
+
| OutletResolvedProjection<Point>;
|
|
139
|
+
|
|
140
|
+
/** Lifecycle checkpoint mezi user-observable projection kroky. */
|
|
141
|
+
export type OutletProjectionCheckpoint = () => void;
|
|
142
|
+
|
|
143
|
+
/** Výchozí resource-free checkpoint čisté projection. */
|
|
144
|
+
function skipOutletProjectionCheckpoint(): void {
|
|
145
|
+
// Čistá projection bez React ownera nemá další lifecycle gate.
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Vytvoří bezpečnou atomickou projection chybu se zachovaným cause. */
|
|
149
|
+
function createProjectionError(
|
|
150
|
+
field: string,
|
|
151
|
+
cause?: unknown,
|
|
152
|
+
preserveCause = false,
|
|
153
|
+
): reactAdapterError.ReactAdapterError {
|
|
154
|
+
return new reactAdapterError.ReactAdapterError(
|
|
155
|
+
"REACT_RESOLVE_FAILED",
|
|
156
|
+
"The resolved contribution collection cannot be projected.",
|
|
157
|
+
preserveCause
|
|
158
|
+
? { cause, details: { field, phase: "resolve" } }
|
|
159
|
+
: { details: { field, phase: "resolve" } },
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Načte jedno známé projection pole právě jednou a ověří autoritu. */
|
|
164
|
+
function readProjectionProperty(
|
|
165
|
+
target: object,
|
|
166
|
+
property: PropertyKey,
|
|
167
|
+
field: string,
|
|
168
|
+
checkpoint: OutletProjectionCheckpoint,
|
|
169
|
+
): unknown {
|
|
170
|
+
let value: unknown;
|
|
171
|
+
try {
|
|
172
|
+
value = Reflect.get(target, property, target);
|
|
173
|
+
} catch (cause: unknown) {
|
|
174
|
+
throw createProjectionError(field, cause, true);
|
|
175
|
+
}
|
|
176
|
+
checkpoint();
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Ověří optional contribution metadata bez coercion. */
|
|
181
|
+
function requireOptionalProjectionString(value: unknown, field: string): string | undefined {
|
|
182
|
+
if (value !== undefined && typeof value !== "string") {
|
|
183
|
+
throw createProjectionError(field);
|
|
184
|
+
}
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Vytvoří exact frozen outlet-local placement. */
|
|
189
|
+
function createPlacement(
|
|
190
|
+
index: number,
|
|
191
|
+
count: number,
|
|
192
|
+
): contributionComponent.ResolvedContributionPlacement {
|
|
193
|
+
return Object.freeze({
|
|
194
|
+
index,
|
|
195
|
+
count,
|
|
196
|
+
isFirst: index === 0,
|
|
197
|
+
isLast: index === count - 1,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Vytvoří bezpečnou implementation error bez raw business hodnoty. */
|
|
202
|
+
function createImplementationError(
|
|
203
|
+
field: string,
|
|
204
|
+
cause?: unknown,
|
|
205
|
+
preserveCause = false,
|
|
206
|
+
): reactAdapterError.ReactAdapterError {
|
|
207
|
+
return new reactAdapterError.ReactAdapterError(
|
|
208
|
+
"REACT_POINT_IMPLEMENTATION_INVALID",
|
|
209
|
+
"The contribution implementation is not supported by the React adapter.",
|
|
210
|
+
preserveCause
|
|
211
|
+
? { cause, details: { field, phase: "render" } }
|
|
212
|
+
: { details: { field, phase: "render" } },
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Zachytí jeden Core record a vytvoří frozen public view i render base. */
|
|
217
|
+
function captureMembershipSeedInput<
|
|
218
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
219
|
+
>(
|
|
220
|
+
recordValue: unknown,
|
|
221
|
+
point: Point,
|
|
222
|
+
context: contribKitCore.ExtensionPointContext<Point>,
|
|
223
|
+
index: number,
|
|
224
|
+
count: number,
|
|
225
|
+
checkpoint: OutletProjectionCheckpoint,
|
|
226
|
+
): {
|
|
227
|
+
readonly registrationId: string;
|
|
228
|
+
readonly implementation: unknown;
|
|
229
|
+
readonly renderState: OutletContributionRenderSeed<Point>;
|
|
230
|
+
} {
|
|
231
|
+
if (typeof recordValue !== "object" || recordValue === null) {
|
|
232
|
+
throw createProjectionError(`result.state.contributions[${index}]`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const field = `result.state.contributions[${index}]`;
|
|
236
|
+
const capturedPoint = readProjectionProperty(recordValue, "point", `${field}.point`, checkpoint);
|
|
237
|
+
const registrationId = readProjectionProperty(
|
|
238
|
+
recordValue,
|
|
239
|
+
"registrationId",
|
|
240
|
+
`${field}.registrationId`,
|
|
241
|
+
checkpoint,
|
|
242
|
+
);
|
|
243
|
+
const contributionId = readProjectionProperty(
|
|
244
|
+
recordValue,
|
|
245
|
+
"contributionId",
|
|
246
|
+
`${field}.contributionId`,
|
|
247
|
+
checkpoint,
|
|
248
|
+
);
|
|
249
|
+
const contributorId = readProjectionProperty(
|
|
250
|
+
recordValue,
|
|
251
|
+
"contributorId",
|
|
252
|
+
`${field}.contributorId`,
|
|
253
|
+
checkpoint,
|
|
254
|
+
);
|
|
255
|
+
const localName = readProjectionProperty(
|
|
256
|
+
recordValue,
|
|
257
|
+
"localName",
|
|
258
|
+
`${field}.localName`,
|
|
259
|
+
checkpoint,
|
|
260
|
+
);
|
|
261
|
+
const label = readProjectionProperty(recordValue, "label", `${field}.label`, checkpoint);
|
|
262
|
+
const implementation = readProjectionProperty(
|
|
263
|
+
recordValue,
|
|
264
|
+
"implementation",
|
|
265
|
+
`${field}.implementation`,
|
|
266
|
+
checkpoint,
|
|
267
|
+
);
|
|
268
|
+
const data = readProjectionProperty(recordValue, "data", `${field}.data`, checkpoint);
|
|
269
|
+
|
|
270
|
+
if (capturedPoint !== point) {
|
|
271
|
+
throw createProjectionError(`${field}.point`);
|
|
272
|
+
}
|
|
273
|
+
if (typeof registrationId !== "string" || registrationId.length === 0) {
|
|
274
|
+
throw createProjectionError(`${field}.registrationId`);
|
|
275
|
+
}
|
|
276
|
+
if (typeof contributionId !== "string" || contributionId.length === 0) {
|
|
277
|
+
throw createProjectionError(`${field}.contributionId`);
|
|
278
|
+
}
|
|
279
|
+
const capturedContributorId = requireOptionalProjectionString(
|
|
280
|
+
contributorId,
|
|
281
|
+
`${field}.contributorId`,
|
|
282
|
+
);
|
|
283
|
+
const capturedLocalName = requireOptionalProjectionString(localName, `${field}.localName`);
|
|
284
|
+
const capturedLabel = requireOptionalProjectionString(label, `${field}.label`);
|
|
285
|
+
const contribution: contributionComponent.ExtensionContributionView<
|
|
286
|
+
contribKitCore.ExtensionPointData<Point>
|
|
287
|
+
> = Object.freeze({
|
|
288
|
+
contributionId,
|
|
289
|
+
...(capturedContributorId === undefined ? {} : { contributorId: capturedContributorId }),
|
|
290
|
+
data: data as contribKitCore.ExtensionPointData<Point>,
|
|
291
|
+
...(capturedLabel === undefined ? {} : { label: capturedLabel }),
|
|
292
|
+
...(capturedLocalName === undefined ? {} : { localName: capturedLocalName }),
|
|
293
|
+
});
|
|
294
|
+
const placement = createPlacement(index, count);
|
|
295
|
+
const renderState: OutletContributionRenderSeed<Point> = Object.freeze({
|
|
296
|
+
context,
|
|
297
|
+
contribution,
|
|
298
|
+
placement,
|
|
299
|
+
point,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
return Object.freeze({ registrationId, implementation, renderState });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Klasifikuje implementation a odliší lifecycle checkpoint failure od accessor failure. */
|
|
306
|
+
function classifyMembershipImplementation(
|
|
307
|
+
implementation: unknown,
|
|
308
|
+
checkpoint: OutletProjectionCheckpoint,
|
|
309
|
+
):
|
|
310
|
+
| { readonly kind: "classified"; readonly value: implementationClassifier.ReactImplementationClassification }
|
|
311
|
+
| { readonly kind: "failed"; readonly cause: unknown } {
|
|
312
|
+
let checkpointCause: unknown;
|
|
313
|
+
let checkpointFailed = false;
|
|
314
|
+
/** Propaguje lifecycle ztrátu autority namísto jejího překladu na membership failure. */
|
|
315
|
+
function checkpointClassification(): void {
|
|
316
|
+
try {
|
|
317
|
+
checkpoint();
|
|
318
|
+
} catch (cause: unknown) {
|
|
319
|
+
checkpointFailed = true;
|
|
320
|
+
checkpointCause = cause;
|
|
321
|
+
throw cause;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
return Object.freeze({
|
|
327
|
+
kind: "classified" as const,
|
|
328
|
+
value: implementationClassifier.classifyReactContributionImplementation(
|
|
329
|
+
implementation,
|
|
330
|
+
checkpointClassification,
|
|
331
|
+
),
|
|
332
|
+
});
|
|
333
|
+
} catch (cause: unknown) {
|
|
334
|
+
if (checkpointFailed) {
|
|
335
|
+
throw checkpointCause;
|
|
336
|
+
}
|
|
337
|
+
return Object.freeze({ kind: "failed" as const, cause });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Vytvoří jeden eager, loading nebo failed membership seed bez efektu. */
|
|
342
|
+
function createMembershipSeed<
|
|
343
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
344
|
+
>(
|
|
345
|
+
input: ReturnType<typeof captureMembershipSeedInput<Point>>,
|
|
346
|
+
field: string,
|
|
347
|
+
checkpoint: OutletProjectionCheckpoint,
|
|
348
|
+
): OutletProjectedMembershipSeed<Point> {
|
|
349
|
+
const classification = classifyMembershipImplementation(input.implementation, checkpoint);
|
|
350
|
+
if (classification.kind === "failed") {
|
|
351
|
+
return Object.freeze({
|
|
352
|
+
kind: "failed",
|
|
353
|
+
registrationId: input.registrationId,
|
|
354
|
+
error: createImplementationError(field, classification.cause, true),
|
|
355
|
+
renderState: input.renderState,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
if (classification.value.kind === "invalid") {
|
|
359
|
+
return Object.freeze({
|
|
360
|
+
kind: "failed",
|
|
361
|
+
registrationId: input.registrationId,
|
|
362
|
+
error: createImplementationError(field),
|
|
363
|
+
renderState: input.renderState,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
if (classification.value.kind === "managedLazy") {
|
|
367
|
+
return Object.freeze({
|
|
368
|
+
kind: "loading",
|
|
369
|
+
registrationId: input.registrationId,
|
|
370
|
+
load: classification.value.load,
|
|
371
|
+
renderState: input.renderState,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const component = input.implementation as contributionComponent.ReactContributionComponent<
|
|
376
|
+
contribKitCore.ExtensionPointContext<Point>,
|
|
377
|
+
contribKitCore.ExtensionPointData<Point>
|
|
378
|
+
>;
|
|
379
|
+
const componentProps: contributionComponent.ExtensionContributionProps<
|
|
380
|
+
contribKitCore.ExtensionPointContext<Point>,
|
|
381
|
+
contribKitCore.ExtensionPointData<Point>
|
|
382
|
+
> = Object.freeze({
|
|
383
|
+
context: input.renderState.context,
|
|
384
|
+
contribution: input.renderState.contribution,
|
|
385
|
+
placement: input.renderState.placement,
|
|
386
|
+
});
|
|
387
|
+
const renderState = Object.freeze({
|
|
388
|
+
...input.renderState,
|
|
389
|
+
component,
|
|
390
|
+
componentProps,
|
|
391
|
+
});
|
|
392
|
+
return Object.freeze({
|
|
393
|
+
kind: "ready",
|
|
394
|
+
registrationId: input.registrationId,
|
|
395
|
+
renderState,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Převede jeden frozen hook result na atomický placement a membership candidate.
|
|
401
|
+
*/
|
|
402
|
+
export function projectOutletSnapshot<
|
|
403
|
+
Point extends reactExtensionPoint.AnyReactExtensionPoint,
|
|
404
|
+
>(
|
|
405
|
+
point: Point,
|
|
406
|
+
context: contribKitCore.ExtensionPointContext<Point>,
|
|
407
|
+
result: resolvedContributions.UseResolvedContributionsResult<Point>,
|
|
408
|
+
checkpoint: OutletProjectionCheckpoint = skipOutletProjectionCheckpoint,
|
|
409
|
+
): OutletProjectionSnapshot<Point> {
|
|
410
|
+
const resultSource = publicInputCapture.requireInputObject(result, "result");
|
|
411
|
+
const stateValue = readProjectionProperty(resultSource, "state", "result.state", checkpoint);
|
|
412
|
+
const refreshValue = readProjectionProperty(resultSource, "refresh", "result.refresh", checkpoint);
|
|
413
|
+
if (typeof refreshValue !== "function") {
|
|
414
|
+
throw createProjectionError("result.refresh");
|
|
415
|
+
}
|
|
416
|
+
if (typeof stateValue !== "object" || stateValue === null) {
|
|
417
|
+
throw createProjectionError("result.state");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const status = readProjectionProperty(stateValue, "status", "result.state.status", checkpoint);
|
|
421
|
+
if (status === "failed") {
|
|
422
|
+
const error = readProjectionProperty(
|
|
423
|
+
stateValue,
|
|
424
|
+
"error",
|
|
425
|
+
"result.state.error",
|
|
426
|
+
checkpoint,
|
|
427
|
+
) as reactAdapterDiagnostics.ReactAdapterErrorEvent;
|
|
428
|
+
return Object.freeze({
|
|
429
|
+
kind: "resolveFailed",
|
|
430
|
+
renderState: Object.freeze({
|
|
431
|
+
context,
|
|
432
|
+
error,
|
|
433
|
+
point,
|
|
434
|
+
refresh: refreshValue as () => void,
|
|
435
|
+
}),
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
if (status !== "resolved") {
|
|
439
|
+
throw createProjectionError("result.state.status");
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const contributionsValue = readProjectionProperty(
|
|
443
|
+
stateValue,
|
|
444
|
+
"contributions",
|
|
445
|
+
"result.state.contributions",
|
|
446
|
+
checkpoint,
|
|
447
|
+
);
|
|
448
|
+
const emptyReason = readProjectionProperty(
|
|
449
|
+
stateValue,
|
|
450
|
+
"emptyReason",
|
|
451
|
+
"result.state.emptyReason",
|
|
452
|
+
checkpoint,
|
|
453
|
+
);
|
|
454
|
+
if (!Array.isArray(contributionsValue)) {
|
|
455
|
+
throw createProjectionError("result.state.contributions");
|
|
456
|
+
}
|
|
457
|
+
const countValue = readProjectionProperty(
|
|
458
|
+
contributionsValue,
|
|
459
|
+
"length",
|
|
460
|
+
"result.state.contributions.length",
|
|
461
|
+
checkpoint,
|
|
462
|
+
);
|
|
463
|
+
if (typeof countValue !== "number" || !Number.isSafeInteger(countValue) || countValue < 0) {
|
|
464
|
+
throw createProjectionError("result.state.contributions.length");
|
|
465
|
+
}
|
|
466
|
+
if (countValue === 0) {
|
|
467
|
+
if (emptyReason !== "noRegistrations" && emptyReason !== "excludedByPolicy") {
|
|
468
|
+
throw createProjectionError("result.state.emptyReason");
|
|
469
|
+
}
|
|
470
|
+
return Object.freeze({
|
|
471
|
+
kind: "empty",
|
|
472
|
+
renderState: Object.freeze({ context, point, reason: emptyReason }),
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
if (emptyReason !== null) {
|
|
476
|
+
throw createProjectionError("result.state.emptyReason");
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const memberships: OutletProjectedMembershipSeed<Point>[] = [];
|
|
480
|
+
const registrationIds = new Set<string>();
|
|
481
|
+
for (let index = 0; index < countValue; index += 1) {
|
|
482
|
+
const recordValue = readProjectionProperty(
|
|
483
|
+
contributionsValue,
|
|
484
|
+
index,
|
|
485
|
+
`result.state.contributions[${index}]`,
|
|
486
|
+
checkpoint,
|
|
487
|
+
);
|
|
488
|
+
const captured = captureMembershipSeedInput(
|
|
489
|
+
recordValue,
|
|
490
|
+
point,
|
|
491
|
+
context,
|
|
492
|
+
index,
|
|
493
|
+
countValue,
|
|
494
|
+
checkpoint,
|
|
495
|
+
);
|
|
496
|
+
if (registrationIds.has(captured.registrationId)) {
|
|
497
|
+
throw createProjectionError(`result.state.contributions[${index}].registrationId`);
|
|
498
|
+
}
|
|
499
|
+
registrationIds.add(captured.registrationId);
|
|
500
|
+
memberships.push(createMembershipSeed(
|
|
501
|
+
captured,
|
|
502
|
+
`result.state.contributions[${index}].implementation`,
|
|
503
|
+
checkpoint,
|
|
504
|
+
));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return Object.freeze({
|
|
508
|
+
kind: "memberships",
|
|
509
|
+
memberships: Object.freeze(memberships),
|
|
510
|
+
});
|
|
511
|
+
}
|