@marko/run 0.10.0 → 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) {
@@ -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,27 +156,113 @@ 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) {
@@ -150,16 +304,26 @@ function createContext(route, request, platform, url = new URL(request.url)) {
150
304
  );
151
305
  }
152
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);
153
314
  }
154
- async function call(handler, next, context) {
315
+ async function call(handler, next, context, data) {
155
316
  let response;
317
+ if (data) {
318
+ Object.assign(context.data, data);
319
+ }
156
320
  if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
157
321
  let nextCallCount = 0;
158
322
  let didThrow = false;
159
323
  try {
160
- response = await handler(context, () => {
324
+ response = await handler(context, (d) => {
161
325
  nextCallCount++;
162
- return next();
326
+ return next(d);
163
327
  });
164
328
  } catch (error) {
165
329
  didThrow = true;
@@ -198,14 +362,14 @@ async function call(handler, next, context) {
198
362
  function compose(handlers) {
199
363
  const len = handlers.length;
200
364
  if (!len) {
201
- return (_context, next) => next();
365
+ return passthroughHandler;
202
366
  } else if (len === 1) {
203
367
  return handlers[0];
204
368
  }
205
369
  return (context, next) => {
206
370
  let i = 0;
207
- return (function nextHandler() {
208
- 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);
209
373
  })();
210
374
  };
211
375
  }
@@ -226,6 +390,87 @@ function normalizeHandler(obj) {
226
390
  }
227
391
  return passthrough;
228
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
+ }
229
474
  function stripResponseBodySync(response) {
230
475
  return response.body ? new Response(null, response) : response;
231
476
  }
@@ -234,6 +479,7 @@ function stripResponseBody(response) {
234
479
  }
235
480
  function passthrough() {
236
481
  }
482
+ var passthroughHandler = (_ctx, next) => next();
237
483
  function noContent() {
238
484
  return new Response(null, {
239
485
  status: 204
@@ -251,12 +497,15 @@ export {
251
497
  call,
252
498
  compose,
253
499
  createContext,
500
+ mergeOptions,
254
501
  noContent,
255
502
  normalizeHandler,
256
503
  getMetaDataLookup as normalizeMeta,
504
+ normalizeValidator,
257
505
  notHandled,
258
506
  notMatched,
259
507
  passthrough,
508
+ render,
260
509
  stripResponseBody,
261
510
  stripResponseBodySync
262
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>>;