@securitydept/client 0.3.0-beta.1 → 0.3.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/create-i5VyYgN6.mjs +989 -0
- package/dist/create-i5VyYgN6.mjs.map +1 -0
- package/dist/handle-DwMgqe7R.mjs +638 -0
- package/dist/handle-DwMgqe7R.mjs.map +1 -0
- package/dist/index-Bo1BtjZH.d.mts +132 -0
- package/dist/index-CFAJ969E.d.mts +674 -0
- package/dist/index-CbeZ3AfB.d.mts +1680 -0
- package/dist/index.d.mts +3 -353
- package/dist/index.mjs +1196 -401
- package/dist/index.mjs.map +1 -1
- package/dist/router-at5bTdJQ.mjs +245 -0
- package/dist/router-at5bTdJQ.mjs.map +1 -0
- package/dist/rx/index.d.mts +68 -0
- package/dist/rx/index.mjs +2 -0
- package/dist/rx-DM6Ouv-m.mjs +1119 -0
- package/dist/rx-DM6Ouv-m.mjs.map +1 -0
- package/dist/server/index.d.mts +20 -0
- package/dist/server/index.mjs +29 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/test/index.d.mts +101 -0
- package/dist/test/index.mjs +270 -0
- package/dist/test/index.mjs.map +1 -0
- package/dist/uri-string-BPNbIMgv.mjs +300 -0
- package/dist/uri-string-BPNbIMgv.mjs.map +1 -0
- package/dist/web/index.d.mts +82 -337
- package/dist/web/index.mjs +334 -337
- package/dist/web/index.mjs.map +1 -1
- package/dist/webext/index.d.mts +73 -0
- package/dist/webext/index.mjs +231 -0
- package/dist/webext/index.mjs.map +1 -0
- package/package.json +28 -21
- package/dist/auth-coordination/index.d.mts +0 -198
- package/dist/auth-coordination/index.mjs +0 -107
- package/dist/auth-coordination/index.mjs.map +0 -1
- package/dist/events/index.d.mts +0 -2
- package/dist/events/index.mjs +0 -2
- package/dist/events-BUlfsRfl.mjs +0 -127
- package/dist/events-BUlfsRfl.mjs.map +0 -1
- package/dist/fetch-transport-Bd5RyWj2.d.mts +0 -339
- package/dist/fetch-transport-DtnSRWAH.mjs +0 -631
- package/dist/fetch-transport-DtnSRWAH.mjs.map +0 -1
- package/dist/index-C_wgl6dW.d.mts +0 -102
- package/dist/index-DbYSFHDe.d.mts +0 -39
- package/dist/persistence/index.d.mts +0 -3
- package/dist/persistence/index.mjs +0 -2
- package/dist/persistence/web/index.d.mts +0 -16
- package/dist/persistence/web/index.mjs +0 -51
- package/dist/persistence/web/index.mjs.map +0 -1
- package/dist/persistence-CSB8770k.mjs +0 -108
- package/dist/persistence-CSB8770k.mjs.map +0 -1
- package/dist/planner-host-CPyGNyQV.d.mts +0 -190
- package/dist/planner-host-CV38bMSn.mjs +0 -195
- package/dist/planner-host-CV38bMSn.mjs.map +0 -1
- package/dist/types-C59fcM5W.d.mts +0 -52
- package/dist/types-DQc088ZP.d.mts +0 -32
- package/dist/web-router/index.d.mts +0 -259
- package/dist/web-router/index.mjs +0 -395
- package/dist/web-router/index.mjs.map +0 -1
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
//#region src/auth-coordination/requirement-planner.ts
|
|
2
|
-
/** Outcome of resolving a single requirement. */
|
|
3
|
-
const ResolutionStatus = {
|
|
4
|
-
Fulfilled: "fulfilled",
|
|
5
|
-
Failed: "failed",
|
|
6
|
-
Skipped: "skipped"
|
|
7
|
-
};
|
|
8
|
-
/** Overall plan status. */
|
|
9
|
-
const PlanStatus = {
|
|
10
|
-
Pending: "pending",
|
|
11
|
-
Settled: "settled"
|
|
12
|
-
};
|
|
13
|
-
/** Error thrown when a planner operation is invalid. */
|
|
14
|
-
var RequirementPlannerError = class extends Error {
|
|
15
|
-
constructor(message) {
|
|
16
|
-
super(message);
|
|
17
|
-
this.name = "RequirementPlannerError";
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* Create a headless requirement planner.
|
|
22
|
-
*
|
|
23
|
-
* @see {@link RequirementPlanner} for usage.
|
|
24
|
-
*/
|
|
25
|
-
function createRequirementPlanner(options) {
|
|
26
|
-
if (options.requirements.length === 0) throw new RequirementPlannerError("Cannot create a planner with zero requirements");
|
|
27
|
-
const ids = /* @__PURE__ */ new Set();
|
|
28
|
-
for (const req of options.requirements) {
|
|
29
|
-
if (ids.has(req.id)) throw new RequirementPlannerError(`Duplicate requirement ID: "${req.id}"`);
|
|
30
|
-
ids.add(req.id);
|
|
31
|
-
}
|
|
32
|
-
let states = [];
|
|
33
|
-
/** Shallow-clone a requirement to prevent external mutation. */
|
|
34
|
-
function cloneRequirement(r) {
|
|
35
|
-
return {
|
|
36
|
-
...r,
|
|
37
|
-
attributes: r.attributes ? { ...r.attributes } : void 0
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
function initStates() {
|
|
41
|
-
states = options.requirements.map((r) => ({
|
|
42
|
-
requirement: cloneRequirement(r),
|
|
43
|
-
resolution: null
|
|
44
|
-
}));
|
|
45
|
-
}
|
|
46
|
-
initStates();
|
|
47
|
-
function findNextPending() {
|
|
48
|
-
return states.find((s) => s.resolution === null) ?? null;
|
|
49
|
-
}
|
|
50
|
-
return {
|
|
51
|
-
snapshot() {
|
|
52
|
-
const next = findNextPending();
|
|
53
|
-
const resolutions = [];
|
|
54
|
-
for (const s of states) if (s.resolution !== null) resolutions.push({ ...s.resolution });
|
|
55
|
-
return {
|
|
56
|
-
status: next === null ? PlanStatus.Settled : PlanStatus.Pending,
|
|
57
|
-
nextPending: next ? cloneRequirement(next.requirement) : null,
|
|
58
|
-
resolutions,
|
|
59
|
-
total: states.length,
|
|
60
|
-
resolved: resolutions.length
|
|
61
|
-
};
|
|
62
|
-
},
|
|
63
|
-
resolve(resolution) {
|
|
64
|
-
const next = findNextPending();
|
|
65
|
-
if (next === null) throw new RequirementPlannerError("Cannot resolve: all requirements are already settled");
|
|
66
|
-
if (resolution.requirementId !== next.requirement.id) throw new RequirementPlannerError(`Cannot resolve requirement "${resolution.requirementId}": the next pending requirement is "${next.requirement.id}"`);
|
|
67
|
-
next.resolution = resolution;
|
|
68
|
-
},
|
|
69
|
-
reset() {
|
|
70
|
-
initStates();
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
//#endregion
|
|
75
|
-
//#region src/auth-coordination/planner-host.ts
|
|
76
|
-
/**
|
|
77
|
-
* How a child scope's requirements client set relates to its parent.
|
|
78
|
-
*
|
|
79
|
-
* - `inherit` — use the parent's set as-is (child adds nothing)
|
|
80
|
-
* - `merge` — inherit parent's options, then append/override with child's
|
|
81
|
-
* (child options with the same `requirementId` replace parent ones)
|
|
82
|
-
* - `replace` — discard parent's set entirely, use child's set only
|
|
83
|
-
*/
|
|
84
|
-
const RequirementsClientSetComposition = {
|
|
85
|
-
Inherit: "inherit",
|
|
86
|
-
Merge: "merge",
|
|
87
|
-
Replace: "replace"
|
|
88
|
-
};
|
|
89
|
-
/**
|
|
90
|
-
* Resolve the effective requirements client set by composing a parent set
|
|
91
|
-
* with a child scope's declaration.
|
|
92
|
-
*
|
|
93
|
-
* @param parentOptions - The parent scope's resolved options (or empty).
|
|
94
|
-
* @param child - The child scope's scoped set (with composition strategy).
|
|
95
|
-
* @returns The effective options after composition.
|
|
96
|
-
*/
|
|
97
|
-
function resolveEffectiveClientSet(parentOptions, child) {
|
|
98
|
-
switch (child.composition) {
|
|
99
|
-
case RequirementsClientSetComposition.Inherit: return [...parentOptions];
|
|
100
|
-
case RequirementsClientSetComposition.Merge: {
|
|
101
|
-
const merged = /* @__PURE__ */ new Map();
|
|
102
|
-
for (const opt of parentOptions) merged.set(opt.requirementId, opt);
|
|
103
|
-
for (const opt of child.options) merged.set(opt.requirementId, opt);
|
|
104
|
-
return [...merged.values()];
|
|
105
|
-
}
|
|
106
|
-
case RequirementsClientSetComposition.Replace: return [...child.options];
|
|
107
|
-
default: {
|
|
108
|
-
const _exhaustive = child.composition;
|
|
109
|
-
throw new Error(`[resolveEffectiveClientSet] Unknown composition: ${_exhaustive}`);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Default candidate selector: picks the first unauthenticated candidate
|
|
115
|
-
* in declaration order (which aligns with RequirementPlanner's nextPending).
|
|
116
|
-
*/
|
|
117
|
-
function defaultSelectCandidate(unauthenticated) {
|
|
118
|
-
return unauthenticated[0];
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Create a planner-host instance backed by {@link createRequirementPlanner}.
|
|
122
|
-
*
|
|
123
|
-
* **How it works:**
|
|
124
|
-
*
|
|
125
|
-
* 1. `evaluate()` builds an `AuthRequirement[]` from `candidates`
|
|
126
|
-
* (using each candidate's `requirementId`, `requirementKind`, `label`,
|
|
127
|
-
* and `attributes`).
|
|
128
|
-
* 2. A `RequirementPlanner` is created for this evaluation cycle.
|
|
129
|
-
* 3. Already-authenticated requirements are auto-resolved as `Fulfilled`,
|
|
130
|
-
* respecting the planner's sequential discipline.
|
|
131
|
-
* 4. The planner's `nextPending` determines the first unresolved requirement
|
|
132
|
-
* in declaration order.
|
|
133
|
-
* 5. `selectCandidate` is called with all unauthenticated candidates so
|
|
134
|
-
* adopters can implement custom chooser strategies (e.g. UI dialog).
|
|
135
|
-
* The default strategy picks the first candidate (matching nextPending).
|
|
136
|
-
*
|
|
137
|
-
* `evaluate()` always returns a `Promise` to accommodate async selectors.
|
|
138
|
-
* For synchronous selectors the Promise resolves in the same microtask tick.
|
|
139
|
-
*
|
|
140
|
-
* @example
|
|
141
|
-
* ```ts
|
|
142
|
-
* import { createPlannerHost } from "@securitydept/client/auth-coordination";
|
|
143
|
-
*
|
|
144
|
-
* // Default sequential strategy (sync selector, wrapped in Promise)
|
|
145
|
-
* const host = createPlannerHost();
|
|
146
|
-
* const result = await host.evaluate(candidates);
|
|
147
|
-
*
|
|
148
|
-
* // Async chooser strategy — show a dialog and await user's pick
|
|
149
|
-
* const hostWithChooser = createPlannerHost({
|
|
150
|
-
* selectCandidate: async (candidates) => {
|
|
151
|
-
* return await showChooserDialog(candidates);
|
|
152
|
-
* },
|
|
153
|
-
* });
|
|
154
|
-
* ```
|
|
155
|
-
*/
|
|
156
|
-
function createPlannerHost(options) {
|
|
157
|
-
const selector = options?.selectCandidate ?? defaultSelectCandidate;
|
|
158
|
-
return { async evaluate(candidates) {
|
|
159
|
-
if (candidates.length === 0) return {
|
|
160
|
-
allAuthenticated: true,
|
|
161
|
-
unauthenticatedCandidates: [],
|
|
162
|
-
pendingCandidate: null
|
|
163
|
-
};
|
|
164
|
-
const planner = createRequirementPlanner({ requirements: candidates.map((c) => ({
|
|
165
|
-
id: c.requirementId,
|
|
166
|
-
kind: c.requirementKind,
|
|
167
|
-
label: c.label,
|
|
168
|
-
attributes: c.attributes
|
|
169
|
-
})) });
|
|
170
|
-
for (const candidate of candidates) {
|
|
171
|
-
const snap = planner.snapshot();
|
|
172
|
-
if (snap.status === PlanStatus.Settled) break;
|
|
173
|
-
if (snap.nextPending?.id === candidate.requirementId && candidate.checkAuthenticated()) planner.resolve({
|
|
174
|
-
requirementId: candidate.requirementId,
|
|
175
|
-
status: ResolutionStatus.Fulfilled
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
const finalSnap = planner.snapshot();
|
|
179
|
-
const unauthenticated = candidates.filter((c) => !c.checkAuthenticated());
|
|
180
|
-
if (finalSnap.status === PlanStatus.Settled || unauthenticated.length === 0) return {
|
|
181
|
-
allAuthenticated: true,
|
|
182
|
-
unauthenticatedCandidates: [],
|
|
183
|
-
pendingCandidate: null
|
|
184
|
-
};
|
|
185
|
-
return {
|
|
186
|
-
allAuthenticated: false,
|
|
187
|
-
unauthenticatedCandidates: unauthenticated,
|
|
188
|
-
pendingCandidate: await selector(unauthenticated)
|
|
189
|
-
};
|
|
190
|
-
} };
|
|
191
|
-
}
|
|
192
|
-
//#endregion
|
|
193
|
-
export { RequirementPlannerError as a, PlanStatus as i, createPlannerHost as n, ResolutionStatus as o, resolveEffectiveClientSet as r, createRequirementPlanner as s, RequirementsClientSetComposition as t };
|
|
194
|
-
|
|
195
|
-
//# sourceMappingURL=planner-host-CV38bMSn.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"planner-host-CV38bMSn.mjs","names":[],"sources":["../src/auth-coordination/requirement-planner.ts","../src/auth-coordination/planner-host.ts"],"sourcesContent":["// Shared auth requirement planner — headless multi-requirement coordination\n//\n// Canonical location: @securitydept/client/auth-coordination\n//\n// Provides a mode-agnostic, framework-agnostic primitive for expressing\n// multi-requirement / multi-auth-context orchestration flows.\n//\n// Core concept:\n// 1. Define a list of AuthRequirements (e.g. \"need session\", \"need oidc token\")\n// 2. The RequirementPlanner tells you which requirement is pending next\n// 3. When a requirement is resolved, it advances to the next one\n// 4. When all are resolved, the plan is settled\n//\n// The planner is intentionally headless — it does NOT:\n// - Know about specific auth modes or protocols\n// - Render UI or manage popup/redirect flows\n// - Own cross-tab state or leader election\n//\n// Owner rationale: This module does not depend on token-set domain objects\n// (token snapshots, bearer propagation, OIDC callback, refresh). Its\n// RequirementKind vocabulary covers session, OIDC, and custom — clearly\n// cross-auth-context. It is a shared coordination capability, not a\n// token-set product surface. Canonical owner is @securitydept/client.\n\n// ---------------------------------------------------------------------------\n// Requirement definition\n// ---------------------------------------------------------------------------\n\n/** A single auth requirement within an orchestration plan. */\nexport interface AuthRequirement {\n\t/** Unique identifier for this requirement within the plan. */\n\tid: string;\n\t/**\n\t * The kind of auth requirement.\n\t *\n\t * An opaque string. Each auth-context family (token-set, session, basic-auth)\n\t * or adopter project may define its own named constants.\n\t *\n\t * @example \"session\", \"frontend_oidc\", \"backend_oidc\", \"custom\"\n\t */\n\tkind: string;\n\t/** Human-readable label (for logging / debugging). */\n\tlabel?: string;\n\t/** Arbitrary metadata the adopter can attach. */\n\tattributes?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\n/** Outcome of resolving a single requirement. */\nexport const ResolutionStatus = {\n\tFulfilled: \"fulfilled\",\n\tFailed: \"failed\",\n\tSkipped: \"skipped\",\n} as const;\n\nexport type ResolutionStatus =\n\t(typeof ResolutionStatus)[keyof typeof ResolutionStatus];\n\n/** The resolution result for a single requirement. */\nexport interface RequirementResolution {\n\t/** The requirement ID this resolution applies to. */\n\trequirementId: string;\n\t/** Outcome status. */\n\tstatus: ResolutionStatus;\n\t/** Optional error or reason for failure/skip. */\n\treason?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Plan state\n// ---------------------------------------------------------------------------\n\n/** Overall plan status. */\nexport const PlanStatus = {\n\t/** There are still pending requirements. */\n\tPending: \"pending\",\n\t/** All requirements have been resolved. */\n\tSettled: \"settled\",\n} as const;\n\nexport type PlanStatus = (typeof PlanStatus)[keyof typeof PlanStatus];\n\n/** Internal state for a single requirement within the plan. */\ninterface RequirementState {\n\trequirement: AuthRequirement;\n\tresolution: RequirementResolution | null;\n}\n\n/** Read-only snapshot of the current plan state. */\nexport interface PlanSnapshot {\n\t/** Overall plan status. */\n\tstatus: PlanStatus;\n\t/** The next pending requirement, or `null` if settled. */\n\tnextPending: AuthRequirement | null;\n\t/** All resolutions collected so far (in order). */\n\tresolutions: readonly RequirementResolution[];\n\t/** Total requirement count. */\n\ttotal: number;\n\t/** Number of resolved requirements. */\n\tresolved: number;\n}\n\n// ---------------------------------------------------------------------------\n// Planner errors\n// ---------------------------------------------------------------------------\n\n/** Error thrown when a planner operation is invalid. */\nexport class RequirementPlannerError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"RequirementPlannerError\";\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// RequirementPlanner\n// ---------------------------------------------------------------------------\n\n/** Options for creating a {@link RequirementPlanner}. */\nexport interface CreateRequirementPlannerOptions {\n\t/** Ordered list of requirements to fulfill. */\n\trequirements: readonly AuthRequirement[];\n}\n\n/**\n * Headless multi-requirement planner.\n *\n * Drives a sequential requirement fulfillment flow:\n * 1. Call {@link snapshot} to see what's pending.\n * 2. Resolve the {@link PlanSnapshot.nextPending} requirement.\n * 3. Call {@link resolve} with the result.\n * 4. Repeat until {@link PlanSnapshot.status} is `\"settled\"`.\n *\n * @example\n * ```ts\n * import { createRequirementPlanner, PlanStatus, ResolutionStatus } from \"@securitydept/client/auth-coordination\";\n *\n * const planner = createRequirementPlanner({\n * requirements: [\n * { id: \"session\", kind: \"session\" },\n * { id: \"api-token\", kind: \"backend_oidc\" },\n * ],\n * });\n *\n * let snap = planner.snapshot();\n * while (snap.status === PlanStatus.Pending) {\n * const req = snap.nextPending!;\n * // ... perform auth action for req.kind ...\n * planner.resolve({ requirementId: req.id, status: ResolutionStatus.Fulfilled });\n * snap = planner.snapshot();\n * }\n * // snap.status === PlanStatus.Settled\n * ```\n */\nexport interface RequirementPlanner {\n\t/** Get a read-only snapshot of the current plan state. */\n\tsnapshot(): PlanSnapshot;\n\t/** Resolve the next pending requirement. */\n\tresolve(resolution: RequirementResolution): void;\n\t/** Reset the planner to its initial state. */\n\treset(): void;\n}\n\n/**\n * Create a headless requirement planner.\n *\n * @see {@link RequirementPlanner} for usage.\n */\nexport function createRequirementPlanner(\n\toptions: CreateRequirementPlannerOptions,\n): RequirementPlanner {\n\tif (options.requirements.length === 0) {\n\t\tthrow new RequirementPlannerError(\n\t\t\t\"Cannot create a planner with zero requirements\",\n\t\t);\n\t}\n\n\t// Validate uniqueness of requirement IDs.\n\tconst ids = new Set<string>();\n\tfor (const req of options.requirements) {\n\t\tif (ids.has(req.id)) {\n\t\t\tthrow new RequirementPlannerError(\n\t\t\t\t`Duplicate requirement ID: \"${req.id}\"`,\n\t\t\t);\n\t\t}\n\t\tids.add(req.id);\n\t}\n\n\tlet states: RequirementState[] = [];\n\n\t/** Shallow-clone a requirement to prevent external mutation. */\n\tfunction cloneRequirement(r: AuthRequirement): AuthRequirement {\n\t\treturn {\n\t\t\t...r,\n\t\t\tattributes: r.attributes ? { ...r.attributes } : undefined,\n\t\t};\n\t}\n\n\tfunction initStates(): void {\n\t\tstates = options.requirements.map((r) => ({\n\t\t\trequirement: cloneRequirement(r),\n\t\t\tresolution: null,\n\t\t}));\n\t}\n\n\tinitStates();\n\n\tfunction findNextPending(): RequirementState | null {\n\t\treturn states.find((s) => s.resolution === null) ?? null;\n\t}\n\n\treturn {\n\t\tsnapshot(): PlanSnapshot {\n\t\t\tconst next = findNextPending();\n\t\t\tconst resolutions: RequirementResolution[] = [];\n\t\t\tfor (const s of states) {\n\t\t\t\tif (s.resolution !== null) resolutions.push({ ...s.resolution });\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tstatus: next === null ? PlanStatus.Settled : PlanStatus.Pending,\n\t\t\t\tnextPending: next ? cloneRequirement(next.requirement) : null,\n\t\t\t\tresolutions,\n\t\t\t\ttotal: states.length,\n\t\t\t\tresolved: resolutions.length,\n\t\t\t};\n\t\t},\n\n\t\tresolve(resolution: RequirementResolution): void {\n\t\t\tconst next = findNextPending();\n\t\t\tif (next === null) {\n\t\t\t\tthrow new RequirementPlannerError(\n\t\t\t\t\t\"Cannot resolve: all requirements are already settled\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (resolution.requirementId !== next.requirement.id) {\n\t\t\t\tthrow new RequirementPlannerError(\n\t\t\t\t\t`Cannot resolve requirement \"${resolution.requirementId}\": ` +\n\t\t\t\t\t\t`the next pending requirement is \"${next.requirement.id}\"`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tnext.resolution = resolution;\n\t\t},\n\n\t\treset(): void {\n\t\t\tinitStates();\n\t\t},\n\t};\n}\n","// Shared planner-host — route-scoped auth coordination host layer\n//\n// Canonical location: @securitydept/client/auth-coordination\n//\n// Provides the host-layer contract for multi-requirement auth guards:\n// - AuthGuardClientOption: a single requirement candidate with its\n// authentication check and unauthenticated action\n// - RequirementsClientSet / ScopedRequirementsClientSet: composable\n// requirement client collections with inherit/merge/replace semantics\n// - PlannerHost: the coordination contract that selects which pending\n// candidate to act on, built on RequirementPlanner for sequential\n// resolution discipline\n//\n// The planner-host is intentionally headless and framework-agnostic.\n// Framework-specific provider/lookup (Angular DI, React Context) is handled\n// by the framework adapter packages (@securitydept/client-angular,\n// @securitydept/client-react).\n//\n// Architecture:\n// PlannerHost.evaluate() uses createRequirementPlanner() internally:\n// 1. Build AuthRequirement[] from candidates' requirementId/kind/label/attributes\n// 2. Auto-resolve already-authenticated requirements as Fulfilled\n// 3. Ask the planner for nextPending — respects declaration order and\n// skips already-satisfied requirements\n// 4. Among candidates still unauthenticated, use selectCandidate() to\n// pick the one to act on (default: the one matching nextPending first)\n//\n// Owner rationale: This module does not depend on token-set, session, or\n// basic-auth domain objects. It is a shared coordination capability.\n// Canonical owner is @securitydept/client.\n\nimport {\n\ttype AuthRequirement,\n\tcreateRequirementPlanner,\n\tPlanStatus,\n\tResolutionStatus,\n} from \"./requirement-planner\";\n\n// ---------------------------------------------------------------------------\n// Auth guard client option — a single requirement candidate\n// ---------------------------------------------------------------------------\n\n/**\n * A single requirement candidate that a guard submits to the planner-host.\n *\n * Each option describes:\n * 1. What auth requirement it represents (id, kind, label)\n * 2. How to check whether it's already satisfied\n * 3. What to do when the planner selects it as the next pending action\n */\nexport interface AuthGuardClientOption {\n\t/** Unique identifier for this requirement candidate. */\n\trequirementId: string;\n\n\t/**\n\t * The kind of auth requirement (opaque string).\n\t * @example \"session\", \"frontend_oidc\", \"backend_oidc\"\n\t */\n\trequirementKind: string;\n\n\t/** Human-readable label (for logging / debugging / chooser UI). */\n\tlabel?: string;\n\n\t/** Arbitrary metadata the adopter can attach. */\n\tattributes?: Record<string, unknown>;\n\n\t/**\n\t * Check whether this requirement candidate is already authenticated.\n\t * Called synchronously by the planner-host during evaluation.\n\t */\n\tcheckAuthenticated: () => boolean;\n\n\t/**\n\t * Action to execute when this candidate is selected as the pending\n\t * requirement by the planner-host.\n\t *\n\t * Return value semantics:\n\t * - `true` — allow navigation (requirement will be resolved externally)\n\t * - `false` — block navigation\n\t * - `string` — redirect to the given URL\n\t * - `Promise<...>` — async version of the above\n\t */\n\tonUnauthenticated: () => boolean | string | Promise<boolean | string>;\n}\n\n// ---------------------------------------------------------------------------\n// Requirements client set — composable requirement collections\n// ---------------------------------------------------------------------------\n\n/** A flat collection of requirement candidates. */\nexport interface RequirementsClientSet {\n\treadonly options: readonly AuthGuardClientOption[];\n}\n\n/**\n * How a child scope's requirements client set relates to its parent.\n *\n * - `inherit` — use the parent's set as-is (child adds nothing)\n * - `merge` — inherit parent's options, then append/override with child's\n * (child options with the same `requirementId` replace parent ones)\n * - `replace` — discard parent's set entirely, use child's set only\n */\nexport const RequirementsClientSetComposition = {\n\tInherit: \"inherit\",\n\tMerge: \"merge\",\n\tReplace: \"replace\",\n} as const;\n\nexport type RequirementsClientSetComposition =\n\t(typeof RequirementsClientSetComposition)[keyof typeof RequirementsClientSetComposition];\n\n/** A requirements client set annotated with its composition strategy. */\nexport interface ScopedRequirementsClientSet {\n\t/** How this set composes with its parent scope. */\n\tcomposition: RequirementsClientSetComposition;\n\t/** The requirement candidates declared at this scope. */\n\toptions: readonly AuthGuardClientOption[];\n}\n\n// ---------------------------------------------------------------------------\n// Resolve effective client set (parent + child composition)\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the effective requirements client set by composing a parent set\n * with a child scope's declaration.\n *\n * @param parentOptions - The parent scope's resolved options (or empty).\n * @param child - The child scope's scoped set (with composition strategy).\n * @returns The effective options after composition.\n */\nexport function resolveEffectiveClientSet(\n\tparentOptions: readonly AuthGuardClientOption[],\n\tchild: ScopedRequirementsClientSet,\n): AuthGuardClientOption[] {\n\tswitch (child.composition) {\n\t\tcase RequirementsClientSetComposition.Inherit:\n\t\t\treturn [...parentOptions];\n\n\t\tcase RequirementsClientSetComposition.Merge: {\n\t\t\t// Start with parent options, then override/append with child options.\n\t\t\t// Child options with the same requirementId replace parent ones.\n\t\t\tconst merged = new Map<string, AuthGuardClientOption>();\n\t\t\tfor (const opt of parentOptions) {\n\t\t\t\tmerged.set(opt.requirementId, opt);\n\t\t\t}\n\t\t\tfor (const opt of child.options) {\n\t\t\t\tmerged.set(opt.requirementId, opt);\n\t\t\t}\n\t\t\treturn [...merged.values()];\n\t\t}\n\n\t\tcase RequirementsClientSetComposition.Replace:\n\t\t\treturn [...child.options];\n\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = child.composition;\n\t\t\tthrow new Error(\n\t\t\t\t`[resolveEffectiveClientSet] Unknown composition: ${_exhaustive}`,\n\t\t\t);\n\t\t}\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Planner-host result\n// ---------------------------------------------------------------------------\n\n/** Result of a planner-host evaluation. */\nexport interface PlannerHostResult {\n\t/** Whether all candidates are authenticated (route can activate). */\n\tallAuthenticated: boolean;\n\n\t/** All candidates that are not yet authenticated. */\n\tunauthenticatedCandidates: readonly AuthGuardClientOption[];\n\n\t/**\n\t * The candidate selected by the planner as the next one to act on.\n\t * `null` when `allAuthenticated` is true.\n\t *\n\t * Determined by the RequirementPlanner's sequential resolution discipline:\n\t * the first unresolved requirement in declaration order that is still\n\t * unauthenticated, then optionally further narrowed by `selectCandidate`.\n\t */\n\tpendingCandidate: AuthGuardClientOption | null;\n}\n\n// ---------------------------------------------------------------------------\n// Planner-host contract and factory\n// ---------------------------------------------------------------------------\n\n/**\n * A candidate selection strategy.\n *\n * Receives the list of unauthenticated candidates (in RequirementPlanner\n * declaration order) and returns the one that should be acted on next.\n * The default strategy picks the first one (matching the planner's\n * `nextPending` requirement).\n *\n * Async selectors are supported: return a `Promise<AuthGuardClientOption>`\n * to implement interactive chooser UIs (e.g. show a dialog and await the\n * user's pick before navigation continues).\n *\n * @example\n * ```ts\n * // Async chooser: show a dialog and await user's selection\n * const selectCandidate: CandidateSelector = async (candidates) => {\n * const chosen = await showChooserDialog(candidates);\n * return chosen;\n * };\n * ```\n */\nexport type CandidateSelector = (\n\tunauthenticated: readonly AuthGuardClientOption[],\n) => AuthGuardClientOption | Promise<AuthGuardClientOption>;\n\n/** The planner-host coordination contract. */\nexport interface PlannerHost {\n\t/**\n\t * Evaluate a set of requirement candidates using RequirementPlanner's\n\t * sequential resolution discipline.\n\t *\n\t * Always returns a Promise so that custom async selectors (e.g. chooser\n\t * dialog) are fully supported. Callers should always `await` the result.\n\t *\n\t * @param candidates - The resolved candidates from the guard's client options.\n\t * @returns Promise resolving to the evaluation result.\n\t */\n\tevaluate(\n\t\tcandidates: readonly AuthGuardClientOption[],\n\t): Promise<PlannerHostResult>;\n}\n\n/** Options for {@link createPlannerHost}. */\nexport interface CreatePlannerHostOptions {\n\t/**\n\t * Custom candidate selection strategy.\n\t *\n\t * The default strategy picks the candidate that corresponds to the\n\t * RequirementPlanner's `nextPending` requirement (first unauthenticated\n\t * in declaration order).\n\t *\n\t * A custom strategy can further filter or reorder the unauthenticated\n\t * candidates, e.g. to implement a chooser UI that lets the user pick.\n\t *\n\t * @default first-unauthenticated-matching-planner-nextPending\n\t *\n\t * @example\n\t * ```ts\n\t * // Async chooser: present all options and await user's pick\n\t * createPlannerHost({\n\t * selectCandidate: async (candidates) => {\n\t * return await showChooserDialog(candidates);\n\t * },\n\t * })\n\t * ```\n\t */\n\tselectCandidate?: CandidateSelector;\n}\n\n/**\n * Default candidate selector: picks the first unauthenticated candidate\n * in declaration order (which aligns with RequirementPlanner's nextPending).\n */\nfunction defaultSelectCandidate(\n\tunauthenticated: readonly AuthGuardClientOption[],\n): AuthGuardClientOption {\n\treturn unauthenticated[0];\n}\n\n/**\n * Create a planner-host instance backed by {@link createRequirementPlanner}.\n *\n * **How it works:**\n *\n * 1. `evaluate()` builds an `AuthRequirement[]` from `candidates`\n * (using each candidate's `requirementId`, `requirementKind`, `label`,\n * and `attributes`).\n * 2. A `RequirementPlanner` is created for this evaluation cycle.\n * 3. Already-authenticated requirements are auto-resolved as `Fulfilled`,\n * respecting the planner's sequential discipline.\n * 4. The planner's `nextPending` determines the first unresolved requirement\n * in declaration order.\n * 5. `selectCandidate` is called with all unauthenticated candidates so\n * adopters can implement custom chooser strategies (e.g. UI dialog).\n * The default strategy picks the first candidate (matching nextPending).\n *\n * `evaluate()` always returns a `Promise` to accommodate async selectors.\n * For synchronous selectors the Promise resolves in the same microtask tick.\n *\n * @example\n * ```ts\n * import { createPlannerHost } from \"@securitydept/client/auth-coordination\";\n *\n * // Default sequential strategy (sync selector, wrapped in Promise)\n * const host = createPlannerHost();\n * const result = await host.evaluate(candidates);\n *\n * // Async chooser strategy — show a dialog and await user's pick\n * const hostWithChooser = createPlannerHost({\n * selectCandidate: async (candidates) => {\n * return await showChooserDialog(candidates);\n * },\n * });\n * ```\n */\nexport function createPlannerHost(\n\toptions?: CreatePlannerHostOptions,\n): PlannerHost {\n\tconst selector = options?.selectCandidate ?? defaultSelectCandidate;\n\n\treturn {\n\t\tasync evaluate(\n\t\t\tcandidates: readonly AuthGuardClientOption[],\n\t\t): Promise<PlannerHostResult> {\n\t\t\tif (candidates.length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tallAuthenticated: true,\n\t\t\t\t\tunauthenticatedCandidates: [],\n\t\t\t\t\tpendingCandidate: null,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Build AuthRequirement[] from candidates for the planner.\n\t\t\tconst requirements: AuthRequirement[] = candidates.map((c) => ({\n\t\t\t\tid: c.requirementId,\n\t\t\t\tkind: c.requirementKind,\n\t\t\t\tlabel: c.label,\n\t\t\t\tattributes: c.attributes,\n\t\t\t}));\n\n\t\t\t// Create a fresh planner for this evaluation cycle.\n\t\t\tconst planner = createRequirementPlanner({ requirements });\n\n\t\t\t// Auto-resolve already-authenticated requirements in declaration order.\n\t\t\t// The planner enforces sequential discipline: we must resolve in order.\n\t\t\tfor (const candidate of candidates) {\n\t\t\t\tconst snap = planner.snapshot();\n\t\t\t\tif (snap.status === PlanStatus.Settled) break;\n\n\t\t\t\t// Resolve the planner's current nextPending if it's authenticated.\n\t\t\t\t// Only advance the planner for requirements that are currently pending\n\t\t\t\t// in order — skip if the current nextPending doesn't match this candidate.\n\t\t\t\tif (\n\t\t\t\t\tsnap.nextPending?.id === candidate.requirementId &&\n\t\t\t\t\tcandidate.checkAuthenticated()\n\t\t\t\t) {\n\t\t\t\t\tplanner.resolve({\n\t\t\t\t\t\trequirementId: candidate.requirementId,\n\t\t\t\t\t\tstatus: ResolutionStatus.Fulfilled,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check overall status after resolving all authenticated requirements.\n\t\t\tconst finalSnap = planner.snapshot();\n\n\t\t\t// Collect all unauthenticated candidates.\n\t\t\tconst unauthenticated = candidates.filter((c) => !c.checkAuthenticated());\n\n\t\t\tif (\n\t\t\t\tfinalSnap.status === PlanStatus.Settled ||\n\t\t\t\tunauthenticated.length === 0\n\t\t\t) {\n\t\t\t\treturn {\n\t\t\t\t\tallAuthenticated: true,\n\t\t\t\t\tunauthenticatedCandidates: [],\n\t\t\t\t\tpendingCandidate: null,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Await selection — supports both sync and async selectors.\n\t\t\t// The selector receives the unauthenticated candidates in declaration\n\t\t\t// order so adopters can implement any picking strategy.\n\t\t\tconst pendingCandidate = await selector(unauthenticated);\n\t\t\treturn {\n\t\t\t\tallAuthenticated: false,\n\t\t\t\tunauthenticatedCandidates: unauthenticated,\n\t\t\t\tpendingCandidate,\n\t\t\t};\n\t\t},\n\t};\n}\n"],"mappings":";;AAoDA,MAAa,mBAAmB;CAC/B,WAAW;CACX,QAAQ;CACR,SAAS;CACT;;AAoBD,MAAa,aAAa;CAEzB,SAAS;CAET,SAAS;CACT;;AA6BD,IAAa,0BAAb,cAA6C,MAAM;CAClD,YAAY,SAAiB;AAC5B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;;;;;AA0Dd,SAAgB,yBACf,SACqB;AACrB,KAAI,QAAQ,aAAa,WAAW,EACnC,OAAM,IAAI,wBACT,iDACA;CAIF,MAAM,sBAAM,IAAI,KAAa;AAC7B,MAAK,MAAM,OAAO,QAAQ,cAAc;AACvC,MAAI,IAAI,IAAI,IAAI,GAAG,CAClB,OAAM,IAAI,wBACT,8BAA8B,IAAI,GAAG,GACrC;AAEF,MAAI,IAAI,IAAI,GAAG;;CAGhB,IAAI,SAA6B,EAAE;;CAGnC,SAAS,iBAAiB,GAAqC;AAC9D,SAAO;GACN,GAAG;GACH,YAAY,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,GAAG,KAAA;GACjD;;CAGF,SAAS,aAAmB;AAC3B,WAAS,QAAQ,aAAa,KAAK,OAAO;GACzC,aAAa,iBAAiB,EAAE;GAChC,YAAY;GACZ,EAAE;;AAGJ,aAAY;CAEZ,SAAS,kBAA2C;AACnD,SAAO,OAAO,MAAM,MAAM,EAAE,eAAe,KAAK,IAAI;;AAGrD,QAAO;EACN,WAAyB;GACxB,MAAM,OAAO,iBAAiB;GAC9B,MAAM,cAAuC,EAAE;AAC/C,QAAK,MAAM,KAAK,OACf,KAAI,EAAE,eAAe,KAAM,aAAY,KAAK,EAAE,GAAG,EAAE,YAAY,CAAC;AAEjE,UAAO;IACN,QAAQ,SAAS,OAAO,WAAW,UAAU,WAAW;IACxD,aAAa,OAAO,iBAAiB,KAAK,YAAY,GAAG;IACzD;IACA,OAAO,OAAO;IACd,UAAU,YAAY;IACtB;;EAGF,QAAQ,YAAyC;GAChD,MAAM,OAAO,iBAAiB;AAC9B,OAAI,SAAS,KACZ,OAAM,IAAI,wBACT,uDACA;AAEF,OAAI,WAAW,kBAAkB,KAAK,YAAY,GACjD,OAAM,IAAI,wBACT,+BAA+B,WAAW,cAAc,sCACnB,KAAK,YAAY,GAAG,GACzD;AAEF,QAAK,aAAa;;EAGnB,QAAc;AACb,eAAY;;EAEb;;;;;;;;;;;;ACnJF,MAAa,mCAAmC;CAC/C,SAAS;CACT,OAAO;CACP,SAAS;CACT;;;;;;;;;AAyBD,SAAgB,0BACf,eACA,OAC0B;AAC1B,SAAQ,MAAM,aAAd;EACC,KAAK,iCAAiC,QACrC,QAAO,CAAC,GAAG,cAAc;EAE1B,KAAK,iCAAiC,OAAO;GAG5C,MAAM,yBAAS,IAAI,KAAoC;AACvD,QAAK,MAAM,OAAO,cACjB,QAAO,IAAI,IAAI,eAAe,IAAI;AAEnC,QAAK,MAAM,OAAO,MAAM,QACvB,QAAO,IAAI,IAAI,eAAe,IAAI;AAEnC,UAAO,CAAC,GAAG,OAAO,QAAQ,CAAC;;EAG5B,KAAK,iCAAiC,QACrC,QAAO,CAAC,GAAG,MAAM,QAAQ;EAE1B,SAAS;GACR,MAAM,cAAqB,MAAM;AACjC,SAAM,IAAI,MACT,oDAAoD,cACpD;;;;;;;;AAyGJ,SAAS,uBACR,iBACwB;AACxB,QAAO,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCxB,SAAgB,kBACf,SACc;CACd,MAAM,WAAW,SAAS,mBAAmB;AAE7C,QAAO,EACN,MAAM,SACL,YAC6B;AAC7B,MAAI,WAAW,WAAW,EACzB,QAAO;GACN,kBAAkB;GAClB,2BAA2B,EAAE;GAC7B,kBAAkB;GAClB;EAYF,MAAM,UAAU,yBAAyB,EAAE,cARH,WAAW,KAAK,OAAO;GAC9D,IAAI,EAAE;GACN,MAAM,EAAE;GACR,OAAO,EAAE;GACT,YAAY,EAAE;GACd,EAAE,EAGsD,CAAC;AAI1D,OAAK,MAAM,aAAa,YAAY;GACnC,MAAM,OAAO,QAAQ,UAAU;AAC/B,OAAI,KAAK,WAAW,WAAW,QAAS;AAKxC,OACC,KAAK,aAAa,OAAO,UAAU,iBACnC,UAAU,oBAAoB,CAE9B,SAAQ,QAAQ;IACf,eAAe,UAAU;IACzB,QAAQ,iBAAiB;IACzB,CAAC;;EAKJ,MAAM,YAAY,QAAQ,UAAU;EAGpC,MAAM,kBAAkB,WAAW,QAAQ,MAAM,CAAC,EAAE,oBAAoB,CAAC;AAEzE,MACC,UAAU,WAAW,WAAW,WAChC,gBAAgB,WAAW,EAE3B,QAAO;GACN,kBAAkB;GAClB,2BAA2B,EAAE;GAC7B,kBAAkB;GAClB;AAOF,SAAO;GACN,kBAAkB;GAClB,2BAA2B;GAC3B,kBAJwB,MAAM,SAAS,gBAAgB;GAKvD;IAEF"}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
//#region src/persistence/types.d.ts
|
|
2
|
-
/** Low-level key-value record store. */
|
|
3
|
-
interface RecordStore {
|
|
4
|
-
get(key: string): Promise<string | null>;
|
|
5
|
-
set(key: string, value: string): Promise<void>;
|
|
6
|
-
/**
|
|
7
|
-
* Atomically read and remove a record within the store's consistency domain.
|
|
8
|
-
*/
|
|
9
|
-
take?(key: string): Promise<string | null>;
|
|
10
|
-
remove(key: string): Promise<void>;
|
|
11
|
-
}
|
|
12
|
-
/** Serialization codec for typed values. */
|
|
13
|
-
interface Codec<T> {
|
|
14
|
-
encode(value: T): string;
|
|
15
|
-
decode(raw: string): T;
|
|
16
|
-
}
|
|
17
|
-
/** Semantic store for long-lived auth state. */
|
|
18
|
-
interface PersistentAuthStore<T> {
|
|
19
|
-
load(): Promise<T | null>;
|
|
20
|
-
save(value: T): Promise<void>;
|
|
21
|
-
clear(): Promise<void>;
|
|
22
|
-
}
|
|
23
|
-
/** Semantic store for recoverable state (survives page reload). */
|
|
24
|
-
interface RecoverableStateStore<T> {
|
|
25
|
-
load(): Promise<T | null>;
|
|
26
|
-
save(value: T): Promise<void>;
|
|
27
|
-
clear(): Promise<void>;
|
|
28
|
-
}
|
|
29
|
-
/** Semantic store for ephemeral flow state (one-time-use). */
|
|
30
|
-
interface EphemeralFlowStore<T> {
|
|
31
|
-
load(): Promise<T | null>;
|
|
32
|
-
save(value: T): Promise<void>;
|
|
33
|
-
consume(): Promise<T | null>;
|
|
34
|
-
clear(): Promise<void>;
|
|
35
|
-
}
|
|
36
|
-
/** Semantic store for keyed ephemeral flow state (one-time-use per key). */
|
|
37
|
-
interface KeyedEphemeralFlowStore<T> {
|
|
38
|
-
load(key: string): Promise<T | null>;
|
|
39
|
-
save(key: string, value: T): Promise<void>;
|
|
40
|
-
take(key: string): Promise<T | null>;
|
|
41
|
-
clear(key: string): Promise<void>;
|
|
42
|
-
}
|
|
43
|
-
/** Versioned storage envelope — first-version migration support. */
|
|
44
|
-
interface StoredEnvelope<T> {
|
|
45
|
-
version: number;
|
|
46
|
-
storedAt: number;
|
|
47
|
-
expiresAt?: number;
|
|
48
|
-
value: T;
|
|
49
|
-
}
|
|
50
|
-
//#endregion
|
|
51
|
-
export { RecordStore as a, PersistentAuthStore as i, EphemeralFlowStore as n, RecoverableStateStore as o, KeyedEphemeralFlowStore as r, StoredEnvelope as s, Codec as t };
|
|
52
|
-
//# sourceMappingURL=types-C59fcM5W.d.mts.map
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
//#region src/signals/types.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Read-only signal interface.
|
|
4
|
-
* Semantics align with TC39 Signals proposal, but uses an SDK-owned thin protocol
|
|
5
|
-
* to avoid coupling to any specific polyfill or standard implementation.
|
|
6
|
-
*/
|
|
7
|
-
interface ReadableSignalTrait<T> {
|
|
8
|
-
/** Return the current snapshot value. */
|
|
9
|
-
get(): T;
|
|
10
|
-
/**
|
|
11
|
-
* Subscribe to value changes.
|
|
12
|
-
* The listener is called whenever the value changes (not on subscribe).
|
|
13
|
-
* @returns An unsubscribe function.
|
|
14
|
-
*/
|
|
15
|
-
subscribe(listener: () => void): () => void;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Writable signal interface.
|
|
19
|
-
* State transitions should be controlled by the client / service layer;
|
|
20
|
-
* external consumers typically only see the read-only projection.
|
|
21
|
-
*/
|
|
22
|
-
interface WritableSignalTrait<T> extends ReadableSignalTrait<T> {
|
|
23
|
-
set(value: T): void;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Computed (derived) signal — always read-only, value is derived from
|
|
27
|
-
* other signals automatically.
|
|
28
|
-
*/
|
|
29
|
-
interface ComputedSignalTrait<T> extends ReadableSignalTrait<T> {}
|
|
30
|
-
//#endregion
|
|
31
|
-
export { ReadableSignalTrait as n, WritableSignalTrait as r, ComputedSignalTrait as t };
|
|
32
|
-
//# sourceMappingURL=types-DQc088ZP.d.mts.map
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import { i as PlannerHost, s as RequirementsClientSetComposition, t as AuthGuardClientOption } from "../planner-host-CPyGNyQV.mjs";
|
|
2
|
-
|
|
3
|
-
//#region src/web-router/navigation-adapter.d.ts
|
|
4
|
-
declare const NavigationAdapterKind: {
|
|
5
|
-
readonly NavigationApi: "navigation-api";
|
|
6
|
-
readonly History: "history";
|
|
7
|
-
};
|
|
8
|
-
type NavigationAdapterKind = (typeof NavigationAdapterKind)[keyof typeof NavigationAdapterKind];
|
|
9
|
-
declare const NavigationCause: {
|
|
10
|
-
readonly Initial: "initial";
|
|
11
|
-
readonly Push: "push";
|
|
12
|
-
readonly Replace: "replace";
|
|
13
|
-
readonly Traverse: "traverse";
|
|
14
|
-
};
|
|
15
|
-
type NavigationCause = (typeof NavigationCause)[keyof typeof NavigationCause];
|
|
16
|
-
interface NavigationIntent {
|
|
17
|
-
readonly url: URL;
|
|
18
|
-
readonly cause: NavigationCause;
|
|
19
|
-
readonly replace: boolean;
|
|
20
|
-
readonly state: unknown;
|
|
21
|
-
/** Abort this navigation before it commits. */
|
|
22
|
-
preventDefault(): void;
|
|
23
|
-
/** Cancel the current intent and navigate to the given URL instead. */
|
|
24
|
-
redirect(url: string | URL, options?: {
|
|
25
|
-
replace?: boolean;
|
|
26
|
-
}): void;
|
|
27
|
-
}
|
|
28
|
-
interface NavigationCommit {
|
|
29
|
-
readonly url: URL;
|
|
30
|
-
}
|
|
31
|
-
interface NavigateOptions {
|
|
32
|
-
/** When true, replace the current history entry instead of pushing. */
|
|
33
|
-
replace?: boolean;
|
|
34
|
-
/** Serializable state to attach to the history entry. */
|
|
35
|
-
state?: unknown;
|
|
36
|
-
/**
|
|
37
|
-
* Arbitrary per-navigation metadata. Passed through to `NavigateEvent.info`
|
|
38
|
-
* when the Navigation API adapter is active; ignored by the History
|
|
39
|
-
* adapter (no wire support).
|
|
40
|
-
*/
|
|
41
|
-
info?: unknown;
|
|
42
|
-
}
|
|
43
|
-
interface NavigationAdapter {
|
|
44
|
-
readonly kind: NavigationAdapterKind;
|
|
45
|
-
currentUrl(): URL;
|
|
46
|
-
navigate(url: string | URL, options?: NavigateOptions): Promise<void>;
|
|
47
|
-
back(): void;
|
|
48
|
-
forward(): void;
|
|
49
|
-
/**
|
|
50
|
-
* Called *before* a navigation commits, so listeners can cancel or
|
|
51
|
-
* redirect. Both adapters forward synchronous and asynchronous handlers
|
|
52
|
-
* (adapters await the intent being fully processed before committing).
|
|
53
|
-
*/
|
|
54
|
-
onBeforeNavigate(listener: (intent: NavigationIntent) => void | Promise<void>): () => void;
|
|
55
|
-
/** Called *after* a navigation commits. */
|
|
56
|
-
onNavigate(listener: (commit: NavigationCommit) => void): () => void;
|
|
57
|
-
destroy(): void;
|
|
58
|
-
}
|
|
59
|
-
declare function createNavigationApiAdapter(): NavigationAdapter;
|
|
60
|
-
interface CreateHistoryAdapterOptions {
|
|
61
|
-
/**
|
|
62
|
-
* When true (default), the adapter captures clicks on in-document
|
|
63
|
-
* `<a href>` elements and routes them through `navigate()` so guards
|
|
64
|
-
* can intercept. Disable to handle anchor navigation manually.
|
|
65
|
-
*/
|
|
66
|
-
captureAnchorClicks?: boolean;
|
|
67
|
-
}
|
|
68
|
-
declare function createHistoryAdapter(options?: CreateHistoryAdapterOptions): NavigationAdapter;
|
|
69
|
-
interface CreateNavigationAdapterOptions {
|
|
70
|
-
/**
|
|
71
|
-
* Force a specific adapter kind. Useful for tests that need to exercise
|
|
72
|
-
* the fallback path in a Chromium environment that ships the Navigation
|
|
73
|
-
* API.
|
|
74
|
-
* @default "auto"
|
|
75
|
-
*/
|
|
76
|
-
prefer?: "auto" | NavigationAdapterKind;
|
|
77
|
-
/**
|
|
78
|
-
* Options forwarded to {@link createHistoryAdapter} when the history
|
|
79
|
-
* adapter is selected.
|
|
80
|
-
*/
|
|
81
|
-
history?: CreateHistoryAdapterOptions;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Pick the best available navigation backend. Prefers the Web Navigation
|
|
85
|
-
* API when `window.navigation` is defined; falls back to the History API
|
|
86
|
-
* otherwise.
|
|
87
|
-
*/
|
|
88
|
-
declare function createNavigationAdapter(options?: CreateNavigationAdapterOptions): NavigationAdapter;
|
|
89
|
-
/**
|
|
90
|
-
* Quick capability probe — true when the Web Navigation API is present.
|
|
91
|
-
*/
|
|
92
|
-
declare function isNavigationApiAvailable(): boolean;
|
|
93
|
-
//#endregion
|
|
94
|
-
//#region src/web-router/web-router.d.ts
|
|
95
|
-
interface WebRouteContext {
|
|
96
|
-
/** The URL being navigated to. */
|
|
97
|
-
url: URL;
|
|
98
|
-
/** Intent metadata. Null for programmatic `activate()` calls. */
|
|
99
|
-
intent: NavigationIntent | null;
|
|
100
|
-
}
|
|
101
|
-
type WebRouteMatcher = string | RegExp | ((url: URL) => Record<string, string> | null | undefined);
|
|
102
|
-
interface WebRouteDefinition<TData = unknown> {
|
|
103
|
-
/** Opaque identifier. */
|
|
104
|
-
id: string;
|
|
105
|
-
/**
|
|
106
|
-
* Matcher for this route segment. Required on leaves — parents that
|
|
107
|
-
* only carry metadata for children may omit `match`.
|
|
108
|
-
*
|
|
109
|
-
* A string is treated as a path-with-params pattern
|
|
110
|
-
* (`"/users/:id/profile"` → `{ id: "123" }` for `/users/123/profile`).
|
|
111
|
-
* A RegExp uses `exec(pathname)` and passes named/indexed groups
|
|
112
|
-
* through as params. A function returns the parsed params or null.
|
|
113
|
-
*/
|
|
114
|
-
match?: WebRouteMatcher;
|
|
115
|
-
/**
|
|
116
|
-
* Auth requirements declared at this route segment. The router walks
|
|
117
|
-
* the matched route chain from root to leaf and aggregates every
|
|
118
|
-
* segment's requirements via {@link extractFullRouteRequirements}
|
|
119
|
-
* before asking the planner-host to evaluate.
|
|
120
|
-
*/
|
|
121
|
-
requirements?: readonly AuthGuardClientOption[];
|
|
122
|
-
/**
|
|
123
|
-
* Composition strategy with the parent segment in the matched chain.
|
|
124
|
-
*
|
|
125
|
-
* - `merge` (default): parent requirements are kept, child requirements
|
|
126
|
-
* append/override by `requirementId`.
|
|
127
|
-
* - `inherit`: child declares no additional requirements; parent set is
|
|
128
|
-
* used as-is.
|
|
129
|
-
* - `replace`: parent requirements are discarded entirely; this child
|
|
130
|
-
* contributes the full effective set (use for "public" subtrees of an
|
|
131
|
-
* otherwise protected parent).
|
|
132
|
-
*/
|
|
133
|
-
composition?: RequirementsClientSetComposition;
|
|
134
|
-
/**
|
|
135
|
-
* Nested child routes. When present, `match` on the parent is optional
|
|
136
|
-
* and is not consulted for matching leaves — only the leaves' matchers
|
|
137
|
-
* participate in URL resolution. The chain from root to leaf is what
|
|
138
|
-
* drives requirement aggregation.
|
|
139
|
-
*/
|
|
140
|
-
children?: readonly WebRouteDefinition<TData>[];
|
|
141
|
-
/** Arbitrary data attached to matched routes. */
|
|
142
|
-
data?: TData;
|
|
143
|
-
}
|
|
144
|
-
interface WebRouteMatch<TData = unknown> {
|
|
145
|
-
/** The leaf route that matched. */
|
|
146
|
-
readonly route: WebRouteDefinition<TData>;
|
|
147
|
-
readonly url: URL;
|
|
148
|
-
readonly params: Record<string, string>;
|
|
149
|
-
/**
|
|
150
|
-
* The complete chain from root ancestor → leaf. A flat route without
|
|
151
|
-
* children produces a single-element chain.
|
|
152
|
-
*/
|
|
153
|
-
readonly chain: readonly WebRouteDefinition<TData>[];
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Sugar for declaring a route segment. Enforces a consistent shape so
|
|
157
|
-
* adopter code reads like the Angular / TanStack helpers without forcing
|
|
158
|
-
* any particular DI or decorator style.
|
|
159
|
-
*/
|
|
160
|
-
declare function defineWebRoute<TData = unknown>(definition: WebRouteDefinition<TData>): WebRouteDefinition<TData>;
|
|
161
|
-
interface FullRouteRequirementsOptions {
|
|
162
|
-
/**
|
|
163
|
-
* Default composition for segments that do not set their own
|
|
164
|
-
* `composition` field. Defaults to `merge` to match the Angular helper
|
|
165
|
-
* and the TanStack adapter.
|
|
166
|
-
*/
|
|
167
|
-
defaultComposition?: RequirementsClientSetComposition;
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Aggregate every segment's `requirements` across the matched chain into
|
|
171
|
-
* a single {@link AuthGuardClientOption} list, honouring each segment's
|
|
172
|
-
* composition strategy (`inherit` / `merge` / `replace`).
|
|
173
|
-
*
|
|
174
|
-
* This is the extraction counter-part to the nested `WebRouteDefinition`
|
|
175
|
-
* tree — the router calls it once per matched navigation and forwards the
|
|
176
|
-
* resulting flat list to the planner-host.
|
|
177
|
-
*/
|
|
178
|
-
declare function extractFullRouteRequirements<TData = unknown>(chain: readonly WebRouteDefinition<TData>[], options?: FullRouteRequirementsOptions): AuthGuardClientOption[];
|
|
179
|
-
interface CreateWebRouterOptions<TData = unknown> {
|
|
180
|
-
/**
|
|
181
|
-
* Initial routes. Nested `children` are supported: the router
|
|
182
|
-
* flattens the declared tree into matchable leaves and reconstructs
|
|
183
|
-
* the root→leaf chain on every match for full-route aggregation.
|
|
184
|
-
*
|
|
185
|
-
* More routes can be added via `router.addRoute(...)` at runtime.
|
|
186
|
-
*/
|
|
187
|
-
routes?: readonly WebRouteDefinition<TData>[];
|
|
188
|
-
/** Planner-host for evaluating aggregated route requirements. */
|
|
189
|
-
plannerHost?: PlannerHost;
|
|
190
|
-
/**
|
|
191
|
-
* Options for the underlying navigation adapter. When omitted the
|
|
192
|
-
* router auto-selects the Navigation API if available.
|
|
193
|
-
*/
|
|
194
|
-
navigationAdapter?: CreateNavigationAdapterOptions | NavigationAdapter;
|
|
195
|
-
/**
|
|
196
|
-
* Called after every committed navigation. Prefer `onNavigate` for
|
|
197
|
-
* reactivity; this option exists for convenience.
|
|
198
|
-
*/
|
|
199
|
-
onNavigate?: (commit: NavigationCommit) => void;
|
|
200
|
-
/**
|
|
201
|
-
* Default requirement composition strategy applied when a route
|
|
202
|
-
* segment does not declare its own `composition`. Defaults to `merge`
|
|
203
|
-
* (the Angular / TanStack default).
|
|
204
|
-
*/
|
|
205
|
-
defaultComposition?: RequirementsClientSetComposition;
|
|
206
|
-
}
|
|
207
|
-
interface WebRouter<TData = unknown> {
|
|
208
|
-
readonly adapter: NavigationAdapter;
|
|
209
|
-
/** Current URL (from the underlying adapter). */
|
|
210
|
-
currentUrl(): URL;
|
|
211
|
-
/** Current matched route (or null if no route matches). */
|
|
212
|
-
currentMatch(): WebRouteMatch<TData> | null;
|
|
213
|
-
/** Register an additional root-level route at runtime. */
|
|
214
|
-
addRoute(route: WebRouteDefinition<TData>): () => void;
|
|
215
|
-
/** All currently registered root-level routes. */
|
|
216
|
-
routes(): readonly WebRouteDefinition<TData>[];
|
|
217
|
-
/**
|
|
218
|
-
* Match a URL against the registered routes. Returns the first match in
|
|
219
|
-
* registration order, or `null` if none match. When a match is found,
|
|
220
|
-
* {@link WebRouteMatch.chain} contains the root→leaf route chain so
|
|
221
|
-
* adopters can feed it to {@link extractFullRouteRequirements}.
|
|
222
|
-
*/
|
|
223
|
-
match(url: URL): WebRouteMatch<TData> | null;
|
|
224
|
-
/**
|
|
225
|
-
* Compute the effective aggregated requirements for a given match,
|
|
226
|
-
* honouring every segment's composition strategy.
|
|
227
|
-
*/
|
|
228
|
-
extractRequirements(match: WebRouteMatch<TData>): AuthGuardClientOption[];
|
|
229
|
-
/** Programmatically navigate. */
|
|
230
|
-
navigate(url: string | URL, options?: {
|
|
231
|
-
replace?: boolean;
|
|
232
|
-
state?: unknown;
|
|
233
|
-
}): Promise<void>;
|
|
234
|
-
back(): void;
|
|
235
|
-
forward(): void;
|
|
236
|
-
/** Subscribe to committed navigations. */
|
|
237
|
-
onNavigate(listener: (commit: NavigationCommit) => void): () => void;
|
|
238
|
-
/** Tear down event listeners on the underlying adapter. */
|
|
239
|
-
destroy(): void;
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Create a {@link WebRouter} wired to the selected navigation backend and
|
|
243
|
-
* optionally to a planner-host for aggregated route requirements evaluation.
|
|
244
|
-
*
|
|
245
|
-
* The router listens for pre-commit navigation intents, matches the
|
|
246
|
-
* destination URL against the registered route tree, aggregates the full
|
|
247
|
-
* root→leaf chain's requirements (respecting each segment's composition
|
|
248
|
-
* strategy), and asks `plannerHost.evaluate()` to rule on the complete
|
|
249
|
-
* candidate set. Depending on the host's decision it either:
|
|
250
|
-
*
|
|
251
|
-
* - lets the navigation commit (all requirements authenticated);
|
|
252
|
-
* - redirects to the URL returned by `onUnauthenticated` (when a candidate
|
|
253
|
-
* picks a redirect); or
|
|
254
|
-
* - cancels the navigation (when `onUnauthenticated` returns `false`).
|
|
255
|
-
*/
|
|
256
|
-
declare function createWebRouter<TData = unknown>(options?: CreateWebRouterOptions<TData>): WebRouter<TData>;
|
|
257
|
-
//#endregion
|
|
258
|
-
export { type CreateHistoryAdapterOptions, type CreateNavigationAdapterOptions, type CreateWebRouterOptions, type FullRouteRequirementsOptions, type NavigateOptions, type NavigationAdapter, NavigationAdapterKind, type NavigationCause, type NavigationCommit, type NavigationIntent, RequirementsClientSetComposition, type WebRouteContext, type WebRouteDefinition, type WebRouteMatch, type WebRouteMatcher, type WebRouter, createHistoryAdapter, createNavigationAdapter, createNavigationApiAdapter, createWebRouter, defineWebRoute, extractFullRouteRequirements, isNavigationApiAvailable };
|
|
259
|
-
//# sourceMappingURL=index.d.mts.map
|