@mirrorstack-ai/app-module-client 0.5.1

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 (70) hide show
  1. package/CHANGELOG.md +152 -0
  2. package/LICENSE +202 -0
  3. package/README.md +488 -0
  4. package/dist/base-url.d.ts +39 -0
  5. package/dist/base-url.js +51 -0
  6. package/dist/base-url.js.map +1 -0
  7. package/dist/client.d.ts +56 -0
  8. package/dist/client.js +85 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/error.d.ts +29 -0
  11. package/dist/error.js +100 -0
  12. package/dist/error.js.map +1 -0
  13. package/dist/index.d.ts +6 -0
  14. package/dist/index.js +6 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/next/auth-routes.d.ts +82 -0
  17. package/dist/next/auth-routes.js +164 -0
  18. package/dist/next/auth-routes.js.map +1 -0
  19. package/dist/next/index.d.ts +12 -0
  20. package/dist/next/index.js +13 -0
  21. package/dist/next/index.js.map +1 -0
  22. package/dist/next/module-proxy-routes.d.ts +81 -0
  23. package/dist/next/module-proxy-routes.js +77 -0
  24. package/dist/next/module-proxy-routes.js.map +1 -0
  25. package/dist/plugin.d.ts +39 -0
  26. package/dist/plugin.js +39 -0
  27. package/dist/plugin.js.map +1 -0
  28. package/dist/response.d.ts +13 -0
  29. package/dist/response.js +80 -0
  30. package/dist/response.js.map +1 -0
  31. package/dist/server/index.d.ts +7 -0
  32. package/dist/server/index.js +8 -0
  33. package/dist/server/index.js.map +1 -0
  34. package/dist/server/member-sessions.d.ts +80 -0
  35. package/dist/server/member-sessions.js +162 -0
  36. package/dist/server/member-sessions.js.map +1 -0
  37. package/dist/transport.d.ts +98 -0
  38. package/dist/transport.js +299 -0
  39. package/dist/transport.js.map +1 -0
  40. package/dist/web/cache.d.ts +33 -0
  41. package/dist/web/cache.js +130 -0
  42. package/dist/web/cache.js.map +1 -0
  43. package/dist/web/component-mount.d.ts +23 -0
  44. package/dist/web/component-mount.js +111 -0
  45. package/dist/web/component-mount.js.map +1 -0
  46. package/dist/web/index.d.ts +6 -0
  47. package/dist/web/index.js +7 -0
  48. package/dist/web/index.js.map +1 -0
  49. package/dist/web/localized-text.d.ts +10 -0
  50. package/dist/web/localized-text.js +36 -0
  51. package/dist/web/localized-text.js.map +1 -0
  52. package/dist/web/react.d.ts +18 -0
  53. package/dist/web/react.js +111 -0
  54. package/dist/web/react.js.map +1 -0
  55. package/dist/web/runtime.d.ts +60 -0
  56. package/dist/web/runtime.js +73 -0
  57. package/dist/web/runtime.js.map +1 -0
  58. package/dist/web/subpath.d.ts +22 -0
  59. package/dist/web/subpath.js +50 -0
  60. package/dist/web/subpath.js.map +1 -0
  61. package/dist/web/types.d.ts +129 -0
  62. package/dist/web/types.js +2 -0
  63. package/dist/web/types.js.map +1 -0
  64. package/dist/web/use-now.d.ts +7 -0
  65. package/dist/web/use-now.js +22 -0
  66. package/dist/web/use-now.js.map +1 -0
  67. package/dist/web/use-platform-unsaved-state.d.ts +10 -0
  68. package/dist/web/use-platform-unsaved-state.js +35 -0
  69. package/dist/web/use-platform-unsaved-state.js.map +1 -0
  70. package/package.json +88 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"member-sessions.js","sourceRoot":"","sources":["../../src/server/member-sessions.ts"],"names":[],"mappings":"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","sourcesContent":["import { assertAppSlug } from \"../base-url.js\";\nimport { normalizeBaseUrl } from \"../transport.js\";\n\n/**\n * App-scoped platform member sessions — the control plane a custom web app\n * uses to turn an auth provider's one-time handoff into a credential it can\n * present on every module call, and to revoke it again.\n *\n * These routes are dispatch's, not a module's: `POST /dispatch/apps/<app>/member-sessions`\n * and `DELETE …/current`. Dispatch resolves the app's auth-provider slot,\n * redeems the code with that provider itself, and issues its own `mss1_`\n * credential, which every installed module then accepts as\n * `Authorization: Bearer …`. That is why this lives beside `platformBaseUrl`\n * and not in any module's client: the exchange is identical for every\n * provider that can fill the slot, and a module plugin only has module-scoped\n * transports.\n */\n\n/** Inputs for {@link memberSessions}. */\nexport interface MemberSessionsOptions {\n /** Absolute HTTP(S) platform API URL, typically `MIRRORSTACK_API_URL`. */\n readonly apiUrl: string;\n /** The custom application's slug, typically `MIRRORSTACK_APP_SLUG`. */\n readonly appSlug: string;\n /** Fetch implementation, for SSR and tests. Defaults to `globalThis.fetch`. */\n readonly fetch?: typeof globalThis.fetch;\n}\n\n/** The identity the platform returns with a freshly issued member session. */\nexport interface MemberIdentity {\n readonly id: string;\n readonly email: string | null;\n readonly displayName: string | null;\n readonly avatarUrl: string | null;\n readonly createdAt: string;\n readonly lastSignInAt: string;\n}\n\n/** A member session as issued by the platform. */\nexport interface MemberSession {\n /** The `mss1_` credential to present as a bearer on module calls. Server-side only. */\n readonly credential: string;\n readonly identity: MemberIdentity;\n /** RFC 3339 expiry of the credential. */\n readonly expiresAt: string;\n}\n\n/**\n * The outcome of a revoke. `revoked` and `alreadyInvalid` are both a finished\n * sign-out; `unavailable` means the platform could not say, so the credential\n * may still be live and a caller must not hide that behind a cleared cookie.\n */\nexport type RevokeOutcome = \"revoked\" | \"alreadyInvalid\" | \"unavailable\";\n\n/** A non-successful response from the member-session control plane. */\nexport class MemberSessionError extends Error {\n readonly status: number;\n readonly code: string | undefined;\n\n constructor(status: number, code: string | undefined, message: string) {\n super(message);\n this.name = \"MemberSessionError\";\n this.status = status;\n this.code = code;\n }\n}\n\n/** The member-sessions API a custom app talks to. */\nexport interface MemberSessionsApi {\n /** A fresh one-time handoff state: 32 lowercase hex characters, inside the provider's 16–256 byte window. */\n newState(): string;\n /** Redeem a one-time handoff code for a member session. `state` must be the one this app issued for the sign-in. */\n exchange(code: string, state: string): Promise<MemberSession>;\n /** Revoke the current member session on the platform. Never throws; see {@link RevokeOutcome}. */\n revoke(credential: string): Promise<RevokeOutcome>;\n /** The control-plane URL this instance talks to, for assertions and logs. */\n readonly url: string;\n}\n\nconst HANDOFF_STATE_BYTES = 16;\nconst ENVELOPE_VERSION = 1;\nconst MAX_ERROR_BODY_BYTES = 4096;\n\nfunction stringField(record: Record<string, unknown>, key: string): string | undefined {\n const value = record[key];\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction nullableStringField(record: Record<string, unknown>, key: string): string | null {\n const value = record[key];\n return typeof value === \"string\" ? value : null;\n}\n\nfunction errorCodeOf(body: unknown): string | undefined {\n if (body === null || typeof body !== \"object\") return undefined;\n const error = (body as Record<string, unknown>).error;\n if (error === null || typeof error !== \"object\") return undefined;\n return stringField(error as Record<string, unknown>, \"code\");\n}\n\nasync function boundedText(response: Response): Promise<string> {\n try {\n const text = await response.text();\n return text.length > MAX_ERROR_BODY_BYTES ? text.slice(0, MAX_ERROR_BODY_BYTES) : text;\n } catch {\n return \"\";\n }\n}\n\nfunction parseMemberSession(body: unknown): MemberSession {\n if (body === null || typeof body !== \"object\") {\n throw new MemberSessionError(502, \"invalid_member_session_response\", \"member session response is not an object\");\n }\n const record = body as Record<string, unknown>;\n const credential = stringField(record, \"credential\");\n const expiresAt = stringField(record, \"expiresAt\");\n const identity = record.identity;\n if (\n credential === undefined || credential.length === 0 ||\n expiresAt === undefined ||\n identity === null || typeof identity !== \"object\"\n ) {\n throw new MemberSessionError(502, \"invalid_member_session_response\", \"member session response is missing credential, identity, or expiresAt\");\n }\n const id = stringField(identity as Record<string, unknown>, \"id\");\n if (id === undefined || id.length === 0) {\n throw new MemberSessionError(502, \"invalid_member_session_response\", \"member session identity has no id\");\n }\n const identityRecord = identity as Record<string, unknown>;\n return {\n credential,\n expiresAt,\n identity: {\n id,\n email: nullableStringField(identityRecord, \"email\"),\n displayName: nullableStringField(identityRecord, \"display_name\"),\n avatarUrl: nullableStringField(identityRecord, \"avatar_url\"),\n createdAt: stringField(identityRecord, \"created_at\") ?? \"\",\n lastSignInAt: stringField(identityRecord, \"last_sign_in_at\") ?? \"\",\n },\n };\n}\n\n/**\n * Creates the member-sessions API for one custom application.\n *\n * ```ts\n * const sessions = memberSessions({\n * apiUrl: process.env.MIRRORSTACK_API_URL!,\n * appSlug: process.env.MIRRORSTACK_APP_SLUG!,\n * });\n * const state = sessions.newState(); // stored in an HttpOnly cookie, sent on startUrl\n * const session = await sessions.exchange(code, state);\n * await sessions.revoke(session.credential);\n * ```\n *\n * Both inputs are validated up front, like {@link platformBaseUrl}, so a\n * misconfigured environment fails at startup rather than on the first sign-in.\n */\nexport function memberSessions(options: MemberSessionsOptions): MemberSessionsApi {\n if (options === null || typeof options !== \"object\") {\n throw new TypeError(\"memberSessions options must be an object\");\n }\n const { apiUrl, appSlug } = options;\n if (typeof apiUrl !== \"string\" || !/^https?:\\/\\//u.test(apiUrl)) {\n throw new TypeError(\"apiUrl must be an absolute HTTP(S) URL\");\n }\n const parsed = new URL(apiUrl);\n if (parsed.username !== \"\" || parsed.password !== \"\" || parsed.search !== \"\" || parsed.hash !== \"\") {\n throw new TypeError(\"apiUrl must not carry credentials, a query, or a fragment\");\n }\n assertAppSlug(appSlug);\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new TypeError(\"a fetch implementation is required\");\n }\n const url = `${normalizeBaseUrl(apiUrl)}/dispatch/apps/${encodeURIComponent(appSlug)}/member-sessions`;\n\n return {\n url,\n newState() {\n const bytes = new Uint8Array(HANDOFF_STATE_BYTES);\n globalThis.crypto.getRandomValues(bytes);\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n },\n async exchange(code, state) {\n if (typeof code !== \"string\" || code.length === 0) {\n throw new TypeError(\"a handoff code is required\");\n }\n if (typeof state !== \"string\" || state.length === 0) {\n throw new TypeError(\"the issued handoff state is required\");\n }\n const response = await fetchImpl(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n body: JSON.stringify({ v: ENVELOPE_VERSION, code, state }),\n cache: \"no-store\",\n });\n if (!response.ok) {\n const text = await boundedText(response);\n let body: unknown;\n try { body = JSON.parse(text); } catch { body = undefined; }\n throw new MemberSessionError(\n response.status,\n errorCodeOf(body),\n `member session exchange failed (${response.status})`,\n );\n }\n return parseMemberSession(await response.json());\n },\n async revoke(credential) {\n if (typeof credential !== \"string\" || credential.length === 0) {\n throw new TypeError(\"a member session credential is required\");\n }\n try {\n const response = await fetchImpl(`${url}/current`, {\n method: \"DELETE\",\n headers: { Authorization: `Bearer ${credential}` },\n cache: \"no-store\",\n });\n if (response.status === 204) return \"revoked\";\n if (response.status === 401) return \"alreadyInvalid\";\n return \"unavailable\";\n } catch {\n return \"unavailable\";\n }\n },\n };\n}\n"]}
