lanka 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +3 -1
  2. package/dist/{ALankaGateway-CkW1LbKE.d.ts → ALankaGateway-BrVPaZN5.d.ts} +1 -1
  3. package/dist/ILankaFieldError-D5931-vT.d.ts +34 -0
  4. package/dist/{ILankaScenarioMetadata-GoWWNEQL.d.ts → ILankaScenarioMetadata-Dj4GCqmX.d.ts} +1 -1
  5. package/dist/{ILankaScenarioVM-DUsI-fSc.d.ts → ILankaScenarioVM-DpKFL3iE.d.ts} +5 -4
  6. package/dist/{LankaError-xpI-qj35.d.ts → LankaError-D6RWNyLM.d.ts} +18 -0
  7. package/dist/{LankaScenarioLocator-D86TIwiu.d.ts → LankaScenarioLocator-CAENLhDO.d.ts} +3 -3
  8. package/dist/_extend/index.d.ts +6 -5
  9. package/dist/_internal/index.d.ts +61 -8
  10. package/dist/_internal/index.js +25 -0
  11. package/dist/_internal/index.js.map +1 -1
  12. package/dist/{activeRuntime-B336NU5I.d.ts → activeRuntime-BYd2D6Lx.d.ts} +2 -2
  13. package/dist/bootstrap/index.d.ts +9 -8
  14. package/dist/bootstrap/index.js +2 -2
  15. package/dist/cache/index.d.ts +99 -0
  16. package/dist/cache/index.js +1 -0
  17. package/dist/cache/index.js.map +1 -0
  18. package/dist/{chunk-UGXSGQPW.js → chunk-B7EYIAW7.js} +39 -3
  19. package/dist/chunk-B7EYIAW7.js.map +1 -0
  20. package/dist/chunk-CRIRTOLB.js +27 -0
  21. package/dist/chunk-CRIRTOLB.js.map +1 -0
  22. package/dist/{chunk-Q7QESSYF.js → chunk-G32H73QY.js} +10 -1
  23. package/dist/chunk-G32H73QY.js.map +1 -0
  24. package/dist/chunk-HVSQOMRE.js +69 -0
  25. package/dist/chunk-HVSQOMRE.js.map +1 -0
  26. package/dist/{chunk-73IVH3C6.js → chunk-NDCJBO47.js} +2 -2
  27. package/dist/{chunk-G3I7QIZR.js → chunk-UBGXDTXC.js} +14 -5
  28. package/dist/chunk-UBGXDTXC.js.map +1 -0
  29. package/dist/{createLanka-DI1CSy2Q.d.ts → createLanka-NrlvN-WQ.d.ts} +1 -1
  30. package/dist/errors/index.d.ts +22 -3
  31. package/dist/errors/index.js +8 -3
  32. package/dist/errors/index.js.map +1 -1
  33. package/dist/gateway/index.d.ts +4 -3
  34. package/dist/gateway/index.js +6 -5
  35. package/dist/gateway/index.js.map +1 -1
  36. package/dist/index.d.ts +9 -8
  37. package/dist/index.js +3 -3
  38. package/dist/{lankaStandardValidator-CL-r-zEV.d.ts → lankaStandardValidator-BUFnysK0.d.ts} +10 -0
  39. package/dist/locator/index.d.ts +3 -2
  40. package/dist/scenario/index.d.ts +56 -6
  41. package/dist/scenario/index.js +1 -1
  42. package/dist/storage/index.d.ts +120 -0
  43. package/dist/storage/index.js +1 -0
  44. package/dist/storage/index.js.map +1 -0
  45. package/dist/stream/index.d.ts +8 -7
  46. package/dist/validation/index.d.ts +7 -4
  47. package/dist/validation/index.js +5 -3
  48. package/dist/viewmodel/index.d.ts +51 -5
  49. package/dist/viewmodel/index.js +75 -47
  50. package/dist/viewmodel/index.js.map +1 -1
  51. package/package.json +11 -3
  52. package/skills/lanka-core/SKILL.md +59 -1
  53. package/skills/lanka-core/reference.md +312 -6
  54. package/skills/lanka-packages/SKILL.md +1 -1
  55. package/dist/chunk-G3I7QIZR.js.map +0 -1
  56. package/dist/chunk-GV5DUYST.js +0 -64
  57. package/dist/chunk-GV5DUYST.js.map +0 -1
  58. package/dist/chunk-Q7QESSYF.js.map +0 -1
  59. package/dist/chunk-UGXSGQPW.js.map +0 -1
  60. /package/dist/{chunk-73IVH3C6.js.map → chunk-NDCJBO47.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/gateway/request/_abstractions/lanka-request/ALankaRequest.ts","../../src/gateway/request/_abstractions/lanka-transport-request/ALankaTransportRequest.ts","../../src/gateway/lanka-fetch-transport/LankaFetchTransport.ts","../../src/gateway/request/lanka-fetch-json-request/LankaFetchJsonRequest.ts","../../src/gateway/_utils/build-lanka-query-params/buildLankaQueryParams.ts","../../src/gateway/_abstractions/lanka-gateway/ALankaGateway.ts","../../src/gateway/_factories/create-lanka-gateway/createLankaGateway.ts","../../src/gateway/request/lanka-fetch-request/LankaFetchRequest.ts","../../src/gateway/request/_factories/create-lanka-fetch-request/createLankaFetchRequest.ts","../../src/gateway/request/_factories/create-lanka-fetch-json-request/createLankaFetchJsonRequest.ts"],"sourcesContent":["import type { TLankaRequestInit } from \"../../../_types/TLankaRequestInit\";\nimport type { TLankaErrorHandler } from \"../../../../errors/_types/TLankaErrorHandler\";\nimport type { ILankaRequest } from \"../../../_interfaces/ILankaRequest\";\nimport type { TLankaExecuteOptions } from \"../../../_types/TLankaExecuteOptions\";\nimport { handleLankaApiError } from \"../../../../errors/handle-lanka-api-error/handleLankaApiError\";\nimport { getLankaFlags } from \"../../../../config/get-lanka-flags/getLankaFlags\";\nimport { getLankaHost } from \"../../../../config/get-lanka-host/getLankaHost\";\nimport { lankaHttpInFlight } from \"../../../inflight/lankaHttpInFlight\";\nimport { LankaError } from \"../../../../errors/lanka-error/LankaError\";\nimport { getActiveRuntime } from \"../../../../_internal/active-runtime/activeRuntime\";\nimport { composeLankaRequestMiddleware } from \"../../lankaRequestMiddleware\";\nimport type { ILankaRequestContext } from \"../../lankaRequestMiddleware\";\n\n/**\n * Tags whatever the transport threw with a kind.\n *\n * Here rather than in the transports: `execute` is the single point EVERY\n * request passes through, and tagging in each of the four transports would be\n * four places to forget it.\n *\n * A real `fetch` throws `TypeError` on a broken connection and a `DOMException`\n * named `AbortError` on cancellation; it does not throw on a status code at all.\n *\n * An already-tagged error is NOT re-tagged: a request-policy plugin may report\n * `domain`, and rewriting that to `network` would lose the one thing the kind\n * exists for.\n */\nfunction classifyTransportError(error: unknown, timedOut: boolean): LankaError {\n\tif (LankaError.is(error)) return error;\n\n\t// The name is read off ANYTHING, not only off `Error`.\n\t//\n\t// `DOMException` — how `fetch` reports cancellation — does not extend `Error`\n\t// everywhere: in a browser yes, in jsdom no. An `instanceof Error` check lets\n\t// cancellation past the tagging, and a raw `AbortError` reaches the app with\n\t// neither `kind` nor `status`: retry policy reads it as non-retryable and the\n\t// app as an unknown error, so a cancelled request is shown to the user as a\n\t// failure.\n\tconst name = readErrorName(error);\n\tconst isAbort = name === \"AbortError\" || name === \"TimeoutError\";\n\tif (isAbort) {\n\t\t// Only whoever assembled the lifetime knows who aborted: `AbortSignal` has\n\t\t// one `abort` for everyone. The distinction carries a decision — a timeout\n\t\t// is shown, a cancellation is not.\n\t\tconst timedOutHere = timedOut || name === \"TimeoutError\";\n\t\treturn new LankaError({\n\t\t\tkind: timedOutHere ? \"timeout\" : \"aborted\",\n\t\t\tmessage: timedOutHere ? getLankaHost().timeoutErrorMessage() : readErrorMessage(error),\n\t\t\tcause: error,\n\t\t});\n\t}\n\n\t// `network` ONLY for what looks like a transport failure. A real `fetch`\n\t// throws `TypeError`; everything else comes from code we did not write — the\n\t// app's error handler, a response transformer, a broken plugin — and calling\n\t// that a network failure would invite the user to retry a request that\n\t// arrived and was processed.\n\t//\n\t// Unknown stays unknown and passes through. The framework asserts only what\n\t// it knows.\n\tif (error instanceof TypeError) {\n\t\treturn new LankaError({\n\t\t\tkind: \"network\",\n\t\t\tmessage: getLankaHost().networkErrorMessage(),\n\t\t\tissues: [error.message],\n\t\t\tcause: error,\n\t\t});\n\t}\n\n\treturn error as LankaError;\n}\n\n/** The error name, off `Error`, `DOMException` or anything else carrying one. */\nfunction readErrorName(error: unknown): string | undefined {\n\tif (typeof error !== \"object\" || error === null) return undefined;\n\tconst name: unknown = (error as { name?: unknown }).name;\n\treturn typeof name === \"string\" ? name : undefined;\n}\n\n/**\n * The error text, when there is one.\n *\n * An object without `message` is not stringified: `String({})` yields\n * `[object Object]`, which occupies the message slot and says nothing. Empty is\n * more honest.\n */\nfunction readErrorMessage(error: unknown): string {\n\tif (typeof error === \"string\") return error;\n\tif (typeof error !== \"object\" || error === null) return \"\";\n\tconst message: unknown = (error as { message?: unknown }).message;\n\treturn typeof message === \"string\" ? message : \"\";\n}\n\n/**\n * How a gateway request goes on the wire.\n *\n * A subclass declares one method, `request()`, and does only its own work there:\n * `LankaFetchJsonRequest` returns parsed JSON, `LankaFetchRequest` returns the\n * whole response, a custom kind returns whatever it likes.\n *\n * Overriding `request()` customises the request flow, failure handling, mock\n * substitution, response transformation and log interception.\n */\nexport abstract class ALankaRequest<\n\tTOptions = TLankaRequestInit,\n> implements ILankaRequest<TOptions> {\n\tprotected readonly errorHandler?: TLankaErrorHandler;\n\tprotected readonly useMock: boolean;\n\n\tprotected constructor(config: { errorHandler?: TLankaErrorHandler; useMock?: boolean }) {\n\t\tconst flags = getLankaFlags();\n\n\t\t// The default error-body handler lives HERE because the request is the only\n\t\t// thing that sees the `Response`. Put on the gateway it would sit in a field\n\t\t// nobody reads — the request takes the handler from ITS OWN config — and a\n\t\t// consumer passing `errorHandler` to the gateway would get silence.\n\t\t//\n\t\t// The parse is cheap: core reads the body once and takes `message` from it;\n\t\t// backend-specific shapes are parsed by `@lankajs/plugin-http`.\n\t\tthis.errorHandler = config.errorHandler ?? handleLankaApiError;\n\n\t\tthis.useMock = config.useMock ?? flags.isMockMode ?? false;\n\t}\n\n\t/**\n\t * Performs the request and returns its result — a response, JSON or a custom\n\t * type.\n\t *\n\t * @param endpoint Full URL\n\t * @param options Transport-specific options\n\t * @param mockHandler Mock, when there is one\n\t */\n\tprotected abstract request<TReturn = Response>(\n\t\tendpoint: string,\n\t\toptions?: TOptions,\n\t\tmockHandler?: () => Promise<TReturn>,\n\t): Promise<TReturn>;\n\n\t/**\n\t * The single point EVERY gateway request passes through.\n\t *\n\t * Hence the in-flight accounting here: intent prefetch stands down while\n\t * anything else is on the wire. The `finally` matters more than the increment —\n\t * a rejected request that never decremented would disable prefetching for the\n\t * rest of the session.\n\t */\n\tpublic async execute<TReturn = Response>(\n\t\tendpoint: string,\n\t\toptions?: TLankaExecuteOptions<TOptions>,\n\t\tmockHandler?: () => Promise<TReturn>,\n\t): Promise<TReturn> {\n\t\tconst runtime = getActiveRuntime();\n\t\tconst { signal, timeoutMs, ...rest } = (options ?? {}) as TLankaExecuteOptions<TOptions>;\n\t\t// The caller's deadline travels IN THE CONTEXT, not only in the closure:\n\t\t// otherwise a policy assigning deadlines per request class could not tell\n\t\t// \"no deadline given\" from \"given by the caller\" and would override an\n\t\t// explicit request with a blanket default.\n\t\tconst fallbackDeadline = runtime?.requestTimeoutMs;\n\n\t\t// No options passed means none are produced. Destructuring yields `{}` even\n\t\t// from `undefined`, and handing that empty object to the transport would\n\t\t// change the request: \"no options\" and \"empty options\" are different\n\t\t// statements, and the transport is entitled to tell them apart.\n\t\tconst passedOptions: unknown = options === undefined ? undefined : rest;\n\n\t\t/*\n\t\t * The lifetime is assembled PER ATTEMPT, not per call, and that does two\n\t\t * things at once.\n\t\t *\n\t\t * Under retry a shared deadline would start the third attempt with whatever\n\t\t * the first two left, so the retry aborts before reaching the server.\n\t\t *\n\t\t * And it is the only way to let middleware set the deadline: `ctx.timeoutMs`\n\t\t * is read on every attempt, which is how a request-policy plugin assigns a\n\t\t * deadline per request CLASS — a file upload and a list read cannot share\n\t\t * one value.\n\t\t *\n\t\t * Caller cancellation stays end-to-end: one `signal` for all attempts.\n\t\t */\n\t\tlet lastTimedOut = false;\n\n\t\t// Tagging happens INSIDE, around the request itself, not in the outer\n\t\t// catch: middleware must receive an already-tagged error, or a retrying\n\t\t// middleware cannot tell a network failure from a domain rejection and\n\t\t// retries what must not be retried. The outer catch stays as a backstop.\n\t\tconst perform = async (ctx: ILankaRequestContext): Promise<unknown> => {\n\t\t\tconst lifetime = createRequestLifetime(signal, ctx.timeoutMs ?? fallbackDeadline);\n\t\t\ttry {\n\t\t\t\treturn await this.request<TReturn>(\n\t\t\t\t\tctx.endpoint,\n\t\t\t\t\twithSignal(ctx.options, lifetime.signal) as TOptions,\n\t\t\t\t\tmockHandler,\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tlastTimedOut = lifetime.timedOut();\n\t\t\t\tthrow classifyTransportError(error, lastTimedOut);\n\t\t\t} finally {\n\t\t\t\tlifetime.dispose();\n\t\t\t}\n\t\t};\n\n\t\tconst run = composeLankaRequestMiddleware(runtime?.requestMiddleware ?? [], perform);\n\n\t\tlankaHttpInFlight.begin();\n\t\ttry {\n\t\t\t// The chain runs INSIDE the same guard as the request. A catch placed\n\t\t\t// outside would leave a permanent +1 when a plugin throws, disabling\n\t\t\t// prefetch for the rest of the session — the very defect this `finally`\n\t\t\t// exists to prevent.\n\t\t\treturn (await run({\n\t\t\t\tendpoint,\n\t\t\t\toptions: passedOptions,\n\t\t\t\tattempt: 1,\n\t\t\t\ttimeoutMs,\n\t\t\t})) as TReturn;\n\t\t} catch (error) {\n\t\t\tthrow classifyTransportError(error, lastTimedOut);\n\t\t} finally {\n\t\t\tlankaHttpInFlight.end();\n\t\t}\n\t}\n}\n\ninterface IRequestLifetime {\n\treadonly signal: AbortSignal | undefined;\n\t/** Whether OUR timer aborted the request rather than the caller. */\n\ttimedOut(): boolean;\n\tdispose(): void;\n}\n\n/**\n * Combines the caller's signal and our own timeout into one request lifetime.\n *\n * The outcome looks the same — an interrupted request — but the decisions\n * differ: a timeout is shown and offered for retry, a user cancellation is not\n * shown at all. `AbortSignal` does not distinguish them: one `abort`, whose\n * reason belongs to whoever got there first. Hence the private flag.\n *\n * Not `AbortSignal.timeout` alone: it cannot combine with a foreign signal\n * without `AbortSignal.any`, which older engines lack. Assembling by hand works\n * everywhere and costs one listener.\n */\nfunction createRequestLifetime(\n\texternal: AbortSignal | undefined,\n\ttimeoutMs: number | undefined,\n): IRequestLifetime {\n\tif (!external && !timeoutMs) {\n\t\treturn { signal: undefined, timedOut: () => false, dispose: () => undefined };\n\t}\n\n\tconst controller = new AbortController();\n\tlet expired = false;\n\n\tconst timer =\n\t\ttimeoutMs === undefined\n\t\t\t? undefined\n\t\t\t: setTimeout(() => {\n\t\t\t\t\texpired = true;\n\t\t\t\t\tcontroller.abort(new DOMException(\"Request timed out\", \"TimeoutError\"));\n\t\t\t\t}, timeoutMs);\n\n\tconst onExternalAbort = (): void => {\n\t\tcontroller.abort(external?.reason);\n\t};\n\n\tif (external) {\n\t\tif (external.aborted) onExternalAbort();\n\t\telse external.addEventListener(\"abort\", onExternalAbort, { once: true });\n\t}\n\n\treturn {\n\t\tsignal: controller.signal,\n\t\ttimedOut: () => expired,\n\t\tdispose: () => {\n\t\t\tif (timer !== undefined) clearTimeout(timer);\n\t\t\texternal?.removeEventListener(\"abort\", onExternalAbort);\n\t\t},\n\t};\n}\n\n/**\n * Attaches the signal to the options, inventing nothing.\n *\n * With no options and no signal the transport receives `undefined` — exactly\n * what the caller passed. An empty object instead looks harmless but is a\n * different statement, and the transport is entitled to tell them apart.\n */\nfunction withSignal(options: unknown, signal: AbortSignal | undefined): unknown {\n\tif (signal === undefined) return options;\n\treturn { ...(options ?? {}), signal };\n}\n","import { ALankaRequest } from \"../lanka-request/ALankaRequest\";\nimport { LankaError } from \"../../../../errors/lanka-error/LankaError\";\nimport { getLankaFlags } from \"../../../../config/get-lanka-flags/getLankaFlags\";\nimport { getLankaHost } from \"../../../../config/get-lanka-host/getLankaHost\";\nimport type { ILankaTransport } from \"../../../_interfaces/ILankaTransport\";\nimport type { TLankaRequestInit } from \"../../../_types/TLankaRequestInit\";\nimport type { TLankaErrorHandler } from \"../../../../errors/_types/TLankaErrorHandler\";\n\nexport interface ILankaTransportRequestConfig<TOptions> {\n\ttransport?: ILankaTransport<TOptions>;\n\terrorHandler?: TLankaErrorHandler;\n\tuseMock?: boolean;\n}\n\n/**\n * The shape every fetch-backed request has: mock, send, check, parse.\n *\n * The concrete requests differ in ONE place — how they turn a successful\n * `Response` into a value — so a third kind is a subclass with one method rather\n * than a third copy of the sequence.\n *\n * The default transport stays a constructor PARAMETER even though both shipped\n * kinds pass the same one. It is the seam a request kind that is not fetch-backed\n * comes through: this template is the mock/send/check/parse sequence, and nothing\n * in it is about HTTP. Defaulting the parameter here would fix `TOptions` to\n * fetch options for everyone who reuses the sequence.\n */\nexport abstract class ALankaTransportRequest<\n\tTOptions = TLankaRequestInit,\n> extends ALankaRequest<TOptions> {\n\tprotected readonly transport: ILankaTransport<TOptions>;\n\n\tprotected constructor(\n\t\tconfig: ILankaTransportRequestConfig<TOptions>,\n\t\tcreateDefaultTransport: () => ILankaTransport<TOptions>,\n\t) {\n\t\tconst flags = getLankaFlags();\n\n\t\tsuper({\n\t\t\terrorHandler: config.errorHandler,\n\t\t\tuseMock: config.useMock ?? flags.isMockMode ?? false,\n\t\t});\n\n\t\tthis.transport = config.transport ?? createDefaultTransport();\n\t}\n\n\tprotected async request<TReturn>(\n\t\tendpoint: string,\n\t\toptions?: TOptions,\n\t\tmockHandler?: () => Promise<TReturn>,\n\t): Promise<TReturn> {\n\t\tif (this.useMock && mockHandler) {\n\t\t\treturn await mockHandler();\n\t\t}\n\n\t\tconst response = await this.transport.request(endpoint, options);\n\t\tif (!response.ok) return await this.refuse(response);\n\n\t\treturn await this.parse<TReturn>(response);\n\t}\n\n\t/**\n\t * Turns a successful response into the value the caller asked for.\n\t *\n\t * The one step that genuinely differs between request kinds.\n\t */\n\tprotected abstract parse<TReturn>(response: Response): Promise<TReturn>;\n\n\t/**\n\t * Refuses an unsuccessful response, and never returns.\n\t *\n\t * `TLankaErrorHandler` is typed `Promise<never>` — a handler must throw. It is\n\t * still CALLED and then followed by a throw, because a handler that breaks its\n\t * contract and returns would otherwise hand `undefined` back as if the request\n\t * had succeeded, and a non-value must never become a value.\n\t */\n\tprotected async refuse(response: Response): Promise<never> {\n\t\tif (this.errorHandler) {\n\t\t\tawait this.errorHandler(response);\n\t\t}\n\n\t\t// Kind `http`, not a bare Error: the server answered, and answered with a\n\t\t// code. Without the kind this reads as a network failure, and the user is\n\t\t// offered a retry of a request that already got a meaningful answer.\n\t\tthrow new LankaError({\n\t\t\tkind: \"http\",\n\t\t\tmessage: getLankaHost().httpErrorMessage(response.status),\n\t\t\tstatus: response.status,\n\t\t});\n\t}\n}\n","import type { ILankaTransport } from \"../_interfaces/ILankaTransport\";\nimport type { TLankaRequestInit } from \"../_types/TLankaRequestInit\";\n\n/** What an unencoded body is sent as, and the header that says so. */\nconst CONTENT_TYPE = \"content-type\";\nconst JSON_CONTENT_TYPE = \"application/json\";\n\n/**\n * The network seam: `fetch`, plus the two things `fetch` cannot be told.\n *\n * ## Why there is ONE of these\n *\n * There were three — a plain one, a JSON one and a multipart one — and the split\n * was wrong at birth. `ILankaTransport` exists so a consumer can change the\n * PROTOCOL: a native bridge, a socket, a double that never leaves the process.\n * The three differed in a `content-type` header. That is not a protocol, it is an\n * encoding, and an encoding is a property of the CALL: a gateway with fourteen\n * JSON endpoints and one upload had no way to say so, because its request kind —\n * and with it its transport — was fixed in its constructor. The application that\n * hit this added a `useFormData` flag to its own options and wrote its own\n * transport to read it.\n *\n * So the encoding is decided here, per call, by looking at the body. A gateway\n * posts `FormData` to one endpoint and an object to the next, and neither it nor\n * the request kind has to know.\n *\n * ## What does NOT belong here\n *\n * The base URL (`ALankaGateway` prefixes `apiBaseUrl`), credentials, static\n * headers, CSRF, retry, auth refresh, idempotency keys and deadlines. Every one\n * of those is policy around a request rather than a way of sending one, and every\n * one is a middleware — `useRequestMiddleware`, which `@lankajs/plugin-http`\n * occupies. A transport that grew them would be a second composition mechanism\n * beside the one core already publishes, and \"where does a header get added\"\n * would have two answers.\n */\nexport class LankaFetchTransport implements ILankaTransport<TLankaRequestInit> {\n\tasync request(resource: RequestInfo, options?: TLankaRequestInit): Promise<Response> {\n\t\t// Nothing to encode: the object the caller assembled reaches `fetch`\n\t\t// unchanged, `undefined` included. \"No options\" and \"empty options\" are\n\t\t// different statements and `fetch` is entitled to tell them apart.\n\t\tif (options === undefined || options.body === undefined || options.body === null) {\n\t\t\treturn await fetch(resource, options as RequestInit | undefined);\n\t\t}\n\n\t\tconst { body, headers } = options;\n\n\t\t// Multipart: the header is REMOVED, not set. The boundary is generated with\n\t\t// the body, and a hand-written `content-type` carries none — the server then\n\t\t// reads zero fields out of a request that looks correct.\n\t\tif (isFormData(body)) {\n\t\t\tif (headers === undefined) return await fetch(resource, options as RequestInit);\n\n\t\t\tconst stripped = new Headers(headers);\n\t\t\tstripped.delete(CONTENT_TYPE);\n\t\t\treturn await fetch(resource, { ...options, headers: stripped } as RequestInit);\n\t\t}\n\n\t\t// Already something `fetch` understands: untouched.\n\t\tif (isEncodedBody(body)) return await fetch(resource, options as RequestInit);\n\n\t\treturn await fetch(resource, {\n\t\t\t...options,\n\t\t\theaders: withJsonContentType(headers),\n\t\t\tbody: JSON.stringify(body),\n\t\t});\n\t}\n}\n\n/**\n * The header set, with `content-type` filled in only if the caller left it out.\n *\n * Not overwritten: `application/merge-patch+json` and `application/ld+json` are\n * JSON that a server distinguishes, and a transport that flattened them to\n * `application/json` would turn a PATCH into a request the server refuses — for\n * a header the caller had already written correctly.\n */\nfunction withJsonContentType(headers: HeadersInit | undefined): Headers {\n\tconst result = new Headers(headers);\n\tif (!result.has(CONTENT_TYPE)) result.set(CONTENT_TYPE, JSON_CONTENT_TYPE);\n\treturn result;\n}\n\n/**\n * Whether `fetch` can already send this body.\n *\n * Every global here is guarded by `typeof`: core runs in node and React Native\n * as well as a browser, and `ReadableStream` in particular is absent on engines\n * this framework supports. An unguarded `instanceof` against a missing global is\n * a `ReferenceError` on the first request, from a transport, in production.\n *\n * Cheapest and most common first. A string — an already-serialised body — stops\n * on line one.\n */\nfunction isEncodedBody(body: unknown): body is BodyInit {\n\tif (typeof body === \"string\") return true;\n\tif (typeof URLSearchParams !== \"undefined\" && body instanceof URLSearchParams) return true;\n\tif (typeof Blob !== \"undefined\" && body instanceof Blob) return true;\n\tif (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return true;\n\treturn typeof ReadableStream !== \"undefined\" && body instanceof ReadableStream;\n}\n\n/**\n * Multipart, checked separately because it is the one body whose header is\n * removed rather than added.\n */\nfunction isFormData(body: unknown): body is FormData {\n\treturn typeof FormData !== \"undefined\" && body instanceof FormData;\n}\n","import { ALankaTransportRequest } from \"../_abstractions/lanka-transport-request/ALankaTransportRequest\";\nimport type { ILankaTransportRequestConfig } from \"../_abstractions/lanka-transport-request/ALankaTransportRequest\";\nimport type { TLankaRequestInit } from \"../../_types/TLankaRequestInit\";\nimport { LankaFetchTransport } from \"../../lanka-fetch-transport/LankaFetchTransport\";\nimport { LankaError } from \"../../../errors/lanka-error/LankaError\";\n\n/** The JSON request: parses the body, and refuses a body that is not JSON. */\n/**\n * A request kind that answers a parsed JSON body — what most endpoints return.\n *\n * A gateway holds one, and is handed it rather than constructing it, which is\n * what lets a test give the same gateway a transport that never leaves the\n * process. `createLankaFetchJsonRequest()` builds the same class.\n */\nexport class LankaFetchJsonRequest<\n\tTOptions extends TLankaRequestInit = TLankaRequestInit,\n> extends ALankaTransportRequest<TOptions> {\n\tconstructor(config: ILankaTransportRequestConfig<TOptions> = {}) {\n\t\tsuper(config, () => new LankaFetchTransport());\n\t}\n\n\t/**\n\t * Parses the body, or names the failure.\n\t *\n\t * The content type is read for the ERROR MESSAGE only. Using it to choose\n\t * between two parse paths, where the second answers `{}` for any non-empty\n\t * body it cannot parse, fails silently: the caller's schema is the first thing\n\t * to notice, and the caller's SCREEN is where it shows up.\n\t *\n\t * A measured case: a dev server whose `/api` fell through to the SPA fallback\n\t * answered `200 text/html` with `index.html`. Turned into `{}`, the schema\n\t * refused it and a person read a validator's issue list on the sign-in card. A\n\t * body a JSON transport cannot parse is never a value — it is a misrouted\n\t * request, and saying so names the actual failure.\n\t */\n\tprotected async parse<TReturn>(response: Response): Promise<TReturn> {\n\t\tconst contentType = response.headers.get(\"content-type\");\n\n\t\tconst text = await response.text();\n\t\tif (!text) {\n\t\t\treturn undefined as TReturn;\n\t\t}\n\n\t\ttry {\n\t\t\treturn JSON.parse(text) as TReturn;\n\t\t} catch (error) {\n\t\t\t// `schema`, not `network`: the request arrived, the server answered, and\n\t\t\t// the answer was the wrong shape. Not cosmetic — a network failure\n\t\t\t// invites a retry, while retrying a broken contract is pointless and\n\t\t\t// blaming the user for it more so.\n\t\t\tthrow new LankaError({\n\t\t\t\tkind: \"schema\",\n\t\t\t\tmessage:\n\t\t\t\t\t`Failed to parse JSON response (content-type: ${contentType ?? \"none\"}): ` +\n\t\t\t\t\t`${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t}\n}\n","import { TLankaQueryParams } from \"../../_types/TLankaQueryParams\";\nimport { TLankaQueryBuilder } from \"../../_types/TLankaQueryBuilder\";\n\n/**\n * The query string a gateway sends, from the object a method was called with.\n *\n * Arrays become `key[]` repeated, nested objects become `key[inner]`: the shape\n * most JSON APIs read back without being told about it.\n *\n * Plain loops over `Object.keys` rather than `Object.entries` and `forEach`: this\n * runs on every request that carries a filter, and the pair array `entries`\n * builds — two allocations per key, thrown away immediately — is a cost with\n * nothing to show for it.\n */\nexport const buildLankaQueryParams: TLankaQueryBuilder = <T extends Record<string, unknown>>(\n\tinput: T,\n): URLSearchParams => {\n\tconst params = new URLSearchParams();\n\n\tconst append = (key: string, value: TLankaQueryParams): void => {\n\t\tif (value == null) return;\n\n\t\tif (Array.isArray(value)) {\n\t\t\t// The bracketed key once for the whole array, not once per element.\n\t\t\tconst itemKey = `${key}[]`;\n\t\t\tfor (let index = 0; index < value.length; index += 1) append(itemKey, value[index]);\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (typeof value === \"object\") {\n\t\t\tconst inner = value as Record<string, TLankaQueryParams>;\n\t\t\tconst innerKeys = Object.keys(inner);\n\n\t\t\tfor (let index = 0; index < innerKeys.length; index += 1) {\n\t\t\t\tconst innerKey = innerKeys[index];\n\t\t\t\tappend(`${key}[${innerKey}]`, inner[innerKey]);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tparams.append(key, String(value));\n\t};\n\n\tconst source = input as Record<string, TLankaQueryParams>;\n\tconst keys = Object.keys(source);\n\n\tfor (let index = 0; index < keys.length; index += 1) append(keys[index], source[keys[index]]);\n\n\treturn params;\n};\n","import type { TLankaRequestInit } from \"../../_types/TLankaRequestInit\";\nimport type { IALankaGatewayConfig } from \"../../_interfaces/IALankaGatewayConfig\";\nimport { lankaLogger } from \"../../../logger/lanka-logger/LankaLogger\";\nimport { LankaFetchJsonRequest } from \"../../request/lanka-fetch-json-request/LankaFetchJsonRequest\";\nimport type { ILankaRequest } from \"../../_interfaces/ILankaRequest\";\nimport type { TLankaExecuteOptions } from \"../../_types/TLankaExecuteOptions\";\nimport { buildLankaQueryParams } from \"../../_utils/build-lanka-query-params/buildLankaQueryParams\";\nimport { TLankaQueryParams } from \"../../_types/TLankaQueryParams\";\nimport { TLankaQueryBuilder } from \"../../_types/TLankaQueryBuilder\";\nimport { getLankaFlags } from \"../../../config/get-lanka-flags/getLankaFlags\";\nimport { getLankaHost } from \"../../../config/get-lanka-host/getLankaHost\";\nimport { lankaStandardValidator } from \"../../../validation/lanka-standard-validator/lankaStandardValidator\";\nimport type { ILankaValidator } from \"../../../validation/lanka-standard-validator/lankaStandardValidator\";\n\nexport abstract class ALankaGateway<TOptions = TLankaRequestInit> {\n\tprotected requestExecutor: ILankaRequest<TOptions>;\n\tprotected queryParamsHandler: TLankaQueryBuilder;\n\n\t/**\n\t * The validator a method checks a response body with.\n\t *\n\t * `config.validationService` when one was given, the Standard Schema port\n\t * otherwise. It used to be accepted by the config and read by nothing: a\n\t * consumer handing a test double to the gateway got the real validator and no\n\t * error, which is the worst kind of ignored option — it looks honoured.\n\t */\n\tprotected readonly validationService: ILankaValidator;\n\n\tprotected readonly useMock: boolean;\n\tprotected readonly basePath: string;\n\n\tprotected constructor(config: IALankaGatewayConfig<TOptions>) {\n\t\tlankaLogger.printGatewayLog(\"Create gateway\", this);\n\t\tconst flags = getLankaFlags();\n\t\tthis.useMock = config.useMock ?? flags.isMockMode ?? false;\n\t\tthis.validationService = config.validationService ?? lankaStandardValidator;\n\n\t\t// A gateway with nothing said about transport talks JSON over `fetch`, which\n\t\t// is what almost every one of them does. Supplying a request is how a gateway\n\t\t// stops being ordinary — a raw `Response`, a multipart upload, a transport\n\t\t// that never leaves the process — and that stays a decision rather than a\n\t\t// line every gateway has to carry to be born.\n\t\t// The cast covers the FRAMEWORK picking its own fallback, and nothing a\n\t\t// consumer does. `TOptions` is unconstrained here on purpose — a gateway may\n\t\t// front a request that never speaks HTTP, and the port promises exactly that\n\t\t// — so the JSON default cannot be proven to fit a `TOptions` nobody has\n\t\t// named yet. A consumer whose options are not fetch-shaped supplies\n\t\t// `request`, and this line never runs for them.\n\t\tthis.requestExecutor = config.request ?? new LankaFetchJsonRequest();\n\n\t\tthis.basePath = config.basePath ?? \"\";\n\t\tthis.queryParamsHandler = config.queryParamsHandler ?? buildLankaQueryParams;\n\t}\n\n\t/**\n\t * Resolves endpoint for request.\n\t * - Absolute paths (starting with \"/\") are returned as-is\n\t * - Relative paths are joined with basePath\n\t * - Query-only strings like \"?a=1\" are attached to basePath\n\t */\n\tprotected endpoint(path: string = \"\"): string {\n\t\t// An absolute URL is detected BEFORE joining with `basePath`, not after:\n\t\t// otherwise `https://other.host/health` first becomes\n\t\t// `/things/https://other.host/health` and there is nothing left to detect.\n\t\tif (isAbsoluteUrl(path)) return path;\n\n\t\treturn this.withApiBase(this.resolvePath(path));\n\t}\n\n\t/**\n\t * Joins `basePath` and the method path.\n\t */\n\tprivate resolvePath(path: string): string {\n\t\tif (!path) return this.basePath;\n\n\t\tif (path.startsWith(\"/\")) return path;\n\n\t\tif (path.startsWith(\"?\")) return `${this.basePath}${path}`;\n\n\t\t// No leading-slash case here: the check above already returned for one, so\n\t\t// stripping it again was a branch no input could take — uncoverable by\n\t\t// construction, and it counted against the coverage floor that gates this\n\t\t// package.\n\t\tconst left = this.basePath.endsWith(\"/\") ? this.basePath.slice(0, -1) : this.basePath;\n\t\treturn `${left}/${path}`;\n\t}\n\n\t/**\n\t * Prefixes the API base URL from the host contract.\n\t *\n\t * Here rather than in every consumer: otherwise each consumer knows the URL\n\t * and the framework does not, and a realtime plugin would have to know a\n\t * specific application's build.\n\t *\n\t * Declaring the field and not using it would be worse than not declaring it: a\n\t * declaration nothing is built from is a second truth, free to diverge from\n\t * the first.\n\t *\n\t * An absolute URL never reaches here — `endpoint()` filters it out before the\n\t * join.\n\t */\n\tprivate withApiBase(path: string): string {\n\t\tconst base = withoutTrailingSlashes(getLankaHost().apiBaseUrl);\n\t\tif (!base) return path;\n\t\tif (!path) return base;\n\n\t\treturn path.startsWith(\"/\") ? `${base}${path}` : `${base}/${path}`;\n\t}\n\n\tprotected buildQueryParams<T extends object>(params: T): URLSearchParams {\n\t\treturn this.queryParamsHandler(params as Record<string, TLankaQueryParams>);\n\t}\n\n\tprotected async request<TReturn = unknown>(\n\t\tpath: string,\n\t\toptions?: TLankaExecuteOptions<TOptions>,\n\t\tmockHandler?: () => Promise<TReturn>,\n\t): Promise<TReturn> {\n\t\treturn this.requestExecutor.execute<TReturn>(this.endpoint(path), options, mockHandler);\n\t}\n\n\t/**\n\t * Allows to replace request implementation at runtime (e.g. feature flags / tests).\n\t * If you prefer static customization - override `request()` in a subclass.\n\t */\n\tprotected setRequest(request: ILankaRequest<TOptions>): void {\n\t\tthis.requestExecutor = request;\n\t}\n\n\tprotected setQueryParamsHandler(handler: TLankaQueryBuilder): void {\n\t\tthis.queryParamsHandler = handler;\n\t}\n}\n\n/**\n * A scheme plus `//` — a URL that already knows where it is going.\n *\n * A standalone function rather than a method: it is not about a particular\n * gateway, and `endpoint()` needs it before any joining.\n */\nfunction isAbsoluteUrl(path: string): boolean {\n\t// The cheap half first: a scheme needs `://`, and `includes` answers without\n\t// starting the regex engine. Every relative path an application writes — which\n\t// is nearly all of them — stops on this line.\n\tif (!path.includes(\"://\")) return false;\n\n\treturn /^[a-z][a-z\\d+\\-.]*:\\/\\//i.test(path);\n}\n\n/**\n * The API base without its trailing slashes, remembered between calls.\n *\n * The host answers the same string for the life of an application, and trimming\n * it is a regex replace otherwise run on every endpoint of every request. One\n * entry is enough: there is one active host, and a second framework in the same\n * process simply replaces what is remembered here.\n */\nlet lastRawBase: string | null = null;\nlet lastTrimmedBase = \"\";\n\nfunction withoutTrailingSlashes(base: string): string {\n\tif (base !== lastRawBase) {\n\t\tlastRawBase = base;\n\t\tlastTrimmedBase = base.replace(/\\/+$/, \"\");\n\t}\n\n\treturn lastTrimmedBase;\n}\n","import { ALankaGateway } from \"../../_abstractions/lanka-gateway/ALankaGateway\";\nimport type { IALankaGatewayConfig } from \"../../_interfaces/IALankaGatewayConfig\";\nimport type { ILankaGatewayContext } from \"../../_interfaces/ILankaGatewayContext\";\n\n/** What a gateway is built from, whichever style builds it. */\nexport interface ILankaGatewayConfig<\n\tTOptions,\n\tTMethods extends object,\n> extends IALankaGatewayConfig<TOptions> {\n\t/** The endpoints this gateway offers, written over its own surface. */\n\tmethods: (context: ILankaGatewayContext<TOptions>) => TMethods;\n}\n\n/**\n * A gateway, without writing a class.\n *\n * The bridge below is the whole mechanism, and it lives here rather than on the\n * base for two reasons. The language reads `protected` from inside a deriving\n * class body and nowhere else, so a factory outside the hierarchy could only\n * reach the public half — the wrong one. And a `toStyleContext` ON the base\n * would put `TOptions` in a method's parameter position, making the class\n * invariant in it: every `ALankaGateway<unknown>` the locator holds would stop\n * accepting a gateway typed for `RequestInit`.\n *\n * One implementation: what comes back is an instance of `ALankaGateway`, so a\n * behaviour fix reaches both styles at once.\n */\nexport const createLankaGateway = <TOptions, TMethods extends object>(\n\tconfig: ILankaGatewayConfig<TOptions, TMethods>,\n): TMethods => {\n\tclass FunctionalGateway extends ALankaGateway<TOptions> {\n\t\t// The base keeps a protected constructor — it is abstract, and a consumer\n\t\t// reaching for `new ALankaGateway()` would get an object with no endpoints.\n\t\t// A subclass may widen it, and this one is the subclass.\n\t\tpublic constructor(gatewayConfig: IALankaGatewayConfig<TOptions>) {\n\t\t\tsuper(gatewayConfig);\n\t\t}\n\n\t\tpublic build(): TMethods {\n\t\t\treturn config.methods({\n\t\t\t\tendpoint: (path) => this.endpoint(path),\n\t\t\t\trequest: (path, options, mockHandler) => this.request(path, options, mockHandler),\n\t\t\t\tbuildQueryParams: (params) => this.buildQueryParams(params),\n\t\t\t\tvalidationService: this.validationService,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn new FunctionalGateway(config).build();\n};\n","import { ALankaTransportRequest } from \"../_abstractions/lanka-transport-request/ALankaTransportRequest\";\nimport type { ILankaTransportRequestConfig } from \"../_abstractions/lanka-transport-request/ALankaTransportRequest\";\nimport type { TLankaRequestInit } from \"../../_types/TLankaRequestInit\";\nimport { LankaFetchTransport } from \"../../lanka-fetch-transport/LankaFetchTransport\";\n\n/**\n * The raw request: hands the `Response` back untouched.\n *\n * The minimal, extensible case — a caller wanting headers, a stream, a blob or a\n * `204` reads them off the response itself. Multipart uploads come through here\n * too: the transport encodes by looking at the body, so posting a `FormData` and\n * posting an object are the same call.\n *\n * `TOptions` is CONSTRAINED to fetch options rather than merely defaulted to\n * them. A consumer widening it — their own `interface IRequestOptions extends\n * TLankaRequestInit` — still gets the shipped transport, because the constraint\n * is what lets the framework hand one over without a cast. Unconstrained, the\n * assignment did not typecheck and core cast its way past it; the cast worked\n * here and was unavailable to the consumer, who wrote a transport instead.\n */\nexport class LankaFetchRequest<\n\tTOptions extends TLankaRequestInit = TLankaRequestInit,\n> extends ALankaTransportRequest<TOptions> {\n\tconstructor(config: ILankaTransportRequestConfig<TOptions> = {}) {\n\t\tsuper(config, () => new LankaFetchTransport());\n\t}\n\n\tprotected parse<TReturn>(response: Response): Promise<TReturn> {\n\t\treturn Promise.resolve(response as unknown as TReturn);\n\t}\n}\n","import { LankaFetchRequest } from \"../../lanka-fetch-request/LankaFetchRequest\";\nimport type { ILankaTransportRequestConfig } from \"../../_abstractions/lanka-transport-request/ALankaTransportRequest\";\nimport type { TLankaRequestInit } from \"../../../_types/TLankaRequestInit\";\n\n/**\n * The functional style of `LankaFetchRequest`: the raw `Response`, for a download or a stream.\n *\n * One line, and that is the point — the factory IS the class, so a behaviour\n * cannot exist in one style and not the other.\n */\nexport const createLankaFetchRequest = <TOptions extends TLankaRequestInit = TLankaRequestInit>(\n\tconfig: ILankaTransportRequestConfig<TOptions> = {},\n): LankaFetchRequest<TOptions> => new LankaFetchRequest<TOptions>(config);\n","import { LankaFetchJsonRequest } from \"../../lanka-fetch-json-request/LankaFetchJsonRequest\";\nimport type { ILankaTransportRequestConfig } from \"../../_abstractions/lanka-transport-request/ALankaTransportRequest\";\nimport type { TLankaRequestInit } from \"../../../_types/TLankaRequestInit\";\n\n/**\n * The functional style of `LankaFetchJsonRequest`: a JSON body, which is what most endpoints answer.\n *\n * One line, and that is the point — the factory IS the class, so a behaviour\n * cannot exist in one style and not the other.\n */\nexport const createLankaFetchJsonRequest = <TOptions extends TLankaRequestInit = TLankaRequestInit>(\n\tconfig: ILankaTransportRequestConfig<TOptions> = {},\n): LankaFetchJsonRequest<TOptions> => new LankaFetchJsonRequest<TOptions>(config);\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/gateway/request/_abstractions/lanka-request/ALankaRequest.ts","../../src/gateway/request/_abstractions/lanka-transport-request/ALankaTransportRequest.ts","../../src/gateway/lanka-fetch-transport/LankaFetchTransport.ts","../../src/gateway/request/lanka-fetch-json-request/LankaFetchJsonRequest.ts","../../src/gateway/_utils/build-lanka-query-params/buildLankaQueryParams.ts","../../src/gateway/_abstractions/lanka-gateway/ALankaGateway.ts","../../src/gateway/_factories/create-lanka-gateway/createLankaGateway.ts","../../src/gateway/request/lanka-fetch-request/LankaFetchRequest.ts","../../src/gateway/request/_factories/create-lanka-fetch-request/createLankaFetchRequest.ts","../../src/gateway/request/_factories/create-lanka-fetch-json-request/createLankaFetchJsonRequest.ts"],"sourcesContent":["import type { TLankaRequestInit } from \"../../../_types/TLankaRequestInit\";\nimport type { TLankaErrorHandler } from \"../../../../errors/_types/TLankaErrorHandler\";\nimport type { ILankaRequest } from \"../../../_interfaces/ILankaRequest\";\nimport type { TLankaExecuteOptions } from \"../../../_types/TLankaExecuteOptions\";\nimport { handleLankaApiError } from \"../../../../errors/handle-lanka-api-error/handleLankaApiError\";\nimport { getLankaFlags } from \"../../../../config/get-lanka-flags/getLankaFlags\";\nimport { getLankaHost } from \"../../../../config/get-lanka-host/getLankaHost\";\nimport { lankaHttpInFlight } from \"../../../inflight/lankaHttpInFlight\";\nimport { LankaError } from \"../../../../errors/lanka-error/LankaError\";\nimport { getActiveRuntime } from \"../../../../_internal/active-runtime/activeRuntime\";\nimport { composeLankaRequestMiddleware } from \"../../lankaRequestMiddleware\";\nimport type { ILankaRequestContext } from \"../../lankaRequestMiddleware\";\n\n/**\n * Tags whatever the transport threw with a kind.\n *\n * Here rather than in the transports: `execute` is the single point EVERY\n * request passes through, and tagging in each of the four transports would be\n * four places to forget it.\n *\n * A real `fetch` throws `TypeError` on a broken connection and a `DOMException`\n * named `AbortError` on cancellation; it does not throw on a status code at all.\n *\n * An already-tagged error is NOT re-tagged: a request-policy plugin may report\n * `domain`, and rewriting that to `network` would lose the one thing the kind\n * exists for.\n */\nfunction classifyTransportError(error: unknown, timedOut: boolean): LankaError {\n\tif (LankaError.is(error)) return error;\n\n\t// The name is read off ANYTHING, not only off `Error`.\n\t//\n\t// `DOMException` — how `fetch` reports cancellation — does not extend `Error`\n\t// everywhere: in a browser yes, in jsdom no. An `instanceof Error` check lets\n\t// cancellation past the tagging, and a raw `AbortError` reaches the app with\n\t// neither `kind` nor `status`: retry policy reads it as non-retryable and the\n\t// app as an unknown error, so a cancelled request is shown to the user as a\n\t// failure.\n\tconst name = readErrorName(error);\n\tconst isAbort = name === \"AbortError\" || name === \"TimeoutError\";\n\tif (isAbort) {\n\t\t// Only whoever assembled the lifetime knows who aborted: `AbortSignal` has\n\t\t// one `abort` for everyone. The distinction carries a decision — a timeout\n\t\t// is shown, a cancellation is not.\n\t\tconst timedOutHere = timedOut || name === \"TimeoutError\";\n\t\treturn new LankaError({\n\t\t\tkind: timedOutHere ? \"timeout\" : \"aborted\",\n\t\t\tmessage: timedOutHere ? getLankaHost().timeoutErrorMessage() : readErrorMessage(error),\n\t\t\tcause: error,\n\t\t});\n\t}\n\n\t// `network` ONLY for what looks like a transport failure. A real `fetch`\n\t// throws `TypeError`; everything else comes from code we did not write — the\n\t// app's error handler, a response transformer, a broken plugin — and calling\n\t// that a network failure would invite the user to retry a request that\n\t// arrived and was processed.\n\t//\n\t// Unknown stays unknown and passes through. The framework asserts only what\n\t// it knows.\n\tif (error instanceof TypeError) {\n\t\treturn new LankaError({\n\t\t\tkind: \"network\",\n\t\t\tmessage: getLankaHost().networkErrorMessage(),\n\t\t\tissues: [error.message],\n\t\t\tcause: error,\n\t\t});\n\t}\n\n\treturn error as LankaError;\n}\n\n/** The error name, off `Error`, `DOMException` or anything else carrying one. */\nfunction readErrorName(error: unknown): string | undefined {\n\tif (typeof error !== \"object\" || error === null) return undefined;\n\tconst name: unknown = (error as { name?: unknown }).name;\n\treturn typeof name === \"string\" ? name : undefined;\n}\n\n/**\n * The error text, when there is one.\n *\n * An object without `message` is not stringified: `String({})` yields\n * `[object Object]`, which occupies the message slot and says nothing. Empty is\n * more honest.\n */\nfunction readErrorMessage(error: unknown): string {\n\tif (typeof error === \"string\") return error;\n\tif (typeof error !== \"object\" || error === null) return \"\";\n\tconst message: unknown = (error as { message?: unknown }).message;\n\treturn typeof message === \"string\" ? message : \"\";\n}\n\n/**\n * How a gateway request goes on the wire.\n *\n * A subclass declares one method, `request()`, and does only its own work there:\n * `LankaFetchJsonRequest` returns parsed JSON, `LankaFetchRequest` returns the\n * whole response, a custom kind returns whatever it likes.\n *\n * Overriding `request()` customises the request flow, failure handling, mock\n * substitution, response transformation and log interception.\n */\nexport abstract class ALankaRequest<\n\tTOptions = TLankaRequestInit,\n> implements ILankaRequest<TOptions> {\n\tprotected readonly errorHandler?: TLankaErrorHandler;\n\tprotected readonly useMock: boolean;\n\n\tprotected constructor(config: { errorHandler?: TLankaErrorHandler; useMock?: boolean }) {\n\t\tconst flags = getLankaFlags();\n\n\t\t// The default error-body handler lives HERE because the request is the only\n\t\t// thing that sees the `Response`. Put on the gateway it would sit in a field\n\t\t// nobody reads — the request takes the handler from ITS OWN config — and a\n\t\t// consumer passing `errorHandler` to the gateway would get silence.\n\t\t//\n\t\t// The parse is cheap: core reads the body once and takes `message` from it;\n\t\t// backend-specific shapes are parsed by `@lankajs/plugin-http`.\n\t\tthis.errorHandler = config.errorHandler ?? handleLankaApiError;\n\n\t\tthis.useMock = config.useMock ?? flags.isMockMode ?? false;\n\t}\n\n\t/**\n\t * Performs the request and returns its result — a response, JSON or a custom\n\t * type.\n\t *\n\t * @param endpoint Full URL\n\t * @param options Transport-specific options\n\t * @param mockHandler Mock, when there is one\n\t */\n\tprotected abstract request<TReturn = Response>(\n\t\tendpoint: string,\n\t\toptions?: TOptions,\n\t\tmockHandler?: () => Promise<TReturn>,\n\t): Promise<TReturn>;\n\n\t/**\n\t * The single point EVERY gateway request passes through.\n\t *\n\t * Hence the in-flight accounting here: intent prefetch stands down while\n\t * anything else is on the wire. The `finally` matters more than the increment —\n\t * a rejected request that never decremented would disable prefetching for the\n\t * rest of the session.\n\t */\n\tpublic async execute<TReturn = Response>(\n\t\tendpoint: string,\n\t\toptions?: TLankaExecuteOptions<TOptions>,\n\t\tmockHandler?: () => Promise<TReturn>,\n\t): Promise<TReturn> {\n\t\tconst runtime = getActiveRuntime();\n\t\tconst { signal, timeoutMs, ...rest } = (options ?? {}) as TLankaExecuteOptions<TOptions>;\n\t\t// The caller's deadline travels IN THE CONTEXT, not only in the closure:\n\t\t// otherwise a policy assigning deadlines per request class could not tell\n\t\t// \"no deadline given\" from \"given by the caller\" and would override an\n\t\t// explicit request with a blanket default.\n\t\tconst fallbackDeadline = runtime?.requestTimeoutMs;\n\n\t\t// No options passed means none are produced. Destructuring yields `{}` even\n\t\t// from `undefined`, and handing that empty object to the transport would\n\t\t// change the request: \"no options\" and \"empty options\" are different\n\t\t// statements, and the transport is entitled to tell them apart.\n\t\tconst passedOptions: unknown = options === undefined ? undefined : rest;\n\n\t\t/*\n\t\t * The lifetime is assembled PER ATTEMPT, not per call, and that does two\n\t\t * things at once.\n\t\t *\n\t\t * Under retry a shared deadline would start the third attempt with whatever\n\t\t * the first two left, so the retry aborts before reaching the server.\n\t\t *\n\t\t * And it is the only way to let middleware set the deadline: `ctx.timeoutMs`\n\t\t * is read on every attempt, which is how a request-policy plugin assigns a\n\t\t * deadline per request CLASS — a file upload and a list read cannot share\n\t\t * one value.\n\t\t *\n\t\t * Caller cancellation stays end-to-end: one `signal` for all attempts.\n\t\t */\n\t\tlet lastTimedOut = false;\n\n\t\t// Tagging happens INSIDE, around the request itself, not in the outer\n\t\t// catch: middleware must receive an already-tagged error, or a retrying\n\t\t// middleware cannot tell a network failure from a domain rejection and\n\t\t// retries what must not be retried. The outer catch stays as a backstop.\n\t\tconst perform = async (ctx: ILankaRequestContext): Promise<unknown> => {\n\t\t\tconst lifetime = createRequestLifetime(signal, ctx.timeoutMs ?? fallbackDeadline);\n\t\t\ttry {\n\t\t\t\treturn await this.request<TReturn>(\n\t\t\t\t\tctx.endpoint,\n\t\t\t\t\twithSignal(ctx.options, lifetime.signal) as TOptions,\n\t\t\t\t\tmockHandler,\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tlastTimedOut = lifetime.timedOut();\n\t\t\t\tthrow classifyTransportError(error, lastTimedOut);\n\t\t\t} finally {\n\t\t\t\tlifetime.dispose();\n\t\t\t}\n\t\t};\n\n\t\tconst run = composeLankaRequestMiddleware(runtime?.requestMiddleware ?? [], perform);\n\n\t\tlankaHttpInFlight.begin();\n\t\ttry {\n\t\t\t// The chain runs INSIDE the same guard as the request. A catch placed\n\t\t\t// outside would leave a permanent +1 when a plugin throws, disabling\n\t\t\t// prefetch for the rest of the session — the very defect this `finally`\n\t\t\t// exists to prevent.\n\t\t\treturn (await run({\n\t\t\t\tendpoint,\n\t\t\t\toptions: passedOptions,\n\t\t\t\tattempt: 1,\n\t\t\t\ttimeoutMs,\n\t\t\t})) as TReturn;\n\t\t} catch (error) {\n\t\t\tthrow classifyTransportError(error, lastTimedOut);\n\t\t} finally {\n\t\t\tlankaHttpInFlight.end();\n\t\t}\n\t}\n}\n\ninterface IRequestLifetime {\n\treadonly signal: AbortSignal | undefined;\n\t/** Whether OUR timer aborted the request rather than the caller. */\n\ttimedOut(): boolean;\n\tdispose(): void;\n}\n\n/**\n * Combines the caller's signal and our own timeout into one request lifetime.\n *\n * The outcome looks the same — an interrupted request — but the decisions\n * differ: a timeout is shown and offered for retry, a user cancellation is not\n * shown at all. `AbortSignal` does not distinguish them: one `abort`, whose\n * reason belongs to whoever got there first. Hence the private flag.\n *\n * Not `AbortSignal.timeout` alone: it cannot combine with a foreign signal\n * without `AbortSignal.any`, which older engines lack. Assembling by hand works\n * everywhere and costs one listener.\n */\nfunction createRequestLifetime(\n\texternal: AbortSignal | undefined,\n\ttimeoutMs: number | undefined,\n): IRequestLifetime {\n\tif (!external && !timeoutMs) {\n\t\treturn { signal: undefined, timedOut: () => false, dispose: () => undefined };\n\t}\n\n\tconst controller = new AbortController();\n\tlet expired = false;\n\n\tconst timer =\n\t\ttimeoutMs === undefined\n\t\t\t? undefined\n\t\t\t: setTimeout(() => {\n\t\t\t\t\texpired = true;\n\t\t\t\t\tcontroller.abort(new DOMException(\"Request timed out\", \"TimeoutError\"));\n\t\t\t\t}, timeoutMs);\n\n\tconst onExternalAbort = (): void => {\n\t\tcontroller.abort(external?.reason);\n\t};\n\n\tif (external) {\n\t\tif (external.aborted) onExternalAbort();\n\t\telse external.addEventListener(\"abort\", onExternalAbort, { once: true });\n\t}\n\n\treturn {\n\t\tsignal: controller.signal,\n\t\ttimedOut: () => expired,\n\t\tdispose: () => {\n\t\t\tif (timer !== undefined) clearTimeout(timer);\n\t\t\texternal?.removeEventListener(\"abort\", onExternalAbort);\n\t\t},\n\t};\n}\n\n/**\n * Attaches the signal to the options, inventing nothing.\n *\n * With no options and no signal the transport receives `undefined` — exactly\n * what the caller passed. An empty object instead looks harmless but is a\n * different statement, and the transport is entitled to tell them apart.\n */\nfunction withSignal(options: unknown, signal: AbortSignal | undefined): unknown {\n\tif (signal === undefined) return options;\n\treturn { ...(options ?? {}), signal };\n}\n","import { ALankaRequest } from \"../lanka-request/ALankaRequest\";\nimport { LankaError } from \"../../../../errors/lanka-error/LankaError\";\nimport { getLankaFlags } from \"../../../../config/get-lanka-flags/getLankaFlags\";\nimport { getLankaHost } from \"../../../../config/get-lanka-host/getLankaHost\";\nimport type { ILankaTransport } from \"../../../_interfaces/ILankaTransport\";\nimport type { TLankaRequestInit } from \"../../../_types/TLankaRequestInit\";\nimport type { TLankaErrorHandler } from \"../../../../errors/_types/TLankaErrorHandler\";\n\nexport interface ILankaTransportRequestConfig<TOptions> {\n\ttransport?: ILankaTransport<TOptions>;\n\terrorHandler?: TLankaErrorHandler;\n\tuseMock?: boolean;\n}\n\n/**\n * The shape every fetch-backed request has: mock, send, check, parse.\n *\n * The concrete requests differ in ONE place — how they turn a successful\n * `Response` into a value — so a third kind is a subclass with one method rather\n * than a third copy of the sequence.\n *\n * The default transport stays a constructor PARAMETER even though both shipped\n * kinds pass the same one. It is the seam a request kind that is not fetch-backed\n * comes through: this template is the mock/send/check/parse sequence, and nothing\n * in it is about HTTP. Defaulting the parameter here would fix `TOptions` to\n * fetch options for everyone who reuses the sequence.\n */\nexport abstract class ALankaTransportRequest<\n\tTOptions = TLankaRequestInit,\n> extends ALankaRequest<TOptions> {\n\tprotected readonly transport: ILankaTransport<TOptions>;\n\n\tprotected constructor(\n\t\tconfig: ILankaTransportRequestConfig<TOptions>,\n\t\tcreateDefaultTransport: () => ILankaTransport<TOptions>,\n\t) {\n\t\tconst flags = getLankaFlags();\n\n\t\tsuper({\n\t\t\terrorHandler: config.errorHandler,\n\t\t\tuseMock: config.useMock ?? flags.isMockMode ?? false,\n\t\t});\n\n\t\tthis.transport = config.transport ?? createDefaultTransport();\n\t}\n\n\tprotected async request<TReturn>(\n\t\tendpoint: string,\n\t\toptions?: TOptions,\n\t\tmockHandler?: () => Promise<TReturn>,\n\t): Promise<TReturn> {\n\t\tif (this.useMock && mockHandler) {\n\t\t\treturn await mockHandler();\n\t\t}\n\n\t\tconst response = await this.transport.request(endpoint, options);\n\t\tif (!response.ok) return await this.refuse(response);\n\n\t\treturn await this.parse<TReturn>(response);\n\t}\n\n\t/**\n\t * Turns a successful response into the value the caller asked for.\n\t *\n\t * The one step that genuinely differs between request kinds.\n\t */\n\tprotected abstract parse<TReturn>(response: Response): Promise<TReturn>;\n\n\t/**\n\t * Refuses an unsuccessful response, and never returns.\n\t *\n\t * `TLankaErrorHandler` is typed `Promise<never>` — a handler must throw. It is\n\t * still CALLED and then followed by a throw, because a handler that breaks its\n\t * contract and returns would otherwise hand `undefined` back as if the request\n\t * had succeeded, and a non-value must never become a value.\n\t */\n\tprotected async refuse(response: Response): Promise<never> {\n\t\tif (this.errorHandler) {\n\t\t\tawait this.errorHandler(response);\n\t\t}\n\n\t\t// Kind `http`, not a bare Error: the server answered, and answered with a\n\t\t// code. Without the kind this reads as a network failure, and the user is\n\t\t// offered a retry of a request that already got a meaningful answer.\n\t\tthrow new LankaError({\n\t\t\tkind: \"http\",\n\t\t\tmessage: getLankaHost().httpErrorMessage(response.status),\n\t\t\tstatus: response.status,\n\t\t});\n\t}\n}\n","import type { ILankaTransport } from \"../_interfaces/ILankaTransport\";\nimport type { TLankaRequestInit } from \"../_types/TLankaRequestInit\";\n\n/** What an unencoded body is sent as, and the header that says so. */\nconst CONTENT_TYPE = \"content-type\";\nconst JSON_CONTENT_TYPE = \"application/json\";\n\n/**\n * The network seam: `fetch`, plus the two things `fetch` cannot be told.\n *\n * ## Why there is ONE of these\n *\n * There were three — a plain one, a JSON one and a multipart one — and the split\n * was wrong at birth. `ILankaTransport` exists so a consumer can change the\n * PROTOCOL: a native bridge, a socket, a double that never leaves the process.\n * The three differed in a `content-type` header. That is not a protocol, it is an\n * encoding, and an encoding is a property of the CALL: a gateway with fourteen\n * JSON endpoints and one upload had no way to say so, because its request kind —\n * and with it its transport — was fixed in its constructor. The application that\n * hit this added a `useFormData` flag to its own options and wrote its own\n * transport to read it.\n *\n * So the encoding is decided here, per call, by looking at the body. A gateway\n * posts `FormData` to one endpoint and an object to the next, and neither it nor\n * the request kind has to know.\n *\n * ## What does NOT belong here\n *\n * The base URL (`ALankaGateway` prefixes `apiBaseUrl`), credentials, static\n * headers, CSRF, retry, auth refresh, idempotency keys and deadlines. Every one\n * of those is policy around a request rather than a way of sending one, and every\n * one is a middleware — `useRequestMiddleware`, which `@lankajs/plugin-http`\n * occupies. A transport that grew them would be a second composition mechanism\n * beside the one core already publishes, and \"where does a header get added\"\n * would have two answers.\n */\nexport class LankaFetchTransport implements ILankaTransport<TLankaRequestInit> {\n\tasync request(resource: RequestInfo, options?: TLankaRequestInit): Promise<Response> {\n\t\t// Nothing to encode: the object the caller assembled reaches `fetch`\n\t\t// unchanged, `undefined` included. \"No options\" and \"empty options\" are\n\t\t// different statements and `fetch` is entitled to tell them apart.\n\t\tif (options === undefined || options.body === undefined || options.body === null) {\n\t\t\treturn await fetch(resource, options as RequestInit | undefined);\n\t\t}\n\n\t\tconst { body, headers } = options;\n\n\t\t// Multipart: the header is REMOVED, not set. The boundary is generated with\n\t\t// the body, and a hand-written `content-type` carries none — the server then\n\t\t// reads zero fields out of a request that looks correct.\n\t\tif (isFormData(body)) {\n\t\t\tif (headers === undefined) return await fetch(resource, options as RequestInit);\n\n\t\t\tconst stripped = new Headers(headers);\n\t\t\tstripped.delete(CONTENT_TYPE);\n\t\t\treturn await fetch(resource, { ...options, headers: stripped } as RequestInit);\n\t\t}\n\n\t\t// Already something `fetch` understands: untouched.\n\t\tif (isEncodedBody(body)) return await fetch(resource, options as RequestInit);\n\n\t\treturn await fetch(resource, {\n\t\t\t...options,\n\t\t\theaders: withJsonContentType(headers),\n\t\t\tbody: JSON.stringify(body),\n\t\t});\n\t}\n}\n\n/**\n * The header set, with `content-type` filled in only if the caller left it out.\n *\n * Not overwritten: `application/merge-patch+json` and `application/ld+json` are\n * JSON that a server distinguishes, and a transport that flattened them to\n * `application/json` would turn a PATCH into a request the server refuses — for\n * a header the caller had already written correctly.\n */\nfunction withJsonContentType(headers: HeadersInit | undefined): Headers {\n\tconst result = new Headers(headers);\n\tif (!result.has(CONTENT_TYPE)) result.set(CONTENT_TYPE, JSON_CONTENT_TYPE);\n\treturn result;\n}\n\n/**\n * Whether `fetch` can already send this body.\n *\n * Every global here is guarded by `typeof`: core runs in node and React Native\n * as well as a browser, and `ReadableStream` in particular is absent on engines\n * this framework supports. An unguarded `instanceof` against a missing global is\n * a `ReferenceError` on the first request, from a transport, in production.\n *\n * Cheapest and most common first. A string — an already-serialised body — stops\n * on line one.\n */\nfunction isEncodedBody(body: unknown): body is BodyInit {\n\tif (typeof body === \"string\") return true;\n\tif (typeof URLSearchParams !== \"undefined\" && body instanceof URLSearchParams) return true;\n\tif (typeof Blob !== \"undefined\" && body instanceof Blob) return true;\n\tif (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return true;\n\treturn typeof ReadableStream !== \"undefined\" && body instanceof ReadableStream;\n}\n\n/**\n * Multipart, checked separately because it is the one body whose header is\n * removed rather than added.\n */\nfunction isFormData(body: unknown): body is FormData {\n\treturn typeof FormData !== \"undefined\" && body instanceof FormData;\n}\n","import { ALankaTransportRequest } from \"../_abstractions/lanka-transport-request/ALankaTransportRequest\";\nimport type { ILankaTransportRequestConfig } from \"../_abstractions/lanka-transport-request/ALankaTransportRequest\";\nimport type { TLankaRequestInit } from \"../../_types/TLankaRequestInit\";\nimport { LankaFetchTransport } from \"../../lanka-fetch-transport/LankaFetchTransport\";\nimport { LankaError } from \"../../../errors/lanka-error/LankaError\";\n\n/** The JSON request: parses the body, and refuses a body that is not JSON. */\n/**\n * A request kind that answers a parsed JSON body — what most endpoints return.\n *\n * A gateway holds one, and is handed it rather than constructing it, which is\n * what lets a test give the same gateway a transport that never leaves the\n * process. `createLankaFetchJsonRequest()` builds the same class.\n */\nexport class LankaFetchJsonRequest<\n\tTOptions extends TLankaRequestInit = TLankaRequestInit,\n> extends ALankaTransportRequest<TOptions> {\n\tconstructor(config: ILankaTransportRequestConfig<TOptions> = {}) {\n\t\tsuper(config, () => new LankaFetchTransport());\n\t}\n\n\t/**\n\t * Parses the body, or names the failure.\n\t *\n\t * The content type is read for the ERROR MESSAGE only. Using it to choose\n\t * between two parse paths, where the second answers `{}` for any non-empty\n\t * body it cannot parse, fails silently: the caller's schema is the first thing\n\t * to notice, and the caller's SCREEN is where it shows up.\n\t *\n\t * A measured case: a dev server whose `/api` fell through to the SPA fallback\n\t * answered `200 text/html` with `index.html`. Turned into `{}`, the schema\n\t * refused it and a person read a validator's issue list on the sign-in card. A\n\t * body a JSON transport cannot parse is never a value — it is a misrouted\n\t * request, and saying so names the actual failure.\n\t */\n\tprotected async parse<TReturn>(response: Response): Promise<TReturn> {\n\t\tconst contentType = response.headers.get(\"content-type\");\n\n\t\tconst text = await response.text();\n\t\tif (!text) {\n\t\t\treturn undefined as TReturn;\n\t\t}\n\n\t\ttry {\n\t\t\treturn JSON.parse(text) as TReturn;\n\t\t} catch (error) {\n\t\t\t// `schema`, not `network`: the request arrived, the server answered, and\n\t\t\t// the answer was the wrong shape. Not cosmetic — a network failure\n\t\t\t// invites a retry, while retrying a broken contract is pointless and\n\t\t\t// blaming the user for it more so.\n\t\t\tthrow new LankaError({\n\t\t\t\tkind: \"schema\",\n\t\t\t\tmessage:\n\t\t\t\t\t`Failed to parse JSON response (content-type: ${contentType ?? \"none\"}): ` +\n\t\t\t\t\t`${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t}\n}\n","import { TLankaQueryParams } from \"../../_types/TLankaQueryParams\";\nimport { TLankaQueryBuilder } from \"../../_types/TLankaQueryBuilder\";\n\n/**\n * The query string a gateway sends, from the object a method was called with.\n *\n * Arrays become `key[]` repeated, nested objects become `key[inner]`: the shape\n * most JSON APIs read back without being told about it.\n *\n * Plain loops over `Object.keys` rather than `Object.entries` and `forEach`: this\n * runs on every request that carries a filter, and the pair array `entries`\n * builds — two allocations per key, thrown away immediately — is a cost with\n * nothing to show for it.\n */\nexport const buildLankaQueryParams: TLankaQueryBuilder = <T extends Record<string, unknown>>(\n\tinput: T,\n): URLSearchParams => {\n\tconst params = new URLSearchParams();\n\n\tconst append = (key: string, value: TLankaQueryParams): void => {\n\t\tif (value == null) return;\n\n\t\tif (Array.isArray(value)) {\n\t\t\t// The bracketed key once for the whole array, not once per element.\n\t\t\tconst itemKey = `${key}[]`;\n\t\t\tfor (let index = 0; index < value.length; index += 1) append(itemKey, value[index]);\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (typeof value === \"object\") {\n\t\t\tconst inner = value as Record<string, TLankaQueryParams>;\n\t\t\tconst innerKeys = Object.keys(inner);\n\n\t\t\tfor (let index = 0; index < innerKeys.length; index += 1) {\n\t\t\t\tconst innerKey = innerKeys[index];\n\t\t\t\tappend(`${key}[${innerKey}]`, inner[innerKey]);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tparams.append(key, String(value));\n\t};\n\n\tconst source = input as Record<string, TLankaQueryParams>;\n\tconst keys = Object.keys(source);\n\n\tfor (let index = 0; index < keys.length; index += 1) append(keys[index], source[keys[index]]);\n\n\treturn params;\n};\n","import type { TLankaRequestInit } from \"../../_types/TLankaRequestInit\";\nimport type { IALankaGatewayConfig } from \"../../_interfaces/IALankaGatewayConfig\";\nimport { lankaLogger } from \"../../../logger/lanka-logger/LankaLogger\";\nimport { LankaFetchJsonRequest } from \"../../request/lanka-fetch-json-request/LankaFetchJsonRequest\";\nimport type { ILankaRequest } from \"../../_interfaces/ILankaRequest\";\nimport type { TLankaExecuteOptions } from \"../../_types/TLankaExecuteOptions\";\nimport { buildLankaQueryParams } from \"../../_utils/build-lanka-query-params/buildLankaQueryParams\";\nimport { TLankaQueryParams } from \"../../_types/TLankaQueryParams\";\nimport { TLankaQueryBuilder } from \"../../_types/TLankaQueryBuilder\";\nimport { getLankaFlags } from \"../../../config/get-lanka-flags/getLankaFlags\";\nimport { getLankaHost } from \"../../../config/get-lanka-host/getLankaHost\";\nimport { lankaStandardValidator } from \"../../../validation/lanka-standard-validator/lankaStandardValidator\";\nimport type { ILankaValidator } from \"../../../validation/lanka-standard-validator/lankaStandardValidator\";\n\nexport abstract class ALankaGateway<TOptions = TLankaRequestInit> {\n\tprotected requestExecutor: ILankaRequest<TOptions>;\n\tprotected queryParamsHandler: TLankaQueryBuilder;\n\n\t/**\n\t * The validator a method checks a response body with.\n\t *\n\t * `config.validationService` when one was given, the Standard Schema port\n\t * otherwise. It used to be accepted by the config and read by nothing: a\n\t * consumer handing a test double to the gateway got the real validator and no\n\t * error, which is the worst kind of ignored option — it looks honoured.\n\t */\n\tprotected readonly validationService: ILankaValidator;\n\n\tprotected readonly useMock: boolean;\n\tprotected readonly basePath: string;\n\n\tprotected constructor(config: IALankaGatewayConfig<TOptions>) {\n\t\tlankaLogger.printGatewayLog(\"Create gateway\", this);\n\t\tconst flags = getLankaFlags();\n\t\tthis.useMock = config.useMock ?? flags.isMockMode ?? false;\n\t\tthis.validationService = config.validationService ?? lankaStandardValidator;\n\n\t\t// A gateway with nothing said about transport talks JSON over `fetch`, which\n\t\t// is what almost every one of them does. Supplying a request is how a gateway\n\t\t// stops being ordinary — a raw `Response`, a multipart upload, a transport\n\t\t// that never leaves the process — and that stays a decision rather than a\n\t\t// line every gateway has to carry to be born.\n\t\t// The cast covers the FRAMEWORK picking its own fallback, and nothing a\n\t\t// consumer does. `TOptions` is unconstrained here on purpose — a gateway may\n\t\t// front a request that never speaks HTTP, and the port promises exactly that\n\t\t// — so the JSON default cannot be proven to fit a `TOptions` nobody has\n\t\t// named yet. A consumer whose options are not fetch-shaped supplies\n\t\t// `request`, and this line never runs for them.\n\t\tthis.requestExecutor = config.request ?? new LankaFetchJsonRequest();\n\n\t\tthis.basePath = config.basePath ?? \"\";\n\t\tthis.queryParamsHandler = config.queryParamsHandler ?? buildLankaQueryParams;\n\t}\n\n\t/**\n\t * Resolves endpoint for request.\n\t * - Absolute paths (starting with \"/\") are returned as-is\n\t * - Relative paths are joined with basePath\n\t * - Query-only strings like \"?a=1\" are attached to basePath\n\t */\n\tprotected endpoint(path: string = \"\"): string {\n\t\t// An absolute URL is detected BEFORE joining with `basePath`, not after:\n\t\t// otherwise `https://other.host/health` first becomes\n\t\t// `/things/https://other.host/health` and there is nothing left to detect.\n\t\tif (isAbsoluteUrl(path)) return path;\n\n\t\treturn this.withApiBase(this.resolvePath(path));\n\t}\n\n\t/**\n\t * Joins `basePath` and the method path.\n\t */\n\tprivate resolvePath(path: string): string {\n\t\tif (!path) return this.basePath;\n\n\t\tif (path.startsWith(\"/\")) return path;\n\n\t\tif (path.startsWith(\"?\")) return `${this.basePath}${path}`;\n\n\t\t// No leading-slash case here: the check above already returned for one, so\n\t\t// stripping it again was a branch no input could take — uncoverable by\n\t\t// construction, and it counted against the coverage floor that gates this\n\t\t// package.\n\t\tconst left = this.basePath.endsWith(\"/\") ? this.basePath.slice(0, -1) : this.basePath;\n\t\treturn `${left}/${path}`;\n\t}\n\n\t/**\n\t * Prefixes the API base URL from the host contract.\n\t *\n\t * Here rather than in every consumer: otherwise each consumer knows the URL\n\t * and the framework does not, and a realtime plugin would have to know a\n\t * specific application's build.\n\t *\n\t * Declaring the field and not using it would be worse than not declaring it: a\n\t * declaration nothing is built from is a second truth, free to diverge from\n\t * the first.\n\t *\n\t * An absolute URL never reaches here — `endpoint()` filters it out before the\n\t * join.\n\t */\n\tprivate withApiBase(path: string): string {\n\t\tconst base = withoutTrailingSlashes(getLankaHost().apiBaseUrl);\n\t\tif (!base) return path;\n\t\tif (!path) return base;\n\n\t\treturn path.startsWith(\"/\") ? `${base}${path}` : `${base}/${path}`;\n\t}\n\n\tprotected buildQueryParams<T extends object>(params: T): URLSearchParams {\n\t\treturn this.queryParamsHandler(params as Record<string, TLankaQueryParams>);\n\t}\n\n\tprotected async request<TReturn = unknown>(\n\t\tpath: string,\n\t\toptions?: TLankaExecuteOptions<TOptions>,\n\t\tmockHandler?: () => Promise<TReturn>,\n\t): Promise<TReturn> {\n\t\treturn this.requestExecutor.execute<TReturn>(this.endpoint(path), options, mockHandler);\n\t}\n\n\t/**\n\t * Allows to replace request implementation at runtime (e.g. feature flags / tests).\n\t * If you prefer static customization - override `request()` in a subclass.\n\t */\n\tprotected setRequest(request: ILankaRequest<TOptions>): void {\n\t\tthis.requestExecutor = request;\n\t}\n\n\tprotected setQueryParamsHandler(handler: TLankaQueryBuilder): void {\n\t\tthis.queryParamsHandler = handler;\n\t}\n}\n\n/**\n * A scheme plus `//` — a URL that already knows where it is going.\n *\n * A standalone function rather than a method: it is not about a particular\n * gateway, and `endpoint()` needs it before any joining.\n */\nfunction isAbsoluteUrl(path: string): boolean {\n\t// The cheap half first: a scheme needs `://`, and `includes` answers without\n\t// starting the regex engine. Every relative path an application writes — which\n\t// is nearly all of them — stops on this line.\n\tif (!path.includes(\"://\")) return false;\n\n\treturn /^[a-z][a-z\\d+\\-.]*:\\/\\//i.test(path);\n}\n\n/**\n * The API base without its trailing slashes, remembered between calls.\n *\n * The host answers the same string for the life of an application, and trimming\n * it is a regex replace otherwise run on every endpoint of every request. One\n * entry is enough: there is one active host, and a second framework in the same\n * process simply replaces what is remembered here.\n */\nlet lastRawBase: string | null = null;\nlet lastTrimmedBase = \"\";\n\nfunction withoutTrailingSlashes(base: string): string {\n\tif (base !== lastRawBase) {\n\t\tlastRawBase = base;\n\t\tlastTrimmedBase = base.replace(/\\/+$/, \"\");\n\t}\n\n\treturn lastTrimmedBase;\n}\n","import { ALankaGateway } from \"../../_abstractions/lanka-gateway/ALankaGateway\";\nimport type { IALankaGatewayConfig } from \"../../_interfaces/IALankaGatewayConfig\";\nimport type { ILankaGatewayContext } from \"../../_interfaces/ILankaGatewayContext\";\n\n/** What a gateway is built from, whichever style builds it. */\nexport interface ILankaGatewayConfig<\n\tTOptions,\n\tTMethods extends object,\n> extends IALankaGatewayConfig<TOptions> {\n\t/** The endpoints this gateway offers, written over its own surface. */\n\tmethods: (context: ILankaGatewayContext<TOptions>) => TMethods;\n}\n\n/**\n * A gateway, without writing a class.\n *\n * The bridge below is the whole mechanism, and it lives here rather than on the\n * base for two reasons. The language reads `protected` from inside a deriving\n * class body and nowhere else, so a factory outside the hierarchy could only\n * reach the public half — the wrong one. And a `toStyleContext` ON the base\n * would put `TOptions` in a method's parameter position, making the class\n * invariant in it: every `ALankaGateway<unknown>` the locator holds would stop\n * accepting a gateway typed for `RequestInit`.\n *\n * One implementation: what comes back is an instance of `ALankaGateway`, so a\n * behaviour fix reaches both styles at once.\n */\nexport const createLankaGateway = <TOptions, TMethods extends object>(\n\tconfig: ILankaGatewayConfig<TOptions, TMethods>,\n): TMethods => {\n\tclass FunctionalGateway extends ALankaGateway<TOptions> {\n\t\t// The base keeps a protected constructor — it is abstract, and a consumer\n\t\t// reaching for `new ALankaGateway()` would get an object with no endpoints.\n\t\t// A subclass may widen it, and this one is the subclass.\n\t\tpublic constructor(gatewayConfig: IALankaGatewayConfig<TOptions>) {\n\t\t\tsuper(gatewayConfig);\n\t\t}\n\n\t\tpublic build(): TMethods {\n\t\t\treturn config.methods({\n\t\t\t\tendpoint: (path) => this.endpoint(path),\n\t\t\t\trequest: (path, options, mockHandler) => this.request(path, options, mockHandler),\n\t\t\t\tbuildQueryParams: (params) => this.buildQueryParams(params),\n\t\t\t\tvalidationService: this.validationService,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn new FunctionalGateway(config).build();\n};\n","import { ALankaTransportRequest } from \"../_abstractions/lanka-transport-request/ALankaTransportRequest\";\nimport type { ILankaTransportRequestConfig } from \"../_abstractions/lanka-transport-request/ALankaTransportRequest\";\nimport type { TLankaRequestInit } from \"../../_types/TLankaRequestInit\";\nimport { LankaFetchTransport } from \"../../lanka-fetch-transport/LankaFetchTransport\";\n\n/**\n * The raw request: hands the `Response` back untouched.\n *\n * The minimal, extensible case — a caller wanting headers, a stream, a blob or a\n * `204` reads them off the response itself. Multipart uploads come through here\n * too: the transport encodes by looking at the body, so posting a `FormData` and\n * posting an object are the same call.\n *\n * `TOptions` is CONSTRAINED to fetch options rather than merely defaulted to\n * them. A consumer widening it — their own `interface IRequestOptions extends\n * TLankaRequestInit` — still gets the shipped transport, because the constraint\n * is what lets the framework hand one over without a cast. Unconstrained, the\n * assignment did not typecheck and core cast its way past it; the cast worked\n * here and was unavailable to the consumer, who wrote a transport instead.\n */\nexport class LankaFetchRequest<\n\tTOptions extends TLankaRequestInit = TLankaRequestInit,\n> extends ALankaTransportRequest<TOptions> {\n\tconstructor(config: ILankaTransportRequestConfig<TOptions> = {}) {\n\t\tsuper(config, () => new LankaFetchTransport());\n\t}\n\n\tprotected parse<TReturn>(response: Response): Promise<TReturn> {\n\t\treturn Promise.resolve(response as unknown as TReturn);\n\t}\n}\n","import { LankaFetchRequest } from \"../../lanka-fetch-request/LankaFetchRequest\";\nimport type { ILankaTransportRequestConfig } from \"../../_abstractions/lanka-transport-request/ALankaTransportRequest\";\nimport type { TLankaRequestInit } from \"../../../_types/TLankaRequestInit\";\n\n/**\n * The functional style of `LankaFetchRequest`: the raw `Response`, for a download or a stream.\n *\n * One line, and that is the point — the factory IS the class, so a behaviour\n * cannot exist in one style and not the other.\n */\nexport const createLankaFetchRequest = <TOptions extends TLankaRequestInit = TLankaRequestInit>(\n\tconfig: ILankaTransportRequestConfig<TOptions> = {},\n): LankaFetchRequest<TOptions> => new LankaFetchRequest<TOptions>(config);\n","import { LankaFetchJsonRequest } from \"../../lanka-fetch-json-request/LankaFetchJsonRequest\";\nimport type { ILankaTransportRequestConfig } from \"../../_abstractions/lanka-transport-request/ALankaTransportRequest\";\nimport type { TLankaRequestInit } from \"../../../_types/TLankaRequestInit\";\n\n/**\n * The functional style of `LankaFetchJsonRequest`: a JSON body, which is what most endpoints answer.\n *\n * One line, and that is the point — the factory IS the class, so a behaviour\n * cannot exist in one style and not the other.\n */\nexport const createLankaFetchJsonRequest = <TOptions extends TLankaRequestInit = TLankaRequestInit>(\n\tconfig: ILankaTransportRequestConfig<TOptions> = {},\n): LankaFetchJsonRequest<TOptions> => new LankaFetchJsonRequest<TOptions>(config);\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,19 +1,20 @@
1
- export { I as ILankaBootstrapConfig, a as ILankaInstance, b as ILankaInstanceConfig, c as ILankaPlugin, d as ILankaScenarioBootstrapConfig, e as ILankaServiceConfig, f as createLanka } from './createLanka-DI1CSy2Q.js';
1
+ export { I as ILankaBootstrapConfig, a as ILankaInstance, b as ILankaInstanceConfig, c as ILankaPlugin, d as ILankaScenarioBootstrapConfig, e as ILankaServiceConfig, f as createLanka } from './createLanka-NrlvN-WQ.js';
2
2
  export { ALankaPlugin, ILankaStartOptions, TLankaStartConfig, resetActiveLanka, startLanka } from './bootstrap/index.js';
3
3
  export { ILankaRoleFactory, ILankaRoleOpening, TLankaRoleOpener, defineLankaRole } from './role/index.js';
4
4
  export { ILankaHostConfig, createLankaHost, getLankaFlags, getLankaHost } from './config/index.js';
5
5
  export { I as ILankaFlags, a as ILankaHost, b as ILankaRuntimeConfig } from './ILankaRuntimeConfig-Vl436GWK.js';
6
6
  export { I as ILankaLocatorConfig } from './LankaSharedStoreLocator-zS2kLu-S.js';
7
7
  export { lankaLogger } from './logger/index.js';
8
- export { I as ILankaErrorInit, L as LankaError, T as TLankaErrorKind } from './LankaError-xpI-qj35.js';
8
+ export { I as ILankaErrorInit, L as LankaError, T as TLankaErrorKind } from './LankaError-D6RWNyLM.js';
9
9
  import './createLankaScope-BiFxNQgl.js';
10
- import './activeRuntime-B336NU5I.js';
11
- import './ILankaScenarioVM-DUsI-fSc.js';
12
- import './LankaScenarioLocator-D86TIwiu.js';
13
- import './ILankaScenarioMetadata-GoWWNEQL.js';
14
- import './ALankaGateway-CkW1LbKE.js';
15
- import './lankaStandardValidator-CL-r-zEV.js';
10
+ import './activeRuntime-BYd2D6Lx.js';
11
+ import './ILankaScenarioVM-DpKFL3iE.js';
12
+ import './LankaScenarioLocator-CAENLhDO.js';
13
+ import './ILankaScenarioMetadata-Dj4GCqmX.js';
14
+ import './ALankaGateway-BrVPaZN5.js';
15
+ import './lankaStandardValidator-BUFnysK0.js';
16
16
  import '@standard-schema/spec';
17
+ import './ILankaFieldError-D5931-vT.js';
17
18
  import './lankaHttpInFlight-Bk1eIuSx.js';
18
19
  import './lankaRequestMiddleware-DAC5kCb7.js';
19
20
  import './ALankaSharedStore-B7uepuuk.js';
package/dist/index.js CHANGED
@@ -6,9 +6,9 @@ import {
6
6
  createLanka,
7
7
  resetActiveLanka,
8
8
  startLanka
9
- } from "./chunk-G3I7QIZR.js";
9
+ } from "./chunk-UBGXDTXC.js";
10
+ import "./chunk-B7EYIAW7.js";
10
11
  import "./chunk-5MAQVBI2.js";
