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

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.
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  renderToString,
23
23
  styleString,
24
24
  view
25
- } from "./chunk-3DETYZMZ.js";
25
+ } from "./chunk-MFTZQMZM.js";
26
26
  import {
27
27
  API_ROUTES_STUB,
28
28
  Application as Application2,
@@ -60,6 +60,7 @@ import {
60
60
  NotificationFake,
61
61
  On,
62
62
  PERSISTENT_MIDDLEWARE,
63
+ Pagination,
63
64
  PendingEvent,
64
65
  PropertyNotUpdatableError,
65
66
  ProviderBootError,
@@ -74,6 +75,7 @@ import {
74
75
  Vite,
75
76
  ViteError,
76
77
  ViteHtml,
78
+ WithPagination,
77
79
  abort,
78
80
  abortIf,
79
81
  abortUnless,
@@ -129,7 +131,7 @@ import {
129
131
  vite,
130
132
  viteReactRefresh,
131
133
  writeResponse
132
- } from "./chunk-SBZQ6L3Z.js";
134
+ } from "./chunk-ZOEPKBVF.js";
133
135
  import {
134
136
  Arr,
135
137
  BindingResolutionError,
@@ -141,18 +143,20 @@ import {
141
143
  LazyCollection,
142
144
  ServiceProvider,
143
145
  Str,
146
+ Stringable,
144
147
  __,
145
148
  app,
146
149
  collect,
147
150
  config,
148
151
  currentContainer,
149
152
  facade,
153
+ fileType_exports,
150
154
  inject,
151
155
  now,
152
156
  retry,
153
157
  tap,
154
158
  value
155
- } from "./chunk-DCI2T7WJ.js";
159
+ } from "./chunk-POEP4B2M.js";
156
160
  import {
157
161
  Application,
158
162
  AuthFactory,
@@ -179,6 +183,7 @@ import {
179
183
  LogManager,
180
184
  MailManager,
181
185
  NotificationDispatcher,
186
+ PaginationState,
182
187
  PasswordBroker,
183
188
  PipelineFactory,
184
189
  QueueManager,
@@ -193,7 +198,7 @@ import {
193
198
  ValidationFactory,
194
199
  ViewFactory,
195
200
  token
196
- } from "./chunk-WSGWYXAO.js";
201
+ } from "./chunk-I63ZFMX6.js";
197
202
 
198
203
  // ../Auth/dist/index.js
199
204
  import { createHash } from "crypto";
@@ -1377,6 +1382,39 @@ var AccessGate = class _AccessGate {
1377
1382
  return registered === void 0 ? [cls.name] : [baseName(registered), cls.name];
1378
1383
  }
1379
1384
  };
1385
+ var list = (abilities) => typeof abilities === "string" ? [abilities] : [...abilities];
1386
+ function Authorizable(Base) {
1387
+ return class AuthorizableUser extends Base {
1388
+ /** Whether the user has the ability (every one, for a list); the arguments go to the policy or ability callback. */
1389
+ async can(abilities, ...args) {
1390
+ const gate = inject(Gate).forUser(this);
1391
+ for (const ability of list(abilities)) if (!await gate.allows(ability, ...args)) return false;
1392
+ return true;
1393
+ }
1394
+ /** Whether the user has any of the abilities. */
1395
+ async canAny(abilities, ...args) {
1396
+ return inject(Gate).forUser(this).any(list(abilities), ...args);
1397
+ }
1398
+ /** Whether the user lacks the ability (any one, for a list). */
1399
+ async cant(abilities, ...args) {
1400
+ return !await this.can(abilities, ...args);
1401
+ }
1402
+ /** The same as cant(). */
1403
+ cannot(abilities, ...args) {
1404
+ return this.cant(abilities, ...args);
1405
+ }
1406
+ };
1407
+ }
1408
+ var Controller = class {
1409
+ /** Authorizes the ability for the signed-in user (the Gate's authorize()). */
1410
+ async authorize(ability, ...args) {
1411
+ return inject(Gate).authorize(ability, ...args);
1412
+ }
1413
+ /** Authorizes the ability for the given user (Gate.forUser(user).authorize()). */
1414
+ async authorizeForUser(user, ability, ...args) {
1415
+ return inject(Gate).forUser(user).authorize(ability, ...args);
1416
+ }
1417
+ };
1380
1418
  var Authorize = class {
1381
1419
  #gate = inject(Gate);
1382
1420
  async handle(request2, next, ability, ...models) {
@@ -1618,7 +1656,7 @@ async function discoverPolicies(directory, gate) {
1618
1656
  return found;
1619
1657
  }
1620
1658
  var Auth = facade(AuthFactory);
1621
- var Gate2 = facade(Gate);
1659
+ var Gate4 = facade(Gate);
1622
1660
  var Password = facade(PasswordBroker, {
1623
1661
  broker: (name) => inject(PasswordBrokerManager).broker(name)
1624
1662
  });
@@ -1689,7 +1727,7 @@ var QUEUE_OPTIONS = ["connection", "queue", "delay", "tries", "backoff", "timeou
1689
1727
  function isQueueable(job) {
1690
1728
  if (typeof job !== "object" || job === null) return false;
1691
1729
  const members = job;
1692
- if (members[SHOULD_QUEUE] === true) return true;
1730
+ if (members.shouldQueue === true || members[SHOULD_QUEUE] === true) return true;
1693
1731
  if (QUEUE_OPTIONS.some((option) => members[option] !== void 0)) return true;
1694
1732
  return typeof members.uniqueId === "function" || typeof members.retryUntil === "function";
1695
1733
  }
@@ -1900,9 +1938,9 @@ var Dispatcher = class {
1900
1938
  }
1901
1939
  #afterResponseList() {
1902
1940
  const owner = currentContainer() ?? this.container;
1903
- let list3 = this.#afterResponse.get(owner);
1904
- if (list3 === void 0) this.#afterResponse.set(owner, list3 = []);
1905
- return list3;
1941
+ let list4 = this.#afterResponse.get(owner);
1942
+ if (list4 === void 0) this.#afterResponse.set(owner, list4 = []);
1943
+ return list4;
1906
1944
  }
1907
1945
  /** Runs the job (like dispatchSync) after the response is sent, or after the current queued job. */
1908
1946
  dispatchAfterResponse(job) {
@@ -2051,6 +2089,10 @@ var Dispatchable = class {
2051
2089
  }
2052
2090
  };
2053
2091
  var Queueable = class extends Dispatchable {
2092
+ /** ShouldQueue's marker, so subclasses may write `implements ShouldQueue` without the field. */
2093
+ get shouldQueue() {
2094
+ return true;
2095
+ }
2054
2096
  };
2055
2097
  Object.defineProperty(Queueable.prototype, SHOULD_QUEUE, { value: true });
2056
2098
  function dispatch(job) {
@@ -2921,8 +2963,8 @@ var RedisRepository = class extends Repository2 {
2921
2963
  redisStore;
2922
2964
  redis;
2923
2965
  tags(names) {
2924
- const list3 = Array.isArray(names) ? names : [names];
2925
- const taggedStore = this.redisStore.tags(list3);
2966
+ const list4 = Array.isArray(names) ? names : [names];
2967
+ const taggedStore = this.redisStore.tags(list4);
2926
2968
  const prefix = this.redisStore.getPrefix();
2927
2969
  return new Repository2(taggedStore, (_store, name, seconds, owner) => new RedisLock(this.redis, prefix + name, seconds, owner));
2928
2970
  }
@@ -3748,6 +3790,34 @@ var LISTENER_STUB_PLAIN = `export class {{ class }} {
3748
3790
  }
3749
3791
  }
3750
3792
  `;
3793
+ var LISTENER_STUB_QUEUED = `import { {{ event }} } from '{{ eventImport }}'
3794
+ import type { ShouldQueue } from '@orieljs/framework/contracts'
3795
+
3796
+ export class {{ class }} implements ShouldQueue {
3797
+ static listensTo = {{ event }}
3798
+
3799
+ /** Run by the queue worker (node oriel queue:work): remove this line and \`implements ShouldQueue\` to run it right away. */
3800
+ readonly shouldQueue = true
3801
+
3802
+ async handle(_event: {{ event }}): Promise<void> {
3803
+ //
3804
+ }
3805
+ }
3806
+ `;
3807
+ var LISTENER_STUB_QUEUED_PLAIN = `import type { ShouldQueue } from '@orieljs/framework/contracts'
3808
+
3809
+ export class {{ class }} implements ShouldQueue {
3810
+ // Name the event class (or event name) this listener handles, so app/Listeners discovery registers it:
3811
+ // static listensTo = OrderShipped
3812
+
3813
+ /** Run by the queue worker (node oriel queue:work): remove this line and \`implements ShouldQueue\` to run it right away. */
3814
+ readonly shouldQueue = true
3815
+
3816
+ async handle(_event: unknown): Promise<void> {
3817
+ //
3818
+ }
3819
+ }
3820
+ `;
3751
3821
  var JOB_STUB = `import { Queueable } from '@orieljs/framework'
3752
3822
 
3753
3823
  export class {{ class }} extends Queueable {
@@ -3765,9 +3835,12 @@ export class {{ class }} extends Dispatchable {
3765
3835
  }
3766
3836
  `;
3767
3837
  var MAIL_STUB = `import { Mailable } from '@orieljs/framework'
3768
- import type { Content, Envelope } from '@orieljs/framework/contracts'
3838
+ import type { Content, Envelope, ShouldQueue } from '@orieljs/framework/contracts'
3839
+
3840
+ export class {{ class }} extends Mailable implements ShouldQueue {
3841
+ /** Sent by the queue worker (node oriel queue:work): remove this line and \`implements ShouldQueue\` to send it right away. */
3842
+ readonly shouldQueue = true
3769
3843
 
3770
- export class {{ class }} extends Mailable {
3771
3844
  override async envelope(): Promise<Envelope> {
3772
3845
  return { subject: '{{ subject }}' }
3773
3846
  }
@@ -3778,9 +3851,12 @@ export class {{ class }} extends Mailable {
3778
3851
  }
3779
3852
  `;
3780
3853
  var NOTIFICATION_STUB = `import { MailMessage, Notification } from '@orieljs/framework'
3781
- import type { Notifiable } from '@orieljs/framework/contracts'
3854
+ import type { Notifiable, ShouldQueue } from '@orieljs/framework/contracts'
3855
+
3856
+ export class {{ class }} extends Notification implements ShouldQueue {
3857
+ /** Sent by the queue worker (node oriel queue:work): remove this line and \`implements ShouldQueue\` to send it right away. */
3858
+ readonly shouldQueue = true
3782
3859
 
3783
- export class {{ class }} extends Notification {
3784
3860
  via(_notifiable: Notifiable): string[] {
3785
3861
  return ['mail']
3786
3862
  }
@@ -3917,15 +3993,12 @@ var MakeCastCommand = class extends GeneratorCommand {
3917
3993
  }
3918
3994
  };
3919
3995
  var MakeCommandCommand = class extends GeneratorCommand {
3920
- /** --command sets the generated signature; without it, Laravel's `app:` plus the kebab-cased name is used. */
3996
+ /** --command sets the generated signature; without it, Laravel's `app:` plus the kebab-cased class name is used. */
3921
3997
  signature = "make:command {name} {--command=} {--force}";
3922
3998
  description = "Create a new Artisan command";
3923
3999
  type() {
3924
4000
  return "Console/Commands";
3925
4001
  }
3926
- suffix() {
3927
- return "Command";
3928
- }
3929
4002
  stub() {
3930
4003
  return COMMAND_STUB;
3931
4004
  }
@@ -3933,7 +4006,7 @@ var MakeCommandCommand = class extends GeneratorCommand {
3933
4006
  const option = this.option("command");
3934
4007
  const className = this.qualifyClass(rawName).className;
3935
4008
  return {
3936
- signature: typeof option === "string" ? option : `app:${Str.kebab(className.replace(/Command$/, ""))}`
4009
+ signature: typeof option === "string" ? option : `app:${Str.kebab(className)}`
3937
4010
  };
3938
4011
  }
3939
4012
  };
@@ -4006,13 +4079,15 @@ var MakeJobCommand = class extends GeneratorCommand {
4006
4079
  }
4007
4080
  };
4008
4081
  var MakeListenerCommand = class extends GeneratorCommand {
4009
- signature = "make:listener {name} {--e|event= : The event class being listened for} {--force}";
4082
+ signature = "make:listener {name} {--e|event= : The event class being listened for} {--queued : Indicates the event listener should be queued} {--force}";
4010
4083
  description = "Create a new event listener class";
4011
4084
  type() {
4012
4085
  return "Listeners";
4013
4086
  }
4014
4087
  stub() {
4015
- return typeof this.option("event") === "string" ? LISTENER_STUB : LISTENER_STUB_PLAIN;
4088
+ const typed = typeof this.option("event") === "string";
4089
+ if (this.option("queued") === true) return typed ? LISTENER_STUB_QUEUED : LISTENER_STUB_QUEUED_PLAIN;
4090
+ return typed ? LISTENER_STUB : LISTENER_STUB_PLAIN;
4016
4091
  }
4017
4092
  extraPlaceholders(rawName) {
4018
4093
  const event2 = this.option("event");
@@ -4200,6 +4275,29 @@ var MakeTestCommand = class extends GeneratorCommand {
4200
4275
  return `${className.replace(/(?<=.)Test$/, "")}.test.ts`;
4201
4276
  }
4202
4277
  };
4278
+ var ScheduleClearCacheCommand = class extends Command {
4279
+ signature = "schedule:clear-cache";
4280
+ description = "Delete the cached mutex files created by scheduler";
4281
+ async handle() {
4282
+ let cleared = false;
4283
+ if (currentContainer()?.bound(CacheManager)) {
4284
+ const cache2 = inject(CacheManager);
4285
+ for (const event2 of inject(Schedule).events()) {
4286
+ if (!event2.preventsOverlapping) continue;
4287
+ const lock = cache2.lock(event2.mutexName(), event2.expiresAt * 60);
4288
+ if (await lock.get()) {
4289
+ await lock.release();
4290
+ continue;
4291
+ }
4292
+ this.info(`Deleting mutex for [${event2.command ?? event2.getSummaryForDisplay()}]`);
4293
+ await lock.forceRelease();
4294
+ cleared = true;
4295
+ }
4296
+ }
4297
+ if (!cleared) this.info("No mutex files were found.");
4298
+ return 0;
4299
+ }
4300
+ };
4203
4301
  var ScheduleListCommand = class extends Command {
4204
4302
  signature = "schedule:list";
4205
4303
  description = "List the scheduled commands";
@@ -4211,18 +4309,15 @@ var ScheduleListCommand = class extends Command {
4211
4309
  }
4212
4310
  this.table(
4213
4311
  ["Command", "Expression"],
4214
- events.map((event2) => [event2.getSummary(), event2.expression])
4312
+ events.map((event2) => [event2.getSummaryForDisplay(), event2.expression])
4215
4313
  );
4216
4314
  return 0;
4217
4315
  }
4218
4316
  };
4219
- function summaryOf(event2) {
4220
- return event2.getSummary();
4221
- }
4222
4317
  async function runDueEvents(events, io, now5) {
4223
4318
  const backgroundRuns = [];
4224
4319
  for (const event2 of events) {
4225
- io.info(`Running scheduled task: ${summaryOf(event2)}`);
4320
+ io.info(`Running scheduled task: ${event2.getSummaryForDisplay()}`);
4226
4321
  const attempt = async () => {
4227
4322
  try {
4228
4323
  await event2.run(now5);
@@ -4527,7 +4622,7 @@ var SHOULD_QUEUE2 = /* @__PURE__ */ Symbol.for("oriel:bus.shouldQueue");
4527
4622
  var QUEUE_OPTIONS2 = ["connection", "queue", "delay", "tries", "backoff", "timeout"];
4528
4623
  function isQueueable2(job) {
4529
4624
  const members = job;
4530
- if (members[SHOULD_QUEUE2] === true) return true;
4625
+ if (members.shouldQueue === true || members[SHOULD_QUEUE2] === true) return true;
4531
4626
  if (QUEUE_OPTIONS2.some((option) => members[option] !== void 0)) return true;
4532
4627
  return typeof members.uniqueId === "function" || typeof members.retryUntil === "function";
4533
4628
  }
@@ -4570,6 +4665,15 @@ var ScheduledEventImpl = class {
4570
4665
  get expression() {
4571
4666
  return this.cronExpression;
4572
4667
  }
4668
+ get command() {
4669
+ return this.commandString;
4670
+ }
4671
+ get preventsOverlapping() {
4672
+ return this.overlapExpiryMinutes !== void 0;
4673
+ }
4674
+ get expiresAt() {
4675
+ return this.overlapExpiryMinutes ?? 1440;
4676
+ }
4573
4677
  cron(expression) {
4574
4678
  this.cronExpression = expression;
4575
4679
  return this;
@@ -4663,15 +4767,19 @@ var ScheduledEventImpl = class {
4663
4767
  this.eventDescription = description;
4664
4768
  return this;
4665
4769
  }
4666
- /** Accessors beyond the contract, read back by schedule:list and the schedule:run/schedule:work loop. */
4770
+ getSummaryForDisplay() {
4771
+ return this.eventName ?? this.eventDescription ?? this.commandString ?? "Closure";
4772
+ }
4773
+ /** Accessors beyond the contract, read back by the schedule:run/schedule:work loop. */
4667
4774
  getName() {
4668
4775
  return this.eventName;
4669
4776
  }
4670
4777
  getDescription() {
4671
4778
  return this.eventDescription;
4672
4779
  }
4780
+ /** The same as getSummaryForDisplay(). */
4673
4781
  getSummary() {
4674
- return this.eventName ?? this.eventDescription ?? "(closure)";
4782
+ return this.getSummaryForDisplay();
4675
4783
  }
4676
4784
  isBackground() {
4677
4785
  return this.background;
@@ -4727,11 +4835,11 @@ var ScheduledEventImpl = class {
4727
4835
  * Follows Laravel: a command event's mutex is `sha1(expression + full command
4728
4836
  * string incl. parameters)` (Laravel's `Event::mutexName()`) — no `name()` required. A closure event (`call()`/
4729
4837
  * `job()`) has no command string, so its mutex is `sha1(name())` (Laravel's `CallbackEvent::mutexName()`, which
4730
- * reads the description `name()` also writes). `requireLockableName()` guarantees `eventName` is set by the time
4731
- * this runs for a closure event.
4838
+ * reads the description `name()` also writes), and `sha1('')` while it has no name, as Laravel's
4839
+ * (`requireLockableName()` keeps an unnamed closure from ever taking the lock).
4732
4840
  */
4733
4841
  mutexName() {
4734
- const key = this.commandString !== void 0 ? this.cronExpression + this.commandString : this.eventName;
4842
+ const key = this.commandString !== void 0 ? this.cronExpression + this.commandString : this.eventName ?? "";
4735
4843
  return `framework/schedule-${sha1(key)}`;
4736
4844
  }
4737
4845
  /**
@@ -4808,6 +4916,7 @@ var ConsoleServiceProvider = class extends ServiceProvider {
4808
4916
  ScheduleRunCommand,
4809
4917
  ScheduleWorkCommand,
4810
4918
  ScheduleListCommand,
4919
+ ScheduleClearCacheCommand,
4811
4920
  MakeControllerCommand,
4812
4921
  MakeModelCommand,
4813
4922
  MakeMiddlewareCommand,
@@ -4837,7 +4946,7 @@ var ConsoleServiceProvider = class extends ServiceProvider {
4837
4946
  }
4838
4947
  };
4839
4948
  var Artisan = facade(ConsoleKernel);
4840
- var Schedule4 = facade(Schedule);
4949
+ var Schedule5 = facade(Schedule);
4841
4950
  async function runConsole(app2, argv = process.argv.slice(2)) {
4842
4951
  app2.setRunningInConsole(true);
4843
4952
  try {
@@ -4848,7 +4957,17 @@ async function runConsole(app2, argv = process.argv.slice(2)) {
4848
4957
  }
4849
4958
  }
4850
4959
  async function runConsoleAndExit(app2, argv = process.argv.slice(2), exit = (code) => process.exit(code)) {
4851
- exit(await runConsole(app2, argv));
4960
+ let code;
4961
+ try {
4962
+ code = await runConsole(app2, argv);
4963
+ } finally {
4964
+ await flushLogs(app2);
4965
+ }
4966
+ exit(code);
4967
+ }
4968
+ async function flushLogs(app2) {
4969
+ if (!app2.bound(LogManager)) return;
4970
+ await app2.make(LogManager).flush?.();
4852
4971
  }
4853
4972
 
4854
4973
  // ../Cookie/dist/index.js
@@ -5452,7 +5571,10 @@ var AbstractPaginator = class _AbstractPaginator {
5452
5571
  this.collection = items;
5453
5572
  return this;
5454
5573
  }
5455
- /** Transforms each item in place, typically for serialization: paginator.through(post => ({ id: post.id })). */
5574
+ /**
5575
+ * Transforms each item in place, typically for serialization, and returns this paginator typed with the new items:
5576
+ * `posts.through((post) => ({ id: post.id }))`. Each paginator narrows the return type to its own class.
5577
+ */
5456
5578
  through(callback) {
5457
5579
  this.collection = this.collection.map(callback);
5458
5580
  return this;
@@ -5552,6 +5674,9 @@ var CursorPaginator = class extends AbstractPaginator {
5552
5674
  getCursorName() {
5553
5675
  return this.cursorName;
5554
5676
  }
5677
+ through(callback) {
5678
+ return super.through(callback);
5679
+ }
5555
5680
  hasMorePages() {
5556
5681
  return this.cursorValue === void 0 && this.hasMore || this.cursorValue !== void 0 && this.cursorValue.pointsToNextItems() && this.hasMore || this.cursorValue !== void 0 && this.cursorValue.pointsToPreviousItems();
5557
5682
  }
@@ -5608,6 +5733,9 @@ var LengthAwarePaginator = class extends AbstractPaginator {
5608
5733
  total() {
5609
5734
  return this.totalValue;
5610
5735
  }
5736
+ through(callback) {
5737
+ return super.through(callback);
5738
+ }
5611
5739
  lastPage() {
5612
5740
  return this.lastPageValue;
5613
5741
  }
@@ -5623,14 +5751,17 @@ var LengthAwarePaginator = class extends AbstractPaginator {
5623
5751
  nextPageUrl() {
5624
5752
  return this.hasMorePages() ? this.url(this.currentPageValue + 1) : null;
5625
5753
  }
5626
- /** The page links Laravel renders: previous, the page window with '...' gaps, next. */
5754
+ /**
5755
+ * The page links Laravel renders: previous, the page window with '...' gaps, next. The labels are plain text
5756
+ * ("« Previous", "Next »"), not Laravel's `&laquo;` entities, so a page can print them as text.
5757
+ */
5627
5758
  linkCollection() {
5628
- const links = [{ url: this.previousPageUrl(), label: "&laquo; Previous", active: false }];
5759
+ const links = [{ url: this.previousPageUrl(), label: "\xAB Previous", active: false }];
5629
5760
  for (const element of this.elements()) {
5630
5761
  if (element === "...") links.push({ url: null, label: "...", active: false });
5631
5762
  else for (const [page, url2] of element) links.push({ url: url2, label: String(page), active: page === this.currentPageValue });
5632
5763
  }
5633
- links.push({ url: this.nextPageUrl(), label: "Next &raquo;", active: false });
5764
+ links.push({ url: this.nextPageUrl(), label: "Next \xBB", active: false });
5634
5765
  return links;
5635
5766
  }
5636
5767
  /** Laravel's UrlWindow: the first pages, the slider around the current page and the last pages, with '...' between. */
@@ -5693,6 +5824,9 @@ var Paginator = class extends AbstractPaginator {
5693
5824
  hasMorePages() {
5694
5825
  return this.hasMore;
5695
5826
  }
5827
+ through(callback) {
5828
+ return super.through(callback);
5829
+ }
5696
5830
  hasPages() {
5697
5831
  return this.currentPageValue !== 1 || this.hasMore;
5698
5832
  }
@@ -5752,6 +5886,9 @@ var EMPTY = sql2.raw("");
5752
5886
  var isSqlFragment = (value2) => typeof value2 === "object" && value2 !== null && typeof value2.toOperationNode === "function";
5753
5887
  var join8 = (parts, separator = ", ") => sql2.join(parts, sql2.raw(separator));
5754
5888
  var clauses = (parts) => join8(parts.filter((part) => part !== void 0), " ");
5889
+ function likeToGlob(pattern) {
5890
+ return pattern.replaceAll("*", "[*]").replaceAll("?", "[?]").replaceAll("%", "*").replaceAll("_", "?");
5891
+ }
5755
5892
  var Grammar = class {
5756
5893
  constructor(driver) {
5757
5894
  this.driver = driver;
@@ -5832,6 +5969,8 @@ var Grammar = class {
5832
5969
  }
5833
5970
  case "null":
5834
5971
  return sql2`${this.wrap(where.column)} ${sql2.raw(where.not ? "is not null" : "is null")}`;
5972
+ case "like":
5973
+ return this.compileWhereLike(where);
5835
5974
  case "between":
5836
5975
  return sql2`${this.wrap(where.column)} ${sql2.raw(where.not ? "not between" : "between")} ${this.parameter(where.values[0])} and ${this.parameter(where.values[1])}`;
5837
5976
  case "column":
@@ -5844,6 +5983,18 @@ var Grammar = class {
5844
5983
  return rawWithBindings(where.sql, where.bindings.map((value2) => this.prepare(value2)));
5845
5984
  }
5846
5985
  }
5986
+ /**
5987
+ * whereLike() per driver, as Laravel 12's grammars: Postgres compares the column as text with ilike (like when
5988
+ * case-sensitive); MySQL uses like (like binary); SQLite uses like, or glob with the pattern translated.
5989
+ */
5990
+ compileWhereLike(where) {
5991
+ const not = where.not ? "not " : "";
5992
+ const column = this.wrap(where.column);
5993
+ if (this.driver === "pgsql") return sql2`${column}::text ${sql2.raw(`${not}${where.caseSensitive ? "like" : "ilike"}`)} ${this.parameter(where.value)}`;
5994
+ if (this.driver === "mysql") return sql2`${column} ${sql2.raw(`${not}${where.caseSensitive ? "like binary" : "like"}`)} ${this.parameter(where.value)}`;
5995
+ if (where.caseSensitive) return sql2`${column} ${sql2.raw(`${not}glob`)} ${this.parameter(likeToGlob(where.value))}`;
5996
+ return sql2`${column} ${sql2.raw(`${not}like`)} ${this.parameter(where.value)}`;
5997
+ }
5847
5998
  compileJoin(joinClause) {
5848
5999
  if (joinClause.joinType === "cross") return sql2`cross join ${this.wrapTable(joinClause.table)}`;
5849
6000
  return sql2`${sql2.raw(joinClause.joinType)} join ${this.wrapTable(joinClause.table)} ${this.compileWheres(joinClause.wheres, "on") ?? EMPTY}`;
@@ -6096,6 +6247,25 @@ var Builder = class _Builder {
6096
6247
  this.wheres.push({ type: "between", column, values, not: true, boolean: "and" });
6097
6248
  return this;
6098
6249
  }
6250
+ /**
6251
+ * Laravel's whereLike(): the value is the pattern, `%` and `_` included (`whereLike('title', `%${term}%`)`).
6252
+ * Case-insensitive unless caseSensitive: ilike on Postgres, like on MySQL and SQLite (their like already ignores
6253
+ * case: MySQL through the column's collation, SQLite for ASCII letters). Case-sensitive is like on Postgres,
6254
+ * like binary on MySQL and glob on SQLite.
6255
+ */
6256
+ whereLike(column, value2, caseSensitive = false, boolean = "and", not = false) {
6257
+ this.wheres.push({ type: "like", column, value: value2, caseSensitive, not, boolean });
6258
+ return this;
6259
+ }
6260
+ orWhereLike(column, value2, caseSensitive = false) {
6261
+ return this.whereLike(column, value2, caseSensitive, "or");
6262
+ }
6263
+ whereNotLike(column, value2, caseSensitive = false, boolean = "and") {
6264
+ return this.whereLike(column, value2, caseSensitive, boolean, true);
6265
+ }
6266
+ orWhereNotLike(column, value2, caseSensitive = false) {
6267
+ return this.whereNotLike(column, value2, caseSensitive, "or");
6268
+ }
6099
6269
  /** Compares two columns: whereColumn('updated_at', '>', 'created_at'). */
6100
6270
  whereColumn(first, operatorOrSecond, second, boolean = "and") {
6101
6271
  const [operator, other] = second === void 0 ? ["=", operatorOrSecond] : [operatorOrSecond, second];
@@ -6219,10 +6389,16 @@ var Builder = class _Builder {
6219
6389
  this.lockMode = "share";
6220
6390
  return this;
6221
6391
  }
6222
- /** Calls callback(this, value) when value is truthy (or the default callback otherwise). */
6223
6392
  when(value2, callback, otherwise) {
6224
- if (value2) callback(this, value2);
6225
- else otherwise?.(this, value2);
6393
+ const resolved = typeof value2 === "function" ? value2(this) : value2;
6394
+ if (resolved) callback(this, resolved);
6395
+ else otherwise?.(this, resolved);
6396
+ return this;
6397
+ }
6398
+ unless(value2, callback, otherwise) {
6399
+ const resolved = typeof value2 === "function" ? value2(this) : value2;
6400
+ if (!resolved) callback(this, resolved);
6401
+ else otherwise?.(this, resolved);
6226
6402
  return this;
6227
6403
  }
6228
6404
  tap(callback) {
@@ -7909,6 +8085,18 @@ var Builder2 = class extends Builder {
7909
8085
  whereBetween(column, values) {
7910
8086
  return super.whereBetween(column, values);
7911
8087
  }
8088
+ whereLike(column, value2, caseSensitive = false, boolean = "and", not = false) {
8089
+ return super.whereLike(column, value2, caseSensitive, boolean, not);
8090
+ }
8091
+ orWhereLike(column, value2, caseSensitive = false) {
8092
+ return super.orWhereLike(column, value2, caseSensitive);
8093
+ }
8094
+ whereNotLike(column, value2, caseSensitive = false, boolean = "and") {
8095
+ return super.whereNotLike(column, value2, caseSensitive, boolean);
8096
+ }
8097
+ orWhereNotLike(column, value2, caseSensitive = false) {
8098
+ return super.orWhereNotLike(column, value2, caseSensitive);
8099
+ }
7912
8100
  orderBy(column, direction = "asc") {
7913
8101
  return super.orderBy(column, direction);
7914
8102
  }
@@ -8098,12 +8286,19 @@ var Builder2 = class extends Builder {
8098
8286
  const key = column.includes(".") ? column.slice(column.lastIndexOf(".") + 1) : column;
8099
8287
  return new Collection(rows.map((row) => this.model.newInstance().setRawAttributes({ [key]: row[key] }).getAttribute(key)));
8100
8288
  }
8289
+ /** Whether `id` can't be one of the model's unique ids (not a UUID for HasUuids): it matches no row. */
8290
+ isMalformedKey(id) {
8291
+ return this.model.uniqueIds().includes(this.model.getKeyName()) && !this.model.isValidUniqueId(id);
8292
+ }
8293
+ /** A malformed unique id (HasUuids, HasUlids) finds nothing without a query, as route model binding does. */
8101
8294
  async find(id) {
8295
+ if (this.isMalformedKey(id)) return void 0;
8102
8296
  return this.clone().whereKey(id).first();
8103
8297
  }
8104
8298
  async findMany(ids) {
8105
- if (!ids.length) return new Collection();
8106
- return this.clone().whereKey([...ids]).get();
8299
+ const keys = ids.filter((id) => !this.isMalformedKey(id));
8300
+ if (!keys.length) return new Collection();
8301
+ return this.clone().whereKey(keys).get();
8107
8302
  }
8108
8303
  async findOrFail(id) {
8109
8304
  if (Array.isArray(id)) {
@@ -8740,6 +8935,8 @@ var HasOneOrMany = class extends Relation {
8740
8935
  foreignKey;
8741
8936
  localKey;
8742
8937
  morph;
8938
+ /** The relation on each related model that chaperone() points back at the parent. */
8939
+ inverseRelationship;
8743
8940
  /** 'post_id' */
8744
8941
  getForeignKeyName() {
8745
8942
  return this.foreignKey.slice(this.foreignKey.lastIndexOf(".") + 1);
@@ -8761,6 +8958,61 @@ var HasOneOrMany = class extends Relation {
8761
8958
  getMorphClass() {
8762
8959
  return this.morph?.value;
8763
8960
  }
8961
+ /**
8962
+ * Laravel's chaperone(): every related model this relation loads, eagerly or lazily, or makes, gets the parent as
8963
+ * its `relation` (guessed when not given: post_id → post, the parent's class, owner), so reading it needs no query:
8964
+ * `comments() { return this.hasMany(Comment).chaperone() }`.
8965
+ */
8966
+ chaperone(relation) {
8967
+ const name = relation ?? this.guessInverseRelation();
8968
+ if (name === void 0 || !this.model.isRelation(name)) throw new RelationNotFoundError(this.model.constructor.name, name ?? "null");
8969
+ this.inverseRelationship = name;
8970
+ return this;
8971
+ }
8972
+ /** Laravel's inverse(): chaperone() under its other name. */
8973
+ inverse(relation) {
8974
+ return this.chaperone(relation);
8975
+ }
8976
+ withoutChaperone() {
8977
+ this.inverseRelationship = void 0;
8978
+ return this;
8979
+ }
8980
+ withoutInverse() {
8981
+ return this.withoutChaperone();
8982
+ }
8983
+ getInverseRelationship() {
8984
+ return this.inverseRelationship;
8985
+ }
8986
+ /** The first of the possible names that is a relation of the related model. */
8987
+ guessInverseRelation() {
8988
+ return this.getPossibleInverseRelations().find((name) => this.model.isRelation(name));
8989
+ }
8990
+ /** Laravel's candidates: the morph name, the foreign key without the parent's key, the parent's class, owner, parent. */
8991
+ getPossibleInverseRelations() {
8992
+ const key = this.parent.getKeyName();
8993
+ const names = [
8994
+ this.morph ? Str.beforeLast(this.getMorphType(), "_type") : "",
8995
+ Str.camel(Str.beforeLast(this.getForeignKeyName(), key)),
8996
+ Str.camel(Str.beforeLast(this.parent.getForeignKey(), key)),
8997
+ Str.camel(this.parent.constructor.name),
8998
+ "owner",
8999
+ this.parent.constructor === this.model.constructor ? "parent" : ""
9000
+ ];
9001
+ return [...new Set(names.filter((name) => name !== ""))];
9002
+ }
9003
+ /** Sets the parent on a related model, when chaperone() named the relation. */
9004
+ applyInverseRelationToModel(model, parent = this.parent) {
9005
+ if (model && this.inverseRelationship !== void 0) model.setRelation(this.inverseRelationship, parent);
9006
+ return model;
9007
+ }
9008
+ applyInverseRelationToMany(models, parent = this.parent) {
9009
+ for (const model of models) this.applyInverseRelationToModel(model, parent);
9010
+ return models;
9011
+ }
9012
+ /** The relation query's rows (get(), first(), paginate(), …), pointing back at the parent with chaperone(). */
9013
+ async runSelect() {
9014
+ return this.applyInverseRelationToMany(await super.runSelect());
9015
+ }
8764
9016
  addConstraints() {
8765
9017
  this.where(this.foreignKey, "=", this.getParentKey());
8766
9018
  this.whereNotNull(this.foreignKey);
@@ -8794,7 +9046,7 @@ var HasOneOrMany = class extends Relation {
8794
9046
  setForeignAttributes(model) {
8795
9047
  model.setAttribute(this.getForeignKeyName(), this.getParentKey());
8796
9048
  if (this.morph) model.setAttribute(this.getMorphType(), this.morph.value);
8797
- return model;
9049
+ return this.applyInverseRelationToModel(model);
8798
9050
  }
8799
9051
  async saveMany(models) {
8800
9052
  const saved = [];
@@ -9113,7 +9365,8 @@ var HasMany = class extends HasOneOrMany {
9113
9365
  match(models, results, relation) {
9114
9366
  const dictionary = this.buildDictionary(results);
9115
9367
  for (const model of models) {
9116
- model.setRelation(relation, new Collection(dictionary.get(String(model.getAttribute(this.localKey))) ?? []));
9368
+ const children = this.applyInverseRelationToMany(dictionary.get(String(model.getAttribute(this.localKey))) ?? [], model);
9369
+ model.setRelation(relation, new Collection(children));
9117
9370
  }
9118
9371
  return models;
9119
9372
  }
@@ -9222,7 +9475,7 @@ var HasOne = class extends HasOneOrMany {
9222
9475
  match(models, results, relation) {
9223
9476
  const dictionary = this.buildDictionary(results);
9224
9477
  for (const model of models) {
9225
- model.setRelation(relation, dictionary.get(String(model.getAttribute(this.localKey)))?.[0] ?? null);
9478
+ model.setRelation(relation, this.applyInverseRelationToModel(dictionary.get(String(model.getAttribute(this.localKey)))?.[0] ?? null, model));
9226
9479
  }
9227
9480
  return models;
9228
9481
  }
@@ -9282,6 +9535,7 @@ function methodOf(model, name) {
9282
9535
  const value2 = Reflect.get(Object.getPrototypeOf(model), name);
9283
9536
  return typeof value2 === "function" ? value2 : void 0;
9284
9537
  }
9538
+ var serializing = /* @__PURE__ */ new WeakSet();
9285
9539
  function serializeDate(date) {
9286
9540
  return date.toISOString().replace(/\.(\d{3})Z$/, ".$1000Z");
9287
9541
  }
@@ -9472,6 +9726,14 @@ var Model = class _Model {
9472
9726
  const query = this.query();
9473
9727
  return Reflect.apply(query.where, query, args);
9474
9728
  }
9729
+ static when(...args) {
9730
+ const query = this.query();
9731
+ return Reflect.apply(query.when, query, args);
9732
+ }
9733
+ static unless(...args) {
9734
+ const query = this.query();
9735
+ return Reflect.apply(query.unless, query, args);
9736
+ }
9475
9737
  static whereKey(id) {
9476
9738
  return this.query().whereKey(id);
9477
9739
  }
@@ -9490,6 +9752,13 @@ var Model = class _Model {
9490
9752
  static whereBetween(column, values) {
9491
9753
  return this.query().whereBetween(column, values);
9492
9754
  }
9755
+ /** Post.whereLike('title', `%${term}%`): case-insensitive unless caseSensitive (see the query builder's whereLike()). */
9756
+ static whereLike(column, value2, caseSensitive = false) {
9757
+ return this.query().whereLike(column, value2, caseSensitive);
9758
+ }
9759
+ static whereNotLike(column, value2, caseSensitive = false) {
9760
+ return this.query().whereNotLike(column, value2, caseSensitive);
9761
+ }
9493
9762
  static whereColumn(first, operatorOrSecond, second) {
9494
9763
  return this.query().whereColumn(first, operatorOrSecond, second);
9495
9764
  }
@@ -9675,8 +9944,8 @@ var Model = class _Model {
9675
9944
  /** Guarded names match case-insensitively: SQLite and MySQL column names are case-insensitive. */
9676
9945
  isGuarded(key) {
9677
9946
  if (this.guarded.length === 0) return false;
9678
- const lower = key.toLowerCase();
9679
- return this.guarded.some((guarded) => guarded === "*" || guarded.toLowerCase() === lower);
9947
+ const lower2 = key.toLowerCase();
9948
+ return this.guarded.some((guarded) => guarded === "*" || guarded.toLowerCase() === lower2);
9680
9949
  }
9681
9950
  totallyGuarded() {
9682
9951
  return !_Model.isUnguarded() && this.fillable.length === 0 && this.guarded.length === 1 && this.guarded[0] === "*";
@@ -9722,7 +9991,7 @@ var Model = class _Model {
9722
9991
  /** Every cast, including the implied ones: the incrementing key (int) and the timestamp columns (datetime). */
9723
9992
  getCasts() {
9724
9993
  const implied = {};
9725
- if (this.incrementing) implied[this.getKeyName()] = this.keyType === "int" ? "int" : "string";
9994
+ if (this.getIncrementing()) implied[this.getKeyName()] = this.getKeyType() === "int" ? "int" : "string";
9726
9995
  if (this.usesTimestamps()) {
9727
9996
  const created = this.getCreatedAtColumn();
9728
9997
  const updated = this.getUpdatedAtColumn();
@@ -9845,6 +10114,29 @@ var Model = class _Model {
9845
10114
  getIncrementing() {
9846
10115
  return this.incrementing;
9847
10116
  }
10117
+ /** Whether new rows get unique string ids before `creating` (HasUuids, HasUlids). */
10118
+ usesUniqueIds() {
10119
+ return false;
10120
+ }
10121
+ /** The columns that get a unique id (HasUuids and HasUlids: the primary key). */
10122
+ uniqueIds() {
10123
+ return [];
10124
+ }
10125
+ /** A new unique id (HasUuids: a version 7 UUID; HasUlids: a lower-case ULID). */
10126
+ newUniqueId() {
10127
+ return void 0;
10128
+ }
10129
+ /** Whether a value has the shape of this model's unique ids (HasUuids: a UUID). Any value passes by default. */
10130
+ isValidUniqueId(_value) {
10131
+ return true;
10132
+ }
10133
+ /** Fills each empty unique id column with newUniqueId() (Laravel's setUniqueIds()). */
10134
+ setUniqueIds() {
10135
+ for (const column of this.uniqueIds()) {
10136
+ const value2 = this.getAttribute(column);
10137
+ if (value2 === void 0 || value2 === null || value2 === "") this.setAttribute(column, this.newUniqueId());
10138
+ }
10139
+ }
9848
10140
  getKey() {
9849
10141
  return this.getAttribute(this.getKeyName());
9850
10142
  }
@@ -9876,11 +10168,13 @@ var Model = class _Model {
9876
10168
  }
9877
10169
  /**
9878
10170
  * The model a route parameter value points at: where <field or route key> = value (global scopes apply, so a
9879
- * soft-deleted row isn't found). A non-numeric value for an integer primary key finds nothing without a query.
10171
+ * soft-deleted row isn't found). A non-numeric value for an integer primary key, or a value that isn't a UUID
10172
+ * (ULID) for a unique id column, finds nothing without a query.
9880
10173
  */
9881
10174
  async resolveRouteBinding(value2, field) {
9882
10175
  const column = field ?? this.getRouteKeyName();
9883
- if (column === this.getKeyName() && this.keyType === "int" && !/^-?\d+$/.test(value2)) return void 0;
10176
+ if (column === this.getKeyName() && this.getKeyType() === "int" && !/^-?\d+$/.test(value2)) return void 0;
10177
+ if (this.uniqueIds().includes(column) && !this.isValidUniqueId(value2)) return void 0;
9884
10178
  return this.resolveRouteBindingQuery(this.newQuery(), value2, column).first();
9885
10179
  }
9886
10180
  /** Override to customize the binding query (Laravel's resolveRouteBindingQuery()). */
@@ -10098,6 +10392,7 @@ var Model = class _Model {
10098
10392
  if (!this.exists && created && !this.isDirty(created)) this.setAttribute(created, time);
10099
10393
  }
10100
10394
  async performInsert(query) {
10395
+ if (this.usesUniqueIds()) this.setUniqueIds();
10101
10396
  if (await this.fireModelEvent("creating") === false) return false;
10102
10397
  if (this.usesTimestamps()) this.updateTimestamps();
10103
10398
  const attributes = this.getAttributes();
@@ -10281,6 +10576,10 @@ var Model = class _Model {
10281
10576
  if (!instance.getConnectionName()) instance.setConnection(this.getConnectionName());
10282
10577
  return instance;
10283
10578
  }
10579
+ /** Whether the model has a relation method called `name` (Laravel's isRelation()). */
10580
+ isRelation(name) {
10581
+ return !(name in _Model.prototype) && methodOf(this, name) !== void 0;
10582
+ }
10284
10583
  /** Calls the relation method `name` (post.comments()) and checks that it returns a relation. */
10285
10584
  getRelationInstance(name) {
10286
10585
  if (name in _Model.prototype) throw new RelationNotFoundError(this.constructor.name, name);
@@ -10375,9 +10674,18 @@ var Model = class _Model {
10375
10674
  this.appends = [.../* @__PURE__ */ new Set([...this.appends, ...attributes.flat()])];
10376
10675
  return this;
10377
10676
  }
10378
- /** Attributes (through accessors and casts, dates as ISO strings), appends and loaded relations, minus hidden ones. */
10677
+ /**
10678
+ * Attributes (through accessors and casts, dates as ISO strings), appends and loaded relations, minus hidden ones.
10679
+ * A model met again while its own toArray() runs (a child chaperone() pointed back at it) gives its attributes only.
10680
+ */
10379
10681
  toArray() {
10380
- return { ...this.attributesToArray(), ...this.relationsToArray() };
10682
+ if (serializing.has(this)) return this.attributesToArray();
10683
+ serializing.add(this);
10684
+ try {
10685
+ return { ...this.attributesToArray(), ...this.relationsToArray() };
10686
+ } finally {
10687
+ serializing.delete(this);
10688
+ }
10381
10689
  }
10382
10690
  toJSON() {
10383
10691
  return this.toArray();
@@ -10448,6 +10756,118 @@ var Pivot = class extends Model {
10448
10756
  incrementing = false;
10449
10757
  timestamps = false;
10450
10758
  };
10759
+ function uniqueStringIds(Base, generate, valid) {
10760
+ return class WithUniqueStringIds extends Base {
10761
+ usesUniqueIds() {
10762
+ return true;
10763
+ }
10764
+ uniqueIds() {
10765
+ return [this.getKeyName()];
10766
+ }
10767
+ newUniqueId() {
10768
+ return generate();
10769
+ }
10770
+ isValidUniqueId(value2) {
10771
+ return valid(value2);
10772
+ }
10773
+ getKeyType() {
10774
+ return this.uniqueIds().includes(this.getKeyName()) ? "string" : super.getKeyType();
10775
+ }
10776
+ getIncrementing() {
10777
+ return this.uniqueIds().includes(this.getKeyName()) ? false : super.getIncrementing();
10778
+ }
10779
+ };
10780
+ }
10781
+ function HasUuids(Base) {
10782
+ return uniqueStringIds(Base, () => Str.uuid7(), Str.isUuid);
10783
+ }
10784
+ function HasUlids(Base) {
10785
+ return uniqueStringIds(Base, () => Str.ulid().toLowerCase(), Str.isUlid);
10786
+ }
10787
+ var DatabaseNotification = class extends HasUuids(Model) {
10788
+ table = "notifications";
10789
+ keyType = "string";
10790
+ incrementing = false;
10791
+ guarded = [];
10792
+ casts = { data: "array", read_at: "datetime" };
10793
+ /** The model the notification was sent to. */
10794
+ notifiable() {
10795
+ return this.morphTo("notifiable");
10796
+ }
10797
+ /** Sets read_at to now and saves, unless it is already read. */
10798
+ async markAsRead() {
10799
+ if (this.unread()) await this.forceFill({ read_at: this.freshTimestamp() }).save();
10800
+ }
10801
+ /** Clears read_at and saves, unless it is already unread. */
10802
+ async markAsUnread() {
10803
+ if (this.read()) await this.forceFill({ read_at: null }).save();
10804
+ }
10805
+ read() {
10806
+ return this.read_at !== null && this.read_at !== void 0;
10807
+ }
10808
+ unread() {
10809
+ return !this.read();
10810
+ }
10811
+ /** `query.read()`: only read notifications. */
10812
+ static scopeRead(query) {
10813
+ return query.whereNotNull("read_at");
10814
+ }
10815
+ /** `query.unread()`: only unread notifications. */
10816
+ static scopeUnread(query) {
10817
+ return query.whereNull("read_at");
10818
+ }
10819
+ };
10820
+ var DatabaseNotificationCollection = class extends Collection {
10821
+ /** Marks every notification as read (one save each, as in Laravel). */
10822
+ async markAsRead() {
10823
+ for (const notification of this.all()) await notification.markAsRead();
10824
+ }
10825
+ /** Marks every notification as unread. */
10826
+ async markAsUnread() {
10827
+ for (const notification of this.all()) await notification.markAsUnread();
10828
+ }
10829
+ };
10830
+ var DatabaseNotificationsRelation = class extends MorphMany {
10831
+ async get() {
10832
+ return new DatabaseNotificationCollection(await this.runSelect());
10833
+ }
10834
+ async getResults() {
10835
+ const key = this.getParentKey();
10836
+ return key === null || key === void 0 ? new DatabaseNotificationCollection() : this.get();
10837
+ }
10838
+ initRelation(models, relation) {
10839
+ for (const model of models) model.setRelation(relation, new DatabaseNotificationCollection());
10840
+ return models;
10841
+ }
10842
+ match(models, results, relation) {
10843
+ const dictionary = this.buildDictionary(results);
10844
+ for (const model of models) {
10845
+ model.setRelation(relation, new DatabaseNotificationCollection(dictionary.get(String(model.getAttribute(this.localKey))) ?? []));
10846
+ }
10847
+ return models;
10848
+ }
10849
+ };
10850
+ function HasDatabaseNotifications(Base) {
10851
+ class WithDatabaseNotifications extends Base {
10852
+ notifications() {
10853
+ const related = this.newRelatedInstance(DatabaseNotification);
10854
+ return new DatabaseNotificationsRelation(
10855
+ related,
10856
+ this,
10857
+ related.qualifyColumn("notifiable_type"),
10858
+ related.qualifyColumn("notifiable_id"),
10859
+ this.getKeyName()
10860
+ ).latest();
10861
+ }
10862
+ readNotifications() {
10863
+ return this.notifications().whereNotNull("read_at");
10864
+ }
10865
+ unreadNotifications() {
10866
+ return this.notifications().whereNull("read_at");
10867
+ }
10868
+ }
10869
+ return WithDatabaseNotifications;
10870
+ }
10451
10871
  var Migration = class {
10452
10872
  /** The connection to run on (default: the default connection). */
10453
10873
  connection = void 0;
@@ -10858,7 +11278,7 @@ var TypeGenerator = class {
10858
11278
  }
10859
11279
  };
10860
11280
  var text = (value2) => typeof value2 === "string" && value2 !== "" ? value2 : void 0;
10861
- var list = (value2) => Array.isArray(value2) ? value2.map(String) : text(value2) ? [String(value2)] : [];
11281
+ var list2 = (value2) => Array.isArray(value2) ? value2.map(String) : text(value2) ? [String(value2)] : [];
10862
11282
  var integer = (value2) => {
10863
11283
  const number3 = Number(value2 ?? 0);
10864
11284
  return Number.isInteger(number3) && number3 > 0 ? number3 : 0;
@@ -10874,7 +11294,7 @@ function confirmToProceed(context, command) {
10874
11294
  return false;
10875
11295
  }
10876
11296
  function migrationPaths(context, command) {
10877
- const paths = list(command.option("path"));
11297
+ const paths = list2(command.option("path"));
10878
11298
  if (!paths.length) return [context.app.databasePath("migrations")];
10879
11299
  return paths.map((path) => isAbsolute(path) ? path : context.app.basePath(path));
10880
11300
  }
@@ -10897,6 +11317,11 @@ async function generateModelTypes(context) {
10897
11317
  await new TypeGenerator({ outputFile, importSource: context.typesImportSource }).write((await context.models()).all());
10898
11318
  return relative2(context.app.basePath(), outputFile);
10899
11319
  }
11320
+ async function writeModelTypes(context, command) {
11321
+ if (context.app.isProduction() || !existsSync4(context.app.basePath("types"))) return;
11322
+ const written = await generateModelTypes(context);
11323
+ if (written) command.line(`Model types written to ${written}.`);
11324
+ }
10900
11325
  async function seedIfRequested(context, command) {
10901
11326
  const seeder = text(command.option("seeder"));
10902
11327
  if (command.option("seed") !== true && seeder === void 0) return;
@@ -10908,10 +11333,7 @@ function registerMigrationCommands(kernel, context) {
10908
11333
  if (!confirmToProceed(context, command)) return 1;
10909
11334
  await migrator(context, command).run(migrationPaths(context, command), { step: command.option("step") === true });
10910
11335
  await seedIfRequested(context, command);
10911
- if (!context.app.isProduction() && existsSync4(context.app.basePath("types"))) {
10912
- const written = await generateModelTypes(context);
10913
- if (written) command.line(`Model types written to ${written}.`);
10914
- }
11336
+ await writeModelTypes(context, command);
10915
11337
  return 0;
10916
11338
  });
10917
11339
  define(kernel, "migrate:install {--database=}", "Create the migration repository", async (command) => {
@@ -10942,12 +11364,14 @@ function registerMigrationCommands(kernel, context) {
10942
11364
  else await instance.reset(paths);
10943
11365
  await instance.run(paths);
10944
11366
  await seedIfRequested(context, command);
11367
+ await writeModelTypes(context, command);
10945
11368
  return 0;
10946
11369
  });
10947
11370
  define(kernel, "migrate:fresh {--database=} {--force} {--path=*} {--seed} {--seeder=}", "Drop all tables and re-run all migrations", async (command) => {
10948
11371
  if (!confirmToProceed(context, command)) return 1;
10949
11372
  await migrator(context, command).fresh(migrationPaths(context, command));
10950
11373
  await seedIfRequested(context, command);
11374
+ await writeModelTypes(context, command);
10951
11375
  return 0;
10952
11376
  });
10953
11377
  define(kernel, "migrate:status {--database=} {--path=*}", "Show the status of each migration", async (command) => {
@@ -11462,9 +11886,21 @@ function currentRequest2() {
11462
11886
  return void 0;
11463
11887
  }
11464
11888
  }
11889
+ function paginationState() {
11890
+ const container = currentContainer();
11891
+ if (!container?.bound(PaginationState)) return void 0;
11892
+ try {
11893
+ return container.make(PaginationState);
11894
+ } catch (error) {
11895
+ if (error instanceof BindingResolutionError) return void 0;
11896
+ throw error;
11897
+ }
11898
+ }
11465
11899
  function usePaginationResolvers() {
11466
- AbstractPaginator.currentPathResolver = () => currentRequest2()?.url() ?? "/";
11900
+ AbstractPaginator.currentPathResolver = () => paginationState()?.currentPath() ?? currentRequest2()?.url() ?? "/";
11467
11901
  AbstractPaginator.currentPageResolver = (pageName) => {
11902
+ const state = paginationState()?.currentPage(pageName);
11903
+ if (state !== void 0) return state;
11468
11904
  const page = Number(currentRequest2()?.input(pageName));
11469
11905
  return Number.isInteger(page) && page >= 1 ? page : 1;
11470
11906
  };
@@ -11713,8 +12149,8 @@ function decrypt(payload, unserialize = true) {
11713
12149
  return inject(Encrypter).decrypt(payload, unserialize);
11714
12150
  }
11715
12151
  function previousKeys(value2) {
11716
- const list3 = Array.isArray(value2) ? value2 : typeof value2 === "string" ? value2.split(",") : [];
11717
- return list3.map((key) => String(key).trim()).filter((key) => key !== "");
12152
+ const list4 = Array.isArray(value2) ? value2 : typeof value2 === "string" ? value2.split(",") : [];
12153
+ return list4.map((key) => String(key).trim()).filter((key) => key !== "");
11718
12154
  }
11719
12155
  var EncryptionServiceProvider = class extends ServiceProvider {
11720
12156
  register() {
@@ -11846,8 +12282,8 @@ var SHOULD_QUEUE22 = /* @__PURE__ */ Symbol.for("oriel:bus.shouldQueue");
11846
12282
  var QUEUE_OPTION_KEYS = ["connection", "queue", "delay", "tries", "backoff", "timeout"];
11847
12283
  function isQueueableListener(cls, instance) {
11848
12284
  const members = instance;
11849
- if (members[SHOULD_QUEUE22] === true) return true;
11850
12285
  const statics = cls;
12286
+ if (members.shouldQueue === true || statics.shouldQueue === true || members[SHOULD_QUEUE22] === true) return true;
11851
12287
  if (QUEUE_OPTION_KEYS.some((key) => members[key] !== void 0 || statics[key] !== void 0)) return true;
11852
12288
  return typeof members.uniqueId === "function" || typeof members.retryUntil === "function" || typeof statics.uniqueId === "function" || typeof statics.retryUntil === "function";
11853
12289
  }
@@ -11892,9 +12328,9 @@ var Dispatcher2 = class {
11892
12328
  throw new InvalidListenerError(label);
11893
12329
  }
11894
12330
  for (const event2 of Array.isArray(events) ? events : [events]) {
11895
- const list3 = this.listeners.get(event2) ?? [];
11896
- list3.push(listener);
11897
- this.listeners.set(event2, list3);
12331
+ const list4 = this.listeners.get(event2) ?? [];
12332
+ list4.push(listener);
12333
+ this.listeners.set(event2, list4);
11898
12334
  }
11899
12335
  }
11900
12336
  hasListeners(event2) {
@@ -11904,10 +12340,14 @@ var Dispatcher2 = class {
11904
12340
  forget(event2) {
11905
12341
  this.listeners.delete(event2);
11906
12342
  }
12343
+ /** Laravel's getRawListeners(): each event's listeners as registered (closures, classes, [class, method]). A copy. */
12344
+ getRawListeners() {
12345
+ return new Map([...this.listeners].map(([event2, list4]) => [event2, [...list4]]));
12346
+ }
11907
12347
  /** Introspection for the event:list command. Not part of the EventDispatcher contract. */
11908
12348
  registeredEvents() {
11909
12349
  const rows = [];
11910
- for (const [key, list3] of this.listeners) rows.push({ event: nameOf(key), listeners: list3.length });
12350
+ for (const [key, list4] of this.listeners) rows.push({ event: nameOf(key), listeners: list4.length });
11911
12351
  return rows.sort((a, b) => a.event.localeCompare(b.event));
11912
12352
  }
11913
12353
  subscribe(subscriber) {
@@ -11951,8 +12391,8 @@ var Dispatcher2 = class {
11951
12391
  const exact = [...this.listeners.get(key) ?? []];
11952
12392
  const wildcardLists = typeof key === "string" ? this.wildcardPatternsFor(key).map((pattern) => [...this.listeners.get(pattern) ?? []]) : [];
11953
12393
  for (const listener of exact) yield { listener, wildcard: false };
11954
- for (const list3 of wildcardLists) {
11955
- for (const listener of list3) yield { listener, wildcard: true };
12394
+ for (const list4 of wildcardLists) {
12395
+ for (const listener of list4) yield { listener, wildcard: true };
11956
12396
  }
11957
12397
  }
11958
12398
  wildcardPatternsFor(event2) {
@@ -12039,6 +12479,7 @@ import { dirname as dirname4, extname as extname2, resolve as resolve2 } from "p
12039
12479
  import { Readable } from "stream";
12040
12480
  import { pipeline } from "stream/promises";
12041
12481
  import { resolve, sep as sep3 } from "path";
12482
+ import { posix } from "path";
12042
12483
  import {
12043
12484
  CopyObjectCommand,
12044
12485
  DeleteObjectCommand,
@@ -12109,24 +12550,54 @@ var MIME_TYPES = {
12109
12550
  zip: "application/zip",
12110
12551
  gz: "application/gzip",
12111
12552
  tar: "application/x-tar",
12553
+ "7z": "application/x-7z-compressed",
12554
+ rar: "application/vnd.rar",
12555
+ epub: "application/epub+zip",
12556
+ rtf: "application/rtf",
12557
+ doc: "application/msword",
12558
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
12559
+ xls: "application/vnd.ms-excel",
12560
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
12561
+ ppt: "application/vnd.ms-powerpoint",
12562
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
12563
+ odt: "application/vnd.oasis.opendocument.text",
12564
+ ods: "application/vnd.oasis.opendocument.spreadsheet",
12565
+ odp: "application/vnd.oasis.opendocument.presentation",
12112
12566
  png: "image/png",
12113
12567
  jpg: "image/jpeg",
12114
12568
  jpeg: "image/jpeg",
12115
12569
  gif: "image/gif",
12116
12570
  webp: "image/webp",
12571
+ avif: "image/avif",
12117
12572
  svg: "image/svg+xml",
12118
12573
  ico: "image/x-icon",
12119
12574
  bmp: "image/bmp",
12575
+ tif: "image/tiff",
12576
+ tiff: "image/tiff",
12577
+ heic: "image/heic",
12578
+ heif: "image/heif",
12120
12579
  mp3: "audio/mpeg",
12580
+ m4a: "audio/mp4",
12121
12581
  wav: "audio/wav",
12582
+ ogg: "audio/ogg",
12583
+ oga: "audio/ogg",
12584
+ opus: "audio/ogg",
12585
+ flac: "audio/flac",
12586
+ aac: "audio/aac",
12122
12587
  mp4: "video/mp4",
12588
+ mov: "video/quicktime",
12123
12589
  webm: "video/webm",
12590
+ ogv: "video/ogg",
12591
+ avi: "video/x-msvideo",
12124
12592
  woff: "font/woff",
12125
12593
  woff2: "font/woff2",
12126
- ttf: "font/ttf"
12594
+ ttf: "font/ttf",
12595
+ otf: "font/otf",
12596
+ wasm: "application/wasm"
12127
12597
  };
12128
12598
  function mimeFromExtension(extension) {
12129
- return MIME_TYPES[extension.replace(/^\./, "").toLowerCase()];
12599
+ const key = extension.replace(/^\./, "").toLowerCase();
12600
+ return Object.hasOwn(MIME_TYPES, key) ? MIME_TYPES[key] : void 0;
12130
12601
  }
12131
12602
  function resolveDiskPath(root, path) {
12132
12603
  const resolvedRoot = resolve(root);
@@ -12206,7 +12677,7 @@ var LocalFilesystem = class {
12206
12677
  async putFileAs(directory, file, name, options) {
12207
12678
  const path = joinDiskPath(directory, name);
12208
12679
  const bytes = new Uint8Array(await file.arrayBuffer());
12209
- await this.put(path, bytes, { mimeType: file.mimeType, ...options });
12680
+ await this.put(path, bytes, options);
12210
12681
  return path;
12211
12682
  }
12212
12683
  async delete(paths) {
@@ -12371,27 +12842,37 @@ var S3Filesystem = class {
12371
12842
  const response2 = await this.getObject(path);
12372
12843
  return response2.Body.transformToWebStream();
12373
12844
  }
12845
+ /** Sends the type of the path when the options name none (Laravel's S3 adapter detects one too). */
12374
12846
  async put(path, contents, options) {
12375
12847
  const body = contents instanceof ReadableStream ? await collectStream(contents) : contents;
12376
- await this.client.send(
12377
- new PutObjectCommand({
12378
- Bucket: this.config.bucket,
12379
- Key: path,
12380
- Body: body,
12381
- ContentType: options?.mimeType,
12382
- ACL: options?.visibility === "public" ? "public-read" : void 0
12383
- })
12384
- );
12848
+ await this.putObject(path, body, options?.mimeType ?? mimeFromExtension(posix.extname(path)), options);
12385
12849
  return true;
12386
12850
  }
12387
12851
  async putFile(directory, file, options) {
12388
12852
  return this.putFileAs(directory, file, uniqueFileName(file.extension()), options);
12389
12853
  }
12854
+ /**
12855
+ * Sends the upload's own type: its extension(), which follows what a request upload's bytes show (the client's claim
12856
+ * counts only where extension() keeps it). Never the MIME type the client sent as such, nor the name it is stored
12857
+ * under (an app may name it after the client's file name): S3, or a CDN in front of it, serves the object with this
12858
+ * type. An upload of no known type is sent with none (served as binary).
12859
+ */
12390
12860
  async putFileAs(directory, file, name, options) {
12391
12861
  const path = joinDiskPath(directory, name);
12392
- await this.put(path, new Uint8Array(await file.arrayBuffer()), { mimeType: file.mimeType, ...options });
12862
+ await this.putObject(path, new Uint8Array(await file.arrayBuffer()), options?.mimeType ?? mimeFromExtension(file.extension()), options);
12393
12863
  return path;
12394
12864
  }
12865
+ async putObject(path, body, contentType, options) {
12866
+ await this.client.send(
12867
+ new PutObjectCommand({
12868
+ Bucket: this.config.bucket,
12869
+ Key: path,
12870
+ Body: body,
12871
+ ContentType: contentType,
12872
+ ACL: options?.visibility === "public" ? "public-read" : void 0
12873
+ })
12874
+ );
12875
+ }
12395
12876
  async delete(paths) {
12396
12877
  for (const path of Array.isArray(paths) ? paths : [paths]) {
12397
12878
  await this.client.send(new DeleteObjectCommand({ Bucket: this.config.bucket, Key: path }));
@@ -13814,8 +14295,8 @@ var InertiaMiddleware = class {
13814
14295
  bag,
13815
14296
  Object.fromEntries(
13816
14297
  Object.entries(fields ?? {}).map(([field, messages]) => {
13817
- const list3 = Array.isArray(messages) ? messages.map(String) : [String(messages)];
13818
- return [field, this.withAllErrors ? list3 : list3[0] ?? ""];
14298
+ const list4 = Array.isArray(messages) ? messages.map(String) : [String(messages)];
14299
+ return [field, this.withAllErrors ? list4 : list4[0] ?? ""];
13819
14300
  })
13820
14301
  )
13821
14302
  ])
@@ -14422,6 +14903,42 @@ var DailyRotatingStream = class extends Writable {
14422
14903
  stream.end();
14423
14904
  }
14424
14905
  };
14906
+ function destinationCloser(destination) {
14907
+ const opened = new Promise((resolve3) => {
14908
+ const onReady = () => {
14909
+ destination.off("error", onError);
14910
+ resolve3(true);
14911
+ };
14912
+ const onError = () => {
14913
+ destination.off("ready", onReady);
14914
+ destination.flushSync = () => {
14915
+ };
14916
+ resolve3(false);
14917
+ };
14918
+ destination.once("ready", onReady);
14919
+ destination.once("error", onError);
14920
+ });
14921
+ let closing;
14922
+ const close = async () => {
14923
+ if (!await opened) return;
14924
+ await new Promise((resolve3) => {
14925
+ destination.once("close", () => resolve3());
14926
+ destination.once("error", () => resolve3());
14927
+ destination.end();
14928
+ });
14929
+ };
14930
+ return () => closing ??= close();
14931
+ }
14932
+ function writableCloser(stream) {
14933
+ let closing;
14934
+ const close = () => new Promise((resolve3) => {
14935
+ if (stream.writableFinished || stream.destroyed) return resolve3();
14936
+ stream.once("finish", () => resolve3());
14937
+ stream.once("error", () => resolve3());
14938
+ stream.end();
14939
+ });
14940
+ return () => closing ??= close();
14941
+ }
14425
14942
  var PINO_METHOD = {
14426
14943
  emergency: "fatal",
14427
14944
  alert: "fatal",
@@ -14433,12 +14950,25 @@ var PINO_METHOD = {
14433
14950
  debug: "debug"
14434
14951
  };
14435
14952
  var PinoLogger = class extends BaseLogger {
14436
- constructor(pinoInstance, minimumLevel = "debug") {
14953
+ constructor(pinoInstance, minimumLevel = "debug", closeDestination = async () => {
14954
+ }) {
14437
14955
  super(minimumLevel);
14438
14956
  this.pinoInstance = pinoInstance;
14957
+ this.closeDestination = closeDestination;
14439
14958
  }
14440
14959
  pinoInstance;
14960
+ closeDestination;
14961
+ flushing;
14962
+ /**
14963
+ * Writes out every entry still buffered and closes the destination. pino writes asynchronously, so a process
14964
+ * awaits this before it exits (runConsoleAndExit does). The channel takes no entries afterwards: they're dropped.
14965
+ * Not in Laravel, whose handlers write synchronously.
14966
+ */
14967
+ flush() {
14968
+ return this.flushing ??= this.closeDestination();
14969
+ }
14441
14970
  write(level2, message, context) {
14971
+ if (this.flushing) return;
14442
14972
  this.pinoInstance[PINO_METHOD[level2]]({ ...context, rfc5424Level: level2 }, message);
14443
14973
  }
14444
14974
  };
@@ -14447,19 +14977,19 @@ function createSingleChannel(path, minimumLevel = "debug") {
14447
14977
  assertLogLevel(minimumLevel);
14448
14978
  const destination = pino2.destination({ dest: path, mkdir: true, sync: false });
14449
14979
  destination.on("error", (error) => reportLogWriteFailure(path, error));
14450
- return new PinoLogger(pino2(PINO_OPTIONS, destination), minimumLevel);
14980
+ return new PinoLogger(pino2(PINO_OPTIONS, destination), minimumLevel, destinationCloser(destination));
14451
14981
  }
14452
14982
  function createDailyChannel(path, days = 14, minimumLevel = "debug", now5) {
14453
14983
  assertLogLevel(minimumLevel);
14454
14984
  const stream = new DailyRotatingStream(path, days, now5);
14455
- return new PinoLogger(pino2(PINO_OPTIONS, stream), minimumLevel);
14985
+ return new PinoLogger(pino2(PINO_OPTIONS, stream), minimumLevel, writableCloser(stream));
14456
14986
  }
14457
14987
  function createStderrChannel(minimumLevel = "debug") {
14458
14988
  assertLogLevel(minimumLevel);
14459
14989
  const destination = pino2.destination({ dest: 2, sync: false });
14460
14990
  destination.on("error", () => {
14461
14991
  });
14462
- return new PinoLogger(pino2(PINO_OPTIONS, destination), minimumLevel);
14992
+ return new PinoLogger(pino2(PINO_OPTIONS, destination), minimumLevel, destinationCloser(destination));
14463
14993
  }
14464
14994
  var Log = facade(LogManager);
14465
14995
  function logger(message, context) {
@@ -14509,6 +15039,7 @@ var StackLogger = class {
14509
15039
  return this;
14510
15040
  }
14511
15041
  };
15042
+ var isFlushable = (channel) => typeof channel.flush === "function";
14512
15043
  var level = (config2) => config2.level ?? "debug";
14513
15044
  var LogManager2 = class {
14514
15045
  constructor(container) {
@@ -14550,6 +15081,16 @@ var LogManager2 = class {
14550
15081
  stack(channels) {
14551
15082
  return new StackLogger(channels.map((name) => this.channel(name)));
14552
15083
  }
15084
+ /**
15085
+ * Writes out and closes every channel resolved so far, then forgets them, so the next entry opens its file again.
15086
+ * pino writes asynchronously, so a console process awaits this before it exits (runConsoleAndExit does). Not in
15087
+ * Laravel, whose handlers write synchronously.
15088
+ */
15089
+ async flush() {
15090
+ const channels = [...this.channels.values()];
15091
+ this.channels.clear();
15092
+ await Promise.all(channels.filter(isFlushable).map((channel) => channel.flush()));
15093
+ }
14553
15094
  log(logLevel, message, context) {
14554
15095
  this.channel().log(logLevel, message, context);
14555
15096
  }
@@ -14667,8 +15208,8 @@ function normalizeAddress(recipient) {
14667
15208
  }
14668
15209
  function normalizeAddresses(recipients) {
14669
15210
  if (recipients === void 0) return [];
14670
- const list3 = Array.isArray(recipients) ? recipients : [recipients];
14671
- return list3.map(normalizeAddress);
15211
+ const list4 = Array.isArray(recipients) ? recipients : [recipients];
15212
+ return list4.map(normalizeAddress);
14672
15213
  }
14673
15214
  function formatAddress(address) {
14674
15215
  return address.name ? `${address.name} <${address.address}>` : address.address;
@@ -14837,7 +15378,7 @@ var Mailable = class {
14837
15378
  var SHOULD_QUEUE4 = /* @__PURE__ */ Symbol.for("oriel:bus.shouldQueue");
14838
15379
  function isQueueableMailable(mailable) {
14839
15380
  const record = mailable;
14840
- if (record[SHOULD_QUEUE4] === true) return true;
15381
+ if (record.shouldQueue === true || record[SHOULD_QUEUE4] === true) return true;
14841
15382
  return record.connection !== void 0 || record.queue !== void 0 || record.delay !== void 0 || record.tries !== void 0 || record.backoff !== void 0 || record.timeout !== void 0 || typeof record.uniqueId === "function" || typeof record.retryUntil === "function";
14842
15383
  }
14843
15384
  var MailerImpl = class {
@@ -14926,6 +15467,9 @@ var PendingMail = class {
14926
15467
  async send(mailable) {
14927
15468
  return this.mailer.send(mailable, this.overrides());
14928
15469
  }
15470
+ async sendNow(mailable) {
15471
+ return this.mailer.sendNow(mailable, this.overrides());
15472
+ }
14929
15473
  async queue(mailable) {
14930
15474
  await this.mailer.queue(mailable, this.overrides());
14931
15475
  }
@@ -15049,9 +15593,9 @@ var MailManager2 = class {
15049
15593
  async queue(mailable) {
15050
15594
  await this.mailer().queue(mailable);
15051
15595
  }
15052
- /** QueuedMailSender: always delivers immediately (see QueuedMailSender.ts for why this must bypass send()). */
15053
- async sendNow(mailerName, mailable, overrides) {
15054
- return this.mailer(mailerName).sendNow(mailable, overrides);
15596
+ /** Sends through the default mailer at once, even when the mailable is queueable (Laravel's Mail::sendNow()). */
15597
+ async sendNow(mailable) {
15598
+ return this.mailer().sendNow(mailable);
15055
15599
  }
15056
15600
  extend(transport, factory) {
15057
15601
  this.drivers.set(transport, factory);
@@ -15074,7 +15618,10 @@ var MailServiceProvider = class extends ServiceProvider {
15074
15618
  "mail"
15075
15619
  );
15076
15620
  this.app.singleton(MailManager, (container) => new MailManager2(container));
15077
- this.app.singleton(QueuedMailSender, (container) => container.make(MailManager));
15621
+ this.app.singleton(QueuedMailSender, (container) => {
15622
+ const manager2 = container.make(MailManager);
15623
+ return { sendNow: (mailerName, mailable, overrides) => manager2.mailer(mailerName).sendNow(mailable, overrides) };
15624
+ });
15078
15625
  this.app.make(ClassRegistry).register(SendQueuedMailable, "Mail.SendQueuedMailable");
15079
15626
  }
15080
15627
  };
@@ -15281,51 +15828,14 @@ var MailNotificationChannel = class {
15281
15828
  const mailable = isMailable(result) ? result : mailableFromMessage(toMailMessage(result, notification.constructor.name), to);
15282
15829
  const container = currentContainer();
15283
15830
  if (!container?.bound(MailManager)) throw new MailUnavailableError();
15284
- await container.make(MailManager).send(mailable);
15831
+ await container.make(MailManager).sendNow(mailable);
15285
15832
  }
15286
15833
  };
15287
15834
  var NOTIFICATIONS_TABLE = "notifications";
15288
- var ZONELESS_TIMESTAMP = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/;
15289
- function parseStoredDate2(value2) {
15290
- if (value2 instanceof Date) return value2;
15291
- if (typeof value2 === "number") return new Date(value2);
15292
- const text3 = String(value2);
15293
- return ZONELESS_TIMESTAMP.test(text3) ? /* @__PURE__ */ new Date(`${text3.replace(" ", "T")}Z`) : new Date(text3);
15294
- }
15295
- function wrapRow(row, db) {
15296
- const record = {
15297
- id: String(row.id),
15298
- type: String(row.type),
15299
- data: JSON.parse(String(row.data)),
15300
- createdAt: parseStoredDate2(row.created_at),
15301
- updatedAt: parseStoredDate2(row.updated_at),
15302
- readAt: row.read_at ? parseStoredDate2(row.read_at) : void 0,
15303
- async markAsRead() {
15304
- if (record.readAt) return;
15305
- const now5 = /* @__PURE__ */ new Date();
15306
- await db.table(NOTIFICATIONS_TABLE).where("id", record.id).update({ read_at: now5, updated_at: now5 });
15307
- record.readAt = now5;
15308
- },
15309
- async markAsUnread() {
15310
- if (!record.readAt) return;
15311
- await db.table(NOTIFICATIONS_TABLE).where("id", record.id).update({ read_at: null, updated_at: /* @__PURE__ */ new Date() });
15312
- record.readAt = void 0;
15313
- }
15314
- };
15315
- return record;
15316
- }
15317
- function requireDatabase() {
15835
+ function requireDatabase(connection) {
15318
15836
  const container = currentContainer();
15319
15837
  if (!container?.bound(DatabaseManager)) throw new DatabaseUnavailableError();
15320
- return container.make(DatabaseManager).connection();
15321
- }
15322
- async function fetchNotifications(notifiableType, notifiableId, filter = "all") {
15323
- const db = requireDatabase();
15324
- let query = db.table(NOTIFICATIONS_TABLE).where("notifiable_type", notifiableType).where("notifiable_id", notifiableId).orderBy("created_at", "desc");
15325
- if (filter === "unread") query = query.whereNull("read_at");
15326
- if (filter === "read") query = query.whereNotNull("read_at");
15327
- const rows = await query.get();
15328
- return rows.all().map((row) => wrapRow(row, db));
15838
+ return container.make(DatabaseManager).connection(connection);
15329
15839
  }
15330
15840
  var DatabaseNotificationChannel = class {
15331
15841
  async send(notifiable, notification) {
@@ -15335,12 +15845,12 @@ var DatabaseNotificationChannel = class {
15335
15845
  const data = notification.toDatabase ? notification.toDatabase(notifiable) : notification.toArray(notifiable);
15336
15846
  const model = notifiable;
15337
15847
  if (typeof model.getKey !== "function") throw new NotDatabaseNotifiableError(notifiable.constructor.name);
15338
- const db = requireDatabase();
15848
+ const db = requireDatabase(typeof model.getConnectionName === "function" ? model.getConnectionName() : void 0);
15339
15849
  const now5 = /* @__PURE__ */ new Date();
15340
15850
  await db.table(NOTIFICATIONS_TABLE).insert({
15341
15851
  id: notification.id ?? randomUUID4(),
15342
15852
  type: notification.constructor.name,
15343
- notifiable_type: notifiable.constructor.name,
15853
+ notifiable_type: typeof model.getMorphClass === "function" ? model.getMorphClass() : notifiable.constructor.name,
15344
15854
  notifiable_id: model.getKey(),
15345
15855
  data: JSON.stringify(data),
15346
15856
  read_at: null,
@@ -15374,13 +15884,8 @@ function resolveDispatcher() {
15374
15884
  if (!container?.bound(NotificationDispatcher)) throw new NotificationDispatcherUnavailableError();
15375
15885
  return container.make(NotificationDispatcher);
15376
15886
  }
15377
- function requireKey(instance) {
15378
- const model = instance;
15379
- if (typeof model.getKey !== "function") throw new NotDatabaseNotifiableError(instance.constructor.name);
15380
- return model.getKey();
15381
- }
15382
- function Notifiable(Base) {
15383
- return class NotifiableMixin extends Base {
15887
+ function RoutesNotifications(Base) {
15888
+ return class RoutesNotificationsMixin extends Base {
15384
15889
  routeNotificationFor(channel, notification) {
15385
15890
  const custom = this[`routeNotificationFor${Str.studly(channel)}`];
15386
15891
  if (typeof custom === "function") return custom.call(this, notification);
@@ -15397,15 +15902,6 @@ function Notifiable(Base) {
15397
15902
  async notifyNow(notification, channels) {
15398
15903
  await resolveDispatcher().sendNow(this, notification, channels);
15399
15904
  }
15400
- notifications() {
15401
- return fetchNotifications(this.constructor.name, requireKey(this));
15402
- }
15403
- unreadNotifications() {
15404
- return fetchNotifications(this.constructor.name, requireKey(this), "unread");
15405
- }
15406
- readNotifications() {
15407
- return fetchNotifications(this.constructor.name, requireKey(this), "read");
15408
- }
15409
15905
  };
15410
15906
  }
15411
15907
  var AnonymousNotifiable = class {
@@ -15475,7 +15971,7 @@ var SendQueuedNotifications = class {
15475
15971
  SendQueuedNotifications.prototype[SHOULD_QUEUE5] = true;
15476
15972
  function isQueueableNotification(notification) {
15477
15973
  const record = notification;
15478
- if (record[SHOULD_QUEUE5] === true) return true;
15974
+ if (record.shouldQueue === true || record[SHOULD_QUEUE5] === true) return true;
15479
15975
  return record.connection !== void 0 || record.queue !== void 0 || record.delay !== void 0 || record.tries !== void 0 || record.backoff !== void 0 || record.timeout !== void 0 || typeof record.uniqueId === "function" || typeof record.retryUntil === "function";
15480
15976
  }
15481
15977
  function forNotifiable(notification) {
@@ -15513,12 +16009,12 @@ var Dispatcher3 = class {
15513
16009
  return channel;
15514
16010
  }
15515
16011
  async send(notifiables, notification) {
15516
- const list3 = toNotifiableList(notifiables);
15517
- if (list3.length === 0) return;
16012
+ const list4 = toNotifiableList(notifiables);
16013
+ if (list4.length === 0) return;
15518
16014
  if (isQueueableNotification(notification)) {
15519
16015
  if (!this.container.bound(BusDispatcher)) throw new NotificationQueueUnavailableError();
15520
16016
  const bus = this.container.make(BusDispatcher);
15521
- for (const notifiable of list3) {
16017
+ for (const notifiable of list4) {
15522
16018
  const own = forNotifiable(notification);
15523
16019
  for (const channel of notification.via(notifiable)) {
15524
16020
  await bus.dispatch(new SendQueuedNotifications([notifiable], own, [channel]));
@@ -15526,11 +16022,11 @@ var Dispatcher3 = class {
15526
16022
  }
15527
16023
  return;
15528
16024
  }
15529
- await this.sendNow(list3, notification);
16025
+ await this.sendNow(list4, notification);
15530
16026
  }
15531
16027
  async sendNow(notifiables, notification, channels) {
15532
- const list3 = toNotifiableList(notifiables);
15533
- for (const notifiable of list3) {
16028
+ const list4 = toNotifiableList(notifiables);
16029
+ for (const notifiable of list4) {
15534
16030
  const own = forNotifiable(notification);
15535
16031
  const names = channels ?? notification.via(notifiable);
15536
16032
  for (const name of names) {
@@ -15687,6 +16183,8 @@ import { randomUUID as randomUUID24 } from "crypto";
15687
16183
  import { Redis as Redis2 } from "ioredis";
15688
16184
  import { randomUUID as randomUUID32 } from "crypto";
15689
16185
  import { setTimeout as sleep4 } from "timers/promises";
16186
+ import { spawn as spawnProcess } from "child_process";
16187
+ import { setTimeout as sleep22 } from "timers/promises";
15690
16188
  import { existsSync as existsSync8, readdirSync as readdirSync6, statSync as statSync3 } from "fs";
15691
16189
  import { join as join12 } from "path";
15692
16190
  import { pathToFileURL as pathToFileURL6 } from "url";
@@ -15775,6 +16273,40 @@ function isModel(value2) {
15775
16273
  const candidate = value2;
15776
16274
  return typeof candidate.getKey === "function" && typeof candidate.getKeyName === "function" && typeof candidate.getTable === "function" && typeof candidate.exists === "boolean";
15777
16275
  }
16276
+ var isModelList = (value2) => typeof value2 === "object" && value2 !== null && !isModel(value2) && typeof value2[Symbol.iterator] === "function";
16277
+ function queueableRelations(model, visiting = /* @__PURE__ */ new Set()) {
16278
+ if (visiting.has(model)) return [];
16279
+ visiting.add(model);
16280
+ try {
16281
+ const relations = [];
16282
+ for (const [name, value2] of Object.entries(model.getRelations?.() ?? {})) {
16283
+ if (typeof model[name] !== "function") continue;
16284
+ relations.push(name);
16285
+ for (const nested of nestedRelations(value2, visiting)) relations.push(`${name}.${nested}`);
16286
+ }
16287
+ return [...new Set(relations)];
16288
+ } finally {
16289
+ visiting.delete(model);
16290
+ }
16291
+ }
16292
+ function nestedRelations(value2, visiting) {
16293
+ if (isModel(value2)) return queueableRelations(value2, visiting);
16294
+ if (!isModelList(value2)) return [];
16295
+ const members = [...value2].filter(isModel).map((member) => queueableRelations(member, visiting));
16296
+ if (members.length === 0) return [];
16297
+ return members.reduce((shared2, next) => shared2.filter((path) => next.includes(path)));
16298
+ }
16299
+ async function findForRestoration(model, key, connection) {
16300
+ if (connection !== void 0) {
16301
+ const instance = new model();
16302
+ if (typeof instance.setConnection === "function" && typeof instance.newQueryWithoutScopes === "function") {
16303
+ instance.setConnection(connection);
16304
+ return instance.newQueryWithoutScopes().find(key);
16305
+ }
16306
+ }
16307
+ const query = typeof model.withoutGlobalScopes === "function" ? model.withoutGlobalScopes() : model;
16308
+ return query.find(key);
16309
+ }
15778
16310
  function classNameOf(registry2, cls) {
15779
16311
  const existing = registry2.nameOf(cls);
15780
16312
  if (existing !== void 0) return existing;
@@ -15844,7 +16376,12 @@ function serializeValue(value2, registry2, path = "data", seen = /* @__PURE__ */
15844
16376
  if (!object.exists) {
15845
16377
  throw new JobSerializationError(`Cannot serialize [${path}]: the [${name2}] model hasn't been saved, so it has no key to store.`);
15846
16378
  }
15847
- return { [TAG]: "model", class: name2, key: serializeValue(object.getKey(), registry2, `${path}.key`, seen) ?? null };
16379
+ const encoded = { [TAG]: "model", class: name2, key: serializeValue(object.getKey(), registry2, `${path}.key`, seen) ?? null };
16380
+ const connection = object.getConnectionName?.();
16381
+ if (connection !== void 0) encoded.connection = connection;
16382
+ const relations = queueableRelations(object);
16383
+ if (relations.length > 0) encoded.relations = relations;
16384
+ return encoded;
15848
16385
  }
15849
16386
  if (isPlainObject2(object)) {
15850
16387
  const fields = serializeFields(object, registry2, path, seen);
@@ -15900,9 +16437,10 @@ async function restoreValue(value2, registry2) {
15900
16437
  const name = value2.class;
15901
16438
  const model = resolveClass(registry2, name);
15902
16439
  const key = await restoreValue(value2.key, registry2);
15903
- const query = typeof model.withoutGlobalScopes === "function" ? model.withoutGlobalScopes() : model;
15904
- const found = await query.find(key);
16440
+ const found = await findForRestoration(model, key, typeof value2.connection === "string" ? value2.connection : void 0);
15905
16441
  if (found === void 0 || found === null) throw new MissingModelError(name, [key]);
16442
+ const relations = Array.isArray(value2.relations) ? value2.relations.map(String) : [];
16443
+ if (relations.length > 0) await (found.loadMissing ?? found.load)?.call(found, ...relations);
15906
16444
  return found;
15907
16445
  }
15908
16446
  case "instance": {
@@ -17019,7 +17557,7 @@ var DatabaseBatchRepository = class {
17019
17557
  var defaultWorkerOptions = () => ({
17020
17558
  name: "default",
17021
17559
  backoff: 0,
17022
- memory: 128,
17560
+ memory: 512,
17023
17561
  timeout: 60,
17024
17562
  sleep: 3,
17025
17563
  maxTries: 1,
@@ -17277,6 +17815,142 @@ var Worker = class _Worker {
17277
17815
  }
17278
17816
  }
17279
17817
  };
17818
+ var defaultListenerOptions = () => ({
17819
+ name: "default",
17820
+ backoff: 0,
17821
+ memory: 512,
17822
+ timeout: 60,
17823
+ sleep: 3,
17824
+ maxTries: 1,
17825
+ rest: 0,
17826
+ stopWhenEmpty: false
17827
+ });
17828
+ function lineSplitter(emit) {
17829
+ let rest = "";
17830
+ return {
17831
+ push(chunk) {
17832
+ const lines = (rest + chunk).split("\n");
17833
+ rest = lines.pop() ?? "";
17834
+ for (const line of lines) emit(line.endsWith("\r") ? line.slice(0, -1) : line);
17835
+ },
17836
+ end() {
17837
+ if (rest !== "") emit(rest);
17838
+ rest = "";
17839
+ }
17840
+ };
17841
+ }
17842
+ function currentRuntime() {
17843
+ const script = process.argv[1];
17844
+ if (script === void 0) throw new QueueError("queue:listen can't tell which script started this process, so it can't start workers.");
17845
+ return { command: process.execPath, args: [...process.execArgv, script] };
17846
+ }
17847
+ var Listener = class _Listener {
17848
+ constructor(manager2, config2) {
17849
+ this.manager = manager2;
17850
+ this.config = config2;
17851
+ }
17852
+ manager;
17853
+ config;
17854
+ static EXIT_SUCCESS = 0;
17855
+ shouldQuit = false;
17856
+ child;
17857
+ resting;
17858
+ /** Runs until stopped, the queue is empty (with stopWhenEmpty) or the memory limit is reached. Returns the exit code. */
17859
+ async listen(connection, queue, options) {
17860
+ const stopListening = this.listenForSignals();
17861
+ try {
17862
+ const command = this.makeCommand(connection, queue, options);
17863
+ while (!this.shouldQuit) {
17864
+ if (options.stopWhenEmpty && await this.isEmpty(connection, queue)) break;
17865
+ if (this.shouldQuit) break;
17866
+ await this.runProcess(command, options.timeout);
17867
+ if (this.shouldQuit || this.memoryExceeded(options.memory)) break;
17868
+ if (options.rest > 0) await this.rest(options.rest);
17869
+ }
17870
+ return _Listener.EXIT_SUCCESS;
17871
+ } finally {
17872
+ stopListening();
17873
+ }
17874
+ }
17875
+ /** Laravel's createCommand(): the runtime, the CLI script and `queue:work <connection> --once …`, plus `--timeout`. */
17876
+ makeCommand(connection, queue, options) {
17877
+ const { command, args } = this.config.runtime ?? currentRuntime();
17878
+ const backoff = Array.isArray(options.backoff) ? options.backoff.join(",") : String(options.backoff);
17879
+ return [
17880
+ command,
17881
+ ...args,
17882
+ "queue:work",
17883
+ connection,
17884
+ "--once",
17885
+ `--name=${options.name}`,
17886
+ `--queue=${queue}`,
17887
+ `--backoff=${backoff}`,
17888
+ `--memory=${options.memory}`,
17889
+ `--sleep=${options.sleep}`,
17890
+ `--tries=${options.maxTries}`,
17891
+ `--timeout=${options.timeout}`
17892
+ ];
17893
+ }
17894
+ /** Stops after the running process, which gets the same signal (SIGTERM/SIGINT/SIGQUIT call this). */
17895
+ stop(signal = "SIGTERM") {
17896
+ this.shouldQuit = true;
17897
+ this.child?.kill(signal);
17898
+ this.resting?.abort();
17899
+ }
17900
+ memoryExceeded(limitMb) {
17901
+ return limitMb > 0 && process.memoryUsage().heapUsed / 1024 / 1024 >= limitMb;
17902
+ }
17903
+ listenForSignals() {
17904
+ const signals = ["SIGTERM", "SIGINT", "SIGQUIT"];
17905
+ const handlers = signals.map((signal) => [signal, () => this.stop(signal)]);
17906
+ for (const [signal, handler3] of handlers) process.on(signal, handler3);
17907
+ return () => {
17908
+ for (const [signal, handler3] of handlers) process.off(signal, handler3);
17909
+ };
17910
+ }
17911
+ async isEmpty(connection, queue) {
17912
+ const queues = this.manager.connection(connection);
17913
+ for (const name of queue.split(",")) if (await queues.size(name.trim()) > 0) return false;
17914
+ return true;
17915
+ }
17916
+ runProcess([command, ...args], timeout) {
17917
+ const output = this.config.output ?? (() => {
17918
+ });
17919
+ return new Promise((resolve3, reject) => {
17920
+ const child = (this.config.spawn ?? spawnProcess)(command, args, { cwd: this.config.commandPath, env: process.env, stdio: ["ignore", "pipe", "pipe"] });
17921
+ this.child = child;
17922
+ const stdout = lineSplitter((line) => output("out", line));
17923
+ const stderr = lineSplitter((line) => output("err", line));
17924
+ child.stdout?.setEncoding("utf8").on("data", (chunk) => stdout.push(chunk));
17925
+ child.stderr?.setEncoding("utf8").on("data", (chunk) => stderr.push(chunk));
17926
+ const timer = timeout > 0 ? setTimeout(() => {
17927
+ output("err", `The process "${[command, ...args].join(" ")}" exceeded the timeout of ${timeout} seconds.`);
17928
+ child.kill("SIGKILL");
17929
+ }, timeout * 1e3) : void 0;
17930
+ let settled = false;
17931
+ const settle = () => {
17932
+ if (settled) return false;
17933
+ settled = true;
17934
+ clearTimeout(timer);
17935
+ this.child = void 0;
17936
+ stdout.end();
17937
+ stderr.end();
17938
+ return true;
17939
+ };
17940
+ child.once("error", (error) => settle() && reject(error));
17941
+ child.once("close", () => settle() && resolve3());
17942
+ });
17943
+ }
17944
+ async rest(seconds) {
17945
+ this.resting = new AbortController();
17946
+ try {
17947
+ await sleep22(seconds * 1e3, void 0, { signal: this.resting.signal });
17948
+ } catch {
17949
+ } finally {
17950
+ this.resting = void 0;
17951
+ }
17952
+ }
17953
+ };
17280
17954
  var EXTENSIONS2 = [".ts", ".js", ".mts", ".mjs"];
17281
17955
  function files2(directory) {
17282
17956
  const found = [];
@@ -17305,7 +17979,7 @@ function discoverJobs(directory, registry2) {
17305
17979
  return discoverClasses(directory, registry2, isJobClass);
17306
17980
  }
17307
17981
  var text2 = (value2) => optionalString2(value2) || void 0;
17308
- var list2 = (value2) => Array.isArray(value2) ? value2.map(String) : text2(value2) ? [String(value2)] : [];
17982
+ var list3 = (value2) => Array.isArray(value2) ? value2.map(String) : text2(value2) ? [String(value2)] : [];
17309
17983
  var number2 = (value2, fallback) => {
17310
17984
  const parsed = Number(value2);
17311
17985
  return value2 === void 0 || value2 === null || value2 === "" || Number.isNaN(parsed) ? fallback : parsed;
@@ -17315,6 +17989,22 @@ function localTimestamp(date = /* @__PURE__ */ new Date()) {
17315
17989
  const pad2 = (n) => String(n).padStart(2, "0");
17316
17990
  return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())} ${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())}`;
17317
17991
  }
17992
+ function processingMessage(queue) {
17993
+ return `Processing jobs from the [${queue}] ${Str.plural("queue", queue.split(",").length)}.`;
17994
+ }
17995
+ function listenerOptionsFrom(option) {
17996
+ const defaults = defaultListenerOptions();
17997
+ return {
17998
+ name: text2(option("name")) ?? defaults.name,
17999
+ backoff: text2(option("backoff")) ?? defaults.backoff,
18000
+ memory: number2(option("memory"), defaults.memory),
18001
+ timeout: number2(option("timeout"), defaults.timeout),
18002
+ sleep: number2(option("sleep"), defaults.sleep),
18003
+ maxTries: number2(option("tries"), defaults.maxTries),
18004
+ rest: number2(option("rest"), defaults.rest),
18005
+ stopWhenEmpty: option("stop-when-empty") === true
18006
+ };
18007
+ }
17318
18008
  function workerOptionsFrom(option) {
17319
18009
  const defaults = defaultWorkerOptions();
17320
18010
  return {
@@ -17345,7 +18035,7 @@ function registerQueueCommands(kernel, context) {
17345
18035
  return false;
17346
18036
  };
17347
18037
  define2(
17348
- "queue:work {connection? : The name of the queue connection to work} {--name=default : The name of the worker} {--queue= : The names of the queues to work} {--once : Only process the next job on the queue} {--stop-when-empty : Stop when the queue is empty} {--backoff=0 : Seconds to wait before retrying a job that failed} {--max-jobs=0 : The number of jobs to process before stopping} {--max-time=0 : The maximum number of seconds the worker should run} {--memory=128 : The memory limit in megabytes} {--sleep=3 : Seconds to sleep when no job is available} {--rest=0 : Seconds to rest between jobs} {--timeout=60 : The number of seconds a child process can run} {--tries=1 : Number of times to attempt a job before logging it failed}",
18038
+ "queue:work {connection? : The name of the queue connection to work} {--name=default : The name of the worker} {--queue= : The names of the queues to work} {--once : Only process the next job on the queue} {--stop-when-empty : Stop when the queue is empty} {--backoff=0 : Seconds to wait before retrying a job that failed} {--max-jobs=0 : The number of jobs to process before stopping} {--max-time=0 : The maximum number of seconds the worker should run} {--memory=512 : The memory limit in megabytes, measured as V8 heap in use (a Node app on tsx uses about 115 MB at idle)} {--sleep=3 : Seconds to sleep when no job is available} {--rest=0 : Seconds to rest between jobs} {--timeout=60 : The number of seconds a child process can run} {--tries=1 : Number of times to attempt a job before logging it failed}",
17349
18039
  "Start processing jobs on the queue as a daemon",
17350
18040
  async (out) => {
17351
18041
  const connection = text2(out.argument("connection")) ?? defaultConnection();
@@ -17362,13 +18052,34 @@ function registerQueueCommands(kernel, context) {
17362
18052
  exception: (error, job) => out.error(`${job ? `${name(job)}: ` : ""}${errorClass(error)}`)
17363
18053
  });
17364
18054
  const options = workerOptionsFrom((option) => out.option(option));
17365
- out.info(`Processing jobs from the [${queue}] ${Str.plural("queue", queue.split(",").length)}.`);
18055
+ out.info(processingMessage(queue));
17366
18056
  if (out.option("once") === true) {
17367
18057
  return worker.runNextJob(connection, queue, options);
17368
18058
  }
17369
18059
  return worker.daemon(connection, queue, options);
17370
18060
  }
17371
18061
  );
18062
+ define2(
18063
+ "queue:listen {connection? : The name of connection} {--name=default : The name of the worker} {--backoff=0 : The number of seconds to wait before retrying a job that encountered an uncaught exception} {--memory=512 : The memory limit in megabytes, measured as V8 heap in use (a Node app on tsx uses about 115 MB at idle)} {--queue= : The queue to listen on} {--sleep=3 : Number of seconds to sleep when no job is available} {--rest=0 : Number of seconds to rest between jobs} {--timeout=60 : The number of seconds a child process can run} {--tries=1 : Number of times to attempt a job before logging it failed} {--stop-when-empty : Stop when the queue is empty}",
18064
+ "Listen to a given queue",
18065
+ async (out) => {
18066
+ const connection = text2(out.argument("connection")) ?? defaultConnection();
18067
+ const queue = text2(out.option("queue")) ?? defaultQueue(connection);
18068
+ const banner = processingMessage(queue);
18069
+ out.info(banner);
18070
+ const listener = new Listener(container.make(QueueManager), {
18071
+ commandPath: context.app.basePath(),
18072
+ spawn: context.spawn,
18073
+ // Each `queue:work` starts with the same line as this command; it's shown once.
18074
+ output: (type, line) => {
18075
+ if (line === banner) return;
18076
+ if (type === "out") out.line(line);
18077
+ else out.error(line);
18078
+ }
18079
+ });
18080
+ return listener.listen(connection, queue, listenerOptionsFrom((option) => out.option(option)));
18081
+ }
18082
+ );
17372
18083
  define2("queue:restart", "Restart queue worker daemons after their current job", async (out) => {
17373
18084
  if (!container.bound(CacheManager)) {
17374
18085
  out.error("queue:restart needs a cache store. Register the CacheServiceProvider.");
@@ -17393,7 +18104,7 @@ function registerQueueCommands(kernel, context) {
17393
18104
  'queue:retry {id?* : The ID of the failed job or "all" to retry all jobs} {--queue= : Retry all of the failed jobs for the specified queue}',
17394
18105
  "Retry a failed queue job",
17395
18106
  async (out) => {
17396
- const ids = await retryableIds(list2(out.argument("id")), text2(out.option("queue")));
18107
+ const ids = await retryableIds(list3(out.argument("id")), text2(out.option("queue")));
17397
18108
  if (ids.length === 0) {
17398
18109
  out.info("No retryable jobs found.");
17399
18110
  return 0;
@@ -17835,6 +18546,32 @@ var RouteDefinition = class {
17835
18546
  this.#compiled = void 0;
17836
18547
  return this;
17837
18548
  }
18549
+ /** Laravel's whereAlpha(): ASCII letters only. One parameter name or several. */
18550
+ whereAlpha(parameters) {
18551
+ return this.#assign(parameters, "[a-zA-Z]+");
18552
+ }
18553
+ /** Laravel's whereAlphaNumeric(): ASCII letters and digits. */
18554
+ whereAlphaNumeric(parameters) {
18555
+ return this.#assign(parameters, "[a-zA-Z0-9]+");
18556
+ }
18557
+ /** Laravel's whereNumber(): digits only. */
18558
+ whereNumber(parameters) {
18559
+ return this.#assign(parameters, "[0-9]+");
18560
+ }
18561
+ /** Laravel's whereUlid(): 26 Crockford base-32 characters, the first at most 7. */
18562
+ whereUlid(parameters) {
18563
+ return this.#assign(parameters, "[0-7][0-9a-hjkmnp-tv-zA-HJKMNP-TV-Z]{25}");
18564
+ }
18565
+ /** Laravel's whereUuid(): five groups of hex digits (8-4-4-4-12), any version. */
18566
+ whereUuid(parameters) {
18567
+ return this.#assign(parameters, "[\\da-fA-F]{8}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{12}");
18568
+ }
18569
+ /** Laravel's whereIn(): one of the values. Unlike Laravel, each value is matched literally (`c++` is not a pattern). */
18570
+ whereIn(parameters, values) {
18571
+ const first = typeof parameters === "string" ? parameters : parameters[0];
18572
+ if (values.length === 0 && first !== void 0) throw new Error(`Routing requirement for "${first}" cannot be empty.`);
18573
+ return this.#assign(parameters, values.map((value2) => escapeRegex(String(value2))).join("|"));
18574
+ }
17838
18575
  gatherMiddleware() {
17839
18576
  return [...this.#middleware];
17840
18577
  }
@@ -17865,6 +18602,10 @@ var RouteDefinition = class {
17865
18602
  if (this.uri === "/") return [];
17866
18603
  return this.uri.split("/").map((segment) => WHOLE_OPTIONAL.test(segment) ? 2 : segment.includes("{") ? 1 : 0);
17867
18604
  }
18605
+ #assign(parameters, pattern) {
18606
+ const names = typeof parameters === "string" ? [parameters] : parameters;
18607
+ return this.where(Object.fromEntries(names.map((name) => [name, pattern])));
18608
+ }
17868
18609
  #regex(patterns, version) {
17869
18610
  if (this.#compiled !== void 0 && this.#compiled.version === version) return this.#compiled.regex;
17870
18611
  const wheres = { ...patterns, ...this.#wheres };
@@ -18215,6 +18956,14 @@ var BaseRouter = class {
18215
18956
  getModel(parameter) {
18216
18957
  return this.#models.get(parameter);
18217
18958
  }
18959
+ /**
18960
+ * Binds a matched route's raw parameter values as a request to it would: Route.bind() callbacks, Route.model(),
18961
+ * then implicit binding through the RouteModelResolver of `container`. A missing model throws
18962
+ * RouteModelNotFoundError (404). Live uses it to check a page route's `can:` middleware again on each update.
18963
+ */
18964
+ substituteBindings(route2, values, container) {
18965
+ return substituteBindings(this, route2, values, container);
18966
+ }
18218
18967
  aliasMiddleware(name, middleware) {
18219
18968
  this.#aliases[name] = middleware;
18220
18969
  return this;
@@ -18228,20 +18977,20 @@ var BaseRouter = class {
18228
18977
  return this;
18229
18978
  }
18230
18979
  pushMiddlewareToGroup(group, middleware) {
18231
- const list3 = this.#groups[group] ??= [];
18232
- if (!list3.includes(middleware)) list3.push(middleware);
18980
+ const list4 = this.#groups[group] ??= [];
18981
+ if (!list4.includes(middleware)) list4.push(middleware);
18233
18982
  return this;
18234
18983
  }
18235
18984
  prependMiddlewareToGroup(group, middleware) {
18236
- const list3 = this.#groups[group] ??= [];
18237
- if (!list3.includes(middleware)) list3.unshift(middleware);
18985
+ const list4 = this.#groups[group] ??= [];
18986
+ if (!list4.includes(middleware)) list4.unshift(middleware);
18238
18987
  return this;
18239
18988
  }
18240
18989
  hasMiddlewareGroup(name) {
18241
18990
  return Object.hasOwn(this.#groups, name);
18242
18991
  }
18243
18992
  getMiddlewareGroups() {
18244
- return Object.fromEntries(Object.entries(this.#groups).map(([name, list3]) => [name, [...list3]]));
18993
+ return Object.fromEntries(Object.entries(this.#groups).map(([name, list4]) => [name, [...list4]]));
18245
18994
  }
18246
18995
  /** Every route, in registration order. */
18247
18996
  getRoutes() {
@@ -18483,6 +19232,48 @@ var BaseUrlGenerator = class {
18483
19232
  }
18484
19233
  };
18485
19234
  var Route = facade(Router);
19235
+ var DiagnosingHealth = class {
19236
+ };
19237
+ var escapeHtml3 = (value2) => value2.replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);
19238
+ var STYLE = ':root{color-scheme:dark}*{box-sizing:border-box;margin:0}html{background:#05050b}body{min-height:100vh;display:grid;place-items:center;padding:20px;background:#1a1a1a;color:#fff;font:400 16px/1.7 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}main{display:flex;align-items:center;gap:20px;width:100%;max-width:640px;padding:20px 32px;border-radius:18px;background:#212121}.dot{flex:none;width:12px;height:12px;border-radius:50%;background:#74ec5a}.dot.down{background:#ff3543}h1{font-size:21px;line-height:1.4;font-weight:600}p{color:#a3a3a3}.time{color:#ba94ff}';
19239
+ function healthResponse(page = {}) {
19240
+ const healthy = page.healthy ?? true;
19241
+ const time = page.milliseconds === void 0 ? "" : ` Response rendered in <span class="time">${Math.round(page.milliseconds)} ms</span>.`;
19242
+ const html2 = `<!DOCTYPE html>
19243
+ <html lang="en">
19244
+ <head>
19245
+ <meta charset="utf-8">
19246
+ <meta name="viewport" content="width=device-width, initial-scale=1">
19247
+ <title>${escapeHtml3(page.appName ?? "Oriel")}</title>
19248
+ <style>${STYLE}</style>
19249
+ </head>
19250
+ <body>
19251
+ <main>
19252
+ <span class="${healthy ? "dot" : "dot down"}" aria-hidden="true"></span>
19253
+ <div>
19254
+ <h1>Application ${healthy ? "up" : "experiencing problems"}</h1>
19255
+ <p>HTTP request received.${time}</p>
19256
+ </div>
19257
+ </main>
19258
+ </body>
19259
+ </html>
19260
+ `;
19261
+ return new Response(html2, { status: healthy ? 200 : 500, headers: { "content-type": "text/html; charset=utf-8" } });
19262
+ }
19263
+ async function diagnoseHealth(app2) {
19264
+ const started = performance.now();
19265
+ const config2 = app2.bound(ConfigRepository) ? app2.make(ConfigRepository) : void 0;
19266
+ let healthy = true;
19267
+ try {
19268
+ if (app2.bound(EventDispatcher)) await app2.make(EventDispatcher).dispatch(new DiagnosingHealth());
19269
+ } catch (error) {
19270
+ if (config2?.get("app.debug", false) === true) throw error;
19271
+ if (app2.bound(ExceptionHandler)) await app2.make(ExceptionHandler).report(error);
19272
+ healthy = false;
19273
+ }
19274
+ const appName2 = String(config2?.get("app.name", "Oriel") ?? "Oriel");
19275
+ return healthResponse({ healthy, appName: appName2, milliseconds: performance.now() - started });
19276
+ }
18486
19277
  var generation2 = 0;
18487
19278
  async function loadRoutesFrom(app2, router, file) {
18488
19279
  const url2 = `${pathToFileURL7(file).href}?oriel-routes=${++generation2}`;
@@ -18492,10 +19283,6 @@ async function loadRoutesFrom(app2, router, file) {
18492
19283
  ));
18493
19284
  if (typeof module.default === "function") await app2.run(() => module.default(router));
18494
19285
  }
18495
- function healthResponse() {
18496
- const html2 = '<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="utf-8">\n<title>Application up</title>\n</head>\n<body><h1>Application up</h1></body>\n</html>';
18497
- return new Response(html2, { status: 200, headers: { "content-type": "text/html; charset=utf-8" } });
18498
- }
18499
19286
  var sha12 = (value2) => createHash8("sha1").update(value2).digest("hex");
18500
19287
  function withHeaders2(response2, headers) {
18501
19288
  try {
@@ -18572,7 +19359,7 @@ var RoutingServiceProvider = class extends ServiceProvider {
18572
19359
  if (!this.app.bound(RoutingPaths)) return;
18573
19360
  const { web, api, health, apiPrefix } = this.app.make(RoutingPaths);
18574
19361
  const router = this.app.make(Router);
18575
- if (health !== void 0) router.get(health, () => healthResponse());
19362
+ if (health !== void 0) router.get(health, () => diagnoseHealth(this.app));
18576
19363
  if (web !== void 0) await router.middleware("web").group(() => this.#load(router, web));
18577
19364
  if (api !== void 0) await router.prefix(apiPrefix).middleware("api").group(() => this.#load(router, api));
18578
19365
  });
@@ -18656,8 +19443,8 @@ var Store = class {
18656
19443
  for (const [name, item] of Object.entries(values)) Arr.set(this.#attributes, name, item);
18657
19444
  }
18658
19445
  push(key, value2) {
18659
- const list3 = this.get(key, []);
18660
- this.put(key, [...Array.isArray(list3) ? list3 : [list3], value2]);
19446
+ const list4 = this.get(key, []);
19447
+ this.put(key, [...Array.isArray(list4) ? list4 : [list4], value2]);
18661
19448
  }
18662
19449
  increment(key, amount = 1) {
18663
19450
  const value2 = Number(this.get(key, 0)) + amount;
@@ -19323,7 +20110,7 @@ var ValidationErrorImpl = class extends Error {
19323
20110
  var ValidationException = class _ValidationException extends ValidationErrorImpl {
19324
20111
  /** Messages per field, each a string or a list. */
19325
20112
  static withMessages(messages) {
19326
- const errors2 = Object.fromEntries(Object.entries(messages).map(([field, list3]) => [field, typeof list3 === "string" ? [list3] : [...list3]]));
20113
+ const errors2 = Object.fromEntries(Object.entries(messages).map(([field, list4]) => [field, typeof list4 === "string" ? [list4] : [...list4]]));
19327
20114
  return new _ValidationException(errors2);
19328
20115
  }
19329
20116
  constructor(errors2, errorBag = "default", redirectTo2) {
@@ -19434,6 +20221,9 @@ var DEFAULT_MESSAGES = {
19434
20221
  file: "The :attribute field must be a file.",
19435
20222
  image: "The :attribute field must be an image.",
19436
20223
  mimes: "The :attribute field must be a file of type: :values.",
20224
+ mimetypes: "The :attribute field must be a file of type: :values.",
20225
+ extensions: "The :attribute field must have one of the following extensions: :values.",
20226
+ dimensions: "The :attribute field has invalid image dimensions.",
19437
20227
  "max.string": "The :attribute field must not be greater than :max characters.",
19438
20228
  "max.numeric": "The :attribute field must not be greater than :max.",
19439
20229
  "max.array": "The :attribute field must not have more than :max items.",
@@ -19524,6 +20314,90 @@ var databaseRules = {
19524
20314
  messageKey: () => "exists"
19525
20315
  }
19526
20316
  };
20317
+ var FRAME_MARKERS = /* @__PURE__ */ new Set([192, 193, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207]);
20318
+ var MAX_JPEG_STEPS = 1024;
20319
+ var MAX_BOXES = 1024;
20320
+ var fourcc = (view2, offset) => String.fromCharCode(view2.getUint8(offset), view2.getUint8(offset + 1), view2.getUint8(offset + 2), view2.getUint8(offset + 3));
20321
+ var u24le = (view2, offset) => view2.getUint8(offset) | view2.getUint8(offset + 1) << 8 | view2.getUint8(offset + 2) << 16;
20322
+ var READERS = {
20323
+ "image/png": (view2) => fourcc(view2, 12) === "IHDR" ? { width: view2.getUint32(16), height: view2.getUint32(20) } : void 0,
20324
+ "image/gif": (view2) => ({ width: view2.getUint16(6, true), height: view2.getUint16(8, true) }),
20325
+ "image/bmp": (view2) => view2.getUint32(14, true) === 12 ? { width: view2.getUint16(18, true), height: view2.getUint16(20, true) } : { width: Math.abs(view2.getInt32(18, true)), height: Math.abs(view2.getInt32(22, true)) },
20326
+ "image/jpeg": jpeg,
20327
+ "image/webp": webp,
20328
+ "image/avif": avif
20329
+ };
20330
+ function imageDimensions(bytes, mime) {
20331
+ const reader = READERS[mime];
20332
+ if (!reader) return void 0;
20333
+ try {
20334
+ const size = reader(new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength));
20335
+ return size && size.width > 0 && size.height > 0 ? size : void 0;
20336
+ } catch (error) {
20337
+ if (error instanceof RangeError) return void 0;
20338
+ throw error;
20339
+ }
20340
+ }
20341
+ function jpeg(view2) {
20342
+ let offset = 2;
20343
+ for (let step = 0; step < MAX_JPEG_STEPS && offset + 4 <= view2.byteLength; step++) {
20344
+ if (view2.getUint8(offset) !== 255) return void 0;
20345
+ const marker = view2.getUint8(offset + 1);
20346
+ if (marker === 255) offset += 1;
20347
+ else if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) offset += 2;
20348
+ else if (marker === 217 || marker === 218) return void 0;
20349
+ else if (FRAME_MARKERS.has(marker)) return { height: view2.getUint16(offset + 5), width: view2.getUint16(offset + 7) };
20350
+ else offset += 2 + view2.getUint16(offset + 2);
20351
+ }
20352
+ return void 0;
20353
+ }
20354
+ function webp(view2) {
20355
+ const chunk = fourcc(view2, 12);
20356
+ if (chunk === "VP8 ") {
20357
+ if (view2.getUint8(23) !== 157 || view2.getUint8(24) !== 1 || view2.getUint8(25) !== 42) return void 0;
20358
+ return { width: view2.getUint16(26, true) & 16383, height: view2.getUint16(28, true) & 16383 };
20359
+ }
20360
+ if (chunk === "VP8L") {
20361
+ if (view2.getUint8(20) !== 47) return void 0;
20362
+ const bits = view2.getUint32(21, true);
20363
+ return { width: (bits & 16383) + 1, height: (bits >>> 14 & 16383) + 1 };
20364
+ }
20365
+ if (chunk === "VP8X") return { width: u24le(view2, 24) + 1, height: u24le(view2, 27) + 1 };
20366
+ return void 0;
20367
+ }
20368
+ function* boxes(view2, start2, end) {
20369
+ let offset = start2;
20370
+ for (let count = 0; count < MAX_BOXES && offset + 8 <= end; count++) {
20371
+ let size = view2.getUint32(offset);
20372
+ let header = 8;
20373
+ if (size === 1) {
20374
+ size = Number(view2.getBigUint64(offset + 8));
20375
+ header = 16;
20376
+ } else if (size === 0) {
20377
+ size = end - offset;
20378
+ }
20379
+ if (size < header || offset + size > end) return;
20380
+ yield { type: fourcc(view2, offset + 4), start: offset + header, end: offset + size };
20381
+ offset += size;
20382
+ }
20383
+ }
20384
+ var findBox = (view2, start2, end, type) => {
20385
+ for (const box of boxes(view2, start2, end)) if (box.type === type) return box;
20386
+ return void 0;
20387
+ };
20388
+ function avif(view2) {
20389
+ const meta = findBox(view2, 0, view2.byteLength, "meta");
20390
+ const iprp = meta && findBox(view2, meta.start + 4, meta.end, "iprp");
20391
+ const ipco = iprp && findBox(view2, iprp.start, iprp.end, "ipco");
20392
+ if (!ipco) return void 0;
20393
+ let largest;
20394
+ for (const box of boxes(view2, ipco.start, ipco.end)) {
20395
+ if (box.type !== "ispe") continue;
20396
+ const size = { width: view2.getUint32(box.start + 4), height: view2.getUint32(box.start + 8) };
20397
+ if (!largest || size.width * size.height > largest.width * largest.height) largest = size;
20398
+ }
20399
+ return largest;
20400
+ }
19527
20401
  function splitParameters(name, raw3) {
19528
20402
  return name === "regex" || name === "not_regex" ? [raw3] : raw3.split(",");
19529
20403
  }
@@ -19571,25 +20445,139 @@ function expandWildcardKeys(pattern, data) {
19571
20445
  }
19572
20446
  return paths;
19573
20447
  }
19574
- var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["jpg", "jpeg", "png", "bmp", "gif", "svg", "webp"]);
20448
+ var { BINARY_AND_MARKUP_TYPES, detectFileType, EMPTY: EMPTY2, RASTER_IMAGES, SVG, TEXT } = fileType_exports;
20449
+ var inspections = /* @__PURE__ */ new WeakMap();
20450
+ function inspect(file) {
20451
+ let inspection = inspections.get(file);
20452
+ if (!inspection) {
20453
+ inspection = file.arrayBuffer().then((buffer) => {
20454
+ const bytes = new Uint8Array(buffer);
20455
+ const type = detectFileType(bytes);
20456
+ return { type, dimensions: RASTER_IMAGES.has(type.mime) ? imageDimensions(bytes, type.mime) : void 0 };
20457
+ });
20458
+ inspections.set(file, inspection);
20459
+ }
20460
+ return inspection;
20461
+ }
20462
+ function clientExtension(file) {
20463
+ const name = file.clientName.slice(Math.max(file.clientName.lastIndexOf("/"), file.clientName.lastIndexOf("\\")) + 1);
20464
+ const dot = name.lastIndexOf(".");
20465
+ return dot === -1 ? "" : name.slice(dot + 1).trim().toLowerCase();
20466
+ }
20467
+ var PHP_EXTENSIONS = /* @__PURE__ */ new Set(["php", "php3", "php4", "php5", "php7", "php8", "phtml", "phar"]);
20468
+ var blocksPhp = (file, allowed) => !allowed.includes("php") && PHP_EXTENSIONS.has(clientExtension(file));
20469
+ var TEXT_FORMATS = /* @__PURE__ */ new Set(["txt", "text", "csv", "tsv", "json", "md"]);
20470
+ var RECOGNISED_EXTENSIONS = new Set(BINARY_AND_MARKUP_TYPES.flatMap(({ extensions }) => extensions));
20471
+ var lower = (values) => values.map((value2) => value2.trim().toLowerCase());
20472
+ function meetsMimes(type, extension, allowed) {
20473
+ if (type === EMPTY2) return false;
20474
+ if (type === TEXT && allowed.has("txt")) return true;
20475
+ if (type !== TEXT && type.extensions.length > 0) return type.extensions.some((name) => allowed.has(name));
20476
+ if (!allowed.has(extension) || RECOGNISED_EXTENSIONS.has(extension)) return false;
20477
+ return type === TEXT || !TEXT_FORMATS.has(extension);
20478
+ }
20479
+ function allowedExtensions(ctx) {
20480
+ const allowed = new Set(lower(ctx.parameters));
20481
+ if (allowed.has("jpg") || allowed.has("jpeg")) allowed.add("jpg").add("jpeg");
20482
+ return allowed;
20483
+ }
20484
+ var listed = (ctx) => ({ values: ctx.parameters.join(", ") });
20485
+ var DIMENSION_KEYS = ["width", "height", "min_width", "min_height", "max_width", "max_height", "ratio"];
20486
+ function namedParameter(parameter) {
20487
+ const at = parameter.indexOf("=");
20488
+ return at === -1 ? [parameter, ""] : [parameter.slice(0, at), parameter.slice(at + 1)];
20489
+ }
20490
+ function parseRatio(value2) {
20491
+ const match = /^(\d+(?:\.\d+)?|\.\d+)(?:\/(\d+))?$/.exec(value2.trim());
20492
+ if (!match) return void 0;
20493
+ const ratio = Number(match[1]) / Number(match[2] ?? 1);
20494
+ return Number.isFinite(ratio) && ratio > 0 ? ratio : void 0;
20495
+ }
20496
+ function dimensionConstraints(parameters) {
20497
+ if (parameters.length === 0) throw new InvalidRuleParameterError("dimensions", "", `expected one or more of ${DIMENSION_KEYS.join(", ")}`);
20498
+ const constraints = {};
20499
+ for (const parameter of parameters) {
20500
+ const [key, value2] = namedParameter(parameter);
20501
+ if (!DIMENSION_KEYS.includes(key)) {
20502
+ throw new InvalidRuleParameterError("dimensions", parameter, `expected one of ${DIMENSION_KEYS.join(", ")}`);
20503
+ }
20504
+ const number3 = key === "ratio" ? parseRatio(value2) : /^\d+$/.test(value2) ? Number(value2) : void 0;
20505
+ if (number3 === void 0) {
20506
+ throw new InvalidRuleParameterError("dimensions", parameter, key === "ratio" ? "expected a ratio such as 3/2 or 1.5" : "expected a whole number of pixels");
20507
+ }
20508
+ constraints[key] = number3;
20509
+ }
20510
+ return constraints;
20511
+ }
20512
+ function meetsDimensions(constraints, { width, height }) {
20513
+ const { width: exactWidth, height: exactHeight, min_width, max_width, min_height, max_height, ratio } = constraints;
20514
+ if (exactWidth !== void 0 && width !== exactWidth) return false;
20515
+ if (exactHeight !== void 0 && height !== exactHeight) return false;
20516
+ if (min_width !== void 0 && width < min_width) return false;
20517
+ if (max_width !== void 0 && width > max_width) return false;
20518
+ if (min_height !== void 0 && height < min_height) return false;
20519
+ if (max_height !== void 0 && height > max_height) return false;
20520
+ return ratio === void 0 || Math.abs(ratio - width / height) <= 1 / (Math.max((width + height) / 2, height) + 1);
20521
+ }
19575
20522
  var fileRules = {
19576
20523
  file: { validate: (ctx) => isUploadedFileLike(ctx.value), messageKey: () => "file" },
20524
+ /** A JPEG, PNG, GIF, WebP, AVIF or BMP by its bytes; SVG only as `image:allow_svg` (Laravel 12). */
19577
20525
  image: {
19578
- validate: (ctx) => {
19579
- if (!isUploadedFileLike(ctx.value)) return false;
19580
- if (ctx.value.mimeType.startsWith("image/")) return true;
19581
- return IMAGE_EXTENSIONS.has(ctx.value.extension().toLowerCase());
20526
+ validate: async (ctx) => {
20527
+ if (!isUploadedFileLike(ctx.value) || blocksPhp(ctx.value, [])) return false;
20528
+ const { type } = await inspect(ctx.value);
20529
+ return RASTER_IMAGES.has(type.mime) || type === SVG && ctx.parameters.includes("allow_svg");
19582
20530
  },
19583
20531
  messageKey: () => "image"
19584
20532
  },
19585
20533
  mimes: {
19586
- validate: (ctx) => {
20534
+ validate: async (ctx) => {
19587
20535
  if (!isUploadedFileLike(ctx.value)) return false;
19588
- const allowed = ctx.parameters.map((p) => p.toLowerCase());
19589
- return allowed.includes(ctx.value.extension().toLowerCase());
20536
+ const allowed = allowedExtensions(ctx);
20537
+ if (blocksPhp(ctx.value, [...allowed])) return false;
20538
+ return meetsMimes((await inspect(ctx.value)).type, clientExtension(ctx.value), allowed);
19590
20539
  },
19591
20540
  messageKey: () => "mimes",
19592
- replacements: (ctx) => ({ values: ctx.parameters.join(", ") })
20541
+ replacements: listed
20542
+ },
20543
+ /**
20544
+ * The detected MIME type is listed, or its `type/*` wildcard is. `image/*` includes SVG (`image/svg+xml`), as in
20545
+ * Laravel: the rule for photos is `image`.
20546
+ */
20547
+ mimetypes: {
20548
+ validate: async (ctx) => {
20549
+ if (!isUploadedFileLike(ctx.value)) return false;
20550
+ const allowed = lower(ctx.parameters);
20551
+ if (blocksPhp(ctx.value, allowed)) return false;
20552
+ const { mime } = (await inspect(ctx.value)).type;
20553
+ return allowed.includes(mime) || allowed.includes(`${mime.split("/")[0]}/*`);
20554
+ },
20555
+ messageKey: () => "mimetypes",
20556
+ replacements: listed
20557
+ },
20558
+ /**
20559
+ * The pixel size read from the image's header meets every constraint: width, height, min_width, max_width,
20560
+ * min_height, max_height, ratio. SVG and images whose header can't be read fail.
20561
+ */
20562
+ dimensions: {
20563
+ validate: async (ctx) => {
20564
+ const constraints = dimensionConstraints(ctx.parameters);
20565
+ if (!isUploadedFileLike(ctx.value)) return false;
20566
+ const { dimensions } = await inspect(ctx.value);
20567
+ return dimensions !== void 0 && meetsDimensions(constraints, dimensions);
20568
+ },
20569
+ messageKey: () => "dimensions",
20570
+ replacements: (ctx) => Object.fromEntries(ctx.parameters.map(namedParameter))
20571
+ },
20572
+ /** The client file name's extension is listed (Laravel 11). It checks the name only: pair it with mimes or image. */
20573
+ extensions: {
20574
+ validate: (ctx) => {
20575
+ if (!isUploadedFileLike(ctx.value)) return false;
20576
+ const allowed = lower(ctx.parameters);
20577
+ return !blocksPhp(ctx.value, allowed) && allowed.includes(clientExtension(ctx.value));
20578
+ },
20579
+ messageKey: () => "extensions",
20580
+ replacements: listed
19593
20581
  }
19594
20582
  };
19595
20583
  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -20276,6 +21264,11 @@ var Notification2 = class extends Notification {
20276
21264
  return NotificationFake.fake(...args);
20277
21265
  }
20278
21266
  };
21267
+
21268
+ // notifiable.ts
21269
+ function Notifiable(Base) {
21270
+ return RoutesNotifications(HasDatabaseNotifications(Base));
21271
+ }
20279
21272
  export {
20280
21273
  API_ROUTES_STUB,
20281
21274
  AboutCommand,
@@ -20299,6 +21292,7 @@ export {
20299
21292
  AuthenticatableModel,
20300
21293
  Authenticate,
20301
21294
  AuthenticationError,
21295
+ Authorizable,
20302
21296
  AuthorizationError,
20303
21297
  AuthorizationErrorImpl,
20304
21298
  Authorize,
@@ -20347,6 +21341,7 @@ export {
20347
21341
  ConsoleServiceProvider,
20348
21342
  Container2 as Container,
20349
21343
  ContainerNotAvailableError,
21344
+ Controller,
20350
21345
  ControllerMethodNotFoundError,
20351
21346
  ConvertEmptyStringsToNull,
20352
21347
  Cookie,
@@ -20368,7 +21363,10 @@ export {
20368
21363
  DatabaseJob,
20369
21364
  DatabaseLock,
20370
21365
  DatabaseManagerImpl,
21366
+ DatabaseNotification,
20371
21367
  DatabaseNotificationChannel,
21368
+ DatabaseNotificationCollection,
21369
+ DatabaseNotificationsRelation,
20372
21370
  DatabaseQueue,
20373
21371
  DatabaseServiceProvider,
20374
21372
  DatabaseSessionHandler,
@@ -20377,6 +21375,7 @@ export {
20377
21375
  DatabaseUnavailableError,
20378
21376
  DecryptError,
20379
21377
  DeferProp,
21378
+ DiagnosingHealth,
20380
21379
  Dispatchable,
20381
21380
  ELEMENT,
20382
21381
  EloquentUserProvider,
@@ -20412,17 +21411,20 @@ export {
20412
21411
  FormRequest,
20413
21412
  FormUploadedFile,
20414
21413
  Fragment,
20415
- Gate2 as Gate,
21414
+ Gate4 as Gate,
20416
21415
  GateResponse,
20417
21416
  GeneratorCommand,
20418
21417
  GenericLock,
20419
21418
  Grammar,
20420
21419
  GroupRegistrar,
20421
21420
  HasApiTokens,
21421
+ HasDatabaseNotifications,
20422
21422
  HasMany,
20423
21423
  HasManyThrough,
20424
21424
  HasOne,
20425
21425
  HasOneOrMany,
21426
+ HasUlids,
21427
+ HasUuids,
20426
21428
  Hash,
20427
21429
  HashAlgorithmError,
20428
21430
  HashManager,
@@ -20559,6 +21561,7 @@ export {
20559
21561
  OptionalProp,
20560
21562
  PERSISTENT_MIDDLEWARE,
20561
21563
  PERSONAL_ACCESS_TOKENS_MIGRATION,
21564
+ Pagination,
20562
21565
  Paginator,
20563
21566
  Password,
20564
21567
  PasswordBroker2 as PasswordBroker,
@@ -20586,6 +21589,7 @@ export {
20586
21589
  Queue2 as Queue,
20587
21590
  QueueBase,
20588
21591
  QueueError,
21592
+ Listener as QueueListener,
20589
21593
  QueueManager2 as QueueManager,
20590
21594
  QueueServiceProvider,
20591
21595
  QueueUnavailableError,
@@ -20613,13 +21617,15 @@ export {
20613
21617
  RouteModelNotFoundError,
20614
21618
  RouteModelResolverImpl,
20615
21619
  RouteNotDefinedError,
21620
+ RoutesNotifications,
20616
21621
  RoutingError,
20617
21622
  RoutingServiceProvider,
20618
21623
  Rule,
20619
21624
  S3Filesystem,
20620
21625
  SSR_RENDER_FAILED,
20621
21626
  STORAGE_LINK_SIGNATURE,
20622
- Schedule4 as Schedule,
21627
+ Schedule5 as Schedule,
21628
+ ScheduleClearCacheCommand,
20623
21629
  ScheduleImpl,
20624
21630
  ScheduleListCommand,
20625
21631
  ScheduleNameRequiredError,
@@ -20651,6 +21657,7 @@ export {
20651
21657
  StartSession,
20652
21658
  Storage2 as Storage,
20653
21659
  Str,
21660
+ Stringable,
20654
21661
  SyncJob,
20655
21662
  SyncQueue,
20656
21663
  TagsNotSupportedError,
@@ -20710,6 +21717,7 @@ export {
20710
21717
  ViteError,
20711
21718
  ViteHtml,
20712
21719
  WORKER_DISCOVERY_DIRECTORIES,
21720
+ WithPagination,
20713
21721
  WithoutOverlapping,
20714
21722
  Worker,
20715
21723
  __,
@@ -20758,6 +21766,7 @@ export {
20758
21766
  decodePayload,
20759
21767
  decrypt,
20760
21768
  defaultAttributeName,
21769
+ defaultListenerOptions,
20761
21770
  defaultLockFactory,
20762
21771
  defaultProviders,
20763
21772
  defaultWorkerOptions,
@@ -20787,7 +21796,6 @@ export {
20787
21796
  extensionFromMime,
20788
21797
  facade,
20789
21798
  fake,
20790
- fetchNotifications,
20791
21799
  filterRouteList,
20792
21800
  formatAddress,
20793
21801
  formatDate,
@@ -20824,6 +21832,7 @@ export {
20824
21832
  isUploadedFileLike,
20825
21833
  joinDiskPath,
20826
21834
  keyGenerateHandler,
21835
+ listenerOptionsFrom,
20827
21836
  live,
20828
21837
  loadRelations,
20829
21838
  loadRoutesFrom,