@marko/run 0.9.7 → 0.11.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,61 @@ var httpVerbs = [
9
13
  "options"
10
14
  ];
11
15
 
16
+ // src/runtime/url-builder.ts
17
+ var encode = encodeURIComponent;
18
+ var pathParts = /* @__PURE__ */ new Map();
19
+ function parsePathParts(path) {
20
+ let parts = pathParts.get(path);
21
+ if (!parts) {
22
+ let lastEnd = 0;
23
+ let paramStart;
24
+ pathParts.set(path, parts = [[]]);
25
+ while (lastEnd >= 0 && (paramStart = path.indexOf("/$", lastEnd) + 1)) {
26
+ parts.push(path.slice(lastEnd, paramStart++));
27
+ if (path.charAt(paramStart) === "$") {
28
+ paramStart++;
29
+ lastEnd = -1;
30
+ } else {
31
+ lastEnd = path.indexOf("/", paramStart);
32
+ }
33
+ parts[0].push(path.slice(paramStart, lastEnd < 0 ? void 0 : lastEnd));
34
+ }
35
+ parts.push(lastEnd >= 0 ? path.slice(lastEnd) : "");
36
+ }
37
+ return parts;
38
+ }
39
+ function joinHref(path, options) {
40
+ let result = path;
41
+ if (options.search) {
42
+ const query = "" + new URLSearchParams(options.search);
43
+ if (query) result += "?" + query;
44
+ }
45
+ if (options.hash) result += "#" + encode(options.hash);
46
+ return result;
47
+ }
48
+ function href(path, ...[options]) {
49
+ return options ? "params" in options ? ((parts) => href_keys(parts, options, ...parts[0]))(
50
+ parsePathParts(path)
51
+ ) : joinHref(path, options) : path;
52
+ }
53
+ function href_path(strings, ...params) {
54
+ let i = 0;
55
+ let j = 0;
56
+ let result = strings[i++];
57
+ if (!result || Array.isArray(result)) result = strings[i++];
58
+ while (i < strings.length) {
59
+ const param = params[j++];
60
+ result += (Array.isArray(param) ? param.map(encode).join("/") : encode(param)) + strings[i++];
61
+ }
62
+ return result;
63
+ }
64
+ function href_values(strings, options, ...params) {
65
+ return joinHref(href_path(strings, ...params), options);
66
+ }
67
+ function href_keys(strings, options, ...keys) {
68
+ return href_values(strings, options, ...keys.map((k) => options.params[k]));
69
+ }
70
+
12
71
  // src/vite/utils/meta-data.ts
13
72
  var verbKeys = new Set(httpVerbs.map((v) => v.toUpperCase()));
