@marko/run 0.10.0 → 0.11.0-rc.2

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.
@@ -1,3 +1,7 @@
1
+ // src/runtime/internal.ts
2
+ import { URLSearchParams as URLSearchParams2 } from "node:url";
3
+ import { parseFormData } from "@remix-run/form-data-parser";
4
+
1
5
  // src/vite/constants.ts
2
6
  var httpVerbs = [
3
7
  "get",
@@ -9,6 +13,83 @@ var httpVerbs = [
9
13
  "options"
10
14
  ];
11
15
 
16
+ // src/runtime/thenable.ts
17
+ var kPromise = /* @__PURE__ */ Symbol("promise");
18
+ var kReadFn = /* @__PURE__ */ Symbol("read fn");
19
+ function thenFn(resolve, reject) {
20
+ return (this[kPromise] || (this[kPromise] = this[kReadFn]())).then(resolve, reject);
21
+ }
22
+ function catchFn(reject) {
23
+ return (this[kPromise] || (this[kPromise] = this[kReadFn]())).catch(reject);
24
+ }
25
+ function finallyFn(resolve) {
26
+ return (this[kPromise] || (this[kPromise] = this[kReadFn]())).finally(resolve);
27
+ }
28
+ function thenable(fn) {
29
+ return {
30
+ [kPromise]: null,
31
+ [kReadFn]: fn,
32
+ then: thenFn,
33
+ catch: catchFn,
34
+ finally: finallyFn
35
+ };
36
+ }
37
+
38
+ // src/runtime/url-builder.ts
39
+ var encode = encodeURIComponent;
40
+ var pathParts = /* @__PURE__ */ new Map();
41
+ function parsePathParts(path) {
42
+ let parts = pathParts.get(path);
43
+ if (!parts) {
44
+ let lastEnd = 0;
45
+ let paramStart;
46
+ pathParts.set(path, parts = [[]]);
47
+ while (lastEnd >= 0 && (paramStart = path.indexOf("/$", lastEnd) + 1)) {
48
+ parts.push(path.slice(lastEnd, paramStart++));
49
+ if (path.charAt(paramStart) === "$") {
50
+ paramStart++;
51
+ lastEnd = -1;
52
+ } else {
53
+ lastEnd = path.indexOf("/", paramStart);
54
+ }
55
+ parts[0].push(path.slice(paramStart, lastEnd < 0 ? void 0 : lastEnd));
56
+ }
57
+ parts.push(lastEnd >= 0 ? path.slice(lastEnd) : "");
58
+ }
59
+ return parts;
60
+ }
61
+ function joinHref(path, options) {
62
+ let result = path;
63
+ if (options.search) {
64
+ const query = "" + new URLSearchParams(options.search);
65
+ if (query) result += "?" + query;
66
+ }
67
+ if (options.hash) result += "#" + encode(options.hash);
68
+ return result;
69
+ }
70
+ function href(path, ...[options]) {
71
+ return options ? "params" in options ? ((parts) => href_keys(parts, options, ...parts[0]))(
72
+ parsePathParts(path)
73
+ ) : joinHref(path, options) : path;
74
+ }
75
+ function href_path(strings, ...params) {
76
+ let i = 0;
77
+ let j = 0;
78
+ let result = strings[i++];
79
+ if (!result || Array.isArray(result)) result = strings[i++];
80
+ while (i < strings.length) {
81
+ const param = params[j++];
82
+ result += (Array.isArray(param) ? param.map(encode).join("/") : encode(param)) + strings[i++];
83
+ }
84
+ return result;
85
+ }
86
+ function href_values(strings, options, ...params) {
87
+ return joinHref(href_path(strings, ...params), options);
88
+ }
89
+ function href_keys(strings, options, ...keys) {
90
+ return href_values(strings, options, ...keys.map((k) => options.params[k]));
91
+ }
92
+
12
93
  // src/vite/utils/meta-data.ts
13
94
  var verbKeys = new Set(httpVerbs.map((v) => v.toUpperCase()));
14
95
  function isObject(obj) {
@@ -56,10 +137,17 @@ var pageResponseInit = {
56
137
  };
57
138
  globalThis.MarkoRun ?? (globalThis.MarkoRun = {
58
139
  NotHandled,
59
- NotMatched,
60
- route(handler) {
61
- return handler;
62
- }
140
+ NotMatched
141
+ });
142
+ globalThis.Run ?? (globalThis.Run = {
143
+ href,
144
+ ALL: createDefineHandler("ALL"),
145
+ ...Object.fromEntries(
146
+ httpVerbs.map((v) => {
147
+ const verb = v.toUpperCase();
148
+ return [v.toUpperCase(), createDefineHandler(verb)];
149
+ })
150
+ )
63
151
  });
64
152
  var toReadable = (rendered) => {
65
153
  toReadable = rendered.toReadable ? (rendered2) => rendered2.toReadable() : (rendered2) => {
@@ -88,27 +176,98 @@ var toReadable = (rendered) => {
88
176
  };
89
177
  return toReadable(rendered);
90
178
  };
91
- function createContext(route, request, platform, url = new URL(request.url)) {
92
- let meta;
93
- let params;
94
- let path;
95
- if (route) {
96
- meta = route.meta;
97
- params = route.params;
98
- path = route.path;
99
- } else {
100
- meta = {};
101
- params = {};
102
- path = "";
179
+ function searchParamsToObject(params) {
180
+ const obj = {};
181
+ for (const [key, value] of params) {
182
+ const prev = obj[key];
183
+ obj[key] = prev ? Array.isArray(prev) ? [...prev, value] : [prev, value] : value;
103
184
  }
104
- return {
105
- request,
185
+ return obj;
186
+ }
187
+ async function readBodyWithLimit(request, maxBytes) {
188
+ if (maxBytes < 0) {
189
+ return await request.text();
190
+ }
191
+ const contentLength = request.headers.get("content-length");
192
+ if (contentLength !== null && Number(contentLength) > maxBytes) {
193
+ throw new Error("Request body too large");
194
+ }
195
+ if (!request.body) {
196
+ throw new Error("Missing request body");
197
+ }
198
+ const reader = request.body.getReader();
199
+ const bytes = new Uint8Array(maxBytes);
200
+ let receivedBytes = 0;
201
+ try {
202
+ while (true) {
203
+ const { done, value } = await reader.read();
204
+ if (done) break;
205
+ if (receivedBytes + value.byteLength > maxBytes) {
206
+ await reader.cancel();
207
+ throw new Error("Request body too large");
208
+ }
209
+ bytes.set(value, receivedBytes);
210
+ receivedBytes += value.byteLength;
211
+ }
212
+ } finally {
213
+ reader.releaseLock();
214
+ }
215
+ return new TextDecoder("utf-8", { fatal: true }).decode(
216
+ bytes.subarray(0, receivedBytes)
217
+ );
218
+ }
219
+ async function readBody(route, context) {
220
+ const { request } = context;
221
+ const contentType = request.headers.get("Content-Type");
222
+ if (contentType == null ? void 0 : contentType.includes("application/json")) {
223
+ const { maxBytes: maxBytes2, validator: validator2 } = route.options.json;
224
+ const json = maxBytes2 < 0 ? await request.json() : JSON.parse(await readBodyWithLimit(request, maxBytes2));
225
+ return validator2 ? validator2(json) : json;
226
+ }
227
+ const { maxBytes, maxParts, maxFiles, maxFileBytes, onFile, validator } = route.options.form;
228
+ const data = searchParamsToObject(
229
+ (contentType == null ? void 0 : contentType.includes("multipart/form-data")) ? await parseFormData(
230
+ request,
231
+ {
232
+ maxParts,
233
+ maxFiles,
234
+ maxFileSize: maxFileBytes,
235
+ maxTotalSize: maxBytes
236
+ },
237
+ onFile ? (file) => onFile(context, file) : void 0
238
+ ) : new URLSearchParams2(await readBodyWithLimit(request, maxBytes))
239
+ );
240
+ return validator && validator(data);
241
+ }
242
+ function createContext(route, request, platform, url = new URL(request.url)) {
243
+ const context = {
244
+ route: (route == null ? void 0 : route.path) || "",
106
245
  method: request.method,
246
+ meta: (route == null ? void 0 : route.meta) || {},
247
+ get params() {
248
+ const value = route ? route.options.params ? route.options.params(route.params) : route.params : {};
249
+ Object.defineProperty(context, "params", {
250
+ configurable: true,
251
+ enumerable: true,
252
+ value
253
+ });
254
+ return value;
255
+ },
256
+ get search() {
257
+ const search = searchParamsToObject(url.searchParams);
258
+ const value = (route == null ? void 0 : route.options.search) ? route.options.search(search) : search;
259
+ Object.defineProperty(context, "search", {
260
+ configurable: true,
261
+ enumerable: true,
262
+ value
263
+ });
264
+ return value;
265
+ },
266
+ body: route && request.body ? thenable(() => readBody(route, context)) : void 0,
267
+ data: {},
107
268
  url,
269
+ request,
108
270
  platform,
109
- meta,
110
- params,
111
- route: path,
112
271
  serializedGlobals,
113
272
  parent: parentContextLookup.get(request),
114
273
  async fetch(resource, init) {
@@ -150,16 +309,37 @@ function createContext(route, request, platform, url = new URL(request.url)) {
150
309
  );
151
310
  }
152
311
  };
312
+ return context;
313
+ }
314
+ function render(context, template, input, data) {
315
+ if (data) {
316
+ Object.assign(context.data, data);
317
+ }
318
+ return context.render(template, input);
153
319
  }
154
- async function call(handler, next, context) {
320
+ var handlerMethod = /* @__PURE__ */ new WeakMap();
321
+ async function call(handler, next, context, data) {
155
322
  let response;
323
+ if (data) {
324
+ Object.assign(context.data, data);
325
+ }
326
+ let method = handlerMethod.get(handler);
327
+ if (method === void 0) {
328
+ handlerMethod.set(
329
+ handler,
330
+ method = "verb" in handler && handler.verb !== "ALL" ? handler.verb : false
331
+ );
332
+ }
333
+ if (method && method !== context.method) {
334
+ return next(data);
335
+ }
156
336
  if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
157
337
  let nextCallCount = 0;
158
338
  let didThrow = false;
159
339
  try {
160
- response = await handler(context, () => {
340
+ response = await handler(context, (d) => {
161
341
  nextCallCount++;
162
- return next();
342
+ return next(d);
163
343
  });
164
344
  } catch (error) {
165
345
  didThrow = true;
@@ -193,19 +373,19 @@ async function call(handler, next, context) {
193
373
  if (response === null || response === NotMatched || response === NotHandled) {
194
374
  throw response || NotMatched;
195
375
  }
196
- return response || next();
376
+ return response || next(data);
197
377
  }
198
378
  function compose(handlers) {
199
379
  const len = handlers.length;
200
380
  if (!len) {
201
- return (_context, next) => next();
381
+ return passthroughHandler;
202
382
  } else if (len === 1) {
203
383
  return handlers[0];
204
384
  }
205
385
  return (context, next) => {
206
386
  let i = 0;
207
- return (function nextHandler() {
208
- return i < len ? call(handlers[i++], nextHandler, context) : next();
387
+ return (function nextHandler(data) {
388
+ return i < len ? call(handlers[i++], nextHandler, context, data) : next(data);
209
389
  })();
210
390
  };
211
391
  }
@@ -226,6 +406,104 @@ function normalizeHandler(obj) {
226
406
  }
227
407
  return passthrough;
228
408
  }
409
+ function assertHandlerVerb(verb, handler) {
410
+ if ("verb" in handler && handler.verb !== verb) {
411
+ throw new Error(
412
+ `Expected verb ${verb} but handler was defined with Run.${handler.verb}`
413
+ );
414
+ }
415
+ }
416
+ function createDefineHandler(verb) {
417
+ return (optionsOrHandlers, handlers) => {
418
+ let handler;
419
+ if (typeof optionsOrHandlers === "function") {
420
+ assertHandlerVerb(verb, optionsOrHandlers);
421
+ handler = optionsOrHandlers;
422
+ handler.options ?? (handler.options = {});
423
+ } else if (Array.isArray(optionsOrHandlers)) {
424
+ for (const h of optionsOrHandlers) assertHandlerVerb(verb, h);
425
+ handler = compose(optionsOrHandlers);
426
+ handler.options = mergeOptions(...optionsOrHandlers);
427
+ } else if (typeof handlers === "function") {
428
+ assertHandlerVerb(verb, handlers);
429
+ handler = handlers;
430
+ handler.options = mergeOptions(handlers, optionsOrHandlers);
431
+ } else if (Array.isArray(handlers)) {
432
+ for (const h of handlers) assertHandlerVerb(verb, h);
433
+ handler = compose(handlers);
434
+ handler.options = mergeOptions(...handlers, optionsOrHandlers);
435
+ } else {
436
+ handler = passthroughHandler;
437
+ handler.options = optionsOrHandlers;
438
+ }
439
+ handler.verb = verb;
440
+ return handler;
441
+ };
442
+ }
443
+ function normalizeValidator(validator) {
444
+ return validator && typeof validator !== "function" ? (input) => {
445
+ const result = validator["~standard"].validate(input);
446
+ if (result instanceof Promise) {
447
+ throw new TypeError("Schema validation must be synchronous");
448
+ }
449
+ return result.issues ? [input, result.issues] : [result.value, void 0];
450
+ } : validator;
451
+ }
452
+ var defaultMaxBytes = 1024 * 1024;
453
+ var defaultMaxParts = 1e3;
454
+ var defaultMaxFiles = 20;
455
+ function mergeOptions(...arr) {
456
+ const merged = {};
457
+ for (const item of arr) {
458
+ let options;
459
+ if (typeof item === "object") {
460
+ options = item;
461
+ } else if ("options" in item) {
462
+ options = item.options;
463
+ } else {
464
+ continue;
465
+ }
466
+ for (const k in options) {
467
+ const key = k;
468
+ const option = options[key];
469
+ if (typeof option === "object" && typeof merged[key] === "object") {
470
+ Object.assign(merged[key], option);
471
+ } else if (option) {
472
+ merged[key] = option;
473
+ }
474
+ }
475
+ }
476
+ const result = {
477
+ params: normalizeValidator(merged.params),
478
+ search: normalizeValidator(merged.search)
479
+ };
480
+ if (merged.json) {
481
+ const { maxBytes = defaultMaxBytes, validator } = typeof merged.json === "function" || "~standard" in merged.json ? { validator: merged.json } : merged.json;
482
+ result.json = {
483
+ maxBytes,
484
+ validator: normalizeValidator(validator)
485
+ };
486
+ }
487
+ if (merged.form) {
488
+ const {
489
+ maxBytes,
490
+ maxFiles = defaultMaxFiles,
491
+ maxFileBytes = defaultMaxBytes,
492
+ maxParts = defaultMaxParts,
493
+ onFile,
494
+ validator
495
+ } = typeof merged.form === "function" || "~standard" in merged.form ? { validator: merged.form } : merged.form;
496
+ result.form = {
497
+ maxBytes: maxBytes ?? maxFiles * maxFileBytes,
498
+ maxFileBytes,
499
+ maxFiles,
500
+ maxParts,
501
+ onFile,
502
+ validator: normalizeValidator(validator)
503
+ };
504
+ }
505
+ return result;
506
+ }
229
507
  function stripResponseBodySync(response) {
230
508
  return response.body ? new Response(null, response) : response;
231
509
  }
@@ -234,6 +512,7 @@ function stripResponseBody(response) {
234
512
  }
235
513
  function passthrough() {
236
514
  }
515
+ var passthroughHandler = (_ctx, next) => next();
237
516
  function noContent() {
238
517
  return new Response(null, {
239
518
  status: 204
@@ -248,15 +527,19 @@ function notMatched() {
248
527
  export {
249
528
  NotHandled,
250
529
  NotMatched,
530
+ assertHandlerVerb,
251
531
  call,
252
532
  compose,
253
533
  createContext,
534
+ mergeOptions,
254
535
  noContent,
255
536
  normalizeHandler,
256
537
  getMetaDataLookup as normalizeMeta,
538
+ normalizeValidator,
257
539
  notHandled,
258
540
  notMatched,
259
541
  passthrough,
542
+ render,
260
543
  stripResponseBody,
261
544
  stripResponseBodySync
262
545
  };
@@ -0,0 +1,60 @@
1
+ import type { AppPaths, Context, HttpVerb, NextFunction, NormalizedMeta, PathsForVerb, Route as NewRoute, RouteDef } from "./types";
2
+ type OneOrMany<T> = T | T[];
3
+ type NoParams = {};
4
+ type AllKeys<T> = T extends T ? keyof T : never;
5
+ type Simplify<T> = T extends unknown ? {
6
+ [K in keyof T]: T[K];
7
+ } : never;
8
+ type SuperSet<T, U extends T> = T & {
9
+ [K in AllKeys<U> as K extends keyof T ? never : K]?: never;
10
+ };
11
+ type SuperSets<T, U extends T, K extends keyof T> = Omit<T, K> & {
12
+ [P in K]: Simplify<SuperSet<T[P], U[P]>>;
13
+ };
14
+ type Union<T> = T[keyof T];
15
+ type MigrateContext<T extends Route> = Context<NewRoute<RouteDef<T["path"], T["method"], T["meta"]>>>;
16
+ export type Awaitable<T> = Promise<T> | T;
17
+ export type MultiRouteContext<TRoute extends Route, _Preserved extends TRoute = TRoute> = TRoute extends any ? MigrateContext<Simplify<SuperSets<TRoute, _Preserved, "params">>> : never;
18
+ export type ParamsObject = Record<string, string>;
19
+ export type InputObject = Record<PropertyKey, any>;
20
+ export type HandlerLike<TRoute extends Route = AnyRoute, Verb extends HttpVerb = HttpVerb> = Awaitable<OneOrMany<RouteHandler<TRoute, Verb>>>;
21
+ export type RouteHandlerResult = Response | typeof MarkoRun.NotHandled | typeof MarkoRun.NotMatched | null | void;
22
+ export type RouteHandler<TRoute extends Route = AnyRoute, Verb extends HttpVerb = HttpVerb> = (context: MultiRouteContext<Extract<TRoute, {
23
+ method: Verb;
24
+ }>>, next: NextFunction) => Awaitable<RouteHandlerResult>;
25
+ export interface Route<Params extends ParamsObject = ParamsObject, Meta = any, Path extends string = string, Verb extends HttpVerb = HttpVerb> {
26
+ path: Path;
27
+ params: Params;
28
+ meta: NormalizedMeta<Meta, Verb>;
29
+ method: Verb;
30
+ }
31
+ type Member<T, U> = T extends T ? (U extends T ? T : never) : never;
32
+ type PathParamKeys<Path extends string> = Path extends `${infer _}$${infer Param}/${infer Rest}` ? [Unescape<Param>, ...PathParamKeys<Rest>] : Path extends `${infer _}$$${infer Param}` ? [Unescape<Param>] : Path extends `${infer _}$${infer Param}` ? [Unescape<Param>] : [];
33
+ type Unescape<Escaped extends string> = Escaped extends `\`${infer Value}\`` ? Value : Escaped;
34
+ type PathParams<Path extends string, Keys extends string[] = PathParamKeys<Path>> = 0 extends Keys["length"] ? NoParams : {
35
+ [K in Keys[number]]: string;
36
+ };
37
+ type Segments<T extends string, Acc extends string[] = []> = T extends "" ? Acc : T extends `${infer Left}/${infer Rest}` ? Segments<Rest, [...Acc, Left]> : [...Acc, T];
38
+ type GTE<A extends any[], B extends any[]> = A["length"] extends B["length"] ? 1 : A extends [infer _Ha, ...infer Ta] ? B extends [infer _Hb, ...infer Tb] ? GTE<Ta, Tb> : 1 : 0;
39
+ type MatchSegments<A extends string, B extends string> = A extends `${infer P}/${string}*` ? 1 extends GTE<Segments<B>, Segments<P>> ? `${P}/${string}` : never : Segments<B>["length"] extends Segments<A>["length"] ? A : never;
40
+ type PathPattern<T extends string> = T extends `${infer Left}/\${${string}}/${infer Rest}` ? PathPattern<`${Left}/${string}/${Rest}`> : T extends `${infer Left}/\${...${string}}` ? PathPattern<`${Left}/${string}*`> : T extends `${infer Left}/\${${string}}` ? PathPattern<`${Left}/${string}`> : T;
41
+ type ValidatePath<Paths extends string, Path extends string> = Paths | (Path extends `/${string}` ? MatchSegments<Member<PathPattern<Paths>, Path>, Path> : Path);
42
+ type ValidateHref<Paths extends string, Href extends string> = Href extends `${infer P}#${infer H}?${infer Q}` ? `${ValidatePath<Paths, P>}#${H}?${Q}` : Href extends `${infer P}?${infer Q}` ? `${ValidatePath<Paths, P>}?${Q}` : Href extends `${infer P}#${infer H}` ? `${ValidatePath<Paths, P>}#${H}` : ValidatePath<Paths, Href>;
43
+ export interface AppData {
44
+ }
45
+ export type Routes = [AppPaths] extends [never] ? Record<string, Route> : {
46
+ [Path in keyof AppPaths]: Union<{
47
+ [V in HttpVerb]: Route<PathParams<Path>, V extends keyof AppPaths[Path]["verbs"] ? AppPaths[Path]["verbs"][V]["def"]["meta"] : {}, Path, V>;
48
+ }>;
49
+ };
50
+ export type AnyRoute = Routes[keyof Routes];
51
+ export type HandlerTypeFn<TRoute extends Route = AnyRoute> = [
52
+ AppPaths
53
+ ] extends [never] ? <T extends HandlerLike<TRoute>>(handler: T) => T : <Params extends ParamsObject = ParamsObject, Meta = any, T extends HandlerLike<Route<Params, Meta>> = HandlerLike<Route<Params, Meta>>>(handler: T) => T;
54
+ export type GetPaths = PathsForVerb<"GET">;
55
+ export type PostPaths = PathsForVerb<"POST">;
56
+ export type GetablePath<T extends string> = ValidatePath<GetPaths, T>;
57
+ export type GetableHref<T extends string> = ValidateHref<GetPaths, T>;
58
+ export type PostablePath<T extends string> = ValidatePath<PostPaths, T>;
59
+ export type PostableHref<T extends string> = ValidateHref<PostPaths, T>;
60
+ export {};
@@ -1,3 +1,4 @@
1
1
  export declare const NotHandled: unique symbol;
2
2
  export declare const NotMatched: unique symbol;
3
- export type { GetableHref, GetablePath, GetPaths, Platform, PostableHref, PostablePath, PostPaths, Verb, } from "./types";
3
+ export type { GetableHref, GetablePath, GetPaths, PostableHref, PostablePath, PostPaths, } from "./legacy-types";
4
+ export type { Platform, HttpVerb as Verb } from "./types";
@@ -1,3 +1,3 @@
1
1
  export declare const fetch: <TPlatform extends import("./types").Platform = import("./types").Platform>(request: Request, platform: TPlatform) => ReturnType<import("./types").Fetch<TPlatform>>;
2
2
  export declare const match: import("./types").Match;
3
- export declare const invoke: <TPlatform extends import("./types").Platform = import("./types").Platform>(route: import("./types").RouteWithHandler<import("./types").ParamsObject, unknown, string> | null, request: Request, platform: TPlatform) => ReturnType<import("./types").Invoke<TPlatform>>;
3
+ export declare const invoke: <TPlatform extends import("./types").Platform = import("./types").Platform>(route: import("./types").RouteMatch<import("./types").Context<import("./types").Route<import("./types").RouteDef<string, import("./types").HttpVerb, any, Record<string, any>, [any]>, any>>> | null, request: Request, platform: TPlatform) => ReturnType<import("./types").Invoke<TPlatform>>;
@@ -0,0 +1,11 @@
1
+ declare const kPromise: unique symbol;
2
+ declare const kReadFn: unique symbol;
3
+ export interface Thenable<T = any> extends Promise<T> {
4
+ [kPromise]: null | Promise<T>;
5
+ [kReadFn]: () => Promise<T>;
6
+ then: Promise<T>["then"];
7
+ catch: Promise<T>["catch"];
8
+ finally: Promise<T>["finally"];
9
+ }
10
+ export default function thenable<T>(fn: () => Promise<T>): Thenable<T>;
11
+ export {};