@@ -0,0 +1,98 @@
1
+ import type { ModuleScope } from "./plugin.js";
2
+ /** A value that may be returned synchronously or asynchronously. */
3
+ export type Awaitable<T> = T | PromiseLike<T>;
4
+ /** Metadata carried to a request header provider but never sent by itself. */
5
+ export type RequestMetadata = Readonly<Record<string, unknown>>;
6
+ /** Scalar values accepted by the object form of {@link QueryParams}. */
7
+ export type QueryScalar = string | number | boolean | bigint | null | undefined;
8
+ /** Query parameters for a request or navigation URL. */
9
+ export type QueryParams = URLSearchParams | Readonly<Record<string, QueryScalar | readonly QueryScalar[]>>;
10
+ /** Immutable information supplied to an asynchronous header provider. */
11
+ export interface ModuleRequestContext {
12
+ readonly moduleRef: string;
13
+ readonly scope: ModuleScope;
14
+ readonly method: string;
15
+ readonly path: string;
16
+ readonly url: string;
17
+ readonly metadata: RequestMetadata;
18
+ }
19
+ /** Static headers or a per-request asynchronous header provider. */
20
+ export type RequestHeaders = HeadersInit | ((context: ModuleRequestContext) => Awaitable<HeadersInit | undefined>);
21
+ /**
22
+ * End-user access Bearer hooks used exclusively for platform-scope
23
+ * `Authorization`. Never supply an `X-MS-*` token, delegation/member
24
+ * assertion, signing secret, or internal credential here.
25
+ */
26
+ export interface PlatformAuth {
27
+ readonly getAccessToken: () => Awaitable<string | null>;
28
+ readonly refreshAccessToken?: () => Awaitable<string | null>;
29
+ }
30
+ /**
31
+ * Successful response representation requested from the transport.
32
+ * A raw `response` transfers body-size enforcement to the caller.
33
+ */
34
+ export type ModuleResponseType = "json" | "text" | "response" | "void";
35
+ /** Maps a response representation to its resolved TypeScript type. */
36
+ export type ModuleResponse<T, TType extends ModuleResponseType> = TType extends "text" ? string : TType extends "response" ? Response : TType extends "void" ? void : T;
37
+ interface CommonOptions extends Omit<RequestInit, "body" | "headers" | "method" | "redirect"> {
38
+ readonly headers?: HeadersInit;
39
+ readonly query?: QueryParams;
40
+ readonly metadata?: RequestMetadata;
41
+ }
42
+ type BodyOptions = {
43
+ readonly body?: BodyInit | null;
44
+ readonly json?: never;
45
+ } | {
46
+ readonly json: unknown;
47
+ readonly body?: never;
48
+ };
49
+ /** Options for a raw scoped fetch. `json` and `body` are mutually exclusive. */
50
+ export type ScopedFetchOptions = CommonOptions & BodyOptions & {
51
+ readonly method?: string;
52
+ };
53
+ /** Options for a parsed module request. `json` and `body` are mutually exclusive. */
54
+ export type ModuleRequestOptions<TType extends ModuleResponseType = ModuleResponseType> = CommonOptions & BodyOptions & {
55
+ /** Defaults to `json`. */
56
+ readonly responseType?: TType;
57
+ };
58
+ /** A transport permanently confined to one module and one public scope. */
59
+ export interface ScopedTransport {
60
+ /** Builds a safe dispatch URL, including for top-level browser navigation. */
61
+ url(path: string, query?: QueryParams): string;
62
+ /** Performs a request and returns the raw response without HTTP-status parsing. */
63
+ fetch(path: string, options?: ScopedFetchOptions): Promise<Response>;
64
+ /** Performs a request, throwing {@link ModuleClientError} for non-2xx responses. */
65
+ request<T = unknown, TType extends ModuleResponseType = "json">(method: string, path: string, options?: ModuleRequestOptions<TType>): Promise<ModuleResponse<T, TType>>;
66
+ /** Performs a parsed GET request. */
67
+ get<T = unknown, TType extends ModuleResponseType = "json">(path: string, options?: ModuleRequestOptions<TType>): Promise<ModuleResponse<T, TType>>;
68
+ /** Performs a parsed POST request. */
69
+ post<T = unknown, TType extends ModuleResponseType = "json">(path: string, options?: ModuleRequestOptions<TType>): Promise<ModuleResponse<T, TType>>;
70
+ /** Performs a parsed PUT request. */
71
+ put<T = unknown, TType extends ModuleResponseType = "json">(path: string, options?: ModuleRequestOptions<TType>): Promise<ModuleResponse<T, TType>>;
72
+ /** Performs a parsed PATCH request. */
73
+ patch<T = unknown, TType extends ModuleResponseType = "json">(path: string, options?: ModuleRequestOptions<TType>): Promise<ModuleResponse<T, TType>>;
74
+ /** Performs a parsed DELETE request. */
75
+ delete<T = unknown, TType extends ModuleResponseType = "json">(path: string, options?: ModuleRequestOptions<TType>): Promise<ModuleResponse<T, TType>>;
76
+ }
77
+ /** @internal */
78
+ export interface SharedTransportConfig {
79
+ readonly baseUrl: string;
80
+ readonly fetch: typeof globalThis.fetch;
81
+ readonly headers?: RequestHeaders;
82
+ readonly credentials: RequestCredentials;
83
+ readonly maxResponseBytes: number;
84
+ readonly metadata?: RequestMetadata;
85
+ readonly platformAuth?: PlatformAuth;
86
+ /**
87
+ * The signed-in member's credential, sent on PUBLIC scope only.
88
+ *
89
+ * Separate from `headers` because a configured header applies to every scope
90
+ * and platform scope rejects a configured Authorization outright.
91
+ */
92
+ readonly memberCredential?: string;
93
+ }
94
+ /** @internal */
95
+ export declare function normalizeBaseUrl(baseUrl: string, label?: string): string;
96
+ /** @internal */
97
+ export declare function createScopedTransports(config: SharedTransportConfig, moduleRef: string, moduleBaseUrl?: string, scopePaths?: Partial<Record<"public" | "platform", string>>): Readonly<Record<ModuleScope, ScopedTransport>>;
98
+ export {};
@@ -0,0 +1,299 @@
1
+ import { errorCodeFromResponse, moduleClientErrorFromResponse, } from "./error.js";
2
+ import { cancelResponseBody, readResponseText } from "./response.js";
3
+ const HTTP_METHOD_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
4
+ const RESERVED_HEADER_PREFIX = "x-ms-";
5
+ const REFRESHABLE_CODES = new Set(["token_expired", "token_missing"]);
6
+ const RETRYABLE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
7
+ const RESPONSE_TYPES = new Set(["json", "text", "response", "void"]);
8
+ function own(value, key) {
9
+ return Object.prototype.hasOwnProperty.call(value, key);
10
+ }
11
+ function assertSafePath(path) {
12
+ if (typeof path !== "string" || (path !== "" && !path.startsWith("/"))) {
13
+ throw new TypeError('module request path must be empty or start with "/"');
14
+ }
15
+ if (path.includes("?") || path.includes("#")) {
16
+ throw new TypeError("put query parameters in the query option, not in path");
17
+ }
18
+ let layer = path;
19
+ for (let depth = 0; depth < 8; depth += 1) {
20
+ if (/\\|[\u0000-\u001f\u007f]/u.test(layer) || layer.includes("//")) {
21
+ throw new TypeError("module request path contains an unsafe separator");
22
+ }
23
+ const segments = layer.split("/");
24
+ if (segments.some((segment) => segment === "." || segment === "..")) {
25
+ throw new TypeError("module request path must not contain dot segments");
26
+ }
27
+ let decoded;
28
+ try {
29
+ decoded = decodeURIComponent(layer);
30
+ }
31
+ catch {
32
+ throw new TypeError("module request path contains invalid percent encoding");
33
+ }
34
+ if (decoded === layer)
35
+ return;
36
+ if ((decoded.match(/\//g)?.length ?? 0) !== (layer.match(/\//g)?.length ?? 0)) {
37
+ throw new TypeError("module request path must not contain encoded separators");
38
+ }
39
+ if (decoded.includes("?") || decoded.includes("#")) {
40
+ throw new TypeError("module request path must not contain encoded query or fragment markers");
41
+ }
42
+ layer = decoded;
43
+ }
44
+ throw new TypeError("module request path is excessively percent encoded");
45
+ }
46
+ /** @internal */
47
+ export function normalizeBaseUrl(baseUrl, label = "baseUrl") {
48
+ if (typeof baseUrl !== "string" || baseUrl === "" || baseUrl.includes("\\")) {
49
+ throw new TypeError(`${label} must be a non-empty HTTP(S) URL or root-relative path`);
50
+ }
51
+ if (/^https?:\/\//u.test(baseUrl)) {
52
+ const parsed = new URL(baseUrl);
53
+ if ((parsed.protocol !== "http:" && parsed.protocol !== "https:") ||
54
+ parsed.username !== "" ||
55
+ parsed.password !== "" ||
56
+ parsed.search !== "" ||
57
+ parsed.hash !== "") {
58
+ throw new TypeError(`${label} must not contain credentials, a query, or a fragment`);
59
+ }
60
+ const authority = `${parsed.protocol}//${parsed.host}`;
61
+ const rawPath = baseUrl.slice(authority.length) || "/";
62
+ assertSafePath(rawPath);
63
+ return baseUrl.replace(/\/+$/u, "");
64
+ }
65
+ if (!baseUrl.startsWith("/") || baseUrl.startsWith("//")) {
66
+ throw new TypeError(`relative ${label} must be root-relative`);
67
+ }
68
+ assertSafePath(baseUrl);
69
+ return baseUrl.replace(/\/+$/u, "");
70
+ }
71
+ function queryString(query) {
72
+ if (query === undefined)
73
+ return "";
74
+ if (query instanceof URLSearchParams)
75
+ return query.toString();
76
+ const params = new URLSearchParams();
77
+ for (const [key, raw] of Object.entries(query)) {
78
+ const values = Array.isArray(raw) ? raw : [raw];
79
+ for (const value of values) {
80
+ if (value === null || value === undefined)
81
+ continue;
82
+ if (typeof value === "number" && !Number.isFinite(value)) {
83
+ throw new TypeError(`query parameter ${key} must be finite`);
84
+ }
85
+ if (!["string", "number", "boolean", "bigint"].includes(typeof value)) {
86
+ throw new TypeError(`query parameter ${key} has an unsupported value`);
87
+ }
88
+ params.append(key, String(value));
89
+ }
90
+ }
91
+ return params.toString();
92
+ }
93
+ function normalizeMethod(method) {
94
+ const normalized = method === undefined ? "GET" : method.toUpperCase();
95
+ if (!HTTP_METHOD_PATTERN.test(normalized)) {
96
+ throw new TypeError("request method must be a valid HTTP token");
97
+ }
98
+ return normalized;
99
+ }
100
+ function validateHeaders(headers, source) {
101
+ for (const name of headers.keys()) {
102
+ if (name.toLowerCase().startsWith(RESERVED_HEADER_PREFIX)) {
103
+ throw new TypeError(`${source} must not set reserved ${RESERVED_HEADER_PREFIX}* headers`);
104
+ }
105
+ }
106
+ }
107
+ function snapshotHeaders(value, source) {
108
+ const headers = new Headers(value);
109
+ validateHeaders(headers, source);
110
+ return headers;
111
+ }
112
+ function mergeMetadata(defaults, request) {
113
+ const result = Object.assign(Object.create(null), defaults, request);
114
+ return Object.freeze(result);
115
+ }
116
+ function serializeBody(options) {
117
+ const hasJson = own(options, "json");
118
+ const hasBody = own(options, "body") && options.body !== undefined;
119
+ if (hasJson && hasBody) {
120
+ throw new TypeError("request options cannot contain both json and body");
121
+ }
122
+ if (!hasJson) {
123
+ return { body: options.body, isJson: false };
124
+ }
125
+ const body = JSON.stringify(options.json);
126
+ if (body === undefined) {
127
+ throw new TypeError("json must be JSON-serializable");
128
+ }
129
+ return { body, isJson: true };
130
+ }
131
+ function isReplayableBody(body) {
132
+ if (body === null || body === undefined || typeof body === "string")
133
+ return true;
134
+ if (body instanceof URLSearchParams || body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
135
+ return true;
136
+ }
137
+ if (typeof Blob !== "undefined" && body instanceof Blob)
138
+ return true;
139
+ if (typeof FormData !== "undefined" && body instanceof FormData)
140
+ return true;
141
+ return false;
142
+ }
143
+ function addBearer(headers, token) {
144
+ headers.delete("authorization");
145
+ if (token !== null)
146
+ headers.set("authorization", `Bearer ${token}`);
147
+ }
148
+ function requireAccessToken(token, operation) {
149
+ if (typeof token !== "string" || token.trim() === "") {
150
+ throw new Error(`platformAuth.${operation} returned no access token`);
151
+ }
152
+ return token;
153
+ }
154
+ async function parseSuccess(response, responseType, maxResponseBytes) {
155
+ switch (responseType) {
156
+ case "response":
157
+ return response;
158
+ case "text":
159
+ return (await readResponseText(response, maxResponseBytes));
160
+ case "void":
161
+ cancelResponseBody(response);
162
+ return undefined;
163
+ case "json": {
164
+ const text = await readResponseText(response, maxResponseBytes);
165
+ if (text === "") {
166
+ throw new SyntaxError('expected a JSON response body; use responseType: "void" for an empty response');
167
+ }
168
+ return JSON.parse(text);
169
+ }
170
+ }
171
+ }
172
+ function assertResponseType(responseType) {
173
+ if (!RESPONSE_TYPES.has(responseType)) {
174
+ throw new TypeError("responseType must be json, text, response, or void");
175
+ }
176
+ }
177
+ /** @internal */
178
+ export function createScopedTransports(config, moduleRef, moduleBaseUrl, scopePaths = {}) {
179
+ const base = moduleBaseUrl === undefined
180
+ ? normalizeBaseUrl(config.baseUrl)
181
+ : moduleBaseUrl === ""
182
+ ? ""
183
+ : normalizeBaseUrl(moduleBaseUrl);
184
+ const encodedModuleRef = encodeURIComponent(moduleRef);
185
+ const staticHeaders = typeof config.headers === "function" || config.headers === undefined
186
+ ? undefined
187
+ : snapshotHeaders(config.headers, "configured headers");
188
+ const headerProvider = typeof config.headers === "function" ? config.headers : undefined;
189
+ // 🔴 A member credential is PUBLIC-scope authentication and must never reach
190
+ // platform scope. Sent as a configured header it applies to EVERY scope, and
191
+ // the platform guard below then throws on any platform-scope call — so an app
192
+ // holding a signed-in member could not call a platform method at all. It is
193
+ // carried separately for that reason, not as a convenience.
194
+ const memberCredential = typeof config.memberCredential === "string" && config.memberCredential !== ""
195
+ ? config.memberCredential
196
+ : undefined;
197
+ function makeScope(scope) {
198
+ const scopePath = scopePaths[scope] ?? scope;
199
+ const scopeSegment = scopePath === "" ? "" : `/${scopePath}`;
200
+ // An app dispatch root composes <baseUrl>/<scope>/<moduleRef>/<path>: the
201
+ // platform routes by scope BEFORE module. A host-supplied module root (the
202
+ // /web runtime) already names the module, so only the scope follows it.
203
+ const scopeBase = moduleBaseUrl === undefined
204
+ ? `${base}${scopeSegment}/${encodedModuleRef}`
205
+ : `${base}${scopeSegment}`;
206
+ function url(path, query) {
207
+ assertSafePath(path);
208
+ const serialized = queryString(query);
209
+ return `${scopeBase}${path}${serialized === "" ? "" : `?${serialized}`}`;
210
+ }
211
+ async function rawFetch(path, options = {}) {
212
+ const optionRecord = options;
213
+ if (own(optionRecord, "redirect")) {
214
+ throw new TypeError("scoped module requests do not accept redirect overrides; redirects are blocked");
215
+ }
216
+ const method = normalizeMethod(options.method);
217
+ const requestUrl = url(path, options.query);
218
+ const metadata = mergeMetadata(config.metadata, options.metadata);
219
+ const context = Object.freeze({ moduleRef, scope, method, path, url: requestUrl, metadata });
220
+ const provided = headerProvider === undefined ? undefined : await headerProvider(context);
221
+ const providerHeaders = provided === undefined ? undefined : snapshotHeaders(provided, "header provider");
222
+ const callerHeaders = options.headers === undefined ? undefined : snapshotHeaders(options.headers, "request headers");
223
+ const headers = new Headers();
224
+ const { body, isJson } = serializeBody(optionRecord);
225
+ // Public scope only, and BEFORE the other sources so an explicit caller
226
+ // header still wins — a caller sending its own Authorization is making a
227
+ // deliberate choice.
228
+ if (memberCredential !== undefined && scope === "public") {
229
+ headers.set("authorization", `Bearer ${memberCredential}`);
230
+ }
231
+ for (const source of [staticHeaders, providerHeaders, callerHeaders]) {
232
+ source?.forEach((value, name) => headers.set(name, value));
233
+ }
234
+ // The transport owns JSON serialization, so callers cannot make its
235
+ // representation disagree with the declared media type.
236
+ if (isJson)
237
+ headers.set("content-type", "application/json");
238
+ if (scope === "platform" && headers.has("authorization")) {
239
+ throw new TypeError("platform requests must use platformAuth; configured, provider, and caller Authorization headers are rejected");
240
+ }
241
+ const { body: _body, headers: _headers, json: _json, metadata: _metadata, method: _method, query: _query, redirect: _redirect, ...requestInit } = optionRecord;
242
+ let token = null;
243
+ if (scope === "platform" && config.platformAuth !== undefined) {
244
+ token = requireAccessToken(await config.platformAuth.getAccessToken(), "getAccessToken");
245
+ addBearer(headers, token);
246
+ }
247
+ const init = {
248
+ ...requestInit,
249
+ method,
250
+ headers,
251
+ body,
252
+ credentials: options.credentials ?? config.credentials,
253
+ // Following a module-controlled redirect could carry cookies or a
254
+ // platform Bearer outside this module/scope URL.
255
+ redirect: "error",
256
+ };
257
+ let response = await config.fetch(requestUrl, init);
258
+ if (scope !== "platform" ||
259
+ response.status !== 401 ||
260
+ config.platformAuth?.refreshAccessToken === undefined ||
261
+ !RETRYABLE_METHODS.has(method) ||
262
+ !isReplayableBody(body) ||
263
+ options.signal?.aborted === true) {
264
+ return response;
265
+ }
266
+ const code = await errorCodeFromResponse(response.clone(), config.maxResponseBytes);
267
+ if (code === undefined || !REFRESHABLE_CODES.has(code))
268
+ return response;
269
+ await response.body?.cancel();
270
+ token = requireAccessToken(await config.platformAuth.refreshAccessToken(), "refreshAccessToken");
271
+ const retryHeaders = new Headers(headers);
272
+ addBearer(retryHeaders, token);
273
+ response = await config.fetch(requestUrl, { ...init, headers: retryHeaders });
274
+ return response;
275
+ }
276
+ async function request(method, path, options = {}) {
277
+ const { responseType = "json", ...fetchOptions } = options;
278
+ assertResponseType(responseType);
279
+ const response = await rawFetch(path, { ...fetchOptions, method });
280
+ if (!response.ok) {
281
+ throw await moduleClientErrorFromResponse(response, { moduleRef, scope, path }, config.maxResponseBytes);
282
+ }
283
+ return parseSuccess(response, responseType, config.maxResponseBytes);
284
+ }
285
+ const transport = {
286
+ url,
287
+ fetch: rawFetch,
288
+ request,
289
+ get: (path, options) => request("GET", path, options),
290
+ post: (path, options) => request("POST", path, options),
291
+ put: (path, options) => request("PUT", path, options),
292
+ patch: (path, options) => request("PATCH", path, options),
293
+ delete: (path, options) => request("DELETE", path, options),
294
+ };
295
+ return Object.freeze(transport);
296
+ }
297
+ return Object.freeze({ public: makeScope("public"), platform: makeScope("platform") });
298
+ }
299
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n errorCodeFromResponse,\n moduleClientErrorFromResponse,\n} from \"./error.js\";\nimport type { ModuleScope } from \"./plugin.js\";\nimport { cancelResponseBody, readResponseText } from \"./response.js\";\n\n/** A value that may be returned synchronously or asynchronously. */\nexport type Awaitable<T> = T | PromiseLike<T>;\n\n/** Metadata carried to a request header provider but never sent by itself. */\nexport type RequestMetadata = Readonly<Record<string, unknown>>;\n\n/** Scalar values accepted by the object form of {@link QueryParams}. */\nexport type QueryScalar = string | number | boolean | bigint | null | undefined;\n\n/** Query parameters for a request or navigation URL. */\nexport type QueryParams =\n | URLSearchParams\n | Readonly<Record<string, QueryScalar | readonly QueryScalar[]>>;\n\n/** Immutable information supplied to an asynchronous header provider. */\nexport interface ModuleRequestContext {\n readonly moduleRef: string;\n readonly scope: ModuleScope;\n readonly method: string;\n readonly path: string;\n readonly url: string;\n readonly metadata: RequestMetadata;\n}\n\n/** Static headers or a per-request asynchronous header provider. */\nexport type RequestHeaders =\n | HeadersInit\n | ((context: ModuleRequestContext) => Awaitable<HeadersInit | undefined>);\n\n/**\n * End-user access Bearer hooks used exclusively for platform-scope\n * `Authorization`. Never supply an `X-MS-*` token, delegation/member\n * assertion, signing secret, or internal credential here.\n */\nexport interface PlatformAuth {\n readonly getAccessToken: () => Awaitable<string | null>;\n readonly refreshAccessToken?: () => Awaitable<string | null>;\n}\n\n/**\n * Successful response representation requested from the transport.\n * A raw `response` transfers body-size enforcement to the caller.\n */\nexport type ModuleResponseType = \"json\" | \"text\" | \"response\" | \"void\";\n\n/** Maps a response representation to its resolved TypeScript type. */\nexport type ModuleResponse<T, TType extends ModuleResponseType> =\n TType extends \"text\"\n ? string\n : TType extends \"response\"\n ? Response\n : TType extends \"void\"\n ? void\n : T;\n\ninterface CommonOptions extends Omit<RequestInit, \"body\" | \"headers\" | \"method\" | \"redirect\"> {\n readonly headers?: HeadersInit;\n readonly query?: QueryParams;\n readonly metadata?: RequestMetadata;\n}\n\ntype BodyOptions =\n | { readonly body?: BodyInit | null; readonly json?: never }\n | { readonly json: unknown; readonly body?: never };\n\n/** Options for a raw scoped fetch. `json` and `body` are mutually exclusive. */\nexport type ScopedFetchOptions = CommonOptions &\n BodyOptions & {\n readonly method?: string;\n };\n\n/** Options for a parsed module request. `json` and `body` are mutually exclusive. */\nexport type ModuleRequestOptions<TType extends ModuleResponseType = ModuleResponseType> =\n CommonOptions &\n BodyOptions & {\n /** Defaults to `json`. */\n readonly responseType?: TType;\n };\n\n/** A transport permanently confined to one module and one public scope. */\nexport interface ScopedTransport {\n /** Builds a safe dispatch URL, including for top-level browser navigation. */\n url(path: string, query?: QueryParams): string;\n\n /** Performs a request and returns the raw response without HTTP-status parsing. */\n fetch(path: string, options?: ScopedFetchOptions): Promise<Response>;\n\n /** Performs a request, throwing {@link ModuleClientError} for non-2xx responses. */\n request<T = unknown, TType extends ModuleResponseType = \"json\">(\n method: string,\n path: string,\n options?: ModuleRequestOptions<TType>,\n ): Promise<ModuleResponse<T, TType>>;\n\n /** Performs a parsed GET request. */\n get<T = unknown, TType extends ModuleResponseType = \"json\">(\n path: string,\n options?: ModuleRequestOptions<TType>,\n ): Promise<ModuleResponse<T, TType>>;\n\n /** Performs a parsed POST request. */\n post<T = unknown, TType extends ModuleResponseType = \"json\">(\n path: string,\n options?: ModuleRequestOptions<TType>,\n ): Promise<ModuleResponse<T, TType>>;\n\n /** Performs a parsed PUT request. */\n put<T = unknown, TType extends ModuleResponseType = \"json\">(\n path: string,\n options?: ModuleRequestOptions<TType>,\n ): Promise<ModuleResponse<T, TType>>;\n\n /** Performs a parsed PATCH request. */\n patch<T = unknown, TType extends ModuleResponseType = \"json\">(\n path: string,\n options?: ModuleRequestOptions<TType>,\n ): Promise<ModuleResponse<T, TType>>;\n\n /** Performs a parsed DELETE request. */\n delete<T = unknown, TType extends ModuleResponseType = \"json\">(\n path: string,\n options?: ModuleRequestOptions<TType>,\n ): Promise<ModuleResponse<T, TType>>;\n}\n\n/** @internal */\nexport interface SharedTransportConfig {\n readonly baseUrl: string;\n readonly fetch: typeof globalThis.fetch;\n readonly headers?: RequestHeaders;\n readonly credentials: RequestCredentials;\n readonly maxResponseBytes: number;\n readonly metadata?: RequestMetadata;\n readonly platformAuth?: PlatformAuth;\n /**\n * The signed-in member's credential, sent on PUBLIC scope only.\n *\n * Separate from `headers` because a configured header applies to every scope\n * and platform scope rejects a configured Authorization outright.\n */\n readonly memberCredential?: string;\n}\n\nconst HTTP_METHOD_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;\nconst RESERVED_HEADER_PREFIX = \"x-ms-\";\nconst REFRESHABLE_CODES = new Set([\"token_expired\", \"token_missing\"]);\nconst RETRYABLE_METHODS = new Set([\"GET\", \"HEAD\", \"OPTIONS\"]);\nconst RESPONSE_TYPES = new Set<ModuleResponseType>([\"json\", \"text\", \"response\", \"void\"]);\n\nfunction own(value: object, key: PropertyKey): boolean {\n return Object.prototype.hasOwnProperty.call(value, key);\n}\n\nfunction assertSafePath(path: string): void {\n if (typeof path !== \"string\" || (path !== \"\" && !path.startsWith(\"/\"))) {\n throw new TypeError('module request path must be empty or start with \"/\"');\n }\n if (path.includes(\"?\") || path.includes(\"#\")) {\n throw new TypeError(\"put query parameters in the query option, not in path\");\n }\n\n let layer = path;\n for (let depth = 0; depth < 8; depth += 1) {\n if (/\\\\|[\\u0000-\\u001f\\u007f]/u.test(layer) || layer.includes(\"//\")) {\n throw new TypeError(\"module request path contains an unsafe separator\");\n }\n const segments = layer.split(\"/\");\n if (segments.some((segment) => segment === \".\" || segment === \"..\")) {\n throw new TypeError(\"module request path must not contain dot segments\");\n }\n\n let decoded: string;\n try {\n decoded = decodeURIComponent(layer);\n } catch {\n throw new TypeError(\"module request path contains invalid percent encoding\");\n }\n if (decoded === layer) return;\n if ((decoded.match(/\\//g)?.length ?? 0) !== (layer.match(/\\//g)?.length ?? 0)) {\n throw new TypeError(\"module request path must not contain encoded separators\");\n }\n if (decoded.includes(\"?\") || decoded.includes(\"#\")) {\n throw new TypeError(\"module request path must not contain encoded query or fragment markers\");\n }\n layer = decoded;\n }\n throw new TypeError(\"module request path is excessively percent encoded\");\n}\n\n/** @internal */\nexport function normalizeBaseUrl(baseUrl: string, label = \"baseUrl\"): string {\n if (typeof baseUrl !== \"string\" || baseUrl === \"\" || baseUrl.includes(\"\\\\\")) {\n throw new TypeError(`${label} must be a non-empty HTTP(S) URL or root-relative path`);\n }\n if (/^https?:\\/\\//u.test(baseUrl)) {\n const parsed = new URL(baseUrl);\n if (\n (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") ||\n parsed.username !== \"\" ||\n parsed.password !== \"\" ||\n parsed.search !== \"\" ||\n parsed.hash !== \"\"\n ) {\n throw new TypeError(`${label} must not contain credentials, a query, or a fragment`);\n }\n const authority = `${parsed.protocol}//${parsed.host}`;\n const rawPath = baseUrl.slice(authority.length) || \"/\";\n assertSafePath(rawPath);\n return baseUrl.replace(/\\/+$/u, \"\");\n }\n if (!baseUrl.startsWith(\"/\") || baseUrl.startsWith(\"//\")) {\n throw new TypeError(`relative ${label} must be root-relative`);\n }\n assertSafePath(baseUrl);\n return baseUrl.replace(/\\/+$/u, \"\");\n}\n\nfunction queryString(query: QueryParams | undefined): string {\n if (query === undefined) return \"\";\n if (query instanceof URLSearchParams) return query.toString();\n\n const params = new URLSearchParams();\n for (const [key, raw] of Object.entries(query)) {\n const values = Array.isArray(raw) ? raw : [raw];\n for (const value of values) {\n if (value === null || value === undefined) continue;\n if (typeof value === \"number\" && !Number.isFinite(value)) {\n throw new TypeError(`query parameter ${key} must be finite`);\n }\n if (![\"string\", \"number\", \"boolean\", \"bigint\"].includes(typeof value)) {\n throw new TypeError(`query parameter ${key} has an unsupported value`);\n }\n params.append(key, String(value));\n }\n }\n return params.toString();\n}\n\nfunction normalizeMethod(method: string | undefined): string {\n const normalized = method === undefined ? \"GET\" : method.toUpperCase();\n if (!HTTP_METHOD_PATTERN.test(normalized)) {\n throw new TypeError(\"request method must be a valid HTTP token\");\n }\n return normalized;\n}\n\nfunction validateHeaders(headers: Headers, source: string): void {\n for (const name of headers.keys()) {\n if (name.toLowerCase().startsWith(RESERVED_HEADER_PREFIX)) {\n throw new TypeError(`${source} must not set reserved ${RESERVED_HEADER_PREFIX}* headers`);\n }\n }\n}\n\nfunction snapshotHeaders(value: HeadersInit, source: string): Headers {\n const headers = new Headers(value);\n validateHeaders(headers, source);\n return headers;\n}\n\nfunction mergeMetadata(\n defaults: RequestMetadata | undefined,\n request: RequestMetadata | undefined,\n): RequestMetadata {\n const result = Object.assign(Object.create(null) as Record<string, unknown>, defaults, request);\n return Object.freeze(result);\n}\n\nfunction serializeBody(options: Record<string, unknown>): {\n readonly body: BodyInit | null | undefined;\n readonly isJson: boolean;\n} {\n const hasJson = own(options, \"json\");\n const hasBody = own(options, \"body\") && options.body !== undefined;\n if (hasJson && hasBody) {\n throw new TypeError(\"request options cannot contain both json and body\");\n }\n if (!hasJson) {\n return { body: options.body as BodyInit | null | undefined, isJson: false };\n }\n const body = JSON.stringify(options.json);\n if (body === undefined) {\n throw new TypeError(\"json must be JSON-serializable\");\n }\n return { body, isJson: true };\n}\n\nfunction isReplayableBody(body: BodyInit | null | undefined): boolean {\n if (body === null || body === undefined || typeof body === \"string\") return true;\n if (body instanceof URLSearchParams || body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {\n return true;\n }\n if (typeof Blob !== \"undefined\" && body instanceof Blob) return true;\n if (typeof FormData !== \"undefined\" && body instanceof FormData) return true;\n return false;\n}\n\nfunction addBearer(headers: Headers, token: string | null): void {\n headers.delete(\"authorization\");\n if (token !== null) headers.set(\"authorization\", `Bearer ${token}`);\n}\n\nfunction requireAccessToken(token: string | null, operation: string): string {\n if (typeof token !== \"string\" || token.trim() === \"\") {\n throw new Error(`platformAuth.${operation} returned no access token`);\n }\n return token;\n}\n\nasync function parseSuccess<T, TType extends ModuleResponseType>(\n response: Response,\n responseType: TType,\n maxResponseBytes: number,\n): Promise<ModuleResponse<T, TType>> {\n switch (responseType) {\n case \"response\":\n return response as ModuleResponse<T, TType>;\n case \"text\":\n return (await readResponseText(response, maxResponseBytes)) as ModuleResponse<T, TType>;\n case \"void\":\n cancelResponseBody(response);\n return undefined as ModuleResponse<T, TType>;\n case \"json\": {\n const text = await readResponseText(response, maxResponseBytes);\n if (text === \"\") {\n throw new SyntaxError(\n 'expected a JSON response body; use responseType: \"void\" for an empty response',\n );\n }\n return JSON.parse(text) as ModuleResponse<T, TType>;\n }\n }\n}\n\nfunction assertResponseType(responseType: unknown): asserts responseType is ModuleResponseType {\n if (!RESPONSE_TYPES.has(responseType as ModuleResponseType)) {\n throw new TypeError(\"responseType must be json, text, response, or void\");\n }\n}\n\n/** @internal */\nexport function createScopedTransports(\n config: SharedTransportConfig,\n moduleRef: string,\n moduleBaseUrl?: string,\n scopePaths: Partial<Record<\"public\" | \"platform\", string>> = {},\n): Readonly<Record<ModuleScope, ScopedTransport>> {\n const base =\n moduleBaseUrl === undefined\n ? normalizeBaseUrl(config.baseUrl)\n : moduleBaseUrl === \"\"\n ? \"\"\n : normalizeBaseUrl(moduleBaseUrl);\n const encodedModuleRef = encodeURIComponent(moduleRef);\n const staticHeaders =\n typeof config.headers === \"function\" || config.headers === undefined\n ? undefined\n : snapshotHeaders(config.headers, \"configured headers\");\n const headerProvider = typeof config.headers === \"function\" ? config.headers : undefined;\n // 🔴 A member credential is PUBLIC-scope authentication and must never reach\n // platform scope. Sent as a configured header it applies to EVERY scope, and\n // the platform guard below then throws on any platform-scope call — so an app\n // holding a signed-in member could not call a platform method at all. It is\n // carried separately for that reason, not as a convenience.\n const memberCredential =\n typeof config.memberCredential === \"string\" && config.memberCredential !== \"\"\n ? config.memberCredential\n : undefined;\n\n function makeScope(scope: ModuleScope): ScopedTransport {\n const scopePath = scopePaths[scope] ?? scope;\n const scopeSegment = scopePath === \"\" ? \"\" : `/${scopePath}`;\n // An app dispatch root composes <baseUrl>/<scope>/<moduleRef>/<path>: the\n // platform routes by scope BEFORE module. A host-supplied module root (the\n // /web runtime) already names the module, so only the scope follows it.\n const scopeBase =\n moduleBaseUrl === undefined\n ? `${base}${scopeSegment}/${encodedModuleRef}`\n : `${base}${scopeSegment}`;\n\n function url(path: string, query?: QueryParams): string {\n assertSafePath(path);\n const serialized = queryString(query);\n return `${scopeBase}${path}${serialized === \"\" ? \"\" : `?${serialized}`}`;\n }\n\n async function rawFetch(path: string, options: ScopedFetchOptions = {}): Promise<Response> {\n const optionRecord = options as unknown as Record<string, unknown>;\n if (own(optionRecord, \"redirect\")) {\n throw new TypeError(\n \"scoped module requests do not accept redirect overrides; redirects are blocked\",\n );\n }\n const method = normalizeMethod(options.method);\n const requestUrl = url(path, options.query);\n const metadata = mergeMetadata(config.metadata, options.metadata);\n const context = Object.freeze({ moduleRef, scope, method, path, url: requestUrl, metadata });\n const provided = headerProvider === undefined ? undefined : await headerProvider(context);\n const providerHeaders =\n provided === undefined ? undefined : snapshotHeaders(provided, \"header provider\");\n const callerHeaders =\n options.headers === undefined ? undefined : snapshotHeaders(options.headers, \"request headers\");\n const headers = new Headers();\n const { body, isJson } = serializeBody(optionRecord);\n // Public scope only, and BEFORE the other sources so an explicit caller\n // header still wins — a caller sending its own Authorization is making a\n // deliberate choice.\n if (memberCredential !== undefined && scope === \"public\") {\n headers.set(\"authorization\", `Bearer ${memberCredential}`);\n }\n for (const source of [staticHeaders, providerHeaders, callerHeaders]) {\n source?.forEach((value, name) => headers.set(name, value));\n }\n // The transport owns JSON serialization, so callers cannot make its\n // representation disagree with the declared media type.\n if (isJson) headers.set(\"content-type\", \"application/json\");\n if (scope === \"platform\" && headers.has(\"authorization\")) {\n throw new TypeError(\n \"platform requests must use platformAuth; configured, provider, and caller Authorization headers are rejected\",\n );\n }\n\n const {\n body: _body,\n headers: _headers,\n json: _json,\n metadata: _metadata,\n method: _method,\n query: _query,\n redirect: _redirect,\n ...requestInit\n } = optionRecord;\n let token: string | null = null;\n if (scope === \"platform\" && config.platformAuth !== undefined) {\n token = requireAccessToken(\n await config.platformAuth.getAccessToken(),\n \"getAccessToken\",\n );\n addBearer(headers, token);\n }\n const init: RequestInit = {\n ...(requestInit as RequestInit),\n method,\n headers,\n body,\n credentials: options.credentials ?? config.credentials,\n // Following a module-controlled redirect could carry cookies or a\n // platform Bearer outside this module/scope URL.\n redirect: \"error\",\n };\n let response = await config.fetch(requestUrl, init);\n\n if (\n scope !== \"platform\" ||\n response.status !== 401 ||\n config.platformAuth?.refreshAccessToken === undefined ||\n !RETRYABLE_METHODS.has(method) ||\n !isReplayableBody(body) ||\n options.signal?.aborted === true\n ) {\n return response;\n }\n const code = await errorCodeFromResponse(response.clone(), config.maxResponseBytes);\n if (code === undefined || !REFRESHABLE_CODES.has(code)) return response;\n\n await response.body?.cancel();\n\n token = requireAccessToken(\n await config.platformAuth.refreshAccessToken(),\n \"refreshAccessToken\",\n );\n const retryHeaders = new Headers(headers);\n addBearer(retryHeaders, token);\n response = await config.fetch(requestUrl, { ...init, headers: retryHeaders });\n return response;\n }\n\n async function request<T = unknown, TType extends ModuleResponseType = \"json\">(\n method: string,\n path: string,\n options: ModuleRequestOptions<TType> = {},\n ): Promise<ModuleResponse<T, TType>> {\n const { responseType = \"json\" as TType, ...fetchOptions } = options;\n assertResponseType(responseType);\n const response = await rawFetch(path, { ...fetchOptions, method } as ScopedFetchOptions);\n if (!response.ok) {\n throw await moduleClientErrorFromResponse(\n response,\n { moduleRef, scope, path },\n config.maxResponseBytes,\n );\n }\n return parseSuccess<T, TType>(response, responseType, config.maxResponseBytes);\n }\n\n const transport: ScopedTransport = {\n url,\n fetch: rawFetch,\n request,\n get: (path, options) => request(\"GET\", path, options),\n post: (path, options) => request(\"POST\", path, options),\n put: (path, options) => request(\"PUT\", path, options),\n patch: (path, options) => request(\"PATCH\", path, options),\n delete: (path, options) => request(\"DELETE\", path, options),\n };\n return Object.freeze(transport);\n }\n\n return Object.freeze({ public: makeScope(\"public\"), platform: makeScope(\"platform\") });\n}\n"]}
@@ -0,0 +1,33 @@
1
+ /** Configuration for one mount-scoped text cache. */
2
+ export interface ModuleTextCacheOptions {
3
+ /** Default freshness window for cached responses. */
4
+ readonly maxAgeMs?: number;
5
+ /** Maximum number of completed responses retained by this instance. */
6
+ readonly maxEntries?: number;
7
+ /** Clock override for deterministic tests. */
8
+ readonly now?: () => number;
9
+ }
10
+ /** Options for one cached read. */
11
+ export interface CachedTextOptions {
12
+ readonly signal?: AbortSignal;
13
+ /** Overrides the instance freshness window; zero forces a network read. */
14
+ readonly maxAgeMs?: number;
15
+ }
16
+ /** A request cache owned and disposed by one mounted module instance. */
17
+ export interface ModuleTextCache {
18
+ /** Returns a fresh cached response or coalesces an equivalent live request. */
19
+ cachedText(key: string, fetchText: (signal: AbortSignal) => Promise<string>, options?: CachedTextOptions): Promise<string>;
20
+ /** Removes completed entries, optionally restricted to a key prefix. */
21
+ invalidate(prefix?: string): void;
22
+ /** Removes completed entries and aborts every live request owned by this cache. */
23
+ clear(): void;
24
+ }
25
+ /** Returns true when an error represents an aborted browser request. */
26
+ export declare function isAbortError(error: unknown): boolean;
27
+ /**
28
+ * Creates an isolated text cache for one module mount.
29
+ *
30
+ * The cache stores response text so each consumer can parse its own object. It
31
+ * never uses browser storage and shares no state with another cache instance.
32
+ */
33
+ export declare function createModuleTextCache(options?: ModuleTextCacheOptions): ModuleTextCache;