@orieljs/framework 0.1.0-alpha.4 → 0.1.0-alpha.5

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.
@@ -8,8 +8,9 @@ import {
8
8
  Str,
9
9
  currentContainer,
10
10
  facade,
11
+ fileType_exports,
11
12
  inject
12
- } from "./chunk-DCI2T7WJ.js";
13
+ } from "./chunk-PM5YTG57.js";
13
14
  import {
14
15
  Application,
15
16
  AuthFactory,
@@ -32,6 +33,7 @@ import {
32
33
  MailManager,
33
34
  MiddlewareCallbacks,
34
35
  NotificationDispatcher,
36
+ PaginationState,
35
37
  PipelineFactory,
36
38
  QueueManager,
37
39
  Redirector,
@@ -43,7 +45,7 @@ import {
43
45
  ValidationFactory,
44
46
  ViewFactory,
45
47
  token
46
- } from "./chunk-WSGWYXAO.js";
48
+ } from "./chunk-BSQKOLSK.js";
47
49
 
48
50
  // ../Config/dist/index.js
49
51
  import { existsSync, readFileSync } from "fs";
@@ -490,7 +492,7 @@ var Application2 = class _Application extends Container {
490
492
  this.scoped(Vite, () => new Vite(this));
491
493
  }
492
494
  base;
493
- version = "0.1.0-alpha.4";
495
+ version = "0.1.0-alpha.5";
494
496
  providers = [];
495
497
  bootingCallbacks = [];
496
498
  bootedCallbacks = [];
@@ -885,20 +887,60 @@ function mimeFromExtension(pathOrExtension) {
885
887
  function extensionFromMime(type) {
886
888
  return EXTENSIONS[type.split(";")[0].trim().toLowerCase()];
887
889
  }
888
- var FormUploadedFile = class {
889
- constructor(file) {
890
+ var NEVER_CLAIMED = /* @__PURE__ */ new Set([
891
+ ...fileType_exports.BINARY_AND_MARKUP_TYPES.flatMap(({ extensions }) => extensions),
892
+ "jpe",
893
+ "svgz",
894
+ "xhtml",
895
+ "xht",
896
+ "shtml",
897
+ "xsl",
898
+ "xslt",
899
+ "mht",
900
+ "mhtml",
901
+ "js",
902
+ "mjs",
903
+ "rss",
904
+ "atom",
905
+ "rdf",
906
+ "mathml",
907
+ "mml",
908
+ "xul"
909
+ ]);
910
+ var PLAIN_EXTENSION = /^[a-z0-9]+$/;
911
+ var FormUploadedFile = class _FormUploadedFile {
912
+ constructor(file, detected) {
890
913
  this.file = file;
914
+ this.detected = detected;
891
915
  this.clientName = file.name ?? "";
892
916
  this.mimeType = file.type === "" ? "application/octet-stream" : file.type;
893
917
  this.size = file.size;
894
918
  }
895
919
  file;
920
+ detected;
896
921
  clientName;
897
922
  mimeType;
898
923
  size;
899
- /** Guessed from the MIME type, falling back to the client's extension (Laravel's extension()). */
924
+ /**
925
+ * Reads the leading bytes first, so extension(), hashName() and store() name the file by its content. The request
926
+ * builds its uploads this way.
927
+ */
928
+ static async fromFile(file) {
929
+ return new _FormUploadedFile(file, fileType_exports.detectFileType(new Uint8Array(await file.slice(0, fileType_exports.SNIFF_BYTES).arrayBuffer())));
930
+ }
931
+ /**
932
+ * Laravel's extension(): the type the bytes show (guessExtension()), when they were read and match a known format.
933
+ * Text and bytes no format matches keep the client's claim (its MIME type, else its file name) when it is a plain
934
+ * extension, unless it names a format the bytes would have shown or one a browser renders or runs: otherwise 'txt'
935
+ * for text, '' for the rest. Unread (built directly), the client's claim when it is a plain extension, else ''.
936
+ */
900
937
  extension() {
901
- return extensionFromMime(this.mimeType) ?? this.clientExtension();
938
+ const claimed = extensionFromMime(this.mimeType) ?? this.clientExtension();
939
+ const detected = this.detected;
940
+ if (detected === void 0) return PLAIN_EXTENSION.test(claimed) ? claimed : "";
941
+ if (detected !== fileType_exports.TEXT && detected.extensions.length > 0) return detected.extensions[0];
942
+ if (PLAIN_EXTENSION.test(claimed) && !NEVER_CLAIMED.has(claimed)) return claimed;
943
+ return detected === fileType_exports.TEXT ? "txt" : "";
902
944
  }
903
945
  clientExtension() {
904
946
  const dot = this.clientName.lastIndexOf(".");
@@ -1078,7 +1120,7 @@ var IncomingRequest = class {
1078
1120
  this.#content = new TextDecoder().decode(bytes);
1079
1121
  this.#body = fromEntries(new URLSearchParams(this.#content).entries());
1080
1122
  } else {
1081
- this.#parseForm(await new Response(bytes, { headers: { "content-type": this.raw.headers.get("content-type") ?? "" } }).formData());
1123
+ await this.#parseForm(await new Response(bytes, { headers: { "content-type": this.raw.headers.get("content-type") ?? "" } }).formData());
1082
1124
  }
1083
1125
  } catch {
1084
1126
  }
@@ -1103,12 +1145,12 @@ var IncomingRequest = class {
1103
1145
  if (Array.isArray(parsed)) return sanitize({ ...parsed });
1104
1146
  return isPlainObject(parsed) ? sanitize(parsed) : {};
1105
1147
  }
1106
- #parseForm(form) {
1148
+ async #parseForm(form) {
1107
1149
  const fields = [];
1108
1150
  const files = [];
1109
1151
  for (const [name, value] of form.entries()) {
1110
1152
  if (typeof value === "string") fields.push([name, value]);
1111
- else if (!((value.name ?? "") === "" && value.size === 0)) files.push([name, new FormUploadedFile(value)]);
1153
+ else if (!((value.name ?? "") === "" && value.size === 0)) files.push([name, await FormUploadedFile.fromFile(value)]);
1112
1154
  }
1113
1155
  this.#body = fromEntries(fields);
1114
1156
  this.#files = fromEntries(files);
@@ -2458,12 +2500,13 @@ var HttpServiceProvider = class extends ServiceProvider {
2458
2500
  // ../Live/dist/index.js
2459
2501
  import { AssertionError } from "assert";
2460
2502
  import { isDeepStrictEqual } from "util";
2461
- import { AsyncLocalStorage } from "async_hooks";
2503
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
2462
2504
  import { randomBytes as randomBytes2 } from "crypto";
2463
2505
  import { createHmac, timingSafeEqual } from "crypto";
2464
2506
  import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
2465
2507
  import { join as join5, relative as relative2, sep as sep2 } from "path";
2466
2508
  import { pathToFileURL as pathToFileURL2 } from "url";
2509
+ import { AsyncLocalStorage } from "async_hooks";
2467
2510
  import { existsSync as existsSync22 } from "fs";
2468
2511
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
2469
2512
  import { dirname as dirname2, join as join23, relative as relative22, sep as sep22 } from "path";
@@ -2673,6 +2716,20 @@ var Component = class {
2673
2716
  return inject(LiveManager).page(this);
2674
2717
  }
2675
2718
  };
2719
+ function trackFlash(session, { saved = true } = {}) {
2720
+ if (session === void 0) return () => void 0;
2721
+ const before = new Set(session.get("_flash.new", []));
2722
+ return (redirected) => {
2723
+ if (redirected) return;
2724
+ const flashed = session.get("_flash.new", []);
2725
+ const fresh = flashed.filter((key) => !before.has(key));
2726
+ if (fresh.length === 0) return;
2727
+ session.put("_flash.new", flashed.filter((key) => before.has(key)));
2728
+ if (!saved) return session.forget(...fresh);
2729
+ const old = session.get("_flash.old", []);
2730
+ session.put("_flash.old", [...old, ...fresh.filter((key) => !old.includes(key))]);
2731
+ };
2732
+ }
2676
2733
  var LiveError = class extends Error {
2677
2734
  name = "LiveError";
2678
2735
  kind = "Server";
@@ -2773,6 +2830,15 @@ function requestScope2() {
2773
2830
  const container = currentContainer() ?? Container.getInstance();
2774
2831
  return container?.bound(TestRequestScope) === true ? container.make(TestRequestScope) : (run) => run();
2775
2832
  }
2833
+ function currentSession() {
2834
+ const container = currentContainer();
2835
+ if (container?.bound(Session) !== true) return void 0;
2836
+ try {
2837
+ return container.make(Session);
2838
+ } catch {
2839
+ return void 0;
2840
+ }
2841
+ }
2776
2842
  var Testable = class _Testable {
2777
2843
  constructor(manager, scope, result) {
2778
2844
  this.manager = manager;
@@ -2827,7 +2893,12 @@ var Testable = class _Testable {
2827
2893
  return this.#update({}, [{ method: "$refresh", params: [] }]);
2828
2894
  }
2829
2895
  async #update(updates, calls) {
2830
- const result = await this.scope(() => this.manager.updateComponent(this.#snapshot, updates, calls));
2896
+ const result = await this.scope(async () => {
2897
+ const flashed = trackFlash(currentSession(), { saved: false });
2898
+ const updated = await this.manager.updateComponent(this.#snapshot, updates, calls);
2899
+ flashed(updated.effects.redirect !== void 0);
2900
+ return updated;
2901
+ });
2831
2902
  this.#snapshot = JSON.stringify(result.snapshot);
2832
2903
  if (result.html !== void 0) this.#html = result.html;
2833
2904
  this.#effects = result.effects;
@@ -2958,10 +3029,11 @@ async function discover(directory) {
2958
3029
  }
2959
3030
  return found;
2960
3031
  }
2961
- var PERSISTENT_MIDDLEWARE = ["auth", "verified", "guest"];
3032
+ var PERSISTENT_MIDDLEWARE = ["auth", "verified", "guest", "can"];
3033
+ var ROUTER_METHODS = ["getRoutesByMatchOrder", "getPatterns", "getPatternVersion", "getMiddleware", "getMiddlewareGroups", "substituteBindings"];
2962
3034
  function canMatch(router) {
2963
3035
  const methods = router;
2964
- return ["getRoutesByMatchOrder", "getPatterns", "getPatternVersion", "getMiddleware", "getMiddlewareGroups"].every((name) => typeof methods[name] === "function");
3036
+ return ROUTER_METHODS.every((name) => typeof methods[name] === "function");
2965
3037
  }
2966
3038
  function isMatchingRoute(route2) {
2967
3039
  const candidate = route2;
@@ -2990,7 +3062,7 @@ function expand(references, aliases, groups, out = [], visiting = /* @__PURE__ *
2990
3062
  }
2991
3063
  return out;
2992
3064
  }
2993
- function persistentMiddleware(router, persistent, method, path) {
3065
+ function persistentPass(router, persistent, method, path) {
2994
3066
  if (!canMatch(router)) throw new LiveError("Live re-applies the page route's middleware to updates, which needs Oriel's router.");
2995
3067
  const routes = router.getRoutesByMatchOrder();
2996
3068
  if (!Array.isArray(routes) || !routes.every(isMatchingRoute)) throw new LiveError("Live re-applies the page route's middleware to updates, which needs Oriel's routes.");
@@ -2998,36 +3070,80 @@ function persistentMiddleware(router, persistent, method, path) {
2998
3070
  const target2 = routePath(path);
2999
3071
  const patterns = router.getPatterns();
3000
3072
  const version = router.getPatternVersion();
3001
- const route2 = routes.find((candidate) => candidate.methods.includes(verb) && candidate.match(target2, patterns, version) !== void 0);
3002
- if (route2 === void 0) return [];
3073
+ let matched;
3074
+ for (const candidate of routes) {
3075
+ const values2 = candidate.methods.includes(verb) ? candidate.match(target2, patterns, version) : void 0;
3076
+ if (values2 !== void 0) {
3077
+ matched = { route: candidate, values: values2 };
3078
+ break;
3079
+ }
3080
+ }
3081
+ if (matched === void 0) return void 0;
3082
+ const { route: route2, values } = matched;
3003
3083
  const aliases = router.getMiddleware();
3004
3084
  const groups = router.getMiddlewareGroups();
3005
3085
  const wanted = new Set(persistent.flatMap((name) => Object.hasOwn(aliases, name) ? [aliases[name]] : []));
3006
3086
  const excluded = new Set(expand(route2.excludedMiddleware(), aliases, groups).map((entry) => entry.cls));
3007
3087
  const ids = /* @__PURE__ */ new Map();
3008
3088
  const seen = /* @__PURE__ */ new Set();
3009
- return expand(route2.gatherMiddleware(), aliases, groups).filter((entry) => {
3089
+ const middleware = expand(route2.gatherMiddleware(), aliases, groups).filter((entry) => {
3010
3090
  if (!ids.has(entry.cls)) ids.set(entry.cls, ids.size);
3011
3091
  const key = `${ids.get(entry.cls)}:${entry.parameters.join(",")}`;
3012
3092
  if (!wanted.has(entry.cls) || excluded.has(entry.cls) || seen.has(key)) return false;
3013
3093
  seen.add(key);
3014
3094
  return true;
3015
3095
  });
3096
+ const early = new Set(expand(["web", "api"], aliases, groups).map((entry) => entry.cls));
3097
+ for (const name of ["auth", "guest"]) if (Object.hasOwn(aliases, name)) early.add(aliases[name]);
3098
+ const before = middleware.filter((entry) => early.has(entry.cls));
3099
+ const after = middleware.filter((entry) => !early.has(entry.cls));
3100
+ return { router, route: route2, values, middleware: [...before, ...after], at: before.length };
3101
+ }
3102
+ function persistentMiddleware(router, persistent, method, path) {
3103
+ return [...persistentPass(router, persistent, method, path)?.middleware ?? []];
3104
+ }
3105
+ function currentRoute(container) {
3106
+ if (!container.bound(CurrentRoute)) return void 0;
3107
+ try {
3108
+ return container.make(CurrentRoute);
3109
+ } catch {
3110
+ return void 0;
3111
+ }
3016
3112
  }
3017
- async function throughMiddleware(container, request2, middleware, destination) {
3113
+ async function throughMiddleware(container, request2, pass, destination) {
3114
+ const original = currentRoute(container);
3115
+ const page = { route: pass.route, parameters: { ...pass.values }, middleware: [] };
3116
+ let swapped = false;
3117
+ const show = () => {
3118
+ container.instance(CurrentRoute, page);
3119
+ swapped = true;
3120
+ };
3121
+ const restore = () => {
3122
+ if (!swapped) return;
3123
+ if (original === void 0) container.forgetInstance(CurrentRoute);
3124
+ else container.instance(CurrentRoute, original);
3125
+ swapped = false;
3126
+ };
3018
3127
  let ran;
3019
3128
  const run = async (index) => {
3020
- if (index === middleware.length) {
3129
+ if (index === pass.at) Object.assign(page.parameters, await pass.router.substituteBindings(pass.route, pass.values, container));
3130
+ if (index === pass.middleware.length) {
3131
+ restore();
3021
3132
  ran = { result: await destination() };
3022
3133
  return new Response(null, { status: 204 });
3023
3134
  }
3024
- const { cls, parameters } = middleware[index];
3135
+ const { cls, parameters } = pass.middleware[index];
3025
3136
  const instance = container.make(cls);
3026
3137
  const next = () => run(index + 1);
3027
3138
  return instance.handle(request2, next, ...parameters);
3028
3139
  };
3029
- const response2 = await run(0);
3030
- return ran ? ran.result : response2;
3140
+ show();
3141
+ try {
3142
+ const response2 = await run(0);
3143
+ return ran ? ran.result : response2;
3144
+ } finally {
3145
+ restore();
3146
+ }
3031
3147
  }
3032
3148
  function isModel(value) {
3033
3149
  if (typeof value !== "object" || value === null) return false;
@@ -3167,8 +3283,64 @@ function isUpdatable(instance, path) {
3167
3283
  }
3168
3284
  return !isModel(node) && !(node instanceof Collection);
3169
3285
  }
3170
- var renderContext = new AsyncLocalStorage();
3171
- var pageRequest = new AsyncLocalStorage();
3286
+ var PAGINATED = /* @__PURE__ */ Symbol.for("oriel:live.pagination");
3287
+ function toPage(value) {
3288
+ const page = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
3289
+ return Number.isFinite(page) && page >= 1 ? Math.min(Math.floor(page), Number.MAX_SAFE_INTEGER) : 1;
3290
+ }
3291
+ async function hook(instance, name, ...args) {
3292
+ const method = instance[name];
3293
+ if (typeof method === "function") await method.apply(instance, args);
3294
+ }
3295
+ function WithPagination(Base) {
3296
+ class WithPaginationMixin extends Base {
3297
+ page = 1;
3298
+ nextPage() {
3299
+ return this.setPage(toPage(this.page) + 1);
3300
+ }
3301
+ previousPage() {
3302
+ return this.setPage(Math.max(toPage(this.page) - 1, 1));
3303
+ }
3304
+ gotoPage(page) {
3305
+ return this.setPage(page);
3306
+ }
3307
+ resetPage() {
3308
+ return this.setPage(1);
3309
+ }
3310
+ async setPage(page) {
3311
+ const next = toPage(page);
3312
+ await hook(this, "updatingPage", next);
3313
+ this.page = next;
3314
+ await hook(this, "updatedPage", next);
3315
+ }
3316
+ }
3317
+ Object.defineProperty(WithPaginationMixin.prototype, PAGINATED, { value: true });
3318
+ return WithPaginationMixin;
3319
+ }
3320
+ function isPaginated(component) {
3321
+ return component[PAGINATED] === true;
3322
+ }
3323
+ var rendering = new AsyncLocalStorage();
3324
+ function renderPaginated(component, url2, run) {
3325
+ return rendering.run(isPaginated(component) ? { component, url: url2 } : void 0, run);
3326
+ }
3327
+ function outsidePagination(run) {
3328
+ return rendering.run(void 0, run);
3329
+ }
3330
+ var livePaginationState = {
3331
+ currentPage(pageName) {
3332
+ const current = rendering.getStore();
3333
+ if (current === void 0 || pageName !== "page") return void 0;
3334
+ const page = toPage(current.component.page);
3335
+ current.component.page = page;
3336
+ return page;
3337
+ },
3338
+ currentPath() {
3339
+ return rendering.getStore()?.url;
3340
+ }
3341
+ };
3342
+ var renderContext = new AsyncLocalStorage2();
3343
+ var pageRequest = new AsyncLocalStorage2();
3172
3344
  function optional(container, token2) {
3173
3345
  if (!container.bound(token2)) return void 0;
3174
3346
  try {
@@ -3181,7 +3353,7 @@ var isRecord = (value) => typeof value === "object" && value !== null && !Array.
3181
3353
  function setOwn(object, key, value) {
3182
3354
  Object.defineProperty(object, key, { value, enumerable: true, writable: true, configurable: true });
3183
3355
  }
3184
- async function hook(instance, name, ...args) {
3356
+ async function hook2(instance, name, ...args) {
3185
3357
  const method = instance[name];
3186
3358
  if (typeof method === "function") await method.apply(instance, args);
3187
3359
  }
@@ -3232,7 +3404,7 @@ var LiveComponentManager = class {
3232
3404
  setOwn(context.children, childKey, existing);
3233
3405
  return `<${existing[0]} o:id="${escapeAttribute2(existing[1])}"></${existing[0]}>`;
3234
3406
  }
3235
- const result = await this.mountComponent(component, params);
3407
+ const result = await outsidePagination(() => this.mountComponent(component, params));
3236
3408
  setOwn(context.children, childKey, [result.tag, result.snapshot.memo.id]);
3237
3409
  return result.html;
3238
3410
  }
@@ -3244,14 +3416,14 @@ var LiveComponentManager = class {
3244
3416
  const state = stateOf(instance);
3245
3417
  state.id = randomBytes2(15).toString("base64url");
3246
3418
  state.name = name;
3247
- await hook(instance, "boot");
3419
+ await hook2(instance, "boot");
3248
3420
  for (const [property, value] of Object.entries(params)) {
3249
3421
  if (Object.hasOwn(instance, property)) instance[property] = value;
3250
3422
  }
3251
3423
  this.prepareComputed(instance);
3252
- await hook(instance, "mount", params);
3253
3424
  const request2 = optional(this.container(), HttpRequest);
3254
3425
  const page = pageRequest.getStore() ?? { path: request2?.path() ?? "", method: request2?.method() ?? "GET" };
3426
+ await renderPaginated(instance, this.pageUrl(page.path), () => hook2(instance, "mount", params));
3255
3427
  const memo = { id: state.id, name, path: page.path, method: page.method, children: {}, errors: {} };
3256
3428
  const { html, tag } = await this.render(instance, memo, {});
3257
3429
  const effects = this.effects(instance);
@@ -3279,22 +3451,24 @@ var LiveComponentManager = class {
3279
3451
  state.id = snapshot.memo.id;
3280
3452
  state.name = snapshot.memo.name;
3281
3453
  state.errors = { ...snapshot.memo.errors };
3282
- await hook(instance, "boot");
3454
+ await hook2(instance, "boot");
3283
3455
  const registry = this.registry();
3284
3456
  for (const [property, value] of Object.entries(snapshot.data)) {
3285
3457
  Object.defineProperty(instance, property, { value: await hydrate(value, registry), enumerable: true, writable: true, configurable: true });
3286
3458
  }
3287
3459
  this.prepareComputed(instance);
3288
- await hook(instance, "hydrate");
3289
3460
  const returns = [];
3290
- try {
3291
- for (const [path, value] of Object.entries(updates)) await this.applyUpdate(instance, path, value);
3292
- for (const call of calls) returns.push(await this.call(instance, call.method, call.params));
3293
- } catch (error) {
3294
- const errors = error.errors;
3295
- if (error.kind !== "Validation" || !isRecord(errors)) throw error;
3296
- state.errors = { ...errors };
3297
- }
3461
+ await renderPaginated(instance, this.pageUrl(snapshot.memo.path), async () => {
3462
+ await hook2(instance, "hydrate");
3463
+ try {
3464
+ for (const [path, value] of Object.entries(updates)) await this.applyUpdate(instance, path, value);
3465
+ for (const call of calls) returns.push(await this.call(instance, call.method, call.params));
3466
+ } catch (error) {
3467
+ const errors = error.errors;
3468
+ if (error.kind !== "Validation" || !isRecord(errors)) throw error;
3469
+ state.errors = { ...errors };
3470
+ }
3471
+ });
3298
3472
  const memo = { ...snapshot.memo, children: { ...snapshot.memo.children } };
3299
3473
  const effects = this.effects(instance);
3300
3474
  let html;
@@ -3309,25 +3483,30 @@ var LiveComponentManager = class {
3309
3483
  return { instance, snapshot: await this.snapshot(instance, memo), effects, html, tag };
3310
3484
  }
3311
3485
  /**
3312
- * The route action of POST /oriel/live/update. Each component's update runs behind the auth middleware of the route
3313
- * that rendered its page (Livewire's persistent middleware): a signed-out tab gets a 401, never the action. When a
3314
- * middleware answers instead (a redirect), that is the response.
3486
+ * The route action of POST /oriel/live/update. Each component's update runs behind the persistent middleware of the
3487
+ * route that rendered its page (Livewire's): `auth`, `verified`, `guest` and `can`, with the page route's parameters
3488
+ * bound again. A signed-out tab gets a 401, a revoked ability a 403 and a deleted model a 404, before the component
3489
+ * hydrates. When a middleware answers instead (a redirect), that is the response. A key first flashed during the
3490
+ * update shows in this response only, unless the update redirects.
3315
3491
  */
3316
3492
  async handle(request2) {
3317
3493
  if (!this.isLiveRequest(request2)) throw new InvalidLiveRequestError("Live update requests need the X-Oriel-Live header.");
3318
3494
  const container = this.container();
3319
3495
  const persistent = optional(container, ConfigRepository)?.get("live.persistent_middleware", PERSISTENT_MIDDLEWARE) ?? PERSISTENT_MIDDLEWARE;
3320
3496
  const components = [];
3497
+ const flashed = trackFlash(optional(container, Session));
3321
3498
  for (const component of this.payload(request2).components) {
3322
3499
  const { memo } = this.verify(component.snapshot);
3323
- const middleware = persistentMiddleware(container.make(Router), persistent, memo.method, memo.path);
3500
+ const pass = persistentPass(container.make(Router), persistent, memo.method, memo.path);
3501
+ const update = () => this.updateComponent(component.snapshot, component.updates, component.calls);
3324
3502
  const result = await pageRequest.run(
3325
3503
  { path: memo.path, method: memo.method },
3326
- () => throughMiddleware(container, request2, middleware, () => this.updateComponent(component.snapshot, component.updates, component.calls))
3504
+ () => pass === void 0 || pass.middleware.length === 0 ? update() : throughMiddleware(container, request2, pass, update)
3327
3505
  );
3328
3506
  if (result instanceof Response) return result;
3329
3507
  components.push({ snapshot: JSON.stringify(result.snapshot), effects: result.effects });
3330
3508
  }
3509
+ flashed(components.some((component) => component.effects.redirect !== void 0));
3331
3510
  return { components };
3332
3511
  }
3333
3512
  /** A full-page component (Route.get('/counter', Counter)): mounted with the route parameters, inside the layout. */
@@ -3402,8 +3581,8 @@ var LiveComponentManager = class {
3402
3581
  const [root, ...rest] = path.split(".");
3403
3582
  const key = rest.length > 0 ? rest.join(".") : void 0;
3404
3583
  const studly = Str.studly(root);
3405
- await hook(instance, "updating", path, value);
3406
- await hook(instance, `updating${studly}`, value, key);
3584
+ await hook2(instance, "updating", path, value);
3585
+ await hook2(instance, `updating${studly}`, value, key);
3407
3586
  const target2 = instance;
3408
3587
  if (rest.length === 0) target2[root] = value;
3409
3588
  else {
@@ -3421,8 +3600,8 @@ var LiveComponentManager = class {
3421
3600
  assertSlot(node, last);
3422
3601
  Object.defineProperty(node, last, { value, enumerable: true, writable: true, configurable: true });
3423
3602
  }
3424
- await hook(instance, "updated", path, value);
3425
- await hook(instance, `updated${studly}`, value, key);
3603
+ await hook2(instance, "updated", path, value);
3604
+ await hook2(instance, `updated${studly}`, value, key);
3426
3605
  }
3427
3606
  /** Runs one call from the browser: a magic action, an event, or a public method. Returns its return value. */
3428
3607
  async call(instance, method, params) {
@@ -3450,18 +3629,35 @@ var LiveComponentManager = class {
3450
3629
  if (!isCallable(instance, method)) throw new MethodNotCallableError(method, name);
3451
3630
  return instance[method](...params);
3452
3631
  }
3453
- /** Renders the component (rendering/rendered hooks, nested children tracked) and checks for a single root. */
3632
+ /**
3633
+ * Renders the component (rendering/rendered hooks, nested children tracked) and checks for a single root. A
3634
+ * WithPagination component's page and page URL are what paginate() reads while it renders, as in its mount() and in
3635
+ * its update's hooks and actions.
3636
+ */
3454
3637
  async render(instance, memo, previous) {
3455
- await hook(instance, "rendering");
3638
+ await hook2(instance, "rendering");
3456
3639
  const context = { previous, children: {}, index: 0 };
3457
- const html = await renderContext.run(context, async () => {
3458
- const output = await instance.render();
3459
- return typeof output === "string" ? output : this.container().make(ViewFactory).renderToString(output);
3460
- });
3461
- await hook(instance, "rendered", html);
3640
+ const html = await renderContext.run(
3641
+ context,
3642
+ () => renderPaginated(instance, this.pageUrl(memo.path), async () => {
3643
+ const output = await instance.render();
3644
+ return typeof output === "string" ? output : this.container().make(ViewFactory).renderToString(output);
3645
+ })
3646
+ );
3647
+ await hook2(instance, "rendered", html);
3462
3648
  memo.children = context.children;
3463
3649
  return { html: html.trim(), tag: rootTag(html, memo.name) };
3464
3650
  }
3651
+ /** The absolute URL of the page a memo path belongs to, on the current request's origin ('/' + path without one). */
3652
+ pageUrl(path) {
3653
+ const relative3 = `/${path.replace(/^\/+/, "")}`;
3654
+ const url2 = optional(this.container(), HttpRequest)?.url();
3655
+ try {
3656
+ return url2 === void 0 ? relative3 : new URL(relative3, url2).href;
3657
+ } catch {
3658
+ return relative3;
3659
+ }
3660
+ }
3465
3661
  /** Dispatches, redirects: the effects every response carries. */
3466
3662
  effects(instance) {
3467
3663
  const state = stateOf(instance);
@@ -3480,7 +3676,7 @@ var LiveComponentManager = class {
3480
3676
  }
3481
3677
  /** dehydrate() hook, then the state as snapshot data, the error bag, and the checksum over both. */
3482
3678
  async snapshot(instance, memo) {
3483
- await hook(instance, "dehydrate");
3679
+ await hook2(instance, "dehydrate");
3484
3680
  const registry = this.registry();
3485
3681
  const data = {};
3486
3682
  for (const [property, value] of Object.entries(instance)) data[property] = dehydrate(value, registry, `${memo.name}.${property}`);
@@ -3553,6 +3749,7 @@ var LiveServiceProvider = class extends ServiceProvider {
3553
3749
  this.mergeConfigFrom({ class_path: this.app.appPath("Live"), layout: "components.layouts.app", persistent_middleware: [...PERSISTENT_MIDDLEWARE] }, "live");
3554
3750
  this.app.singleton(LiveComponentManager, () => new LiveComponentManager(this.app));
3555
3751
  this.app.singleton(LiveManager, (container) => container.make(LiveComponentManager));
3752
+ this.app.singleton(PaginationState, () => livePaginationState);
3556
3753
  }
3557
3754
  async boot() {
3558
3755
  const manager = this.app.make(LiveComponentManager);
@@ -3566,6 +3763,40 @@ var LiveServiceProvider = class extends ServiceProvider {
3566
3763
  }
3567
3764
  }
3568
3765
  };
3766
+ function urlWindow(current, last, onEachSide) {
3767
+ const range = (from, to) => Array.from({ length: Math.max(to - from + 1, 0) }, (_, index) => from + index);
3768
+ if (last < onEachSide * 2 + 8) return range(1, last);
3769
+ const size = onEachSide + 4;
3770
+ if (current <= size) return [...range(1, size + onEachSide), "...", ...range(last - 1, last)];
3771
+ if (current > last - size) return [...range(1, 2), "...", ...range(last - (size + (onEachSide - 1)), last)];
3772
+ return [...range(1, 2), "...", ...range(current - onEachSide, current + onEachSide), "...", ...range(last - 1, last)];
3773
+ }
3774
+ function button(label, action, attributes = "") {
3775
+ const live2 = action === void 0 ? " disabled" : ` o:click="${escapeAttribute2(action)}"`;
3776
+ return `<button type="button" class="btn btn-secondary"${attributes}${live2}>${escapeAttribute2(label)}</button>`;
3777
+ }
3778
+ function Pagination({ paginator, class: className }) {
3779
+ if (typeof paginator.nextCursor === "function") {
3780
+ throw new LiveError("<Pagination> needs paginate() or simplePaginate(): a cursor paginator has no page numbers.");
3781
+ }
3782
+ const current = paginator.currentPage();
3783
+ const onFirst = paginator.onFirstPage();
3784
+ const hasMore = paginator.hasMorePages();
3785
+ if (onFirst && !hasMore) return { toHtml: () => "" };
3786
+ const parts = [button("\xAB Previous", onFirst ? void 0 : "previousPage")];
3787
+ if (typeof paginator.lastPage === "function") {
3788
+ const onEachSide = Number.isInteger(paginator.onEachSideCount) && paginator.onEachSideCount >= 0 ? paginator.onEachSideCount : 3;
3789
+ for (const page of urlWindow(current, paginator.lastPage(), onEachSide)) {
3790
+ if (page === "...") parts.push('<span aria-hidden="true">...</span>');
3791
+ else if (page === current) parts.push(`<button type="button" class="btn" aria-current="page" disabled>${page}</button>`);
3792
+ else parts.push(button(String(page), `gotoPage(${page})`, ` aria-label="Go to page ${page}"`));
3793
+ }
3794
+ }
3795
+ parts.push(button("Next \xBB", hasMore ? "nextPage" : void 0));
3796
+ const classes = className === void 0 || className === "" ? "" : ` class="${escapeAttribute2(className)}"`;
3797
+ const html = `<nav aria-label="Pagination"${classes}>${parts.join("")}</nav>`;
3798
+ return { toHtml: () => html };
3799
+ }
3569
3800
 
3570
3801
  // ../Testing/dist/index.js
3571
3802
  import { AssertionError as AssertionError2 } from "assert";
@@ -3577,6 +3808,7 @@ import { randomUUID } from "crypto";
3577
3808
  import { randomUUID as randomUUID2 } from "crypto";
3578
3809
  import { randomUUID as randomUUID3 } from "crypto";
3579
3810
  import { randomBytes as randomBytes3 } from "crypto";
3811
+ import { posix } from "path";
3580
3812
  import { deflateSync } from "zlib";
3581
3813
  function fail2(message, details = {}) {
3582
3814
  const error = new AssertionError2({ message, actual: details.actual, expected: details.expected, operator: "fail" });
@@ -5495,7 +5727,7 @@ var QUEUE_OPTIONS = ["connection", "queue", "delay", "tries", "backoff", "timeou
5495
5727
  function isQueueable(job) {
5496
5728
  if (typeof job !== "object" || job === null) return false;
5497
5729
  const members = job;
5498
- if (members[SHOULD_QUEUE] === true) return true;
5730
+ if (members.shouldQueue === true || members[SHOULD_QUEUE] === true) return true;
5499
5731
  if (QUEUE_OPTIONS.some((option) => members[option] !== void 0)) return true;
5500
5732
  return typeof members.uniqueId === "function" || typeof members.retryUntil === "function";
5501
5733
  }
@@ -6313,8 +6545,74 @@ var MIME = {
6313
6545
  mp4: "video/mp4"
6314
6546
  };
6315
6547
  function guessMimeType(path) {
6316
- const extension = path.split(".").pop()?.toLowerCase() ?? "";
6317
- return MIME[extension];
6548
+ const base = path.slice(path.lastIndexOf("/") + 1);
6549
+ const dot = base.lastIndexOf(".");
6550
+ const extension = dot === -1 ? "" : base.slice(dot + 1).toLowerCase();
6551
+ return Object.hasOwn(MIME, extension) ? MIME[extension] : void 0;
6552
+ }
6553
+ var DISK_TYPES = {
6554
+ txt: "text/plain",
6555
+ html: "text/html",
6556
+ htm: "text/html",
6557
+ css: "text/css",
6558
+ csv: "text/csv",
6559
+ md: "text/markdown",
6560
+ js: "text/javascript",
6561
+ mjs: "text/javascript",
6562
+ json: "application/json",
6563
+ xml: "application/xml",
6564
+ pdf: "application/pdf",
6565
+ zip: "application/zip",
6566
+ gz: "application/gzip",
6567
+ tar: "application/x-tar",
6568
+ "7z": "application/x-7z-compressed",
6569
+ rar: "application/vnd.rar",
6570
+ epub: "application/epub+zip",
6571
+ rtf: "application/rtf",
6572
+ doc: "application/msword",
6573
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
6574
+ xls: "application/vnd.ms-excel",
6575
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
6576
+ ppt: "application/vnd.ms-powerpoint",
6577
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
6578
+ odt: "application/vnd.oasis.opendocument.text",
6579
+ ods: "application/vnd.oasis.opendocument.spreadsheet",
6580
+ odp: "application/vnd.oasis.opendocument.presentation",
6581
+ png: "image/png",
6582
+ jpg: "image/jpeg",
6583
+ jpeg: "image/jpeg",
6584
+ gif: "image/gif",
6585
+ webp: "image/webp",
6586
+ avif: "image/avif",
6587
+ svg: "image/svg+xml",
6588
+ ico: "image/x-icon",
6589
+ bmp: "image/bmp",
6590
+ tif: "image/tiff",
6591
+ tiff: "image/tiff",
6592
+ heic: "image/heic",
6593
+ heif: "image/heif",
6594
+ mp3: "audio/mpeg",
6595
+ m4a: "audio/mp4",
6596
+ wav: "audio/wav",
6597
+ ogg: "audio/ogg",
6598
+ oga: "audio/ogg",
6599
+ opus: "audio/ogg",
6600
+ flac: "audio/flac",
6601
+ aac: "audio/aac",
6602
+ mp4: "video/mp4",
6603
+ mov: "video/quicktime",
6604
+ webm: "video/webm",
6605
+ ogv: "video/ogg",
6606
+ avi: "video/x-msvideo",
6607
+ woff: "font/woff",
6608
+ woff2: "font/woff2",
6609
+ ttf: "font/ttf",
6610
+ otf: "font/otf",
6611
+ wasm: "application/wasm"
6612
+ };
6613
+ function diskType(extension) {
6614
+ const key = extension.replace(/^\./, "").toLowerCase();
6615
+ return Object.hasOwn(DISK_TYPES, key) ? DISK_TYPES[key] : void 0;
6318
6616
  }
6319
6617
  function hashName(file) {
6320
6618
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -6370,17 +6668,24 @@ var FakeDisk = class {
6370
6668
  async put(path, contents, options = {}) {
6371
6669
  const key = normalize(path);
6372
6670
  const bytes = typeof contents === "string" ? new TextEncoder().encode(contents) : contents instanceof Uint8Array ? contents.slice() : await collect(contents);
6373
- this.stored.set(key, { bytes, visibility: options.visibility ?? "private", mimeType: options.mimeType ?? guessMimeType(key), modified: /* @__PURE__ */ new Date() });
6671
+ this.write(key, bytes, options.mimeType ?? diskType(posix.extname(key)), options);
6374
6672
  return true;
6375
6673
  }
6376
6674
  putFile(directory, file, options = {}) {
6377
6675
  return this.putFileAs(directory, file, hashName(file), options);
6378
6676
  }
6677
+ /**
6678
+ * The upload's own type, as the S3 disk sends it: its extension(), never the MIME type the client sent as such nor
6679
+ * the stored name's type.
6680
+ */
6379
6681
  async putFileAs(directory, file, name, options = {}) {
6380
6682
  const path = normalize(`${directory}/${name}`);
6381
- await this.put(path, new Uint8Array(await file.arrayBuffer()), { ...options, mimeType: options.mimeType ?? file.mimeType });
6683
+ this.write(path, new Uint8Array(await file.arrayBuffer()), options.mimeType ?? diskType(file.extension()), options);
6382
6684
  return path;
6383
6685
  }
6686
+ write(key, bytes, mimeType, options) {
6687
+ this.stored.set(key, { bytes, visibility: options.visibility ?? "private", mimeType, modified: /* @__PURE__ */ new Date() });
6688
+ }
6384
6689
  async delete(paths) {
6385
6690
  for (const path of [paths].flat()) this.stored.delete(normalize(path));
6386
6691
  return true;
@@ -6574,6 +6879,37 @@ var StorageFake = class _StorageFake {
6574
6879
  return this.disk().path(path);
6575
6880
  }
6576
6881
  };
6882
+ var ascii = (text) => [...text].map((char) => char.charCodeAt(0));
6883
+ var u16be = (n) => [n >>> 8 & 255, n & 255];
6884
+ var u16le = (n) => [n & 255, n >>> 8 & 255];
6885
+ var u32le = (n) => [n & 255, n >>> 8 & 255, n >>> 16 & 255, n >>> 24 & 255];
6886
+ function concat(...parts) {
6887
+ const out = new Uint8Array(parts.reduce((n, part) => n + part.length, 0));
6888
+ let offset = 0;
6889
+ for (const part of parts) {
6890
+ out.set(part, offset);
6891
+ offset += part.length;
6892
+ }
6893
+ return out;
6894
+ }
6895
+ var BitWriter = class {
6896
+ bytes = [];
6897
+ buffer = 0;
6898
+ count = 0;
6899
+ write(value, bits) {
6900
+ this.buffer |= value << this.count;
6901
+ this.count += bits;
6902
+ while (this.count >= 8) {
6903
+ this.bytes.push(this.buffer & 255);
6904
+ this.buffer >>>= 8;
6905
+ this.count -= 8;
6906
+ }
6907
+ }
6908
+ finish() {
6909
+ if (this.count > 0) this.bytes.push(this.buffer & 255);
6910
+ return Uint8Array.from(this.bytes);
6911
+ }
6912
+ };
6577
6913
  var CRC_TABLE = Array.from({ length: 256 }, (_, n) => {
6578
6914
  let c = n;
6579
6915
  for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
@@ -6601,23 +6937,175 @@ function png(width, height) {
6601
6937
  header.set([8, 2, 0, 0, 0], 8);
6602
6938
  const rows = new Uint8Array((width * 3 + 1) * height).fill(255);
6603
6939
  for (let y = 0; y < height; y++) rows[y * (width * 3 + 1)] = 0;
6604
- const parts = [new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), chunk("IHDR", header), chunk("IDAT", deflateSync(rows)), chunk("IEND", new Uint8Array())];
6605
- const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
6606
- let offset = 0;
6607
- for (const part of parts) {
6608
- out.set(part, offset);
6609
- offset += part.length;
6610
- }
6940
+ return concat([137, 80, 78, 71, 13, 10, 26, 10], chunk("IHDR", header), chunk("IDAT", deflateSync(rows)), chunk("IEND", new Uint8Array()));
6941
+ }
6942
+ var oneCodeTable = (tableClass) => [255, 196, ...u16be(20), tableClass, 1, ...new Array(15).fill(0), 0];
6943
+ function jpeg(width, height) {
6944
+ const bits = Math.ceil(width / 8) * Math.ceil(height / 8) * 2;
6945
+ const scan = new Uint8Array(Math.ceil(bits / 8));
6946
+ const padding = scan.length * 8 - bits;
6947
+ if (padding > 0) scan[scan.length - 1] = (1 << padding) - 1;
6948
+ return concat(
6949
+ [255, 216],
6950
+ // SOI
6951
+ [255, 224, ...u16be(16), ...ascii("JFIF"), 0, 1, 1, 0, 0, 1, 0, 1, 0, 0],
6952
+ // APP0
6953
+ [255, 219, ...u16be(67), 0, ...new Array(64).fill(1)],
6954
+ // DQT: table 0, all ones
6955
+ [255, 192, ...u16be(11), 8, ...u16be(height), ...u16be(width), 1, 1, 17, 0],
6956
+ // SOF0: one component
6957
+ oneCodeTable(0),
6958
+ // DHT: DC table 0
6959
+ oneCodeTable(16),
6960
+ // DHT: AC table 0
6961
+ [255, 218, ...u16be(8), 1, 1, 0, 0, 63, 0],
6962
+ // SOS
6963
+ scan,
6964
+ [255, 217]
6965
+ // EOI
6966
+ );
6967
+ }
6968
+ function lzwZeros(pixels, minCodeSize) {
6969
+ const clear = 1 << minCodeSize;
6970
+ const end = clear + 1;
6971
+ const out = new BitWriter();
6972
+ const table = /* @__PURE__ */ new Map();
6973
+ let codeSize = minCodeSize + 1;
6974
+ let next = end + 1;
6975
+ let current = 0;
6976
+ out.write(clear, codeSize);
6977
+ for (let i = 1; i < pixels; i++) {
6978
+ const key = current << 8;
6979
+ const known = table.get(key);
6980
+ if (known !== void 0) {
6981
+ current = known;
6982
+ continue;
6983
+ }
6984
+ out.write(current, codeSize);
6985
+ if (next === 4096) {
6986
+ out.write(clear, codeSize);
6987
+ table.clear();
6988
+ codeSize = minCodeSize + 1;
6989
+ next = end + 1;
6990
+ } else {
6991
+ if (next >= 1 << codeSize) codeSize++;
6992
+ table.set(key, next++);
6993
+ }
6994
+ current = 0;
6995
+ }
6996
+ out.write(current, codeSize);
6997
+ out.write(end, codeSize);
6998
+ return out.finish();
6999
+ }
7000
+ function gif(width, height) {
7001
+ const data = lzwZeros(width * height, 2);
7002
+ const blocks = [];
7003
+ for (let offset = 0; offset < data.length; offset += 255) {
7004
+ const block = data.subarray(offset, offset + 255);
7005
+ blocks.push(block.length, ...block);
7006
+ }
7007
+ return concat(
7008
+ ascii("GIF89a"),
7009
+ [...u16le(width), ...u16le(height), 128, 0, 0],
7010
+ // a global palette of two colours
7011
+ [255, 255, 255, 0, 0, 0],
7012
+ [44, 0, 0, 0, 0, ...u16le(width), ...u16le(height), 0],
7013
+ // the image descriptor
7014
+ [2],
7015
+ blocks,
7016
+ [0, 59]
7017
+ // the end of the data, the trailer
7018
+ );
7019
+ }
7020
+ function webp(width, height) {
7021
+ const bits = new BitWriter();
7022
+ bits.write(47, 8);
7023
+ bits.write(width - 1, 14);
7024
+ bits.write(height - 1, 14);
7025
+ bits.write(0, 4);
7026
+ bits.write(0, 3);
7027
+ for (const symbol of [255, 255, 255, 255, 0]) {
7028
+ bits.write(1, 1);
7029
+ bits.write(0, 1);
7030
+ bits.write(1, 1);
7031
+ bits.write(symbol, 8);
7032
+ }
7033
+ const data = bits.finish();
7034
+ const padded = data.length % 2 === 0 ? data : concat(data, [0]);
7035
+ return concat(ascii("RIFF"), u32le(12 + padded.length), ascii("WEBP"), ascii("VP8L"), u32le(data.length), padded);
7036
+ }
7037
+ function bmp(width, height) {
7038
+ const row = Math.ceil(width * 3 / 4) * 4;
7039
+ const out = new Uint8Array(54 + row * height);
7040
+ const view = new DataView(out.buffer);
7041
+ out.set(ascii("BM"));
7042
+ view.setUint32(2, out.length, true);
7043
+ view.setUint32(10, 54, true);
7044
+ view.setUint32(14, 40, true);
7045
+ view.setInt32(18, width, true);
7046
+ view.setInt32(22, height, true);
7047
+ view.setUint16(26, 1, true);
7048
+ view.setUint16(28, 24, true);
7049
+ view.setUint32(34, row * height, true);
7050
+ for (let y = 0; y < height; y++) out.fill(255, 54 + y * row, 54 + y * row + width * 3);
7051
+ return out;
7052
+ }
7053
+ var IMAGE_FORMATS = {
7054
+ png: { type: "image/png", encode: png },
7055
+ jpg: { type: "image/jpeg", encode: jpeg },
7056
+ jpeg: { type: "image/jpeg", encode: jpeg },
7057
+ gif: { type: "image/gif", encode: gif },
7058
+ webp: { type: "image/webp", encode: webp },
7059
+ bmp: { type: "image/bmp", encode: bmp }
7060
+ };
7061
+ var extensionOf = (name) => name.includes(".") ? name.slice(name.lastIndexOf(".") + 1).toLowerCase() : "";
7062
+ var MORE_MIME_TYPES = {
7063
+ bmp: "image/bmp",
7064
+ avif: "image/avif",
7065
+ xml: "text/xml",
7066
+ htm: "text/html",
7067
+ md: "text/markdown",
7068
+ tsv: "text/tab-separated-values",
7069
+ text: "text/plain"
7070
+ };
7071
+ var LEADING_BYTES = {
7072
+ "image/jpeg": { bytes: [255, 216, 255, 224, ...u16be(16), ...ascii("JFIF"), 0, 1, 1, 0, 0, 1, 0, 1, 0, 0] },
7073
+ "image/png": { bytes: [137, 80, 78, 71, 13, 10, 26, 10] },
7074
+ "image/gif": { bytes: "GIF89a" },
7075
+ "image/webp": { bytes: "RIFF\0\0\0\0WEBP" },
7076
+ "image/avif": { bytes: [0, 0, 0, 20, ...ascii("ftypavif"), 0, 0, 0, 0, ...ascii("avifmif1")] },
7077
+ "image/bmp": { bytes: [...ascii("BM"), 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, 0] },
7078
+ "application/pdf": { bytes: "%PDF-1.4\n" },
7079
+ "image/svg+xml": { bytes: '<svg xmlns="http://www.w3.org/2000/svg"/>\n', text: true },
7080
+ "text/html": { bytes: "<!doctype html>\n", text: true },
7081
+ "text/xml": { bytes: '<?xml version="1.0"?>\n', text: true }
7082
+ };
7083
+ function contentFor(type, size) {
7084
+ const text = type.startsWith("text/") || type === "application/json";
7085
+ const leading = LEADING_BYTES[type] ?? { bytes: text ? "\n" : [], text };
7086
+ const head = typeof leading.bytes === "string" ? new TextEncoder().encode(leading.bytes) : Uint8Array.from(leading.bytes);
7087
+ const out = new Uint8Array(Math.max(size, head.length, 1)).fill(leading.text ? 32 : 0);
7088
+ out.set(head);
6611
7089
  return out;
6612
7090
  }
6613
7091
  var FakeFile = {
6614
- /** A file of `kilobytes` KiB (zero bytes), typed by `mimeType` or the extension. */
7092
+ /**
7093
+ * A file of `kilobytes` KiB typed by `mimeType` or the name's extension. Validation reads a file's bytes, so the
7094
+ * file starts like its type (a PDF's `%PDF-`, a PNG's signature, text for text types) and passes `mimes` for its
7095
+ * name, as Laravel's fakes do. It is at least its type's leading bytes and never empty, whatever the size asked for.
7096
+ * An image made this way has no readable size: use image() for `dimensions`.
7097
+ */
6615
7098
  create(name, kilobytes = 0, mimeType) {
6616
- return new File([new Uint8Array(Math.round(kilobytes * 1024))], name, { type: mimeType ?? guessMimeType(name) ?? "application/octet-stream" });
7099
+ const type = mimeType ?? guessMimeType(name) ?? MORE_MIME_TYPES[extensionOf(name)] ?? "application/octet-stream";
7100
+ return new File([contentFor(type, Math.round(kilobytes * 1024))], name, { type });
6617
7101
  },
6618
- /** A real PNG image of the given size (typed by the name's extension, like Laravel's image()). */
7102
+ /**
7103
+ * A real white image of the given size in the format the name's extension asks for: png, jpg/jpeg, gif, webp or
7104
+ * bmp (any other name gets a JPEG, as Laravel's fakes do), so image, mimes and dimensions pass on it.
7105
+ */
6619
7106
  image(name, width = 10, height = 10) {
6620
- return new File([png(width, height)], name, { type: guessMimeType(name) ?? "image/png" });
7107
+ const format2 = IMAGE_FORMATS[extensionOf(name)] ?? IMAGE_FORMATS.jpeg;
7108
+ return new File([format2.encode(width, height)], name, { type: guessMimeType(name) ?? format2.type });
6621
7109
  }
6622
7110
  };
6623
7111
 
@@ -6718,9 +7206,11 @@ export {
6718
7206
  componentName,
6719
7207
  PERSISTENT_MIDDLEWARE,
6720
7208
  persistentMiddleware,
7209
+ WithPagination,
6721
7210
  LiveComponentManager,
6722
7211
  UPDATE_URI,
6723
7212
  LiveServiceProvider,
7213
+ Pagination,
6724
7214
  PendingCommand,
6725
7215
  RefreshDatabaseState,
6726
7216
  runCommand,
@@ -6766,4 +7256,4 @@ export {
6766
7256
  StorageFake,
6767
7257
  FakeFile
6768
7258
  };
6769
- //# sourceMappingURL=chunk-SBZQ6L3Z.js.map
7259
+ //# sourceMappingURL=chunk-WNJP46B7.js.map