14
73
  function isObject(obj) {
@@ -42,10 +101,10 @@ function getMetaDataLookup(data) {
42
101
  }
43
102
 
44
103
  // src/runtime/internal.ts
45
- var NotHandled = Symbol(
104
+ var NotHandled = /* @__PURE__ */ Symbol(
46
105
  "marko-run not handled"
47
106
  );
48
- var NotMatched = Symbol(
107
+ var NotMatched = /* @__PURE__ */ Symbol(
49
108
  "marko-run not matched"
50
109
  );
51
110
  var parentContextLookup = /* @__PURE__ */ new WeakMap();
@@ -56,11 +115,20 @@ var pageResponseInit = {
56
115
  };
57
116
  globalThis.MarkoRun ?? (globalThis.MarkoRun = {
58
117
  NotHandled,
59
- NotMatched,
60
- route(handler) {
61
- return handler;
62
- }
118
+ NotMatched
63
119
  });
120
+ if (!globalThis.Run) {
121
+ const namespace = {
122
+ href
123
+ };
124
+ for (const v of [...httpVerbs, "all"]) {
125
+ const verb = v.toUpperCase();
126
+ const def = createDefineHandler();
127
+ def.href = href;
128
+ namespace[verb] = def;
129
+ }
130
+ globalThis.Run = namespace;
131
+ }
64
132
  var toReadable = (rendered) => {
65
133
  toReadable = rendered.toReadable ? (rendered2) => rendered2.toReadable() : (rendered2) => {
66
134
  let cancelled = false;
@@ -88,39 +156,120 @@ var toReadable = (rendered) => {
88
156
  };
89
157
  return toReadable(rendered);
90
158
  };
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 = "";
159
+ function searchParamsToObject(params) {
160
+ const obj = {};
161
+ for (const [key, value] of params) {
162
+ const prev = obj[key];
163
+ obj[key] = prev ? Array.isArray(prev) ? [...prev, value] : [prev, value] : value;
103
164
  }
104
- return {
105
- request,
165
+ return obj;
166
+ }
167
+ async function readBodyWithLimit(request, maxBytes) {
168
+ if (maxBytes < 0) {
169
+ return await request.text();
170
+ }
171
+ const contentLength = request.headers.get("content-length");
172
+ if (contentLength !== null && Number(contentLength) > maxBytes) {
173
+ throw new Error("Request body too large");
174
+ }
175
+ if (!request.body) {
176
+ throw new Error("Missing request body");
177
+ }
178
+ const reader = request.body.getReader();
179
+ const bytes = new Uint8Array(maxBytes);
180
+ let receivedBytes = 0;
181
+ try {
182
+ while (true) {
183
+ const { done, value } = await reader.read();
184
+ if (done) break;
185
+ if (receivedBytes + value.byteLength > maxBytes) {
186
+ await reader.cancel();
187
+ throw new Error("Request body too large");
188
+ }
189
+ bytes.set(value, receivedBytes);
190
+ receivedBytes += value.byteLength;
191
+ }
192
+ } finally {
193
+ reader.releaseLock();
194
+ }
195
+ return new TextDecoder("utf-8", { fatal: true }).decode(
196
+ bytes.subarray(0, receivedBytes)
197
+ );
198
+ }
199
+ function createContext(route, request, platform, url = new URL(request.url)) {
200
+ const context = {
201
+ route: (route == null ? void 0 : route.path) || "",
106
202
  method: request.method,
203
+ meta: (route == null ? void 0 : route.meta) || {},
204
+ get params() {
205
+ const value = route ? route.options.params ? route.options.params(route.params) : route.params : {};
206
+ Object.defineProperty(context, "params", {
207
+ configurable: true,
208
+ enumerable: true,
209
+ value
210
+ });
211
+ return value;
212
+ },
213
+ get search() {
214
+ const search = searchParamsToObject(url.searchParams);
215
+ const value = (route == null ? void 0 : route.options.search) ? route.options.search(search) : search;
216
+ Object.defineProperty(context, "search", {
217
+ configurable: true,
218
+ enumerable: true,
219
+ value
220
+ });
221
+ return value;
222
+ },
223
+ body: route && request.body ? async () => {
224
+ const contentType = request.headers.get("Content-Type");
225
+ let value;
226
+ if (contentType == null ? void 0 : contentType.includes("application/json")) {
227
+ const { maxBytes, validator } = route.options.json;
228
+ const json = maxBytes < 0 ? await request.json() : JSON.parse(await readBodyWithLimit(request, maxBytes));
229
+ value = validator ? validator(json) : json;
230
+ } else {
231
+ const {
232
+ maxBytes,
233
+ maxParts,
234
+ maxFiles,
235
+ maxFileBytes,
236
+ onFile,
237
+ validator
238
+ } = route.options.form;
239
+ const data = searchParamsToObject(
240
+ (contentType == null ? void 0 : contentType.includes("multipart/form-data")) ? await parseFormData(
241
+ request,
242
+ {
243
+ maxParts,
244
+ maxFiles,
245
+ maxFileSize: maxFileBytes,
246
+ maxTotalSize: maxBytes
247
+ },
248
+ onFile ? (file) => onFile(context, file) : void 0
249
+ ) : new URLSearchParams2(
250
+ await readBodyWithLimit(request, maxBytes)
251
+ )
252
+ );
253
+ value = validator ? validator(data) : validator;
254
+ }
255
+ Object.defineProperty(context, "body", {
256
+ configurable: true,
257
+ enumerable: true,
258
+ value
259
+ });
260
+ return value;
261
+ } : void 0,
262
+ data: {},
107
263
  url,
264
+ request,
108
265
  platform,
109
- meta,
110
- params,
111
- route: path,
112
266
  serializedGlobals,
113
267
  parent: parentContextLookup.get(request),
114
268
  async fetch(resource, init) {
115
- let request2;
116
- let url2;
117
- if (resource instanceof Request) {
118
- request2 = new Request(resource, init);
119
- url2 = new URL(request2.url);
120
- } else {
121
- url2 = typeof resource === "string" ? new URL(resource, this.url) : resource;
122
- request2 = new Request(url2, init);
123
- }
269
+ const request2 = new Request(
270
+ typeof resource === "string" ? new URL(resource, this.url) : resource,
271
+ init
272
+ );
124
273
  parentContextLookup.set(request2, this);
125
274
  return await globalThis.__marko_run__.fetch(request2, this.platform) || new Response(null, { status: 404 });
126
275
  },
@@ -155,16 +304,26 @@ function createContext(route, request, platform, url = new URL(request.url)) {
155
304
  );
156
305
  }
157
306
  };
307
+ return context;
308
+ }
309
+ function render(context, template, input, data) {
310
+ if (data) {
311
+ Object.assign(context.data, data);
312
+ }
313
+ return context.render(template, input);
158
314
  }
159
- async function call(handler, next, context) {
315
+ async function call(handler, next, context, data) {
160
316
  let response;
317
+ if (data) {
318
+ Object.assign(context.data, data);
319
+ }
161
320
  if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
162
321
  let nextCallCount = 0;
163
322
  let didThrow = false;
164
323
  try {
165
- response = await handler(context, () => {
324
+ response = await handler(context, (d) => {
166
325
  nextCallCount++;
167
- return next();
326
+ return next(d);
168
327
  });
169
328
  } catch (error) {
170
329
  didThrow = true;
@@ -203,14 +362,14 @@ async function call(handler, next, context) {
203
362
  function compose(handlers) {
204
363
  const len = handlers.length;
205
364
  if (!len) {
206
- return (_context, next) => next();
365
+ return passthroughHandler;
207
366
  } else if (len === 1) {
208
367
  return handlers[0];
209
368
  }
210
369
  return (context, next) => {
211
370
  let i = 0;
212
- return (function nextHandler() {
213
- return i < len ? call(handlers[i++], nextHandler, context) : next();
371
+ return (function nextHandler(data) {
372
+ return i < len ? call(handlers[i++], nextHandler, context, data) : next(data);
214
373
  })();
215
374
  };
216
375
  }
@@ -231,6 +390,87 @@ function normalizeHandler(obj) {
231
390
  }
232
391
  return passthrough;
233
392
  }
393
+ function createDefineHandler() {
394
+ return (optionsOrHandlers, handlers) => {
395
+ let handler;
396
+ if (typeof optionsOrHandlers === "function") {
397
+ handler = optionsOrHandlers;
398
+ handler.options = {};
399
+ } else if (Array.isArray(optionsOrHandlers)) {
400
+ handler = compose(optionsOrHandlers);
401
+ handler.options = {};
402
+ } else if (typeof handlers === "function") {
403
+ handler = handlers;
404
+ handler.options = optionsOrHandlers;
405
+ } else if (Array.isArray(handlers)) {
406
+ handler = compose(handlers);
407
+ handler.options = optionsOrHandlers;
408
+ } else {
409
+ handler = passthroughHandler;
410
+ handler.options = optionsOrHandlers;
411
+ }
412
+ return handler;
413
+ };
414
+ }
415
+ function normalizeValidator(validator) {
416
+ return validator && typeof validator !== "function" ? (input) => {
417
+ const result = validator["~standard"].validate(input);
418
+ if (result instanceof Promise) {
419
+ throw new TypeError("Schema validation must be synchronous");
420
+ }
421
+ return result.issues ? [input, result.issues] : [result.value, void 0];
422
+ } : validator;
423
+ }
424
+ var defaultMaxBytes = 1024 * 1024;
425
+ var defaultMaxParts = 1e3;
426
+ var defaultMaxFiles = 20;
427
+ function mergeOptions(...fns) {
428
+ const merged = {};
429
+ for (const fn of fns) {
430
+ if (typeof fn === "function" && "options" in fn) {
431
+ const { options } = fn;
432
+ for (const k in options) {
433
+ const key = k;
434
+ const option = options[key];
435
+ if (typeof option === "object" && typeof merged[key] === "object") {
436
+ Object.assign(merged[key], option);
437
+ } else if (option) {
438
+ merged[key] = option;
439
+ }
440
+ }
441
+ }
442
+ }
443
+ const result = {
444
+ params: normalizeValidator(merged.params),
445
+ search: normalizeValidator(merged.search)
446
+ };
447
+ if (merged.json) {
448
+ const { maxBytes = defaultMaxBytes, validator } = typeof merged.json === "function" || "~standard" in merged.json ? { validator: merged.json } : merged.json;
449
+ result.json = {
450
+ maxBytes,
451
+ validator: normalizeValidator(validator)
452
+ };
453
+ }
454
+ if (merged.form) {
455
+ const {
456
+ maxBytes,
457
+ maxFiles = defaultMaxFiles,
458
+ maxFileBytes = defaultMaxBytes,
459
+ maxParts = defaultMaxParts,
460
+ onFile,
461
+ validator
462
+ } = typeof merged.form === "function" || "~standard" in merged.form ? { validator: merged.form } : merged.form;
463
+ result.form = {
464
+ maxBytes: maxBytes ?? maxFiles * maxFileBytes,
465
+ maxFileBytes,
466
+ maxFiles,
467
+ maxParts,
468
+ onFile,
469
+ validator: normalizeValidator(validator)
470
+ };
471
+ }
472
+ return result;
473
+ }
234
474
  function stripResponseBodySync(response) {
235
475
  return response.body ? new Response(null, response) : response;
236
476
  }
@@ -239,6 +479,7 @@ function stripResponseBody(response) {
239
479
  }
240
480
  function passthrough() {
241
481
  }
482
+ var passthroughHandler = (_ctx, next) => next();
242
483
  function noContent() {
243
484
  return new Response(null, {
244
485
  status: 204
@@ -256,12 +497,15 @@ export {
256
497
  call,
257
498
  compose,
258
499
  createContext,
500
+ mergeOptions,
259
501
  noContent,
260
502
  normalizeHandler,
261
503
  getMetaDataLookup as normalizeMeta,
504
+ normalizeValidator,
262
505
  notHandled,
263
506
  notMatched,
264
507
  passthrough,
508
+ render,
265
509
  stripResponseBody,
266
510
  stripResponseBodySync
267
511
  };
@@ -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>>;