lanka 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +242 -0
  3. package/dist/ALankaGateway-ExlRGT3D.d.ts +122 -0
  4. package/dist/ALankaSharedStore-B7uepuuk.d.ts +29 -0
  5. package/dist/ILankaApiError-zI_2bNs0.d.ts +16 -0
  6. package/dist/ILankaRuntimeConfig-Vl436GWK.d.ts +62 -0
  7. package/dist/ILankaScenarioMetadata-Bu-yggTZ.d.ts +14 -0
  8. package/dist/ILankaScenarioVM-DuCyPoyT.d.ts +260 -0
  9. package/dist/LankaError-B1HtuIkw.d.ts +86 -0
  10. package/dist/LankaScenarioLocator-BGQHwf3n.d.ts +144 -0
  11. package/dist/LankaSharedStoreLocator-MvCpav5F.d.ts +126 -0
  12. package/dist/TLankaErrorHandler-Yfqtdh1M.d.ts +3 -0
  13. package/dist/_extend/index.d.ts +34 -0
  14. package/dist/_extend/index.js +36 -0
  15. package/dist/_extend/index.js.map +1 -0
  16. package/dist/_internal/index.d.ts +43 -0
  17. package/dist/_internal/index.js +34 -0
  18. package/dist/_internal/index.js.map +1 -0
  19. package/dist/activeRuntime-FcsSJvUg.d.ts +84 -0
  20. package/dist/bootstrap/index.d.ts +242 -0
  21. package/dist/bootstrap/index.js +22 -0
  22. package/dist/bootstrap/index.js.map +1 -0
  23. package/dist/chunk-73IVH3C6.js +52 -0
  24. package/dist/chunk-73IVH3C6.js.map +1 -0
  25. package/dist/chunk-BGVDPDX4.js +29 -0
  26. package/dist/chunk-BGVDPDX4.js.map +1 -0
  27. package/dist/chunk-C2HP7CRD.js +224 -0
  28. package/dist/chunk-C2HP7CRD.js.map +1 -0
  29. package/dist/chunk-D27MREPB.js +13 -0
  30. package/dist/chunk-D27MREPB.js.map +1 -0
  31. package/dist/chunk-DTO27QFR.js +59 -0
  32. package/dist/chunk-DTO27QFR.js.map +1 -0
  33. package/dist/chunk-EWVDJYCC.js +296 -0
  34. package/dist/chunk-EWVDJYCC.js.map +1 -0
  35. package/dist/chunk-FIR4XTBL.js +136 -0
  36. package/dist/chunk-FIR4XTBL.js.map +1 -0
  37. package/dist/chunk-ILQNYQY5.js +200 -0
  38. package/dist/chunk-ILQNYQY5.js.map +1 -0
  39. package/dist/chunk-J5LA3Q6D.js +14 -0
  40. package/dist/chunk-J5LA3Q6D.js.map +1 -0
  41. package/dist/chunk-MDZ42XRH.js +10 -0
  42. package/dist/chunk-MDZ42XRH.js.map +1 -0
  43. package/dist/chunk-Q3SOVBIJ.js +176 -0
  44. package/dist/chunk-Q3SOVBIJ.js.map +1 -0
  45. package/dist/chunk-Q7QESSYF.js +49 -0
  46. package/dist/chunk-Q7QESSYF.js.map +1 -0
  47. package/dist/chunk-RKYKK6MN.js +19 -0
  48. package/dist/chunk-RKYKK6MN.js.map +1 -0
  49. package/dist/chunk-RYFZCAQ3.js +637 -0
  50. package/dist/chunk-RYFZCAQ3.js.map +1 -0
  51. package/dist/chunk-UJEC7H6K.js +39 -0
  52. package/dist/chunk-UJEC7H6K.js.map +1 -0
  53. package/dist/chunk-XESL274R.js +23 -0
  54. package/dist/chunk-XESL274R.js.map +1 -0
  55. package/dist/chunk-YR4MZXMU.js +13 -0
  56. package/dist/chunk-YR4MZXMU.js.map +1 -0
  57. package/dist/config/index.d.ts +63 -0
  58. package/dist/config/index.js +16 -0
  59. package/dist/config/index.js.map +1 -0
  60. package/dist/createLankaScope-Bc_vChRs.d.ts +30 -0
  61. package/dist/errors/index.d.ts +37 -0
  62. package/dist/errors/index.js +16 -0
  63. package/dist/errors/index.js.map +1 -0
  64. package/dist/gateway/index.d.ts +258 -0
  65. package/dist/gateway/index.js +438 -0
  66. package/dist/gateway/index.js.map +1 -0
  67. package/dist/index.d.ts +20 -0
  68. package/dist/index.js +43 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/lankaHttpInFlight-Bk1eIuSx.d.ts +43 -0
  71. package/dist/lankaRequestMiddleware-DAC5kCb7.d.ts +62 -0
  72. package/dist/lankaStandardValidator-CL-r-zEV.d.ts +34 -0
  73. package/dist/locator/index.d.ts +282 -0
  74. package/dist/locator/index.js +67 -0
  75. package/dist/locator/index.js.map +1 -0
  76. package/dist/logger/index.d.ts +146 -0
  77. package/dist/logger/index.js +11 -0
  78. package/dist/logger/index.js.map +1 -0
  79. package/dist/mock/index.d.ts +16 -0
  80. package/dist/mock/index.js +58 -0
  81. package/dist/mock/index.js.map +1 -0
  82. package/dist/role/index.d.ts +48 -0
  83. package/dist/role/index.js +7 -0
  84. package/dist/role/index.js.map +1 -0
  85. package/dist/scenario/index.d.ts +204 -0
  86. package/dist/scenario/index.js +28 -0
  87. package/dist/scenario/index.js.map +1 -0
  88. package/dist/validation/index.d.ts +18 -0
  89. package/dist/validation/index.js +53 -0
  90. package/dist/validation/index.js.map +1 -0
  91. package/dist/viewmodel/index.d.ts +478 -0
  92. package/dist/viewmodel/index.js +701 -0
  93. package/dist/viewmodel/index.js.map +1 -0
  94. package/package.json +99 -0
  95. package/skills/lanka-core/SKILL.md +241 -0
  96. package/skills/lanka-core/reference.md +1090 -0
  97. package/skills/lanka-packages/SKILL.md +70 -0
