@securitydept/client 0.3.0-beta.3 → 0.3.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +6 -0
  2. package/dist/create-Bm-XRA3G.mjs +1573 -0
  3. package/dist/create-Bm-XRA3G.mjs.map +1 -0
  4. package/dist/index-BT-u3qNI.d.mts +133 -0
  5. package/dist/index-Ch8lrE44.d.mts +625 -0
  6. package/dist/index-D5mW18cM.d.mts +1648 -0
  7. package/dist/index.d.mts +3 -353
  8. package/dist/index.mjs +1124 -362
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/router-BJ8LqNJ1.mjs +245 -0
  11. package/dist/router-BJ8LqNJ1.mjs.map +1 -0
  12. package/dist/rx/index.d.mts +69 -0
  13. package/dist/rx/index.mjs +2 -0
  14. package/dist/rx-BoB32Dzg.mjs +1106 -0
  15. package/dist/rx-BoB32Dzg.mjs.map +1 -0
  16. package/dist/server/index.d.mts +21 -0
  17. package/dist/server/index.mjs +29 -0
  18. package/dist/server/index.mjs.map +1 -0
  19. package/dist/test/index.d.mts +102 -0
  20. package/dist/test/index.mjs +270 -0
  21. package/dist/test/index.mjs.map +1 -0
  22. package/dist/uri-string-BfnIT4gJ.mjs +298 -0
  23. package/dist/uri-string-BfnIT4gJ.mjs.map +1 -0
  24. package/dist/web/index.d.mts +82 -364
  25. package/dist/web/index.mjs +329 -428
  26. package/dist/web/index.mjs.map +1 -1
  27. package/dist/webext/index.d.mts +74 -0
  28. package/dist/webext/index.mjs +229 -0
  29. package/dist/webext/index.mjs.map +1 -0
  30. package/package.json +28 -21
  31. package/dist/auth-coordination/index.d.mts +0 -198
  32. package/dist/auth-coordination/index.mjs +0 -107
  33. package/dist/auth-coordination/index.mjs.map +0 -1
  34. package/dist/client-environment-CaumNmir.mjs +0 -812
  35. package/dist/client-environment-CaumNmir.mjs.map +0 -1
  36. package/dist/client-environment-CdHtVxKW.d.mts +0 -401
  37. package/dist/events/index.d.mts +0 -2
  38. package/dist/events/index.mjs +0 -2
  39. package/dist/events-BUlfsRfl.mjs +0 -127
  40. package/dist/events-BUlfsRfl.mjs.map +0 -1
  41. package/dist/index-C4xlKhG7.d.mts +0 -102
  42. package/dist/index-COUt5x9Z.d.mts +0 -39
  43. package/dist/persistence/index.d.mts +0 -3
  44. package/dist/persistence/index.mjs +0 -2
  45. package/dist/persistence/web/index.d.mts +0 -16
  46. package/dist/persistence/web/index.mjs +0 -2
  47. package/dist/persistence-CSB8770k.mjs +0 -108
  48. package/dist/persistence-CSB8770k.mjs.map +0 -1
  49. package/dist/planner-host-BTWie0lo.mjs +0 -197
  50. package/dist/planner-host-BTWie0lo.mjs.map +0 -1
  51. package/dist/planner-host-DN9dRsQx.d.mts +0 -190
  52. package/dist/types-BJqUdH2e.d.mts +0 -32
  53. package/dist/types-BwcnUGNl.d.mts +0 -52
  54. package/dist/web-DQmMqZnR.mjs +0 -51
  55. package/dist/web-DQmMqZnR.mjs.map +0 -1
  56. package/dist/web-router/index.d.mts +0 -259
  57. package/dist/web-router/index.mjs +0 -395
  58. package/dist/web-router/index.mjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"planner-host-BTWie0lo.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":"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"}