11
- import "./chunk-UGXSGQPW.js";
12
12
  import "./chunk-O5ROO7QF.js";
13
13
  import {
14
14
  createLankaHost
@@ -16,7 +16,7 @@ import {
16
16
  import "./chunk-UJEC7H6K.js";
17
17
  import {
18
18
  LankaError
19
- } from "./chunk-Q7QESSYF.js";
19
+ } from "./chunk-G32H73QY.js";
20
20
  import {
21
21
  getLankaHost
22
22
  } from "./chunk-RKYKK6MN.js";
@@ -1,11 +1,21 @@
1
1
  import { StandardSchemaV1 } from '@standard-schema/spec';
2
+ import { I as ILankaFieldError } from './ILankaFieldError-D5931-vT.js';
2
3
 
4
+ /**
5
+ * What `validateSafe` answers.
6
+ *
7
+ * On failure, `errors` is the banner's list and `fields` the form's: the same
8
+ * issues, joined into text and kept in segments. `fields` is optional because
9
+ * `ILankaValidator` is a port a consumer may implement, and a port that grows a
10
+ * required member breaks every implementation it did not write.
11
+ */
3
12
  type TLankaValidationResult<T> = {
4
13
  success: true;
5
14
  data: T;
6
15
  } | {
7
16
  success: false;
8
17
  errors: string[];
18
+ fields?: readonly ILankaFieldError[];
9
19
  };
10
20
 
11
21
  /**
@@ -1,13 +1,14 @@
1
1
  export { I as ILankaScope } from '../createLankaScope-BiFxNQgl.js';
2
2
  export { a as ILankaLocator, I as ILankaLocatorConfig, c as ILankaSharedStoreLocatorConfig, d as ILankaSingletonLocatorConfig } from '../LankaSharedStoreLocator-zS2kLu-S.js';
3
3
  import * as GatewaysModule from '@lanka_di/Gateways';
4
- import { A as ALankaGateway } from '../ALankaGateway-CkW1LbKE.js';
4
+ import { A as ALankaGateway } from '../ALankaGateway-BrVPaZN5.js';
5
5
  import * as ScenariosModule from '@lanka_di/Scenarios';
6
6
  import * as SingletonsModule from '@lanka_di/Singletons';
7
7
  import * as SharedStoresModule from '@lanka_di/SharedStores';
8
8
  import { A as ALankaSharedStore } from '../ALankaSharedStore-B7uepuuk.js';
9
- import '../lankaStandardValidator-CL-r-zEV.js';
9
+ import '../lankaStandardValidator-BUFnysK0.js';
10
10
  import '@standard-schema/spec';
11
+ import '../ILankaFieldError-D5931-vT.js';
11
12
  import 'zustand/vanilla';
12
13
 
13
14
  /**
@@ -1,6 +1,6 @@
1
- import { a as ILankaScenario, T as TLankaReplayRequest, b as ILankaEventMetadata, c as TLankaEventBusMiddleware, d as TLankaEventBusObserver, e as ILankaEventLog, I as ILankaScenarioVM } from '../ILankaScenarioVM-DUsI-fSc.js';
2
- export { f as ILankaEventBusOutcome, g as TLankaEventBusDecision } from '../ILankaScenarioVM-DUsI-fSc.js';
3
- export { I as ILankaScenarioMetadata } from '../ILankaScenarioMetadata-GoWWNEQL.js';
1
+ import { a as ILankaScenario, T as TLankaReplayRequest, b as ILankaEventMetadata, c as TLankaEventBusMiddleware, d as TLankaEventBusObserver, e as ILankaEventLog, I as ILankaScenarioVM } from '../ILankaScenarioVM-DpKFL3iE.js';
2
+ export { f as ILankaEventBusOutcome, g as TLankaEventBusDecision } from '../ILankaScenarioVM-DpKFL3iE.js';
3
+ export { I as ILankaScenarioMetadata } from '../ILankaScenarioMetadata-Dj4GCqmX.js';
4
4
 
5
5
  /**
6
6
  * The base of a scenario — a named unit of coordination over the event bus.
@@ -123,6 +123,23 @@ declare const lankaEventBus: Readonly<{
123
123
  reset: () => void;
124
124
  }>;
125
125
 
126
+ /** What a scenario-layer reset should clear beyond the default. */
127
+ interface ILankaScenarioResetConfig {
128
+ /**
129
+ * Also forget which ViewModels were declared.
130
+ *
131
+ * Off by default, and the default is the one an APPLICATION needs: a
132
+ * module-level ViewModel is built once per process, so its declaration is the
133
+ * only thing that lets a second instance find it again.
134
+ *
135
+ * On, for a suite that builds ViewModels inside test bodies. Nothing
136
+ * un-declares one otherwise, so the next bootstrap re-adopts every ViewModel
137
+ * ever built in the process — and a finished test's handlers then run against
138
+ * the double that test created.
139
+ */
140
+ withDeclarations?: boolean;
141
+ }
142
+
126
143
  /**
127
144
  * Bootstrapping the scenario layer, for callers with no instance in hand.
128
145
  *
@@ -186,7 +203,25 @@ declare class LankaScenarioBootstrap {
186
203
  * to.
187
204
  */
188
205
  private readonly declaredViewModels;
189
- /** Registers everything declared into a NEW instance. Called by `createLanka`. */
206
+ /**
207
+ * Registers everything declared into a NEW instance.
208
+ *
209
+ * Called twice, and the second call is the one that makes this work on a
210
+ * server: `createLanka` calls it, and so does `bootstrap()`.
211
+ *
212
+ * The reason is an order nobody can change. Where "which instance is active"
213
+ * is answered by a RESOLVER — one instance per request, the shape
214
+ * `@lankajs/host` installs — an instance becomes findable only once the CALLER
215
+ * has put it in its store, and a caller can only do that after `createLanka`
216
+ * has returned. So during creation the answer is honestly "none", and asking
217
+ * for it threw: every server render of an application with a module-level
218
+ * ViewModel failed inside the call that was creating the scope, with a message
219
+ * about running outside one.
220
+ *
221
+ * Skipping is safe precisely because bootstrap adopts again. It is idempotent
222
+ * at the other end too: `attachViewModel` returns early for a ViewModel the
223
+ * registry already holds.
224
+ */
190
225
  adoptDeclaredViewModels(): void;
191
226
  registerViewModel(viewModel: ILankaScenarioVM, name?: string): void;
192
227
  private attachViewModel;
@@ -197,10 +232,25 @@ declare class LankaScenarioBootstrap {
197
232
  * the ViewModel registry together with all their subscriptions, and the bus —
198
233
  * events and
199
234
  * middleware.
235
+ *
236
+ * What it does NOT clear by default is which ViewModels were DECLARED, and
237
+ * that default is load-bearing: a module-level ViewModel is built once per
238
+ * process, so the declaration is the only thing that lets a second instance
239
+ * find it. Drop it and its handlers bind to nothing, silently, for the rest of
240
+ * the process — see `declaredViewModels`.
241
+ *
242
+ * `withDeclarations` is for the case that default gets wrong: a suite that
243
+ * builds ViewModels inside test BODIES. Those are declared like any other and
244
+ * nothing un-declares them, so the next bootstrap re-adopts every one ever
245
+ * built and a finished test's handlers run again — against the gateway that
246
+ * test built, which is somebody else's double. Forgetting is not a tombstone:
247
+ * a ViewModel declared again afterwards is adopted again.
248
+ *
249
+ * @param config `withDeclarations` also forgets which ViewModels exist
200
250
  */
201
- reset(): void;
251
+ reset(config?: ILankaScenarioResetConfig): void;
202
252
  }
203
253
  /** The one every caller wants. */
204
254
  declare const lankaScenarioBootstrap: LankaScenarioBootstrap;
205
255
 
206
- export { ALankaScenario, ILankaEventLog, ILankaEventMetadata, ILankaScenario, type ILankaScenarioConfig, ILankaScenarioVM, TLankaEventBusMiddleware, TLankaEventBusObserver, TLankaReplayRequest, createLankaScenario, lankaEventBus, lankaScenarioBootstrap };
256
+ export { ALankaScenario, ILankaEventLog, ILankaEventMetadata, ILankaScenario, type ILankaScenarioConfig, type ILankaScenarioResetConfig, ILankaScenarioVM, TLankaEventBusMiddleware, TLankaEventBusObserver, TLankaReplayRequest, createLankaScenario, lankaEventBus, lankaScenarioBootstrap };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  lankaScenarioBootstrap
3
- } from "../chunk-UGXSGQPW.js";
3
+ } from "../chunk-B7EYIAW7.js";
4
4
  import {
5
5
  ALankaScenario,
6
6
  lankaEventBus
@@ -0,0 +1,120 @@
1
+ /**
2
+ * The half every engine has: four operations over string values.
3
+ *
4
+ * The clauses each one keeps are on `ILankaStorageAdapter`, together with the
5
+ * ones about how the two halves agree.
6
+ */
7
+ interface ILankaAsyncStorageAdapter {
8
+ setItem(key: string, value: string): Promise<void>;
9
+ getItem(key: string): Promise<string | null>;
10
+ removeItem(key: string): Promise<void>;
11
+ clear(): Promise<void>;
12
+ /**
13
+ * Every key this store holds, when it can say.
14
+ *
15
+ * OPTIONAL, because a store need not be able to: a Cache Storage answers
16
+ * `Request` objects rather than the keys it was given, and a native bridge
17
+ * may answer nothing at all. A required member here would be a compile error
18
+ * in every adapter a consumer had already written.
19
+ *
20
+ * What it is for: `LankaCipher.clear()` removes what IT wrote instead of
21
+ * emptying the whole store — a store that cannot enumerate leaves it no
22
+ * choice but the latter.
23
+ */
24
+ keys?(): Promise<string[]>;
25
+ }
26
+
27
+ /**
28
+ * The half an engine only has when it answers without awaiting.
29
+ *
30
+ * ALL FOUR OR NONE. A caller narrows on three of them and then uses the fourth —
31
+ * an adapter declaring two type-checks and lies at the one call site that has no
32
+ * `await` to fall back to.
33
+ *
34
+ * What it is for: a store read during the first render. `localStorage` and MMKV
35
+ * can answer there; a bridge and a keychain cannot, and a screen that awaits its
36
+ * persisted state renders once without it and again with it — the flash a user
37
+ * reads as a bug.
38
+ */
39
+ interface ILankaSyncStorageAdapter {
40
+ setItemSync(key: string, value: string): void;
41
+ getItemSync(key: string): string | null;
42
+ removeItemSync(key: string): void;
43
+ clearSync(): void;
44
+ }
45
+
46
+ /**
47
+ * What a key-value engine must do to stand behind `@lankajs/storage`.
48
+ *
49
+ * A PORT and nothing else: core declares the shape and ships no engine, because
50
+ * every platform already has one and they have nothing in common but these four
51
+ * operations. The asynchronous half is the whole port; the synchronous half is a
52
+ * capability an engine either has or does not, and a caller asks before using it
53
+ * rather than forcing the type.
54
+ *
55
+ * Implementations: `@lankajs/storage` carries three over what a browser already
56
+ * provides — `LankaWebStorageAdapter`, `LankaCacheStorageAdapter`,
57
+ * `LankaIndexedDbAdapter` — and `modules/storage-adapters/` holds one per engine
58
+ * an application installs. An application is free to write its own; the
59
+ * conformance suite in `@lankajs/tool-testing` is what says whether it is right.
60
+ *
61
+ * ## The clauses an implementation must keep
62
+ *
63
+ * Behaviour, not signatures, is where two honest adapters diverge, so each
64
+ * clause below is a promise and every one of them is an assertion in
65
+ * `lankaStorageAdapterConformance`. Run it; do not read a member's source.
66
+ *
67
+ * 1. Values are STRINGS, and what was written comes back byte for byte — `""`,
68
+ * `"null"` and `"{}"` included. An engine that serialises on the way in and
69
+ * parses on the way out answers a different type than it was given.
70
+ * 2. A missing key answers `null` — never `undefined`, never a throw.
71
+ * 3. `removeItem` of a key that is not there succeeds.
72
+ * 4. `setItem` over an existing key replaces it. Nothing merges.
73
+ * 5. `clear()` empties this adapter's NAMESPACE: afterwards every key it wrote
74
+ * answers `null`.
75
+ * 6. `keys()` is optional. Declared, it answers what this adapter wrote and
76
+ * nothing else — the namespace's keys, not the engine's.
77
+ * 7. An engine that can neither enumerate nor wipe keeps its own index of what
78
+ * it wrote, so clause 5 holds anyway. `expo-secure-store` is the reason this
79
+ * clause exists: it deletes a key it is handed and offers nothing else.
80
+ * 8. The synchronous half is all four methods or none.
81
+ * 9. The two halves see ONE store: a value written synchronously is visible to
82
+ * `getItem`, and the other way round.
83
+ * 10. A value the engine cannot hold FAILS. Truncation surfaces a week later as
84
+ * a token that decrypts to nonsense.
85
+ * 11. A key is used AS GIVEN. A key holding a space, a slash, a colon or a
86
+ * letter outside ASCII names its own value, and `keys()` answers it in the
87
+ * spelling it was written with. An engine that restricts which characters a
88
+ * key may hold — `expo-secure-store` allows only letters, digits, `.`, `-`
89
+ * and `_` — encodes on the way in and decodes on the way out, rather than
90
+ * handing the caller a key it never wrote.
91
+ *
92
+ * ## Why `clear()` is required where `ILankaReadCache.cancel` is optional
93
+ *
94
+ * The port next door made an operation optional when three of four libraries
95
+ * could not do it. This one does the opposite with the same kind of gap, and the
96
+ * difference is what absence COSTS. A cache that cannot cancel finishes a
97
+ * request nobody wants: wasteful, never wrong. A store that cannot clear ends a
98
+ * session with the tokens still in it — which is not waste, it is the failure.
99
+ *
100
+ * So the cost lands on the one adapter with the problem (clause 7) instead of on
101
+ * every caller having to ask whether sign-out worked.
102
+ *
103
+ * ## What this port does not decide
104
+ *
105
+ * Three promises cannot be observed from inside an implementation and belong to
106
+ * whoever wires one:
107
+ *
108
+ * - **The adapter takes its engine, and never constructs it.** `new
109
+ * LankaWebStorageAdapter(localStorage)` is the existing shape and the reason
110
+ * an engine that only exists on a device is testable in node at all.
111
+ * - **An engine that encrypts itself is not encrypted twice.** MMKV with a key
112
+ * and a keychain-backed store are already ciphertext at rest;
113
+ * `LankaEncryptedStorage` over one of them is a second lock on one door.
114
+ * - **On a server the namespace is per REQUEST**, never a module-level
115
+ * singleton — one process serves every reader.
116
+ */
117
+ interface ILankaStorageAdapter extends ILankaAsyncStorageAdapter, Partial<ILankaSyncStorageAdapter> {
118
+ }
119
+
120
+ export type { ILankaAsyncStorageAdapter, ILankaStorageAdapter, ILankaSyncStorageAdapter };
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,15 +1,16 @@
1
- import { c as ILankaPlugin } from '../createLanka-DI1CSy2Q.js';
1
+ import { c as ILankaPlugin } from '../createLanka-NrlvN-WQ.js';
2
2
  import '../createLankaScope-BiFxNQgl.js';
3
3
  import '../LankaSharedStoreLocator-zS2kLu-S.js';
4
4
  import '../ALankaSharedStore-B7uepuuk.js';
5
5
  import 'zustand/vanilla';
6
- import '../activeRuntime-B336NU5I.js';
7
- import '../ILankaScenarioVM-DUsI-fSc.js';
8
- import '../LankaScenarioLocator-D86TIwiu.js';
9
- import '../ILankaScenarioMetadata-GoWWNEQL.js';
10
- import '../ALankaGateway-CkW1LbKE.js';
11
- import '../lankaStandardValidator-CL-r-zEV.js';
6
+ import '../activeRuntime-BYd2D6Lx.js';
7
+ import '../ILankaScenarioVM-DpKFL3iE.js';
8
+ import '../LankaScenarioLocator-CAENLhDO.js';
9
+ import '../ILankaScenarioMetadata-Dj4GCqmX.js';
10
+ import '../ALankaGateway-BrVPaZN5.js';
11
+ import '../lankaStandardValidator-BUFnysK0.js';
12
12
  import '@standard-schema/spec';
13
+ import '../ILankaFieldError-D5931-vT.js';
13
14
  import '../ILankaRuntimeConfig-Vl436GWK.js';
14
15
  import '../lankaHttpInFlight-Bk1eIuSx.js';
15
16
  import '../lankaRequestMiddleware-DAC5kCb7.js';
@@ -1,5 +1,6 @@
1
- export { I as ILankaValidator, T as TLankaSchema, a as TLankaValidationResult, l as lankaStandardValidator } from '../lankaStandardValidator-CL-r-zEV.js';
2
- import { L as LankaError } from '../LankaError-xpI-qj35.js';
1
+ export { I as ILankaValidator, T as TLankaSchema, a as TLankaValidationResult, l as lankaStandardValidator } from '../lankaStandardValidator-BUFnysK0.js';
2
+ import { L as LankaError } from '../LankaError-D6RWNyLM.js';
3
+ import { I as ILankaFieldError } from '../ILankaFieldError-D5931-vT.js';
3
4
  import '@standard-schema/spec';
4
5
 
5
6
  /**
@@ -12,10 +13,12 @@ import '@standard-schema/spec';
12
13
  * that told a contract drift from a network failure by `kind` never saw it.
13
14
  *
14
15
  * `name` stays `LankaValidationError` and `status` stays 422: both are what a
15
- * consumer's existing `catch` reads, and the message list is `errors`.
16
+ * consumer's existing `catch` reads, and the message list is `errors`. The same
17
+ * issues arrive a second time as `fields`, with the path in segments, for the
18
+ * consumer that has an input to show each one at.
16
19
  */
17
20
  declare class LankaValidationError extends LankaError {
18
- constructor(message: string, errors?: string[]);
21
+ constructor(message: string, errors?: string[], fields?: readonly ILankaFieldError[]);
19
22
  /** Always present here, unlike the base's optional list. */
20
23
  get errors(): string[];
21
24
  }
@@ -1,8 +1,10 @@
1
1
  import {
2
- LankaValidationError,
3
2
  lankaStandardValidator
4
- } from "../chunk-GV5DUYST.js";
5
- import "../chunk-Q7QESSYF.js";
3
+ } from "../chunk-HVSQOMRE.js";
4
+ import {
5
+ LankaValidationError
6
+ } from "../chunk-CRIRTOLB.js";
7
+ import "../chunk-G32H73QY.js";
6
8
  export {
7
9
  LankaValidationError,
8
10
  lankaStandardValidator