@@ -0,0 +1,258 @@
1
+ import { T as TLankaExecuteOptions, I as IALankaGatewayConfig, a as ILankaRequest, b as TLankaQueryBuilder } from '../ALankaGateway-ExlRGT3D.js';
2
+ export { A as ALankaGateway, c as TLankaQueryParams } from '../ALankaGateway-ExlRGT3D.js';
3
+ import { T as TLankaErrorHandler } from '../TLankaErrorHandler-Yfqtdh1M.js';
4
+ export { I as ILankaInFlightCounter, l as lankaHttpInFlight } from '../lankaHttpInFlight-Bk1eIuSx.js';
5
+ export { I as ILankaRequestContext, T as TLankaRequestMiddleware } from '../lankaRequestMiddleware-DAC5kCb7.js';
6
+ import '../lankaStandardValidator-CL-r-zEV.js';
7
+ import '@standard-schema/spec';
8
+
9
+ /**
10
+ * A gateway's protected surface, handed to whoever builds one by calling.
11
+ *
12
+ * The names match `ALankaGateway`'s protected members exactly, and that is the
13
+ * parity contract: a consumer who switches styles moves the same call from
14
+ * `this.endpoint(...)` to `endpoint(...)` and changes nothing else.
15
+ */
16
+ interface ILankaGatewayContext<TOptions> {
17
+ /** The full URL for a path: base API, then this gateway's own base path. */
18
+ endpoint: (path?: string) => string;
19
+ /** Sends through this gateway's request, mock handler included. */
20
+ request: <TReturn = unknown>(path: string, options?: TLankaExecuteOptions<TOptions>, mockHandler?: () => Promise<TReturn>) => Promise<TReturn>;
21
+ /** Serialises query parameters the way this gateway was configured to. */
22
+ buildQueryParams: <T extends object>(params: T) => URLSearchParams;
23
+ }
24
+
25
+ /** What a gateway is built from, whichever style builds it. */
26
+ interface ILankaGatewayConfig<TOptions, TMethods extends object> extends IALankaGatewayConfig<TOptions> {
27
+ /** The endpoints this gateway offers, written over its own surface. */
28
+ methods: (context: ILankaGatewayContext<TOptions>) => TMethods;
29
+ }
30
+ /**
31
+ * A gateway, without writing a class.
32
+ *
33
+ * The bridge below is the whole mechanism, and it lives here rather than on the
34
+ * base for two reasons. The language reads `protected` from inside a deriving
35
+ * class body and nowhere else, so a factory outside the hierarchy could only
36
+ * reach the public half — the wrong one. And a `toStyleContext` ON the base
37
+ * would put `TOptions` in a method's parameter position, making the class
38
+ * invariant in it: every `ALankaGateway<unknown>` the locator holds would stop
39
+ * accepting a gateway typed for `RequestInit`.
40
+ *
41
+ * One implementation: what comes back is an instance of `ALankaGateway`, so a
42
+ * behaviour fix reaches both styles at once.
43
+ */
44
+ declare const createLankaGateway: <TOptions, TMethods extends object>(config: ILankaGatewayConfig<TOptions, TMethods>) => TMethods;
45
+
46
+ /**
47
+ * How a gateway request goes on the wire.
48
+ *
49
+ * A subclass declares one method, `request()`, and does only its own work there:
50
+ * `LankaFetchJsonRequest` returns parsed JSON, `LankaFetchFormDataRequest`
51
+ * returns the whole response, a custom transport returns whatever it likes.
52
+ *
53
+ * Overriding `request()` customises the request flow, failure handling, mock
54
+ * substitution, response transformation and log interception.
55
+ */
56
+ declare abstract class ALankaRequest<TOptions = RequestInit> implements ILankaRequest<TOptions> {
57
+ protected readonly errorHandler?: TLankaErrorHandler;
58
+ protected readonly useMock: boolean;
59
+ protected constructor(config: {
60
+ errorHandler?: TLankaErrorHandler;
61
+ useMock?: boolean;
62
+ });
63
+ /**
64
+ * Performs the request and returns its result — a response, JSON or a custom
65
+ * type.
66
+ *
67
+ * @param endpoint Full URL
68
+ * @param options Transport-specific options
69
+ * @param mockHandler Mock, when there is one
70
+ */
71
+ protected abstract request<TReturn = Response>(endpoint: string, options?: TOptions, mockHandler?: () => Promise<TReturn>): Promise<TReturn>;
72
+ /**
73
+ * The single point EVERY gateway request passes through.
74
+ *
75
+ * Hence the in-flight accounting here: intent prefetch stands down while
76
+ * anything else is on the wire. The `finally` matters more than the increment —
77
+ * a rejected request that never decremented would disable prefetching for the
78
+ * rest of the session.
79
+ */
80
+ execute<TReturn = Response>(endpoint: string, options?: TLankaExecuteOptions<TOptions>, mockHandler?: () => Promise<TReturn>): Promise<TReturn>;
81
+ }
82
+
83
+ /**
84
+ * Transport layer interface for Gateway.
85
+ * Allows to swap between different communication protocols (HTTP Fetch, gRPC, WebSocket, etc.)
86
+ */
87
+ interface ILankaTransport<TOptions = RequestInit> {
88
+ /**
89
+ * Executes a request and returns a Response.
90
+ * @param resource - Request resource (URL or Request object)
91
+ * @param options - Request options specific to transport implementation
92
+ * @returns Promise resolving to Response
93
+ */
94
+ request(resource: RequestInfo, options?: TOptions): Promise<Response>;
95
+ }
96
+
97
+ interface ILankaTransportRequestConfig<TOptions> {
98
+ transport?: ILankaTransport<TOptions>;
99
+ errorHandler?: TLankaErrorHandler;
100
+ useMock?: boolean;
101
+ }
102
+ /**
103
+ * The shape every fetch-backed request has: mock, send, check, parse.
104
+ *
105
+ * The three concrete requests differ in exactly two places — which transport
106
+ * they default to, and how they turn a successful `Response` into a value. Both
107
+ * are parameters of this template, so a fourth kind is a subclass with one
108
+ * method rather than a fourth copy of the sequence.
109
+ */
110
+ declare abstract class ALankaTransportRequest<TOptions = RequestInit> extends ALankaRequest<TOptions> {
111
+ protected readonly transport: ILankaTransport<TOptions>;
112
+ protected constructor(config: ILankaTransportRequestConfig<TOptions>, createDefaultTransport: () => ILankaTransport<TOptions>);
113
+ protected request<TReturn>(endpoint: string, options?: TOptions, mockHandler?: () => Promise<TReturn>): Promise<TReturn>;
114
+ /**
115
+ * Turns a successful response into the value the caller asked for.
116
+ *
117
+ * The one step that genuinely differs between request kinds.
118
+ */
119
+ protected abstract parse<TReturn>(response: Response): Promise<TReturn>;
120
+ /**
121
+ * Refuses an unsuccessful response, and never returns.
122
+ *
123
+ * `TLankaErrorHandler` is typed `Promise<never>` — a handler must throw. It is
124
+ * still CALLED and then followed by a throw, because a handler that breaks its
125
+ * contract and returns would otherwise hand `undefined` back as if the request
126
+ * had succeeded, and a non-value must never become a value.
127
+ */
128
+ protected refuse(response: Response): Promise<never>;
129
+ }
130
+
131
+ /**
132
+ * The raw request: hands the `Response` back untouched.
133
+ *
134
+ * The minimal, extensible case — a caller wanting headers, a stream or a blob
135
+ * reads them off the response itself.
136
+ */
137
+ declare class LankaFetchRequest<TOptions = RequestInit> extends ALankaTransportRequest<TOptions> {
138
+ constructor(config?: ILankaTransportRequestConfig<TOptions>);
139
+ protected parse<TReturn>(response: Response): Promise<TReturn>;
140
+ }
141
+
142
+ /**
143
+ * The functional style of `LankaFetchRequest`: the raw `Response`, for a download or a stream.
144
+ *
145
+ * One line, and that is the point — the factory IS the class, so a behaviour
146
+ * cannot exist in one style and not the other.
147
+ */
148
+ declare const createLankaFetchRequest: <TOptions = RequestInit>(config?: ILankaTransportRequestConfig<TOptions>) => LankaFetchRequest<TOptions>;
149
+
150
+ /** The JSON request: parses the body, and refuses a body that is not JSON. */
151
+ /**
152
+ * A request kind that answers a parsed JSON body — what most endpoints return.
153
+ *
154
+ * A gateway holds one, and is handed it rather than constructing it, which is
155
+ * what lets a test give the same gateway a transport that never leaves the
156
+ * process. `createLankaFetchJsonRequest()` builds the same class.
157
+ */
158
+ declare class LankaFetchJsonRequest<TOptions = RequestInit> extends ALankaTransportRequest<TOptions> {
159
+ constructor(config?: ILankaTransportRequestConfig<TOptions>);
160
+ /**
161
+ * Parses the body, or names the failure.
162
+ *
163
+ * The content type is read for the ERROR MESSAGE only. Using it to choose
164
+ * between two parse paths, where the second answers `{}` for any non-empty
165
+ * body it cannot parse, fails silently: the caller's schema is the first thing
166
+ * to notice, and the caller's SCREEN is where it shows up.
167
+ *
168
+ * A measured case: a dev server whose `/api` fell through to the SPA fallback
169
+ * answered `200 text/html` with `index.html`. Turned into `{}`, the schema
170
+ * refused it and a person read a validator's issue list on the sign-in card. A
171
+ * body a JSON transport cannot parse is never a value — it is a misrouted
172
+ * request, and saying so names the actual failure.
173
+ */
174
+ protected parse<TReturn>(response: Response): Promise<TReturn>;
175
+ }
176
+
177
+ /**
178
+ * The functional style of `LankaFetchJsonRequest`: a JSON body, which is what most endpoints answer.
179
+ *
180
+ * One line, and that is the point — the factory IS the class, so a behaviour
181
+ * cannot exist in one style and not the other.
182
+ */
183
+ declare const createLankaFetchJsonRequest: <TOptions = RequestInit>(config?: ILankaTransportRequestConfig<TOptions>) => LankaFetchJsonRequest<TOptions>;
184
+
185
+ /**
186
+ * The multipart request: hands the `Response` back untouched.
187
+ *
188
+ * Differs from `LankaFetchRequest` only in its transport — the one that must NOT
189
+ * set `content-type`, because the browser writes it with the boundary and a
190
+ * hand-set header leaves the body unparseable to the server.
191
+ */
192
+ declare class LankaFetchFormDataRequest<TOptions = RequestInit> extends ALankaTransportRequest<TOptions> {
193
+ constructor(config?: ILankaTransportRequestConfig<TOptions>);
194
+ protected parse<TReturn>(response: Response): Promise<TReturn>;
195
+ }
196
+
197
+ /**
198
+ * The functional style of `LankaFetchFormDataRequest`: a multipart body, for an upload.
199
+ *
200
+ * One line, and that is the point — the factory IS the class, so a behaviour
201
+ * cannot exist in one style and not the other.
202
+ */
203
+ declare const createLankaFetchFormDataRequest: <TOptions = RequestInit>(config?: ILankaTransportRequestConfig<TOptions>) => LankaFetchFormDataRequest<TOptions>;
204
+
205
+ /**
206
+ * HTTP Fetch transport implementation.
207
+ * Uses native fetch API without any project-specific decorators.
208
+ * For project-specific logic (auth, error handling, etc.), use `request` parameter
209
+ * in Gateway config or create a custom transport.
210
+ */
211
+ declare class LankaFetchTransport implements ILankaTransport<RequestInit> {
212
+ request(resource: RequestInfo, options?: RequestInit): Promise<Response>;
213
+ }
214
+
215
+ /**
216
+ * HTTP Fetch JSON transport implementation.
217
+ * Uses native fetch API with JSON-specific headers.
218
+ * Automatically sets Content-Type to application/json for requests with body.
219
+ * For project-specific logic (auth, error handling, etc.), use `request` parameter
220
+ * in Gateway config or create a custom transport.
221
+ */
222
+ declare class LankaFetchJsonTransport implements ILankaTransport<RequestInit> {
223
+ request(resource: RequestInfo, options?: RequestInit): Promise<Response>;
224
+ }
225
+
226
+ /**
227
+ * HTTP Fetch FormData transport implementation.
228
+ * Uses native fetch API optimized for FormData requests.
229
+ * Does not set Content-Type header (browser will set it automatically with boundary).
230
+ * For project-specific logic (auth, error handling, etc.), use `request` parameter
231
+ * in Gateway config or create a custom transport.
232
+ */
233
+ declare class LankaFetchFormDataTransport implements ILankaTransport<RequestInit> {
234
+ request(resource: RequestInfo, options?: RequestInit): Promise<Response>;
235
+ }
236
+
237
+ /**
238
+ * The query string a gateway sends, from the object a method was called with.
239
+ *
240
+ * Arrays become `key[]` repeated, nested objects become `key[inner]`: the shape
241
+ * most JSON APIs read back without being told about it.
242
+ *
243
+ * Plain loops over `Object.keys` rather than `Object.entries` and `forEach`: this
244
+ * runs on every request that carries a filter, and the pair array `entries`
245
+ * builds — two allocations per key, thrown away immediately — is a cost with
246
+ * nothing to show for it.
247
+ */
248
+ declare const buildLankaQueryParams: TLankaQueryBuilder;
249
+
250
+ interface ILankaListQueryParams {
251
+ page: number;
252
+ limit: number;
253
+ sortField?: string | null;
254
+ sortOrder?: string | null;
255
+ filters?: Record<string, unknown>;
256
+ }
257
+
258
+ export { ALankaRequest, IALankaGatewayConfig, type ILankaGatewayConfig, type ILankaGatewayContext, type ILankaListQueryParams, ILankaRequest, type ILankaTransport, LankaFetchFormDataRequest, LankaFetchFormDataTransport, LankaFetchJsonRequest, LankaFetchJsonTransport, LankaFetchRequest, LankaFetchTransport, TLankaExecuteOptions, TLankaQueryBuilder, buildLankaQueryParams, createLankaFetchFormDataRequest, createLankaFetchJsonRequest, createLankaFetchRequest, createLankaGateway };
@@ -0,0 +1,438 @@
1
+ import {
2
+ composeLankaRequestMiddleware
3
+ } from "../chunk-YR4MZXMU.js";
4
+ import {
5
+ lankaHttpInFlight
6
+ } from "../chunk-UJEC7H6K.js";
7
+ import {
8
+ handleLankaApiError
9
+ } from "../chunk-73IVH3C6.js";
10
+ import "../chunk-J5LA3Q6D.js";
11
+ import {
12
+ LankaError
13
+ } from "../chunk-Q7QESSYF.js";
14
+ import {
15
+ getLankaHost
16
+ } from "../chunk-RKYKK6MN.js";
17
+ import {
18
+ lankaLogger
19
+ } from "../chunk-C2HP7CRD.js";
20
+ import {
21
+ getLankaFlags
22
+ } from "../chunk-D27MREPB.js";
23
+ import {
24
+ getActiveRuntime
25
+ } from "../chunk-BGVDPDX4.js";
26
+
27
+ // src/gateway/request/_abstractions/lanka-request/ALankaRequest.ts
28
+ function classifyTransportError(error, timedOut) {
29
+ if (LankaError.is(error)) return error;
30
+ const name = readErrorName(error);
31
+ const isAbort = name === "AbortError" || name === "TimeoutError";
32
+ if (isAbort) {
33
+ const timedOutHere = timedOut || name === "TimeoutError";
34
+ return new LankaError({
35
+ kind: timedOutHere ? "timeout" : "aborted",
36
+ message: timedOutHere ? getLankaHost().timeoutErrorMessage() : readErrorMessage(error),
37
+ cause: error
38
+ });
39
+ }
40
+ if (error instanceof TypeError) {
41
+ return new LankaError({
42
+ kind: "network",
43
+ message: getLankaHost().networkErrorMessage(),
44
+ issues: [error.message],
45
+ cause: error
46
+ });
47
+ }
48
+ return error;
49
+ }
50
+ function readErrorName(error) {
51
+ if (typeof error !== "object" || error === null) return void 0;
52
+ const name = error.name;
53
+ return typeof name === "string" ? name : void 0;
54
+ }
55
+ function readErrorMessage(error) {
56
+ if (typeof error === "string") return error;
57
+ if (typeof error !== "object" || error === null) return "";
58
+ const message = error.message;
59
+ return typeof message === "string" ? message : "";
60
+ }
61
+ var ALankaRequest = class {
62
+ errorHandler;
63
+ useMock;
64
+ constructor(config) {
65
+ const flags = getLankaFlags();
66
+ this.errorHandler = config.errorHandler ?? handleLankaApiError;
67
+ this.useMock = config.useMock ?? flags.isMockMode ?? false;
68
+ }
69
+ /**
70
+ * The single point EVERY gateway request passes through.
71
+ *
72
+ * Hence the in-flight accounting here: intent prefetch stands down while
73
+ * anything else is on the wire. The `finally` matters more than the increment —
74
+ * a rejected request that never decremented would disable prefetching for the
75
+ * rest of the session.
76
+ */
77
+ async execute(endpoint, options, mockHandler) {
78
+ const runtime = getActiveRuntime();
79
+ const { signal, timeoutMs, ...rest } = options ?? {};
80
+ const fallbackDeadline = runtime?.requestTimeoutMs;
81
+ const passedOptions = options === void 0 ? void 0 : rest;
82
+ let lastTimedOut = false;
83
+ const perform = async (ctx) => {
84
+ const lifetime = createRequestLifetime(signal, ctx.timeoutMs ?? fallbackDeadline);
85
+ try {
86
+ return await this.request(
87
+ ctx.endpoint,
88
+ withSignal(ctx.options, lifetime.signal),
89
+ mockHandler
90
+ );
91
+ } catch (error) {
92
+ lastTimedOut = lifetime.timedOut();
93
+ throw classifyTransportError(error, lastTimedOut);
94
+ } finally {
95
+ lifetime.dispose();
96
+ }
97
+ };
98
+ const run = composeLankaRequestMiddleware(runtime?.requestMiddleware ?? [], perform);
99
+ lankaHttpInFlight.begin();
100
+ try {
101
+ return await run({
102
+ endpoint,
103
+ options: passedOptions,
104
+ attempt: 1,
105
+ timeoutMs
106
+ });
107
+ } catch (error) {
108
+ throw classifyTransportError(error, lastTimedOut);
109
+ } finally {
110
+ lankaHttpInFlight.end();
111
+ }
112
+ }
113
+ };
114
+ function createRequestLifetime(external, timeoutMs) {
115
+ if (!external && !timeoutMs) {
116
+ return { signal: void 0, timedOut: () => false, dispose: () => void 0 };
117
+ }
118
+ const controller = new AbortController();
119
+ let expired = false;
120
+ const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
121
+ expired = true;
122
+ controller.abort(new DOMException("Request timed out", "TimeoutError"));
123
+ }, timeoutMs);
124
+ const onExternalAbort = () => {
125
+ controller.abort(external?.reason);
126
+ };
127
+ if (external) {
128
+ if (external.aborted) onExternalAbort();
129
+ else external.addEventListener("abort", onExternalAbort, { once: true });
130
+ }
131
+ return {
132
+ signal: controller.signal,
133
+ timedOut: () => expired,
134
+ dispose: () => {
135
+ if (timer !== void 0) clearTimeout(timer);
136
+ external?.removeEventListener("abort", onExternalAbort);
137
+ }
138
+ };
139
+ }
140
+ function withSignal(options, signal) {
141
+ if (signal === void 0) return options;
142
+ return { ...options ?? {}, signal };
143
+ }
144
+
145
+ // src/gateway/request/_abstractions/lanka-transport-request/ALankaTransportRequest.ts
146
+ var ALankaTransportRequest = class extends ALankaRequest {
147
+ transport;
148
+ constructor(config, createDefaultTransport) {
149
+ const flags = getLankaFlags();
150
+ super({
151
+ errorHandler: config.errorHandler,
152
+ useMock: config.useMock ?? flags.isMockMode ?? false
153
+ });
154
+ this.transport = config.transport ?? createDefaultTransport();
155
+ }
156
+ async request(endpoint, options, mockHandler) {
157
+ if (this.useMock && mockHandler) {
158
+ return await mockHandler();
159
+ }
160
+ const response = await this.transport.request(endpoint, options);
161
+ if (!response.ok) return await this.refuse(response);
162
+ return await this.parse(response);
163
+ }
164
+ /**
165
+ * Refuses an unsuccessful response, and never returns.
166
+ *
167
+ * `TLankaErrorHandler` is typed `Promise<never>` — a handler must throw. It is
168
+ * still CALLED and then followed by a throw, because a handler that breaks its
169
+ * contract and returns would otherwise hand `undefined` back as if the request
170
+ * had succeeded, and a non-value must never become a value.
171
+ */
172
+ async refuse(response) {
173
+ if (this.errorHandler) {
174
+ await this.errorHandler(response);
175
+ }
176
+ throw new LankaError({
177
+ kind: "http",
178
+ message: getLankaHost().httpErrorMessage(response.status),
179
+ status: response.status
180
+ });
181
+ }
182
+ };
183
+
184
+ // src/gateway/transport/lanka-fetch-json-transport/LankaFetchJsonTransport.ts
185
+ var LankaFetchJsonTransport = class {
186
+ async request(resource, options) {
187
+ if (options?.body && !(options.body instanceof FormData)) {
188
+ const headers = new Headers(options.headers);
189
+ let body = options.body;
190
+ if (typeof body === "object" && !(body instanceof FormData) && !(body instanceof Blob)) {
191
+ body = JSON.stringify(body);
192
+ }
193
+ headers.set("Content-Type", "application/json");
194
+ return await fetch(resource, {
195
+ ...options,
196
+ headers,
197
+ body
198
+ });
199
+ }
200
+ return await fetch(resource, options);
201
+ }
202
+ };
203
+
204
+ // src/gateway/request/lanka-fetch-json-request/LankaFetchJsonRequest.ts
205
+ var LankaFetchJsonRequest = class extends ALankaTransportRequest {
206
+ constructor(config = {}) {
207
+ super(config, () => new LankaFetchJsonTransport());
208
+ }
209
+ /**
210
+ * Parses the body, or names the failure.
211
+ *
212
+ * The content type is read for the ERROR MESSAGE only. Using it to choose
213
+ * between two parse paths, where the second answers `{}` for any non-empty
214
+ * body it cannot parse, fails silently: the caller's schema is the first thing
215
+ * to notice, and the caller's SCREEN is where it shows up.
216
+ *
217
+ * A measured case: a dev server whose `/api` fell through to the SPA fallback
218
+ * answered `200 text/html` with `index.html`. Turned into `{}`, the schema
219
+ * refused it and a person read a validator's issue list on the sign-in card. A
220
+ * body a JSON transport cannot parse is never a value — it is a misrouted
221
+ * request, and saying so names the actual failure.
222
+ */
223
+ async parse(response) {
224
+ const contentType = response.headers.get("content-type");
225
+ const text = await response.text();
226
+ if (!text) {
227
+ return void 0;
228
+ }
229
+ try {
230
+ return JSON.parse(text);
231
+ } catch (error) {
232
+ throw new LankaError({
233
+ kind: "schema",
234
+ message: `Failed to parse JSON response (content-type: ${contentType ?? "none"}): ${error instanceof Error ? error.message : String(error)}`,
235
+ cause: error
236
+ });
237
+ }
238
+ }
239
+ };
240
+
241
+ // src/gateway/_utils/build-lanka-query-params/buildLankaQueryParams.ts
242
+ var buildLankaQueryParams = (input) => {
243
+ const params = new URLSearchParams();
244
+ const append = (key, value) => {
245
+ if (value == null) return;
246
+ if (Array.isArray(value)) {
247
+ const itemKey = `${key}[]`;
248
+ for (let index = 0; index < value.length; index += 1) append(itemKey, value[index]);
249
+ return;
250
+ }
251
+ if (typeof value === "object") {
252
+ const inner = value;
253
+ const innerKeys = Object.keys(inner);
254
+ for (let index = 0; index < innerKeys.length; index += 1) {
255
+ const innerKey = innerKeys[index];
256
+ append(`${key}[${innerKey}]`, inner[innerKey]);
257
+ }
258
+ return;
259
+ }
260
+ params.append(key, String(value));
261
+ };
262
+ const source = input;
263
+ const keys = Object.keys(source);
264
+ for (let index = 0; index < keys.length; index += 1) append(keys[index], source[keys[index]]);
265
+ return params;
266
+ };
267
+
268
+ // src/gateway/_abstractions/lanka-gateway/ALankaGateway.ts
269
+ var ALankaGateway = class {
270
+ requestExecutor;
271
+ queryParamsHandler;
272
+ useMock;
273
+ basePath;
274
+ constructor(config) {
275
+ lankaLogger.printGatewayLog("Create gateway", this);
276
+ const flags = getLankaFlags();
277
+ this.useMock = config.useMock ?? flags.isMockMode ?? false;
278
+ this.requestExecutor = config.request ?? new LankaFetchJsonRequest();
279
+ this.basePath = config.basePath ?? "";
280
+ this.queryParamsHandler = config.queryParamsHandler ?? buildLankaQueryParams;
281
+ }
282
+ /**
283
+ * Resolves endpoint for request.
284
+ * - Absolute paths (starting with "/") are returned as-is
285
+ * - Relative paths are joined with basePath
286
+ * - Query-only strings like "?a=1" are attached to basePath
287
+ */
288
+ endpoint(path = "") {
289
+ if (isAbsoluteUrl(path)) return path;
290
+ return this.withApiBase(this.resolvePath(path));
291
+ }
292
+ /**
293
+ * Joins `basePath` and the method path.
294
+ */
295
+ resolvePath(path) {
296
+ if (!path) return this.basePath;
297
+ if (path.startsWith("/")) return path;
298
+ if (path.startsWith("?")) return `${this.basePath}${path}`;
299
+ const left = this.basePath.endsWith("/") ? this.basePath.slice(0, -1) : this.basePath;
300
+ const right = path.startsWith("/") ? path.slice(1) : path;
301
+ return `${left}/${right}`;
302
+ }
303
+ /**
304
+ * Prefixes the API base URL from the host contract.
305
+ *
306
+ * Here rather than in every consumer: otherwise each consumer knows the URL
307
+ * and the framework does not, and a realtime plugin would have to know a
308
+ * specific application's build.
309
+ *
310
+ * Declaring the field and not using it would be worse than not declaring it: a
311
+ * declaration nothing is built from is a second truth, free to diverge from
312
+ * the first.
313
+ *
314
+ * An absolute URL never reaches here — `endpoint()` filters it out before the
315
+ * join.
316
+ */
317
+ withApiBase(path) {
318
+ const base = withoutTrailingSlashes(getLankaHost().apiBaseUrl);
319
+ if (!base) return path;
320
+ if (!path) return base;
321
+ return path.startsWith("/") ? `${base}${path}` : `${base}/${path}`;
322
+ }
323
+ buildQueryParams(params) {
324
+ return this.queryParamsHandler(params);
325
+ }
326
+ async request(path, options, mockHandler) {
327
+ return this.requestExecutor.execute(this.endpoint(path), options, mockHandler);
328
+ }
329
+ /**
330
+ * Allows to replace request implementation at runtime (e.g. feature flags / tests).
331
+ * If you prefer static customization - override `request()` in a subclass.
332
+ */
333
+ setRequest(request) {
334
+ this.requestExecutor = request;
335
+ }
336
+ setQueryParamsHandler(handler) {
337
+ this.queryParamsHandler = handler;
338
+ }
339
+ };
340
+ function isAbsoluteUrl(path) {
341
+ if (!path.includes("://")) return false;
342
+ return /^[a-z][a-z\d+\-.]*:\/\//i.test(path);
343
+ }
344
+ var lastRawBase = null;
345
+ var lastTrimmedBase = "";
346
+ function withoutTrailingSlashes(base) {
347
+ if (base !== lastRawBase) {
348
+ lastRawBase = base;
349
+ lastTrimmedBase = base.replace(/\/+$/, "");
350
+ }
351
+ return lastTrimmedBase;
352
+ }
353
+
354
+ // src/gateway/_factories/create-lanka-gateway/createLankaGateway.ts
355
+ var createLankaGateway = (config) => {
356
+ class FunctionalGateway extends ALankaGateway {
357
+ // The base keeps a protected constructor — it is abstract, and a consumer
358
+ // reaching for `new ALankaGateway()` would get an object with no endpoints.
359
+ // A subclass may widen it, and this one is the subclass.
360
+ constructor(gatewayConfig) {
361
+ super(gatewayConfig);
362
+ }
363
+ build() {
364
+ return config.methods({
365
+ endpoint: (path) => this.endpoint(path),
366
+ request: (path, options, mockHandler) => this.request(path, options, mockHandler),
367
+ buildQueryParams: (params) => this.buildQueryParams(params)
368
+ });
369
+ }
370
+ }
371
+ return new FunctionalGateway(config).build();
372
+ };
373
+
374
+ // src/gateway/transport/lanka-fetch-transport/LankaFetchTransport.ts
375
+ var LankaFetchTransport = class {
376
+ async request(resource, options) {
377
+ return await fetch(resource, options);
378
+ }
379
+ };
380
+
381
+ // src/gateway/request/lanka-fetch-request/LankaFetchRequest.ts
382
+ var LankaFetchRequest = class extends ALankaTransportRequest {
383
+ constructor(config = {}) {
384
+ super(config, () => new LankaFetchTransport());
385
+ }
386
+ parse(response) {
387
+ return Promise.resolve(response);
388
+ }
389
+ };
390
+
391
+ // src/gateway/request/_factories/create-lanka-fetch-request/createLankaFetchRequest.ts
392
+ var createLankaFetchRequest = (config = {}) => new LankaFetchRequest(config);
393
+
394
+ // src/gateway/request/_factories/create-lanka-fetch-json-request/createLankaFetchJsonRequest.ts
395
+ var createLankaFetchJsonRequest = (config = {}) => new LankaFetchJsonRequest(config);
396
+
397
+ // src/gateway/transport/lanka-fetch-form-data-transport/LankaFetchFormDataTransport.ts
398
+ var LankaFetchFormDataTransport = class {
399
+ async request(resource, options) {
400
+ const formDataOptions = { ...options };
401
+ if (formDataOptions.body instanceof FormData && formDataOptions.headers) {
402
+ const headers = new Headers(formDataOptions.headers);
403
+ headers.delete("Content-Type");
404
+ formDataOptions.headers = headers;
405
+ }
406
+ return await fetch(resource, formDataOptions);
407
+ }
408
+ };
409
+
410
+ // src/gateway/request/lanka-fetch-form-data-request/LankaFetchFormDataRequest.ts
411
+ var LankaFetchFormDataRequest = class extends ALankaTransportRequest {
412
+ constructor(config = {}) {
413
+ super(config, () => new LankaFetchFormDataTransport());
414
+ }
415
+ parse(response) {
416
+ return Promise.resolve(response);
417
+ }
418
+ };
419
+
420
+ // src/gateway/request/_factories/create-lanka-fetch-form-data-request/createLankaFetchFormDataRequest.ts
421
+ var createLankaFetchFormDataRequest = (config = {}) => new LankaFetchFormDataRequest(config);
422
+ export {
423
+ ALankaGateway,
424
+ ALankaRequest,
425
+ LankaFetchFormDataRequest,
426
+ LankaFetchFormDataTransport,
427
+ LankaFetchJsonRequest,
428
+ LankaFetchJsonTransport,
429
+ LankaFetchRequest,
430
+ LankaFetchTransport,
431
+ buildLankaQueryParams,
432
+ createLankaFetchFormDataRequest,
433
+ createLankaFetchJsonRequest,
434
+ createLankaFetchRequest,
435
+ createLankaGateway,
436
+ lankaHttpInFlight
437
+ };
438
+ //# sourceMappingURL=index.js.map