@@ -1,190 +0,0 @@
1
- //#region src/auth-coordination/planner-host.d.ts
2
- /**
3
- * A single requirement candidate that a guard submits to the planner-host.
4
- *
5
- * Each option describes:
6
- * 1. What auth requirement it represents (id, kind, label)
7
- * 2. How to check whether it's already satisfied
8
- * 3. What to do when the planner selects it as the next pending action
9
- */
10
- interface AuthGuardClientOption {
11
- /** Unique identifier for this requirement candidate. */
12
- requirementId: string;
13
- /**
14
- * The kind of auth requirement (opaque string).
15
- * @example "session", "frontend_oidc", "backend_oidc"
16
- */
17
- requirementKind: string;
18
- /** Human-readable label (for logging / debugging / chooser UI). */
19
- label?: string;
20
- /** Arbitrary metadata the adopter can attach. */
21
- attributes?: Record<string, unknown>;
22
- /**
23
- * Check whether this requirement candidate is already authenticated.
24
- * Called synchronously by the planner-host during evaluation.
25
- */
26
- checkAuthenticated: () => boolean;
27
- /**
28
- * Action to execute when this candidate is selected as the pending
29
- * requirement by the planner-host.
30
- *
31
- * Return value semantics:
32
- * - `true` — allow navigation (requirement will be resolved externally)
33
- * - `false` — block navigation
34
- * - `string` — redirect to the given URL
35
- * - `Promise<...>` — async version of the above
36
- */
37
- onUnauthenticated: () => boolean | string | Promise<boolean | string>;
38
- }
39
- /** A flat collection of requirement candidates. */
40
- interface RequirementsClientSet {
41
- readonly options: readonly AuthGuardClientOption[];
42
- }
43
- /**
44
- * How a child scope's requirements client set relates to its parent.
45
- *
46
- * - `inherit` — use the parent's set as-is (child adds nothing)
47
- * - `merge` — inherit parent's options, then append/override with child's
48
- * (child options with the same `requirementId` replace parent ones)
49
- * - `replace` — discard parent's set entirely, use child's set only
50
- */
51
- declare const RequirementsClientSetComposition: {
52
- readonly Inherit: "inherit";
53
- readonly Merge: "merge";
54
- readonly Replace: "replace";
55
- };
56
- type RequirementsClientSetComposition = (typeof RequirementsClientSetComposition)[keyof typeof RequirementsClientSetComposition];
57
- /** A requirements client set annotated with its composition strategy. */
58
- interface ScopedRequirementsClientSet {
59
- /** How this set composes with its parent scope. */
60
- composition: RequirementsClientSetComposition;
61
- /** The requirement candidates declared at this scope. */
62
- options: readonly AuthGuardClientOption[];
63
- }
64
- /**
65
- * Resolve the effective requirements client set by composing a parent set
66
- * with a child scope's declaration.
67
- *
68
- * @param parentOptions - The parent scope's resolved options (or empty).
69
- * @param child - The child scope's scoped set (with composition strategy).
70
- * @returns The effective options after composition.
71
- */
72
- declare function resolveEffectiveClientSet(parentOptions: readonly AuthGuardClientOption[], child: ScopedRequirementsClientSet): AuthGuardClientOption[];
73
- /** Result of a planner-host evaluation. */
74
- interface PlannerHostResult {
75
- /** Whether all candidates are authenticated (route can activate). */
76
- allAuthenticated: boolean;
77
- /** All candidates that are not yet authenticated. */
78
- unauthenticatedCandidates: readonly AuthGuardClientOption[];
79
- /**
80
- * The candidate selected by the planner as the next one to act on.
81
- * `null` when `allAuthenticated` is true.
82
- *
83
- * Determined by the RequirementPlanner's sequential resolution discipline:
84
- * the first unresolved requirement in declaration order that is still
85
- * unauthenticated, then optionally further narrowed by `selectCandidate`.
86
- */
87
- pendingCandidate: AuthGuardClientOption | null;
88
- }
89
- /**
90
- * A candidate selection strategy.
91
- *
92
- * Receives the list of unauthenticated candidates (in RequirementPlanner
93
- * declaration order) and returns the one that should be acted on next.
94
- * The default strategy picks the first one (matching the planner's
95
- * `nextPending` requirement).
96
- *
97
- * Async selectors are supported: return a `Promise<AuthGuardClientOption>`
98
- * to implement interactive chooser UIs (e.g. show a dialog and await the
99
- * user's pick before navigation continues).
100
- *
101
- * @example
102
- * ```ts
103
- * // Async chooser: show a dialog and await user's selection
104
- * const selectCandidate: CandidateSelector = async (candidates) => {
105
- * const chosen = await showChooserDialog(candidates);
106
- * return chosen;
107
- * };
108
- * ```
109
- */
110
- type CandidateSelector = (unauthenticated: readonly AuthGuardClientOption[]) => AuthGuardClientOption | Promise<AuthGuardClientOption>;
111
- /** The planner-host coordination contract. */
112
- interface PlannerHost {
113
- /**
114
- * Evaluate a set of requirement candidates using RequirementPlanner's
115
- * sequential resolution discipline.
116
- *
117
- * Always returns a Promise so that custom async selectors (e.g. chooser
118
- * dialog) are fully supported. Callers should always `await` the result.
119
- *
120
- * @param candidates - The resolved candidates from the guard's client options.
121
- * @returns Promise resolving to the evaluation result.
122
- */
123
- evaluate(candidates: readonly AuthGuardClientOption[]): Promise<PlannerHostResult>;
124
- }
125
- /** Options for {@link createPlannerHost}. */
126
- interface CreatePlannerHostOptions {
127
- /**
128
- * Custom candidate selection strategy.
129
- *
130
- * The default strategy picks the candidate that corresponds to the
131
- * RequirementPlanner's `nextPending` requirement (first unauthenticated
132
- * in declaration order).
133
- *
134
- * A custom strategy can further filter or reorder the unauthenticated
135
- * candidates, e.g. to implement a chooser UI that lets the user pick.
136
- *
137
- * @default first-unauthenticated-matching-planner-nextPending
138
- *
139
- * @example
140
- * ```ts
141
- * // Async chooser: present all options and await user's pick
142
- * createPlannerHost({
143
- * selectCandidate: async (candidates) => {
144
- * return await showChooserDialog(candidates);
145
- * },
146
- * })
147
- * ```
148
- */
149
- selectCandidate?: CandidateSelector;
150
- }
151
- /**
152
- * Create a planner-host instance backed by {@link createRequirementPlanner}.
153
- *
154
- * **How it works:**
155
- *
156
- * 1. `evaluate()` builds an `AuthRequirement[]` from `candidates`
157
- * (using each candidate's `requirementId`, `requirementKind`, `label`,
158
- * and `attributes`).
159
- * 2. A `RequirementPlanner` is created for this evaluation cycle.
160
- * 3. Already-authenticated requirements are auto-resolved as `Fulfilled`,
161
- * respecting the planner's sequential discipline.
162
- * 4. The planner's `nextPending` determines the first unresolved requirement
163
- * in declaration order.
164
- * 5. `selectCandidate` is called with all unauthenticated candidates so
165
- * adopters can implement custom chooser strategies (e.g. UI dialog).
166
- * The default strategy picks the first candidate (matching nextPending).
167
- *
168
- * `evaluate()` always returns a `Promise` to accommodate async selectors.
169
- * For synchronous selectors the Promise resolves in the same microtask tick.
170
- *
171
- * @example
172
- * ```ts
173
- * import { createPlannerHost } from "@securitydept/client/auth-coordination";
174
- *
175
- * // Default sequential strategy (sync selector, wrapped in Promise)
176
- * const host = createPlannerHost();
177
- * const result = await host.evaluate(candidates);
178
- *
179
- * // Async chooser strategy — show a dialog and await user's pick
180
- * const hostWithChooser = createPlannerHost({
181
- * selectCandidate: async (candidates) => {
182
- * return await showChooserDialog(candidates);
183
- * },
184
- * });
185
- * ```
186
- */
187
- declare function createPlannerHost(options?: CreatePlannerHostOptions): PlannerHost;
188
- //#endregion
189
- export { PlannerHostResult as a, ScopedRequirementsClientSet as c, PlannerHost as i, createPlannerHost as l, CandidateSelector as n, RequirementsClientSet as o, CreatePlannerHostOptions as r, RequirementsClientSetComposition as s, AuthGuardClientOption as t, resolveEffectiveClientSet as u };
190
- //# sourceMappingURL=planner-host-DN9dRsQx.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-BJqUdH2e.d.mts.map
@@ -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-BwcnUGNl.d.mts.map
@@ -1,51 +0,0 @@
1
- //#region src/persistence/web/stores.ts
2
- /**
3
- * `RecordStore` backed by `localStorage`.
4
- * @param prefix Optional key prefix to isolate the keyspace.
5
- */
6
- function createLocalStorageStore(prefix = "") {
7
- return {
8
- async get(key) {
9
- return globalThis.localStorage.getItem(prefix + key);
10
- },
11
- async set(key, value) {
12
- globalThis.localStorage.setItem(prefix + key, value);
13
- },
14
- async take(key) {
15
- const storageKey = prefix + key;
16
- const value = globalThis.localStorage.getItem(storageKey);
17
- if (value !== null) globalThis.localStorage.removeItem(storageKey);
18
- return value;
19
- },
20
- async remove(key) {
21
- globalThis.localStorage.removeItem(prefix + key);
22
- }
23
- };
24
- }
25
- /**
26
- * `RecordStore` backed by `sessionStorage`.
27
- * @param prefix Optional key prefix to isolate the keyspace.
28
- */
29
- function createSessionStorageStore(prefix = "") {
30
- return {
31
- async get(key) {
32
- return globalThis.sessionStorage.getItem(prefix + key);
33
- },
34
- async set(key, value) {
35
- globalThis.sessionStorage.setItem(prefix + key, value);
36
- },
37
- async take(key) {
38
- const storageKey = prefix + key;
39
- const value = globalThis.sessionStorage.getItem(storageKey);
40
- if (value !== null) globalThis.sessionStorage.removeItem(storageKey);
41
- return value;
42
- },
43
- async remove(key) {
44
- globalThis.sessionStorage.removeItem(prefix + key);
45
- }
46
- };
47
- }
48
- //#endregion
49
- export { createSessionStorageStore as n, createLocalStorageStore as t };
50
-
51
- //# sourceMappingURL=web-DQmMqZnR.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"web-DQmMqZnR.mjs","names":[],"sources":["../src/persistence/web/stores.ts"],"sourcesContent":["import type { RecordStore } from \"../types\";\n\n/**\n * `RecordStore` backed by `localStorage`.\n * @param prefix Optional key prefix to isolate the keyspace.\n */\nexport function createLocalStorageStore(prefix = \"\"): RecordStore {\n\treturn {\n\t\tasync get(key: string): Promise<string | null> {\n\t\t\treturn globalThis.localStorage.getItem(prefix + key);\n\t\t},\n\t\tasync set(key: string, value: string): Promise<void> {\n\t\t\tglobalThis.localStorage.setItem(prefix + key, value);\n\t\t},\n\t\tasync take(key: string): Promise<string | null> {\n\t\t\tconst storageKey = prefix + key;\n\t\t\tconst value = globalThis.localStorage.getItem(storageKey);\n\t\t\tif (value !== null) {\n\t\t\t\tglobalThis.localStorage.removeItem(storageKey);\n\t\t\t}\n\t\t\treturn value;\n\t\t},\n\t\tasync remove(key: string): Promise<void> {\n\t\t\tglobalThis.localStorage.removeItem(prefix + key);\n\t\t},\n\t};\n}\n\n/**\n * `RecordStore` backed by `sessionStorage`.\n * @param prefix Optional key prefix to isolate the keyspace.\n */\nexport function createSessionStorageStore(prefix = \"\"): RecordStore {\n\treturn {\n\t\tasync get(key: string): Promise<string | null> {\n\t\t\treturn globalThis.sessionStorage.getItem(prefix + key);\n\t\t},\n\t\tasync set(key: string, value: string): Promise<void> {\n\t\t\tglobalThis.sessionStorage.setItem(prefix + key, value);\n\t\t},\n\t\tasync take(key: string): Promise<string | null> {\n\t\t\tconst storageKey = prefix + key;\n\t\t\tconst value = globalThis.sessionStorage.getItem(storageKey);\n\t\t\tif (value !== null) {\n\t\t\t\tglobalThis.sessionStorage.removeItem(storageKey);\n\t\t\t}\n\t\t\treturn value;\n\t\t},\n\t\tasync remove(key: string): Promise<void> {\n\t\t\tglobalThis.sessionStorage.removeItem(prefix + key);\n\t\t},\n\t};\n}\n"],"mappings":";;;;;AAMA,SAAgB,wBAAwB,SAAS,IAAiB;CACjE,OAAO;EACN,MAAM,IAAI,KAAqC;GAC9C,OAAO,WAAW,aAAa,QAAQ,SAAS,IAAI;;EAErD,MAAM,IAAI,KAAa,OAA8B;GACpD,WAAW,aAAa,QAAQ,SAAS,KAAK,MAAM;;EAErD,MAAM,KAAK,KAAqC;GAC/C,MAAM,aAAa,SAAS;GAC5B,MAAM,QAAQ,WAAW,aAAa,QAAQ,WAAW;GACzD,IAAI,UAAU,MACb,WAAW,aAAa,WAAW,WAAW;GAE/C,OAAO;;EAER,MAAM,OAAO,KAA4B;GACxC,WAAW,aAAa,WAAW,SAAS,IAAI;;EAEjD;;;;;;AAOF,SAAgB,0BAA0B,SAAS,IAAiB;CACnE,OAAO;EACN,MAAM,IAAI,KAAqC;GAC9C,OAAO,WAAW,eAAe,QAAQ,SAAS,IAAI;;EAEvD,MAAM,IAAI,KAAa,OAA8B;GACpD,WAAW,eAAe,QAAQ,SAAS,KAAK,MAAM;;EAEvD,MAAM,KAAK,KAAqC;GAC/C,MAAM,aAAa,SAAS;GAC5B,MAAM,QAAQ,WAAW,eAAe,QAAQ,WAAW;GAC3D,IAAI,UAAU,MACb,WAAW,eAAe,WAAW,WAAW;GAEjD,OAAO;;EAER,MAAM,OAAO,KAA4B;GACxC,WAAW,eAAe,WAAW,SAAS,IAAI;;EAEnD"}
@@ -1,259 +0,0 @@
1
- import { i as PlannerHost, s as RequirementsClientSetComposition, t as AuthGuardClientOption } from "../planner-host-DN9dRsQx.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