@orieljs/framework 0.1.0-alpha.11 → 0.1.0-alpha.13

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-24673JQF.js";
25
+ } from "./chunk-ZRTWDJKD.js";
26
26
  import {
27
27
  API_ROUTES_STUB,
28
28
  Application as Application2,
@@ -74,6 +74,7 @@ import {
74
74
  QueueFake,
75
75
  RenameClasses,
76
76
  Repository,
77
+ SESSION_URI,
77
78
  StorageFake,
78
79
  TEMPORARY_UPLOAD_DEFAULTS,
79
80
  TemporaryFileNotFoundError,
@@ -148,7 +149,7 @@ import {
148
149
  vite,
149
150
  viteReactRefresh,
150
151
  writeResponse
151
- } from "./chunk-KDE5BLI3.js";
152
+ } from "./chunk-OTCTQVDQ.js";
152
153
  import {
153
154
  Arr,
154
155
  BindingResolutionError,
@@ -175,7 +176,7 @@ import {
175
176
  retry,
176
177
  tap,
177
178
  value
178
- } from "./chunk-MURTCTRH.js";
179
+ } from "./chunk-6GMXKQ6O.js";
179
180
  import {
180
181
  Application,
181
182
  AuthFactory,
@@ -217,7 +218,7 @@ import {
217
218
  ValidationFactory,
218
219
  ViewFactory,
219
220
  token
220
- } from "./chunk-HTJKT2R2.js";
221
+ } from "./chunk-CKH4KCYF.js";
221
222
 
222
223
  // ../Auth/dist/index.js
223
224
  import { createHash } from "crypto";
@@ -229,6 +230,13 @@ import { createHmac } from "crypto";
229
230
  import { readdir } from "fs/promises";
230
231
  import { join } from "path";
231
232
  import { pathToFileURL } from "url";
233
+ import { relative } from "path";
234
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from "fs";
235
+ import { join as join2 } from "path";
236
+ import { createHmac as createHmac3, randomBytes as randomBytes2, randomInt as randomInt2, timingSafeEqual as timingSafeEqual3 } from "crypto";
237
+ import { renderSVG } from "uqr";
238
+ import { randomInt } from "crypto";
239
+ import { createHmac as createHmac2, randomBytes, timingSafeEqual as timingSafeEqual2 } from "crypto";
232
240
  var AuthenticationError = class extends Error {
233
241
  constructor(message = "Unauthenticated.", guards = [], redirectTo2) {
234
242
  super(message);
@@ -1682,6 +1690,455 @@ function auth(guard2) {
1682
1690
  const manager2 = inject(AuthFactory);
1683
1691
  return guard2 === void 0 ? manager2 : manager2.guard(guard2);
1684
1692
  }
1693
+ var TWO_FACTOR_DEFAULTS = {
1694
+ enabled: false,
1695
+ methods: ["email", "app"],
1696
+ default: "email",
1697
+ code_ttl: 600,
1698
+ pending_ttl: 600,
1699
+ max_attempts: 5,
1700
+ resend_after: 30,
1701
+ window: 1,
1702
+ recovery_codes: 8
1703
+ };
1704
+ function twoFactorConfig(config2) {
1705
+ return { ...TWO_FACTOR_DEFAULTS, ...config2.get("auth.two_factor", {}) };
1706
+ }
1707
+ var TWO_FACTOR_COLUMNS_MIGRATION = `import { Migration, Schema } from '@orieljs/framework'
1708
+
1709
+ export default class extends Migration {
1710
+ async up() {
1711
+ await Schema.table('users', (t) => {
1712
+ t.text('two_factor_secret').nullable()
1713
+ t.text('two_factor_recovery_codes').nullable()
1714
+ t.timestamp('two_factor_confirmed_at').nullable()
1715
+ t.string('two_factor_method', 10).nullable()
1716
+ })
1717
+ }
1718
+
1719
+ async down() {
1720
+ await Schema.table('users', (t) => {
1721
+ t.dropColumn('two_factor_secret', 'two_factor_recovery_codes', 'two_factor_confirmed_at', 'two_factor_method')
1722
+ })
1723
+ }
1724
+ }
1725
+ `;
1726
+ var NAME = "add_two_factor_columns_to_users_table";
1727
+ var pad = (value2) => String(value2).padStart(2, "0");
1728
+ function installTwoFactorMigration(directory, now5 = /* @__PURE__ */ new Date()) {
1729
+ if (existsSync(directory) && readdirSync(directory).some((file) => file.endsWith(`_${NAME}.ts`))) {
1730
+ throw new Error("The two-factor migration is there already: run `oriel migrate`.");
1731
+ }
1732
+ mkdirSync(directory, { recursive: true });
1733
+ const stamp = `${now5.getFullYear()}_${pad(now5.getMonth() + 1)}_${pad(now5.getDate())}_${pad(now5.getHours())}${pad(now5.getMinutes())}${pad(now5.getSeconds())}`;
1734
+ const path = join2(directory, `${stamp}_${NAME}.ts`);
1735
+ writeFileSync(path, TWO_FACTOR_COLUMNS_MIGRATION);
1736
+ return path;
1737
+ }
1738
+ var TwoFactorChallenged = class {
1739
+ constructor(user, method) {
1740
+ this.user = user;
1741
+ this.method = method;
1742
+ }
1743
+ user;
1744
+ method;
1745
+ };
1746
+ var TwoFactorFailed = class {
1747
+ constructor(user, method) {
1748
+ this.user = user;
1749
+ this.method = method;
1750
+ }
1751
+ user;
1752
+ method;
1753
+ };
1754
+ var TwoFactorAuthenticated = class {
1755
+ constructor(user, method) {
1756
+ this.user = user;
1757
+ this.method = method;
1758
+ }
1759
+ user;
1760
+ method;
1761
+ };
1762
+ var RecoveryCodeUsed = class {
1763
+ constructor(user) {
1764
+ this.user = user;
1765
+ }
1766
+ user;
1767
+ };
1768
+ var TwoFactorAppEnabled = class {
1769
+ constructor(user) {
1770
+ this.user = user;
1771
+ }
1772
+ user;
1773
+ };
1774
+ var TwoFactorAppDisabled = class {
1775
+ constructor(user) {
1776
+ this.user = user;
1777
+ }
1778
+ user;
1779
+ };
1780
+ function qrSvg(text4) {
1781
+ return renderSVG(text4);
1782
+ }
1783
+ var CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789";
1784
+ var part = () => Array.from({ length: 5 }, () => CHARACTERS[randomInt(CHARACTERS.length)]).join("");
1785
+ function generateRecoveryCodes(count = 8) {
1786
+ const codes = /* @__PURE__ */ new Set();
1787
+ while (codes.size < count) codes.add(`${part()}-${part()}`);
1788
+ return [...codes];
1789
+ }
1790
+ function normalizeCode(input) {
1791
+ return input.replace(/[\s-]/g, "").toLowerCase();
1792
+ }
1793
+ var TwoFactorStore = class {
1794
+ constructor(encrypter, config2) {
1795
+ this.encrypter = encrypter;
1796
+ this.config = config2;
1797
+ }
1798
+ encrypter;
1799
+ config;
1800
+ secret(user) {
1801
+ const stored = user.getAttribute("two_factor_secret");
1802
+ return stored ? this.encrypter.decryptString(stored) : void 0;
1803
+ }
1804
+ /** A new secret (or none) is unconfirmed until confirm(). */
1805
+ async setSecret(user, secret) {
1806
+ user.setAttribute("two_factor_secret", secret === null ? null : this.encrypter.encryptString(secret));
1807
+ user.setAttribute("two_factor_confirmed_at", null);
1808
+ await user.save();
1809
+ }
1810
+ confirmedAt(user) {
1811
+ const stored = user.getAttribute("two_factor_confirmed_at");
1812
+ return stored ? new Date(stored) : void 0;
1813
+ }
1814
+ async confirm(user) {
1815
+ user.setAttribute("two_factor_confirmed_at", /* @__PURE__ */ new Date());
1816
+ await user.save();
1817
+ }
1818
+ recoveryCodes(user) {
1819
+ const stored = user.getAttribute("two_factor_recovery_codes");
1820
+ return stored ? JSON.parse(this.encrypter.decryptString(stored)) : [];
1821
+ }
1822
+ async setRecoveryCodes(user, codes) {
1823
+ user.setAttribute("two_factor_recovery_codes", this.encrypter.encryptString(JSON.stringify(codes)));
1824
+ await user.save();
1825
+ }
1826
+ /** Whether the user's authenticator counts: set up and confirmed. */
1827
+ hasApp(user) {
1828
+ return this.secret(user) !== void 0 && this.confirmedAt(user) !== void 0;
1829
+ }
1830
+ /** The methods open to the user now: the app's, of those the user can use. */
1831
+ methods(user) {
1832
+ return this.config().methods.filter((method) => method === "email" || this.hasApp(user));
1833
+ }
1834
+ /** The method the user is asked for: the one they chose if they can use it, else the app's default, else another. */
1835
+ method(user) {
1836
+ const open = this.methods(user);
1837
+ const chosen = user.getAttribute("two_factor_method");
1838
+ if (chosen && open.includes(chosen)) return chosen;
1839
+ return open.includes(this.config().default) ? this.config().default : open[0];
1840
+ }
1841
+ async setMethod(user, method) {
1842
+ user.setAttribute("two_factor_method", method);
1843
+ await user.save();
1844
+ }
1845
+ };
1846
+ var toMailCallback3;
1847
+ var TwoFactorCode = class {
1848
+ constructor(code, minutes) {
1849
+ this.code = code;
1850
+ this.minutes = minutes;
1851
+ }
1852
+ code;
1853
+ minutes;
1854
+ /** Build the mail yourself (e.g. a MailMessage from the Notifications component). */
1855
+ static toMailUsing(callback) {
1856
+ toMailCallback3 = callback;
1857
+ }
1858
+ via() {
1859
+ return ["mail"];
1860
+ }
1861
+ toMail(notifiable) {
1862
+ if (toMailCallback3 !== void 0) return toMailCallback3(notifiable, this.code, this.minutes);
1863
+ return {
1864
+ subject: "Your sign-in code",
1865
+ lines: [`Your sign-in code is ${this.code}.`, `It works once, for ${this.minutes} minutes.`],
1866
+ outroLines: ["If you didn't just try to sign in, someone else knows your password: change it."]
1867
+ };
1868
+ }
1869
+ };
1870
+ var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
1871
+ var STEP_SECONDS = 30;
1872
+ function base32Encode(bytes) {
1873
+ let bits = 0;
1874
+ let value2 = 0;
1875
+ let out = "";
1876
+ for (const byte of bytes) {
1877
+ value2 = value2 << 8 | byte;
1878
+ bits += 8;
1879
+ while (bits >= 5) {
1880
+ out += ALPHABET[value2 >>> bits - 5 & 31];
1881
+ bits -= 5;
1882
+ }
1883
+ }
1884
+ if (bits > 0) out += ALPHABET[value2 << 5 - bits & 31];
1885
+ return out;
1886
+ }
1887
+ function base32Decode(text4) {
1888
+ const clean = text4.replace(/[\s=]/g, "").toUpperCase();
1889
+ const out = [];
1890
+ let bits = 0;
1891
+ let value2 = 0;
1892
+ for (const char of clean) {
1893
+ const index = ALPHABET.indexOf(char);
1894
+ if (index === -1) throw new Error("Not a base32 secret.");
1895
+ value2 = value2 << 5 | index;
1896
+ bits += 5;
1897
+ if (bits >= 8) {
1898
+ out.push(value2 >>> bits - 8 & 255);
1899
+ bits -= 8;
1900
+ }
1901
+ }
1902
+ return new Uint8Array(out);
1903
+ }
1904
+ function generateSecret() {
1905
+ return base32Encode(new Uint8Array(randomBytes(20)));
1906
+ }
1907
+ function totpCode(secret, at, step = Math.floor(at / 1e3 / STEP_SECONDS)) {
1908
+ const counter = Buffer.alloc(8);
1909
+ counter.writeBigUInt64BE(BigInt(step));
1910
+ const hmac = createHmac2("sha1", base32Decode(secret)).update(counter).digest();
1911
+ const offset = hmac[hmac.length - 1] & 15;
1912
+ const binary = (hmac[offset] & 127) << 24 | hmac[offset + 1] << 16 | hmac[offset + 2] << 8 | hmac[offset + 3];
1913
+ return String(binary % 1e6).padStart(6, "0");
1914
+ }
1915
+ function verifyTotp(secret, code, at, window = 1) {
1916
+ if (!/^\d{6}$/.test(code)) return void 0;
1917
+ const now5 = Math.floor(at / 1e3 / STEP_SECONDS);
1918
+ let matched;
1919
+ for (let step = now5 - window; step <= now5 + window; step++) {
1920
+ if (timingSafeEqual2(Buffer.from(totpCode(secret, at, step)), Buffer.from(code)) && matched === void 0) matched = step;
1921
+ }
1922
+ return matched;
1923
+ }
1924
+ function otpauthUrl(options) {
1925
+ const issuer = encodeURIComponent(options.issuer);
1926
+ return `otpauth://totp/${issuer}:${encodeURIComponent(options.account)}?secret=${options.secret}&issuer=${issuer}&algorithm=SHA1&digits=6&period=${STEP_SECONDS}`;
1927
+ }
1928
+ var PENDING_LOGIN = "oriel.two_factor.login";
1929
+ var pendingKey = (nonce) => `oriel:two-factor:pending:${nonce}`;
1930
+ var emailKey = (id) => `oriel:two-factor:email:${String(id)}`;
1931
+ var stepKey = (id) => `oriel:two-factor:totp:${String(id)}`;
1932
+ function maskEmail(email) {
1933
+ const [name = "", domain = ""] = String(email ?? "").split("@");
1934
+ return `${name.slice(0, 1)}***@${domain}`;
1935
+ }
1936
+ var TwoFactorManager = class {
1937
+ #requireWhen;
1938
+ /** Who is asked for a second factor when two-factor is on. Default: everyone. */
1939
+ requireWhen(callback) {
1940
+ this.#requireWhen = callback;
1941
+ }
1942
+ /**
1943
+ * Checks the credentials. `'authenticated'`: logged in (two-factor off, not required for this user, or no method
1944
+ * open to them). `'challenge'`: the password was right and the code is asked for (an email method's code is sent
1945
+ * now). `false`: wrong credentials.
1946
+ */
1947
+ async attempt(credentials, remember = false) {
1948
+ const config2 = this.#config();
1949
+ const guard2 = this.#guard();
1950
+ if (!config2.enabled) return await guard2.attempt(credentials, remember) ? "authenticated" : false;
1951
+ const guardName = inject(ConfigRepository).get("auth.defaults.guard", "web");
1952
+ await this.#dispatch(new Attempting(guardName, credentials, remember));
1953
+ const valid = await guard2.validate(credentials);
1954
+ const user = guard2.getLastAttempted();
1955
+ if (!valid || user === void 0) {
1956
+ await this.#dispatch(new Failed(guardName, user, credentials));
1957
+ return false;
1958
+ }
1959
+ const method = this.#store().method(user);
1960
+ if (method === void 0 || !await (this.#requireWhen?.(user) ?? true)) {
1961
+ await guard2.login(user, remember);
1962
+ return "authenticated";
1963
+ }
1964
+ const nonce = randomBytes2(16).toString("hex");
1965
+ const record = { id: user.getAuthIdentifier(), remember, method, expires: Date.now() + config2.pending_ttl * 1e3, attempts: 0, nonce };
1966
+ inject(Session).put(PENDING_LOGIN, record);
1967
+ await inject(CacheManager).put(pendingKey(nonce), true, config2.pending_ttl);
1968
+ if (method === "email") await this.#sendCode(user);
1969
+ await this.#dispatch(new TwoFactorChallenged(user, method));
1970
+ return "challenge";
1971
+ }
1972
+ /** The pending login, for the challenge page: the method asked for, the others open, and where an email code went. */
1973
+ async challenge() {
1974
+ const pending = await this.#pending();
1975
+ if (pending === void 0) return void 0;
1976
+ return { method: pending.record.method, methods: this.#store().methods(pending.user), email: maskEmail(pending.user.getAttribute("email")) };
1977
+ }
1978
+ /** A 6-digit code for the pending method, or a recovery code. True: the user is logged in. */
1979
+ async verify(code) {
1980
+ const pending = await this.#pending();
1981
+ if (pending === void 0) return false;
1982
+ const { record, user } = pending;
1983
+ const typed = normalizeCode(code);
1984
+ let used;
1985
+ if (/^\d{6}$/.test(typed)) {
1986
+ if (record.method === "app" ? await this.#checkApp(user, typed) : await this.#checkEmail(user, typed)) used = record.method;
1987
+ } else if (/^[a-z0-9]{10}$/.test(typed) && await this.#checkRecovery(user, typed)) {
1988
+ used = "recovery";
1989
+ }
1990
+ if (used === void 0) {
1991
+ record.attempts++;
1992
+ await this.#dispatch(new TwoFactorFailed(user, record.method));
1993
+ if (record.attempts >= this.#config().max_attempts) await this.#end(record);
1994
+ else inject(Session).put(PENDING_LOGIN, record);
1995
+ return false;
1996
+ }
1997
+ await this.#end(record);
1998
+ await this.#guard().login(user, record.remember);
1999
+ await this.#dispatch(new TwoFactorAuthenticated(user, used));
2000
+ return true;
2001
+ }
2002
+ /** Asks for another of the user's methods; switching to email sends a code. */
2003
+ async useMethod(method) {
2004
+ const pending = await this.#pending();
2005
+ if (pending === void 0 || !this.#store().methods(pending.user).includes(method)) return;
2006
+ pending.record.method = method;
2007
+ inject(Session).put(PENDING_LOGIN, pending.record);
2008
+ if (method === "email") await this.#sendCode(pending.user);
2009
+ }
2010
+ /** Sends a new email code; false within `resend_after` seconds of the last one, or when the method isn't email. */
2011
+ async resend() {
2012
+ const pending = await this.#pending();
2013
+ if (pending === void 0 || pending.record.method !== "email") return false;
2014
+ const last = await inject(CacheManager).get(emailKey(pending.record.id));
2015
+ if (last !== void 0 && Date.now() - last.sent < this.#config().resend_after * 1e3) return false;
2016
+ await this.#sendCode(pending.user);
2017
+ return true;
2018
+ }
2019
+ /**
2020
+ * Starts setting up an authenticator app: a new secret (unconfirmed, so it doesn't count yet), the `otpauth://` URL
2021
+ * and a QR code of it for the app to scan.
2022
+ */
2023
+ async enableApp(user) {
2024
+ const secret = generateSecret();
2025
+ await this.#store().setSecret(user, secret);
2026
+ const issuer = this.#config().issuer ?? inject(ConfigRepository).get("app.name", "Oriel");
2027
+ const url2 = otpauthUrl({ secret, issuer, account: String(user.getAttribute("email") ?? user.getAuthIdentifier()) });
2028
+ return { secret, url: url2, svg: qrSvg(url2) };
2029
+ }
2030
+ /** Confirms the app with a code from it: then it counts, becomes the user's method, and recovery codes are made. */
2031
+ async confirmApp(user, code) {
2032
+ const store = this.#store();
2033
+ const secret = store.secret(user);
2034
+ if (secret === void 0 || verifyTotp(secret, normalizeCode(code), Date.now(), this.#config().window) === void 0) return false;
2035
+ await store.confirm(user);
2036
+ await store.setMethod(user, "app");
2037
+ if (store.recoveryCodes(user).length === 0) await store.setRecoveryCodes(user, generateRecoveryCodes(this.#config().recovery_codes));
2038
+ await this.#dispatch(new TwoFactorAppEnabled(user));
2039
+ return true;
2040
+ }
2041
+ /** Removes the authenticator app: the secret is forgotten and the user is asked by email again. */
2042
+ async disableApp(user) {
2043
+ const store = this.#store();
2044
+ await store.setSecret(user, null);
2045
+ await store.setMethod(user, null);
2046
+ await this.#dispatch(new TwoFactorAppDisabled(user));
2047
+ }
2048
+ /** The method the user prefers; one they can't use (an unconfirmed app, one the app turned off) isn't asked for. */
2049
+ async setMethod(user, method) {
2050
+ await this.#store().setMethod(user, method);
2051
+ }
2052
+ /** The user's recovery codes, made on first ask (show them once, and offer to make new ones). */
2053
+ async recoveryCodes(user) {
2054
+ const store = this.#store();
2055
+ const codes = store.recoveryCodes(user);
2056
+ if (codes.length > 0) return codes;
2057
+ return this.regenerateRecoveryCodes(user);
2058
+ }
2059
+ /** New recovery codes; the old ones stop working. */
2060
+ async regenerateRecoveryCodes(user) {
2061
+ const codes = generateRecoveryCodes(this.#config().recovery_codes);
2062
+ await this.#store().setRecoveryCodes(user, codes);
2063
+ return codes;
2064
+ }
2065
+ #config() {
2066
+ return twoFactorConfig(inject(ConfigRepository));
2067
+ }
2068
+ #store() {
2069
+ return new TwoFactorStore(inject(Encrypter), () => this.#config());
2070
+ }
2071
+ #guard() {
2072
+ return inject(AuthFactory).guard();
2073
+ }
2074
+ async #dispatch(event2) {
2075
+ const container = currentContainer();
2076
+ if (container?.bound(EventDispatcher)) await container.make(EventDispatcher).dispatch(event2);
2077
+ }
2078
+ /** The live pending login and its user; an expired, replayed or orphaned one is forgotten. */
2079
+ async #pending() {
2080
+ const session2 = inject(Session);
2081
+ const record = session2.get(PENDING_LOGIN);
2082
+ if (record === void 0 || record === null) return void 0;
2083
+ const live2 = record.expires > Date.now() && await inject(CacheManager).get(pendingKey(record.nonce)) === true;
2084
+ const user = live2 ? await this.#guard().provider.retrieveById(record.id) : void 0;
2085
+ if (user === void 0) {
2086
+ session2.forget(PENDING_LOGIN);
2087
+ return void 0;
2088
+ }
2089
+ return { record, user };
2090
+ }
2091
+ async #end(record) {
2092
+ inject(Session).forget(PENDING_LOGIN);
2093
+ await inject(CacheManager).forget(pendingKey(record.nonce));
2094
+ }
2095
+ #hash(code) {
2096
+ return createHmac3("sha256", inject(Encrypter).getKey()).update(code).digest("hex");
2097
+ }
2098
+ async #sendCode(user) {
2099
+ const config2 = this.#config();
2100
+ const code = String(randomInt2(1e6)).padStart(6, "0");
2101
+ const now5 = Date.now();
2102
+ const stored = { hash: this.#hash(code), sent: now5, expires: now5 + config2.code_ttl * 1e3 };
2103
+ await inject(CacheManager).put(emailKey(user.getAuthIdentifier()), stored, config2.code_ttl);
2104
+ await sendNotification(user, new TwoFactorCode(code, Math.round(config2.code_ttl / 60)));
2105
+ }
2106
+ async #checkEmail(user, code) {
2107
+ const cache2 = inject(CacheManager);
2108
+ const stored = await cache2.get(emailKey(user.getAuthIdentifier()));
2109
+ if (stored === void 0 || stored.expires <= Date.now()) return false;
2110
+ if (!timingSafeEqual3(Buffer.from(stored.hash), Buffer.from(this.#hash(code)))) return false;
2111
+ await cache2.forget(emailKey(user.getAuthIdentifier()));
2112
+ return true;
2113
+ }
2114
+ async #checkApp(user, code) {
2115
+ const store = this.#store();
2116
+ const secret = store.secret(user);
2117
+ if (secret === void 0 || !store.hasApp(user)) return false;
2118
+ const step = verifyTotp(secret, code, Date.now(), this.#config().window);
2119
+ if (step === void 0) return false;
2120
+ const cache2 = inject(CacheManager);
2121
+ const last = await cache2.get(stepKey(user.getAuthIdentifier()));
2122
+ if (last !== void 0 && step <= last) return false;
2123
+ await cache2.put(stepKey(user.getAuthIdentifier()), step, 300);
2124
+ return true;
2125
+ }
2126
+ async #checkRecovery(user, code) {
2127
+ const store = this.#store();
2128
+ const codes = store.recoveryCodes(user);
2129
+ const index = codes.findIndex((candidate) => normalizeCode(candidate) === code);
2130
+ if (index === -1) return false;
2131
+ await store.setRecoveryCodes(user, codes.filter((_, i) => i !== index));
2132
+ await this.#dispatch(new RecoveryCodeUsed(user));
2133
+ return true;
2134
+ }
2135
+ };
2136
+ var TwoFactorChallenge = class {
2137
+ async handle(request2, next) {
2138
+ if (await inject(TwoFactorManager).challenge() === void 0) throw new AuthenticationError("Log in first.");
2139
+ return next(request2);
2140
+ }
2141
+ };
1685
2142
  var AuthServiceProvider = class extends ServiceProvider {
1686
2143
  register() {
1687
2144
  this.mergeConfigFrom(
@@ -1693,7 +2150,8 @@ var AuthServiceProvider = class extends ServiceProvider {
1693
2150
  },
1694
2151
  providers: { users: { driver: "eloquent", model: null } },
1695
2152
  passwords: { users: { provider: "users", table: "password_reset_tokens", expire: 60, throttle: 60 } },
1696
- password_timeout: 10800
2153
+ password_timeout: 10800,
2154
+ two_factor: { ...TWO_FACTOR_DEFAULTS }
1697
2155
  },
1698
2156
  "auth"
1699
2157
  );
@@ -1703,15 +2161,29 @@ var AuthServiceProvider = class extends ServiceProvider {
1703
2161
  this.app.singleton(PasswordBrokerManager, (container) => new PasswordBrokerManager(container));
1704
2162
  this.app.singleton(PasswordBroker, (container) => container.make(PasswordBrokerManager).broker());
1705
2163
  this.app.singleton(TokenRepository, (container) => new TokenRepository(() => container.make(DatabaseManager)));
2164
+ this.app.singleton(TwoFactorManager, () => new TwoFactorManager());
1706
2165
  }
1707
2166
  async boot() {
1708
2167
  if (this.app.bound(Router)) {
1709
- this.app.make(Router).aliasMiddleware("auth", Authenticate).aliasMiddleware("guest", RedirectIfAuthenticated).aliasMiddleware("can", Authorize).aliasMiddleware("verified", EnsureEmailIsVerified);
2168
+ this.app.make(Router).aliasMiddleware("auth", Authenticate).aliasMiddleware("guest", RedirectIfAuthenticated).aliasMiddleware("can", Authorize).aliasMiddleware("verified", EnsureEmailIsVerified).aliasMiddleware("two-factor.challenge", TwoFactorChallenge);
1710
2169
  }
1711
2170
  if (this.app.bound(EventDispatcher)) this.app.make(EventDispatcher).listen(Registered, SendEmailVerificationNotification);
1712
2171
  await discoverPolicies(this.app.appPath("Policies"), this.app.make(Gate));
2172
+ if (this.app.bound(ConsoleKernel)) {
2173
+ const app2 = this.app;
2174
+ this.app.make(ConsoleKernel).command("two-factor:install", function() {
2175
+ try {
2176
+ this.info(`Wrote ${relative(app2.basePath(), installTwoFactorMigration(app2.databasePath("migrations")))}: run \`oriel migrate\`.`);
2177
+ return 0;
2178
+ } catch (error) {
2179
+ this.error(error instanceof Error ? error.message : String(error));
2180
+ return 1;
2181
+ }
2182
+ }).purpose("Write the migration for two-factor login's columns on users");
2183
+ }
1713
2184
  }
1714
2185
  };
2186
+ var TwoFactor = facade(TwoFactorManager);
1715
2187
 
1716
2188
  // ../Bus/dist/index.js
1717
2189
  var BusError = class extends Error {
@@ -1973,9 +2445,9 @@ var Dispatcher = class {
1973
2445
  }
1974
2446
  #afterResponseList() {
1975
2447
  const owner = currentContainer() ?? this.container;
1976
- let list5 = this.#afterResponse.get(owner);
1977
- if (list5 === void 0) this.#afterResponse.set(owner, list5 = []);
1978
- return list5;
2448
+ let list6 = this.#afterResponse.get(owner);
2449
+ if (list6 === void 0) this.#afterResponse.set(owner, list6 = []);
2450
+ return list6;
1979
2451
  }
1980
2452
  /** Runs the job (like dispatchSync) after the response is sent, or after the current queued job. */
1981
2453
  dispatchAfterResponse(job) {
@@ -2154,7 +2626,7 @@ var BusServiceProvider = class extends ServiceProvider {
2154
2626
  import { setTimeout as sleep } from "timers/promises";
2155
2627
  import { createHash as createHash5 } from "crypto";
2156
2628
  import { link, mkdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
2157
- import { dirname, join as join2 } from "path";
2629
+ import { dirname, join as join3 } from "path";
2158
2630
  import { setTimeout as sleep2 } from "timers/promises";
2159
2631
  import { createHash as createHash22, randomUUID } from "crypto";
2160
2632
  import Redis from "ioredis";
@@ -2430,7 +2902,7 @@ var FileStore = class {
2430
2902
  keyPrefix;
2431
2903
  path(key) {
2432
2904
  const hash2 = createHash5("sha1").update(this.keyPrefix + key).digest("hex");
2433
- return join2(this.directory, hash2.slice(0, 2), hash2.slice(2, 4), hash2);
2905
+ return join3(this.directory, hash2.slice(0, 2), hash2.slice(2, 4), hash2);
2434
2906
  }
2435
2907
  tempPath(path) {
2436
2908
  return `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
@@ -3016,8 +3488,8 @@ var RedisRepository = class extends Repository2 {
3016
3488
  return quit;
3017
3489
  }
3018
3490
  tags(names) {
3019
- const list5 = Array.isArray(names) ? names : [names];
3020
- const taggedStore = this.redisStore.tags(list5);
3491
+ const list6 = Array.isArray(names) ? names : [names];
3492
+ const taggedStore = this.redisStore.tags(list6);
3021
3493
  const prefix = this.redisStore.getPrefix();
3022
3494
  return new Repository2(taggedStore, (_store, name, seconds, owner) => new RedisLock(this.redis, prefix + name, seconds, owner));
3023
3495
  }
@@ -3234,18 +3706,18 @@ var CacheServiceProvider = class extends ServiceProvider {
3234
3706
  };
3235
3707
 
3236
3708
  // ../Console/dist/index.js
3237
- import { existsSync } from "fs";
3238
- import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
3239
- import { dirname as dirname2, join as join3, relative, sep } from "path";
3240
3709
  import { existsSync as existsSync2 } from "fs";
3710
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
3711
+ import { dirname as dirname2, join as join4, relative as relative2, sep } from "path";
3712
+ import { existsSync as existsSync22 } from "fs";
3241
3713
  import { join as join22 } from "path";
3242
3714
  import { existsSync as existsSync3 } from "fs";
3243
3715
  import { mkdir as mkdir22, writeFile as writeFile22 } from "fs/promises";
3244
3716
  import { dirname as dirname22, join as join32 } from "path";
3245
- import { join as join4 } from "path";
3717
+ import { join as join42 } from "path";
3246
3718
  import { spawn } from "child_process";
3247
3719
  import { start as start2 } from "repl";
3248
- import { existsSync as existsSync4, readdirSync } from "fs";
3720
+ import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
3249
3721
  import { join as join5 } from "path";
3250
3722
  import { pathToFileURL as pathToFileURL2 } from "url";
3251
3723
  import { createHash as createHash6 } from "crypto";
@@ -3703,7 +4175,7 @@ var GeneratorCommand = class extends Command {
3703
4175
  async prepare(_rawName) {
3704
4176
  }
3705
4177
  qualifyClass(rawName) {
3706
- const segments = rawName.split("/").filter(Boolean).map((part) => Str.studly(part));
4178
+ const segments = rawName.split("/").filter(Boolean).map((part2) => Str.studly(part2));
3707
4179
  const last = segments.pop();
3708
4180
  if (!last) throw new Error("A name is required.");
3709
4181
  const suffix2 = this.suffix();
@@ -3720,7 +4192,7 @@ var GeneratorCommand = class extends Command {
3720
4192
  }
3721
4193
  targetPath(rawName) {
3722
4194
  const { directories, className } = this.qualifyClass(rawName);
3723
- return join3(this.root(this.type()), ...directories, this.fileName(className));
4195
+ return join4(this.root(this.type()), ...directories, this.fileName(className));
3724
4196
  }
3725
4197
  /** The singular kind of file for messages, like Laravel's `$type`: 'Http/Controllers' -> 'Controller'. */
3726
4198
  kind() {
@@ -3728,13 +4200,13 @@ var GeneratorCommand = class extends Command {
3728
4200
  }
3729
4201
  /** The import specifier from the generated file to `target` (an absolute path without extension): './X', '../Y/X'. */
3730
4202
  importPath(rawName, target) {
3731
- const specifier = relative(dirname2(this.targetPath(rawName)), target).split(sep).join("/");
4203
+ const specifier = relative2(dirname2(this.targetPath(rawName)), target).split(sep).join("/");
3732
4204
  return specifier.startsWith(".") ? specifier : `./${specifier}`;
3733
4205
  }
3734
4206
  /** A --model value as studly segments: 'Admin/blog_post' -> ['Admin', 'BlogPost']; undefined when there is none. */
3735
4207
  modelSegments(value2) {
3736
4208
  if (typeof value2 !== "string") return void 0;
3737
- const segments = value2.split(/[/\\]/).filter(Boolean).map((part) => Str.studly(part));
4209
+ const segments = value2.split(/[/\\]/).filter(Boolean).map((part2) => Str.studly(part2));
3738
4210
  return segments.length > 0 ? segments : void 0;
3739
4211
  }
3740
4212
  /** The model's file under app/Models, without the extension (Laravel's qualifyModel() into App\Models). */
@@ -3748,7 +4220,7 @@ var GeneratorCommand = class extends Command {
3748
4220
  return 1;
3749
4221
  }
3750
4222
  const target = this.targetPath(rawName);
3751
- if (existsSync(target) && !this.option("force")) {
4223
+ if (existsSync2(target) && !this.option("force")) {
3752
4224
  this.error(`${this.kind()} already exists.`);
3753
4225
  return 1;
3754
4226
  }
@@ -4279,7 +4751,7 @@ var MakeControllerCommand = class extends GeneratorCommand {
4279
4751
  async prepare() {
4280
4752
  const segments = this.modelSegments(this.option("model"));
4281
4753
  if (!segments) return;
4282
- if (!existsSync2(`${this.modelPath(segments)}.ts`)) await this.call("make:model", { name: segments.join("/") });
4754
+ if (!existsSync22(`${this.modelPath(segments)}.ts`)) await this.call("make:model", { name: segments.join("/") });
4283
4755
  if (this.option("requests")) {
4284
4756
  const model = segments.at(-1);
4285
4757
  await this.call("make:request", { name: `Store${model}Request` });
@@ -4343,7 +4815,7 @@ var MakeFactoryCommand = class extends GeneratorCommand {
4343
4815
  extraPlaceholders(rawName) {
4344
4816
  const option = this.option("model");
4345
4817
  const modelName = typeof option === "string" ? option : this.qualifyClass(rawName).className.replace(/Factory$/, "");
4346
- const segments = modelName.split("/").filter(Boolean).map((part) => Str.studly(part));
4818
+ const segments = modelName.split("/").filter(Boolean).map((part2) => Str.studly(part2));
4347
4819
  return {
4348
4820
  model: segments.at(-1) ?? modelName,
4349
4821
  modelImport: this.importPath(rawName, inject(Application).appPath(["Models", ...segments].join("/")))
@@ -4374,7 +4846,7 @@ var MakeListenerCommand = class extends GeneratorCommand {
4374
4846
  extraPlaceholders(rawName) {
4375
4847
  const event2 = this.option("event");
4376
4848
  if (typeof event2 !== "string") return {};
4377
- const segments = event2.split("/").filter(Boolean).map((part) => Str.studly(part));
4849
+ const segments = event2.split("/").filter(Boolean).map((part2) => Str.studly(part2));
4378
4850
  return {
4379
4851
  event: segments.at(-1) ?? event2,
4380
4852
  eventImport: this.importPath(rawName, inject(Application).appPath(["Events", ...segments].join("/")))
@@ -4404,7 +4876,7 @@ var MakeMailCommand = class extends GeneratorCommand {
4404
4876
  viewName(rawName) {
4405
4877
  const option = this.option("view");
4406
4878
  if (typeof option === "string" && option !== "") return option;
4407
- const parts = rawName.split("/").filter(Boolean).map((part) => Str.kebab(Str.studly(part)));
4879
+ const parts = rawName.split("/").filter(Boolean).map((part2) => Str.kebab(Str.studly(part2)));
4408
4880
  return ["mail", ...parts].join(".");
4409
4881
  }
4410
4882
  /** Laravel's writeView(): an existing view is reported and left alone, even with --force. */
@@ -4620,7 +5092,7 @@ var MakeTestCommand = class extends GeneratorCommand {
4620
5092
  return TEST_STUB;
4621
5093
  }
4622
5094
  root(subpath) {
4623
- return inject(Application).basePath(join4("tests", subpath));
5095
+ return inject(Application).basePath(join42("tests", subpath));
4624
5096
  }
4625
5097
  fileName(className) {
4626
5098
  return `${className.replace(/(?<=.)Test$/, "")}.test.ts`;
@@ -4835,7 +5307,7 @@ var TinkerCommand = class extends Command {
4835
5307
  };
4836
5308
  function sourceFiles(directory) {
4837
5309
  const files3 = [];
4838
- for (const entry of readdirSync(directory, { withFileTypes: true })) {
5310
+ for (const entry of readdirSync2(directory, { withFileTypes: true })) {
4839
5311
  const path = join5(directory, entry.name);
4840
5312
  if (entry.isDirectory()) files3.push(...sourceFiles(path));
4841
5313
  else if (/\.(ts|js|mts|mjs)$/.test(entry.name) && !/\.d\.[mc]?ts$/.test(entry.name)) files3.push(path);
@@ -4933,6 +5405,15 @@ var Kernel2 = class {
4933
5405
  defaultOutput;
4934
5406
  lastOutput = "";
4935
5407
  running = 0;
5408
+ loader;
5409
+ bootstrapping;
5410
+ /** What bootstrap() runs once: the provider hands it the loading of the app's commands and routes/console.ts. */
5411
+ bootstrapWith(loader) {
5412
+ this.loader = loader;
5413
+ }
5414
+ bootstrap() {
5415
+ return this.bootstrapping ??= this.loader?.() ?? Promise.resolve();
5416
+ }
4936
5417
  register(...commands) {
4937
5418
  for (const entry of commands) {
4938
5419
  const instance = typeof entry === "function" ? new entry() : entry;
@@ -5362,6 +5843,27 @@ var ScheduledEventImpl = class {
5362
5843
  if (this.betweenWindow && !this.isWithinWindow(this.betweenWindow, now5)) return false;
5363
5844
  return !this.unlessWindow || !this.isWithinWindow(this.unlessWindow, now5);
5364
5845
  }
5846
+ /**
5847
+ * The latest minute after `from` and up to `to` at which this event was due, its between()/unlessBetween() windows
5848
+ * judged at that minute; undefined when there is none. Walks back from `to` (at most 10,000 due minutes).
5849
+ */
5850
+ dueAt(from, to) {
5851
+ const end = Math.floor(to.getTime() / 6e4) * 6e4;
5852
+ const parsed = CronExpressionParser.parse(this.cronExpression, { tz: this.zone(), currentDate: new Date(end + 6e4) });
5853
+ for (let step = 0; step < 1e4; step++) {
5854
+ let minute;
5855
+ try {
5856
+ minute = parsed.prev().toDate();
5857
+ } catch {
5858
+ return void 0;
5859
+ }
5860
+ if (minute.getTime() <= from.getTime()) return void 0;
5861
+ if (this.betweenWindow && !this.isWithinWindow(this.betweenWindow, minute)) continue;
5862
+ if (this.unlessWindow && this.isWithinWindow(this.unlessWindow, minute)) continue;
5863
+ return minute;
5864
+ }
5865
+ return void 0;
5866
+ }
5365
5867
  zone() {
5366
5868
  if (this.tz !== void 0) return this.tz;
5367
5869
  const container = currentContainer();
@@ -5482,6 +5984,9 @@ var ScheduleImpl = class {
5482
5984
  dueEvents(now5) {
5483
5985
  return this.scheduledEvents.filter((event2) => event2.isDue(now5));
5484
5986
  }
5987
+ dueBetween(from, to) {
5988
+ return this.scheduledEvents.filter((event2) => event2.dueAt(from, to) !== void 0);
5989
+ }
5485
5990
  push(event2) {
5486
5991
  this.scheduledEvents.push(event2);
5487
5992
  return event2;
@@ -5490,7 +5995,14 @@ var ScheduleImpl = class {
5490
5995
  var ConsoleServiceProvider = class extends ServiceProvider {
5491
5996
  register() {
5492
5997
  this.mergeConfigFrom({ stubs: {} }, "console");
5493
- this.app.singleton(ConsoleKernel, () => new Kernel2(this.app));
5998
+ this.app.singleton(ConsoleKernel, () => {
5999
+ const kernel = new Kernel2(this.app);
6000
+ kernel.bootstrapWith(async () => {
6001
+ await discoverCommands(kernel, this.app.appPath("Console/Commands"));
6002
+ await loadConsoleRoutes(this.app, kernel);
6003
+ });
6004
+ return kernel;
6005
+ });
5494
6006
  this.app.singleton(Schedule, () => new ScheduleImpl());
5495
6007
  }
5496
6008
  boot() {
@@ -5527,9 +6039,7 @@ var ConsoleServiceProvider = class extends ServiceProvider {
5527
6039
  );
5528
6040
  this.app.booted(async (app2) => {
5529
6041
  if (!app2.runningInConsole()) return;
5530
- const kernel = app2.make(ConsoleKernel);
5531
- await discoverCommands(kernel, app2.appPath("Console/Commands"));
5532
- await loadConsoleRoutes(app2, kernel);
6042
+ await app2.make(ConsoleKernel).bootstrap();
5533
6043
  });
5534
6044
  }
5535
6045
  };
@@ -5555,7 +6065,7 @@ async function runConsoleAndExit(app2, argv = process.argv.slice(2), exit = (cod
5555
6065
  }
5556
6066
 
5557
6067
  // ../Cookie/dist/index.js
5558
- import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
6068
+ import { createHmac as createHmac4, timingSafeEqual as timingSafeEqual4 } from "crypto";
5559
6069
  var SAME_SITE = { lax: "Lax", strict: "Strict", none: "None" };
5560
6070
  function serializeCookie(cookie2, now5 = Date.now()) {
5561
6071
  const parts = [];
@@ -5593,12 +6103,12 @@ function parseSetCookie(header) {
5593
6103
  }
5594
6104
  function parseCookieHeader(header) {
5595
6105
  const cookies = {};
5596
- for (const part of (header ?? "").split(";")) {
5597
- const equals4 = part.indexOf("=");
6106
+ for (const part2 of (header ?? "").split(";")) {
6107
+ const equals4 = part2.indexOf("=");
5598
6108
  if (equals4 === -1) continue;
5599
- const name = part.slice(0, equals4).trim();
6109
+ const name = part2.slice(0, equals4).trim();
5600
6110
  if (name === "" || Object.hasOwn(cookies, name) || name === "__proto__") continue;
5601
- cookies[name] = decode(part.slice(equals4 + 1).trim());
6111
+ cookies[name] = decode(part2.slice(equals4 + 1).trim());
5602
6112
  }
5603
6113
  return cookies;
5604
6114
  }
@@ -5609,7 +6119,7 @@ function withHeaders(response2, change) {
5609
6119
  }
5610
6120
  var CookieValuePrefix = {
5611
6121
  create(name, key) {
5612
- return `${createHmac2("sha1", key).update(`${name}v2`).digest("hex")}|`;
6122
+ return `${createHmac4("sha1", key).update(`${name}v2`).digest("hex")}|`;
5613
6123
  },
5614
6124
  remove(value2) {
5615
6125
  return value2.slice(41);
@@ -5619,7 +6129,7 @@ var CookieValuePrefix = {
5619
6129
  const actual = Buffer.from(value2.slice(0, 41));
5620
6130
  for (const key of keys) {
5621
6131
  const expected = Buffer.from(CookieValuePrefix.create(name, key));
5622
- if (actual.length === expected.length && timingSafeEqual2(actual, expected)) return CookieValuePrefix.remove(value2);
6132
+ if (actual.length === expected.length && timingSafeEqual4(actual, expected)) return CookieValuePrefix.remove(value2);
5623
6133
  }
5624
6134
  return void 0;
5625
6135
  }
@@ -5688,11 +6198,11 @@ var AddQueuedCookiesToResponse = class {
5688
6198
  };
5689
6199
  var neverEncrypt = /* @__PURE__ */ new Set();
5690
6200
  function isDeletion(attributes, now5 = Date.now()) {
5691
- for (const part of attributes.split(";")) {
5692
- const equals4 = part.indexOf("=");
6201
+ for (const part2 of attributes.split(";")) {
6202
+ const equals4 = part2.indexOf("=");
5693
6203
  if (equals4 === -1) continue;
5694
- const key = part.slice(0, equals4).trim().toLowerCase();
5695
- const value2 = part.slice(equals4 + 1).trim();
6204
+ const key = part2.slice(0, equals4).trim().toLowerCase();
6205
+ const value2 = part2.slice(equals4 + 1).trim();
5696
6206
  if (key === "max-age" && Number(value2) <= 0) return true;
5697
6207
  if (key === "expires" && Date.parse(value2) <= now5) return true;
5698
6208
  }
@@ -5791,21 +6301,21 @@ import { sql as sql4 } from "kysely";
5791
6301
  import { sql as sql5 } from "kysely";
5792
6302
  import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
5793
6303
  import { isDeepStrictEqual } from "util";
5794
- import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
5795
- import { join as join42 } from "path";
6304
+ import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
6305
+ import { join as join43 } from "path";
5796
6306
  import { pathToFileURL as pathToFileURL3 } from "url";
5797
6307
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
5798
- import { existsSync as existsSync22, mkdirSync, writeFileSync } from "fs";
6308
+ import { existsSync as existsSync23, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
5799
6309
  import { join as join52 } from "path";
5800
6310
  import { existsSync as existsSync32, readdirSync as readdirSync22 } from "fs";
5801
6311
  import { basename, extname, join as join6 } from "path";
5802
6312
  import { pathToFileURL as pathToFileURL22 } from "url";
5803
6313
  import { existsSync as existsSync42 } from "fs";
5804
- import { isAbsolute, join as join7, relative as relative2 } from "path";
6314
+ import { isAbsolute, join as join7, relative as relative22 } from "path";
5805
6315
  import { pathToFileURL as pathToFileURL32 } from "url";
5806
6316
  import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
5807
6317
  import { dirname as dirname3, relative as relative3, sep as sep2 } from "path";
5808
- import { existsSync as existsSync52, readdirSync as readdirSync3, statSync } from "fs";
6318
+ import { existsSync as existsSync52, readdirSync as readdirSync32, statSync } from "fs";
5809
6319
  import { join as join8 } from "path";
5810
6320
  import { pathToFileURL as pathToFileURL4 } from "url";
5811
6321
  import { randomUUID as randomUUID2 } from "crypto";
@@ -6217,9 +6727,9 @@ var Cursor = class _Cursor {
6217
6727
  static fromEncoded(encoded) {
6218
6728
  if (typeof encoded !== "string" || encoded === "") return void 0;
6219
6729
  try {
6220
- const json2 = JSON.parse(Buffer.from(encoded.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8"));
6221
- if (typeof json2 !== "object" || json2 === null || Array.isArray(json2)) return void 0;
6222
- const { _pointsToNextItems: pointsToNext, ...parameters } = json2;
6730
+ const json3 = JSON.parse(Buffer.from(encoded.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8"));
6731
+ if (typeof json3 !== "object" || json3 === null || Array.isArray(json3)) return void 0;
6732
+ const { _pointsToNextItems: pointsToNext, ...parameters } = json3;
6223
6733
  if (typeof pointsToNext !== "boolean") return void 0;
6224
6734
  return new _Cursor(parameters, pointsToNext);
6225
6735
  } catch {
@@ -6436,9 +6946,9 @@ var Paginator = class extends AbstractPaginator {
6436
6946
  };
6437
6947
  }
6438
6948
  };
6439
- var pad = (n, width = 2) => String(n).padStart(width, "0");
6949
+ var pad2 = (n, width = 2) => String(n).padStart(width, "0");
6440
6950
  function formatDateTime(date) {
6441
- return `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())} ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())}`;
6951
+ return `${pad2(date.getUTCFullYear(), 4)}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())} ${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}:${pad2(date.getUTCSeconds())}`;
6442
6952
  }
6443
6953
  function formatDate(date) {
6444
6954
  return formatDateTime(date).slice(0, 10);
@@ -6470,7 +6980,7 @@ var OPERATORS = /* @__PURE__ */ new Set(["=", "<", ">", "<=", ">=", "<>", "!=",
6470
6980
  var EMPTY = sql2.raw("");
6471
6981
  var isSqlFragment = (value2) => typeof value2 === "object" && value2 !== null && typeof value2.toOperationNode === "function";
6472
6982
  var join9 = (parts, separator = ", ") => sql2.join(parts, sql2.raw(separator));
6473
- var clauses = (parts) => join9(parts.filter((part) => part !== void 0), " ");
6983
+ var clauses = (parts) => join9(parts.filter((part2) => part2 !== void 0), " ");
6474
6984
  function likeToGlob(pattern) {
6475
6985
  return pattern.replaceAll("*", "[*]").replaceAll("?", "[?]").replaceAll("%", "*").replaceAll("_", "?");
6476
6986
  }
@@ -6741,17 +7251,17 @@ function keyOf(item, alias) {
6741
7251
  }
6742
7252
  return value2;
6743
7253
  }
6744
- var pad2 = (value2, width = 2) => String(value2).padStart(width, "0");
6745
- function dateWhereValue(part, value2) {
7254
+ var pad22 = (value2, width = 2) => String(value2).padStart(width, "0");
7255
+ function dateWhereValue(part2, value2) {
6746
7256
  if (value2 instanceof Expression) return value2;
6747
7257
  if (value2 instanceof Date) {
6748
- if (part === "date") return `${pad2(value2.getUTCFullYear(), 4)}-${pad2(value2.getUTCMonth() + 1)}-${pad2(value2.getUTCDate())}`;
6749
- if (part === "time") return `${pad2(value2.getUTCHours())}:${pad2(value2.getUTCMinutes())}:${pad2(value2.getUTCSeconds())}`;
6750
- if (part === "day") return pad2(value2.getUTCDate());
6751
- if (part === "month") return pad2(value2.getUTCMonth() + 1);
7258
+ if (part2 === "date") return `${pad22(value2.getUTCFullYear(), 4)}-${pad22(value2.getUTCMonth() + 1)}-${pad22(value2.getUTCDate())}`;
7259
+ if (part2 === "time") return `${pad22(value2.getUTCHours())}:${pad22(value2.getUTCMinutes())}:${pad22(value2.getUTCSeconds())}`;
7260
+ if (part2 === "day") return pad22(value2.getUTCDate());
7261
+ if (part2 === "month") return pad22(value2.getUTCMonth() + 1);
6752
7262
  return String(value2.getUTCFullYear());
6753
7263
  }
6754
- if (part === "day" || part === "month") return pad2(Math.trunc(Number(value2)) || 0);
7264
+ if (part2 === "day" || part2 === "month") return pad22(Math.trunc(Number(value2)) || 0);
6755
7265
  return typeof value2 === "number" ? String(value2) : value2;
6756
7266
  }
6757
7267
  var isRow = (value2) => typeof value2 === "object" && value2 !== null && !(value2 instanceof Expression) && !(value2 instanceof Builder) && !Array.isArray(value2);
@@ -7310,15 +7820,15 @@ var Builder = class _Builder {
7310
7820
  return [...this.connection.compile(this.grammar.compileSelect(this)).parameters];
7311
7821
  }
7312
7822
  clone() {
7313
- const copy = Object.create(Object.getPrototypeOf(this));
7314
- Object.assign(copy, this);
7315
- copy.columns = [...this.columns];
7316
- copy.joins = [...this.joins];
7317
- copy.wheres = [...this.wheres];
7318
- copy.groups = [...this.groups];
7319
- copy.havings = [...this.havings];
7320
- copy.orders = [...this.orders];
7321
- return copy;
7823
+ const copy2 = Object.create(Object.getPrototypeOf(this));
7824
+ Object.assign(copy2, this);
7825
+ copy2.columns = [...this.columns];
7826
+ copy2.joins = [...this.joins];
7827
+ copy2.wheres = [...this.wheres];
7828
+ copy2.groups = [...this.groups];
7829
+ copy2.havings = [...this.havings];
7830
+ copy2.orders = [...this.orders];
7831
+ return copy2;
7322
7832
  }
7323
7833
  /** A fresh builder on the same connection and table (for nested where groups). */
7324
7834
  newQuery() {
@@ -7361,10 +7871,10 @@ var Builder = class _Builder {
7361
7871
  return this;
7362
7872
  }
7363
7873
  /** Laravel's addDateBasedWhere(): one value means '='; the operator is checked against the whitelist. */
7364
- addDateBasedWhere(part, column, args, boolean) {
7874
+ addDateBasedWhere(part2, column, args, boolean) {
7365
7875
  const [operator, value2] = args.length === 1 ? ["=", args[0]] : [args[0].toLowerCase(), args[1]];
7366
7876
  this.grammar.operator(operator);
7367
- this.wheres.push({ type: "date", part, column, operator, value: dateWhereValue(part, value2), boolean });
7877
+ this.wheres.push({ type: "date", part: part2, column, operator, value: dateWhereValue(part2, value2), boolean });
7368
7878
  return this;
7369
7879
  }
7370
7880
  pushNull(column, not, boolean) {
@@ -7931,7 +8441,7 @@ var SchemaGrammar = class {
7931
8441
  return sql4`${sql4.raw(type)} check (${sql4.id(column.name)} in (${allowed}))`;
7932
8442
  }
7933
8443
  };
7934
- var present = (parts) => parts.filter((part) => part !== void 0);
8444
+ var present = (parts) => parts.filter((part2) => part2 !== void 0);
7935
8445
  function mergeRebuilds(steps) {
7936
8446
  const merged = [];
7937
8447
  for (const step of steps) {
@@ -9624,12 +10134,12 @@ var Builder2 = class extends Builder {
9624
10134
  return Builder.prototype.getBindings.call(this.applyScopes());
9625
10135
  }
9626
10136
  clone() {
9627
- const copy = super.clone();
9628
- copy.scopes = new Map(this.scopes);
9629
- copy.removedScopes = [...this.removedScopes];
9630
- copy.eagerLoad = new Map(this.eagerLoad);
9631
- copy.selectCasts = { ...this.selectCasts };
9632
- return new Proxy(copy, handler);
10137
+ const copy2 = super.clone();
10138
+ copy2.scopes = new Map(this.scopes);
10139
+ copy2.removedScopes = [...this.removedScopes];
10140
+ copy2.eagerLoad = new Map(this.eagerLoad);
10141
+ copy2.selectCasts = { ...this.selectCasts };
10142
+ return new Proxy(copy2, handler);
9633
10143
  }
9634
10144
  /** Nested where groups get a builder of the same model (without global scopes), so local scopes work inside them. */
9635
10145
  newQuery() {
@@ -9939,11 +10449,11 @@ var EloquentCollection = class extends Collection {
9939
10449
  };
9940
10450
  async function loadMissingOn(models, paths) {
9941
10451
  for (const path of paths) {
9942
- const [head = "", ...rest] = path.split(".");
9943
- const missing = models.filter((model) => !model.relationLoaded(head));
10452
+ const [head2 = "", ...rest] = path.split(".");
10453
+ const missing = models.filter((model) => !model.relationLoaded(head2));
9944
10454
  if (missing.length) await missing[0].newQueryWithoutRelationships().with(path).eagerLoadRelations(missing);
9945
10455
  if (!rest.length) continue;
9946
- const related = models.flatMap((model) => relatedModels(model.getRelations()[head]));
10456
+ const related = models.flatMap((model) => relatedModels(model.getRelations()[head2]));
9947
10457
  if (related.length) await loadMissingOn(related, [rest.join(".")]);
9948
10458
  }
9949
10459
  }
@@ -10648,9 +11158,9 @@ var Factory2 = class _Factory {
10648
11158
  return new Collection(created);
10649
11159
  }
10650
11160
  newInstance(changes) {
10651
- const copy = Object.create(Object.getPrototypeOf(this));
10652
- Object.assign(copy, this, changes);
10653
- return copy;
11161
+ const copy2 = Object.create(Object.getPrototypeOf(this));
11162
+ Object.assign(copy2, this, changes);
11163
+ return copy2;
10654
11164
  }
10655
11165
  async makeModels(attributes) {
10656
11166
  const models = [];
@@ -10710,9 +11220,9 @@ var Factory2 = class _Factory {
10710
11220
  };
10711
11221
  async function discoverFactories(path) {
10712
11222
  if (!existsSync5(path)) return;
10713
- for (const file of readdirSync2(path).sort()) {
11223
+ for (const file of readdirSync3(path).sort()) {
10714
11224
  if (!/\.(ts|js|mts|mjs)$/.test(file) || /\.d\.[mc]?ts$/.test(file)) continue;
10715
- const module = await import(pathToFileURL3(join42(path, file)).href);
11225
+ const module = await import(pathToFileURL3(join43(path, file)).href);
10716
11226
  for (const exported of Object.values(module)) {
10717
11227
  if (typeof exported === "function" && exported.prototype instanceof Factory2) {
10718
11228
  Factory2.register(exported);
@@ -12482,9 +12992,9 @@ var MigrationCreator = class {
12482
12992
  }
12483
12993
  const stub = table === void 0 ? MIGRATION_STUB : create ? MIGRATION_CREATE_STUB : MIGRATION_UPDATE_STUB;
12484
12994
  const file = join52(path, `${migrationDatePrefix(options.date)}_${snake}.ts`);
12485
- if (existsSync22(file)) throw new LogicError(`Migration [${file}] already exists.`);
12486
- mkdirSync(path, { recursive: true });
12487
- writeFileSync(
12995
+ if (existsSync23(file)) throw new LogicError(`Migration [${file}] already exists.`);
12996
+ mkdirSync2(path, { recursive: true });
12997
+ writeFileSync2(
12488
12998
  file,
12489
12999
  stub.replaceAll("{{ importSource }}", options.importSource ?? "@orieljs/framework").replaceAll("{{ table }}", table ?? "")
12490
13000
  );
@@ -12879,7 +13389,7 @@ async function generateModelTypes(context) {
12879
13389
  if (!context.models) return void 0;
12880
13390
  const outputFile = context.app.basePath(join7("types", "models.d.ts"));
12881
13391
  await new TypeGenerator({ outputFile, importSource: context.typesImportSource }).write((await context.models()).all());
12882
- return relative2(context.app.basePath(), outputFile);
13392
+ return relative22(context.app.basePath(), outputFile);
12883
13393
  }
12884
13394
  async function writeModelTypes(context, command) {
12885
13395
  if (context.app.isProduction() || !existsSync42(context.app.basePath("types"))) return;
@@ -12970,7 +13480,7 @@ function registerMigrationCommands(kernel, context) {
12970
13480
  table: text(command.option("table")),
12971
13481
  importSource: context.stubsImportSource
12972
13482
  });
12973
- command.info(`Migration [${relative2(context.app.basePath(), file)}] created successfully.`);
13483
+ command.info(`Migration [${relative22(context.app.basePath(), file)}] created successfully.`);
12974
13484
  return 0;
12975
13485
  });
12976
13486
  }
@@ -13025,7 +13535,7 @@ var ModelRegistry = class {
13025
13535
  function listFiles(directory) {
13026
13536
  if (!existsSync52(directory)) return [];
13027
13537
  const files3 = [];
13028
- for (const entry of readdirSync3(directory).sort()) {
13538
+ for (const entry of readdirSync32(directory).sort()) {
13029
13539
  const path = join8(directory, entry);
13030
13540
  if (statSync(path).isDirectory()) files3.push(...listFiles(path));
13031
13541
  else if (MODEL_FILE.test(entry)) files3.push(path);
@@ -13505,7 +14015,7 @@ var DatabaseServiceProvider = class extends ServiceProvider {
13505
14015
  };
13506
14016
 
13507
14017
  // ../Encryption/dist/index.js
13508
- import { createCipheriv, createDecipheriv, createHmac as createHmac3, randomBytes, timingSafeEqual as timingSafeEqual3 } from "crypto";
14018
+ import { createCipheriv, createDecipheriv, createHmac as createHmac5, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual5 } from "crypto";
13509
14019
  import { readFile as readFile2, writeFile as writeFile4 } from "fs/promises";
13510
14020
  var EncryptionFailure = class extends Error {
13511
14021
  kind = "Server";
@@ -13541,7 +14051,7 @@ var cipherSpec = (cipher) => Object.hasOwn(CIPHERS, cipher.toLowerCase()) ? CIPH
13541
14051
  function equals2(known, user) {
13542
14052
  const a = Buffer.from(known);
13543
14053
  const b = Buffer.from(user);
13544
- return a.length === b.length && timingSafeEqual3(a, b);
14054
+ return a.length === b.length && timingSafeEqual5(a, b);
13545
14055
  }
13546
14056
  function parseKey(key) {
13547
14057
  if (typeof key !== "string" || key === "") throw new MissingAppKeyError();
@@ -13565,13 +14075,13 @@ var Encrypter2 = class _Encrypter {
13565
14075
  static generateKey(cipher = "aes-256-gcm") {
13566
14076
  const spec = cipherSpec(cipher);
13567
14077
  if (spec === void 0) throw new UnsupportedCipherError();
13568
- return new Uint8Array(randomBytes(spec.size));
14078
+ return new Uint8Array(randomBytes3(spec.size));
13569
14079
  }
13570
14080
  encrypt(value2, serialize3 = true) {
13571
14081
  const plain2 = serialize3 ? JSON.stringify(value2) : value2;
13572
14082
  if (typeof plain2 !== "string") throw new EncryptError("Could not encrypt the data.");
13573
14083
  const spec = CIPHERS[this.#cipher];
13574
- const ivBytes = randomBytes(spec.ivLength);
14084
+ const ivBytes = randomBytes3(spec.ivLength);
13575
14085
  let encrypted;
13576
14086
  let tagBytes;
13577
14087
  try {
@@ -13637,7 +14147,7 @@ var Encrypter2 = class _Encrypter {
13637
14147
  return this;
13638
14148
  }
13639
14149
  #hash(iv, value2, key) {
13640
- return createHmac3("sha256", key).update(iv + value2).digest("hex");
14150
+ return createHmac5("sha256", key).update(iv + value2).digest("hex");
13641
14151
  }
13642
14152
  #decryptWith(key, iv, value2, tag) {
13643
14153
  try {
@@ -13717,8 +14227,8 @@ function decrypt(payload, unserialize = true) {
13717
14227
  return inject(Encrypter).decrypt(payload, unserialize);
13718
14228
  }
13719
14229
  function previousKeys(value2) {
13720
- const list5 = Array.isArray(value2) ? value2 : typeof value2 === "string" ? value2.split(",") : [];
13721
- return list5.map((key) => String(key).trim()).filter((key) => key !== "");
14230
+ const list6 = Array.isArray(value2) ? value2 : typeof value2 === "string" ? value2.split(",") : [];
14231
+ return list6.map((key) => String(key).trim()).filter((key) => key !== "");
13722
14232
  }
13723
14233
  var EncryptionServiceProvider = class extends ServiceProvider {
13724
14234
  register() {
@@ -13921,9 +14431,9 @@ var Dispatcher2 = class {
13921
14431
  throw new InvalidListenerError(label);
13922
14432
  }
13923
14433
  for (const event2 of Array.isArray(events) ? events : [events]) {
13924
- const list5 = this.listeners.get(event2) ?? [];
13925
- list5.push(listener);
13926
- this.listeners.set(event2, list5);
14434
+ const list6 = this.listeners.get(event2) ?? [];
14435
+ list6.push(listener);
14436
+ this.listeners.set(event2, list6);
13927
14437
  }
13928
14438
  }
13929
14439
  hasListeners(event2) {
@@ -13935,12 +14445,12 @@ var Dispatcher2 = class {
13935
14445
  }
13936
14446
  /** Laravel's getRawListeners(): each event's listeners as registered (closures, classes, [class, method]). A copy. */
13937
14447
  getRawListeners() {
13938
- return new Map([...this.listeners].map(([event2, list5]) => [event2, [...list5]]));
14448
+ return new Map([...this.listeners].map(([event2, list6]) => [event2, [...list6]]));
13939
14449
  }
13940
14450
  /** Introspection for the event:list command. Not part of the EventDispatcher contract. */
13941
14451
  registeredEvents() {
13942
14452
  const rows = [];
13943
- for (const [key, list5] of this.listeners) rows.push({ event: nameOf(key), listeners: list5.length });
14453
+ for (const [key, list6] of this.listeners) rows.push({ event: nameOf(key), listeners: list6.length });
13944
14454
  return rows.sort((a, b) => a.event.localeCompare(b.event));
13945
14455
  }
13946
14456
  subscribe(subscriber) {
@@ -13984,8 +14494,8 @@ var Dispatcher2 = class {
13984
14494
  const exact = [...this.listeners.get(key) ?? []];
13985
14495
  const wildcardLists = typeof key === "string" ? this.wildcardPatternsFor(key).map((pattern) => [...this.listeners.get(pattern) ?? []]) : [];
13986
14496
  for (const listener of exact) yield { listener, wildcard: false };
13987
- for (const list5 of wildcardLists) {
13988
- for (const listener of list5) yield { listener, wildcard: true };
14497
+ for (const list6 of wildcardLists) {
14498
+ for (const listener of list6) yield { listener, wildcard: true };
13989
14499
  }
13990
14500
  }
13991
14501
  wildcardPatternsFor(event2) {
@@ -14084,6 +14594,8 @@ import {
14084
14594
  PutObjectCommand
14085
14595
  } from "@aws-sdk/client-s3";
14086
14596
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
14597
+ import { copy, del, head, list as list4, put } from "@vercel/blob";
14598
+ import { posix as posix2 } from "path";
14087
14599
  import { lstat, mkdir as mkdir23, symlink, unlink } from "fs/promises";
14088
14600
  import { dirname as dirname23, relative as relative4 } from "path";
14089
14601
  var PathTraversalError = class extends Error {
@@ -14209,6 +14721,9 @@ function joinDiskPath(directory, name) {
14209
14721
  function uniqueFileName(extension) {
14210
14722
  return /^[a-z0-9]+$/i.test(extension) ? `${Str.random(40)}.${extension}` : Str.random(40);
14211
14723
  }
14724
+ function encodeKey(key) {
14725
+ return key.split("/").map((segment) => encodeURIComponent(segment).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`)).join("/");
14726
+ }
14212
14727
  var FILE_MODE = { public: 420, private: 384 };
14213
14728
  var DIR_MODE = { public: 493, private: 448 };
14214
14729
  var LocalFilesystem = class {
@@ -14369,9 +14884,6 @@ var LocalFilesystem = class {
14369
14884
  await chmod(full, mode);
14370
14885
  }
14371
14886
  };
14372
- function encodeKey(key) {
14373
- return key.split("/").map((segment) => encodeURIComponent(segment).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`)).join("/");
14374
- }
14375
14887
  var isNotFound = (error) => {
14376
14888
  const name = error.name;
14377
14889
  const status = error.$metadata?.httpStatusCode;
@@ -14605,6 +15117,176 @@ var S3Filesystem = class {
14605
15117
  return { objects, prefixes: [...dirs] };
14606
15118
  }
14607
15119
  };
15120
+ var asBody = (body) => body instanceof Uint8Array && !Buffer.isBuffer(body) ? Buffer.from(body.buffer, body.byteOffset, body.byteLength) : body;
15121
+ function vercelBlobClient(token2) {
15122
+ return {
15123
+ put: (pathname, body, options) => put(pathname, asBody(body), { ...options, token: token2 }),
15124
+ head: (pathname) => head(pathname, { token: token2 }),
15125
+ del: (pathnames) => del(pathnames, { token: token2 }),
15126
+ list: (options) => list4({ ...options, token: token2 }),
15127
+ copy: (from, to, options) => copy(from, to, { ...options, token: token2 }),
15128
+ fetch: (url2) => fetch(url2)
15129
+ };
15130
+ }
15131
+ function blobStoreUrl(token2) {
15132
+ const match = /^vercel_blob_rw_([A-Za-z0-9]+)_/.exec(token2 ?? "");
15133
+ return match === null ? void 0 : `https://${match[1].toLowerCase()}.public.blob.vercel-storage.com`;
15134
+ }
15135
+ var CREATE = { access: "public", addRandomSuffix: false, allowOverwrite: true };
15136
+ var isNotFound2 = (error) => error.name === "BlobNotFoundError";
15137
+ var VercelBlobFilesystem = class {
15138
+ constructor(client, config2) {
15139
+ this.client = client;
15140
+ this.config = config2;
15141
+ if (config2.visibility === "private") throw new UnsupportedOperationError("visibility", "Vercel Blob disks are public");
15142
+ this.root = (config2.root ?? "").replace(/^\/+|\/+$/g, "");
15143
+ }
15144
+ client;
15145
+ config;
15146
+ root;
15147
+ /** The pathname in the store: the disk's root, then the path. */
15148
+ key(path) {
15149
+ return joinDiskPath(this.root, path.replace(/^\/+/, ""));
15150
+ }
15151
+ /** A store pathname back to the disk's path. */
15152
+ unroot(pathname) {
15153
+ return this.root === "" ? pathname : pathname.slice(this.root.length + 1);
15154
+ }
15155
+ async head(path) {
15156
+ try {
15157
+ return await this.client.head(this.key(path));
15158
+ } catch (error) {
15159
+ if (isNotFound2(error)) throw new FileNotFoundError(path);
15160
+ throw error;
15161
+ }
15162
+ }
15163
+ async exists(path) {
15164
+ try {
15165
+ await this.head(path);
15166
+ return true;
15167
+ } catch (error) {
15168
+ if (error instanceof FileNotFoundError) return false;
15169
+ throw error;
15170
+ }
15171
+ }
15172
+ async missing(path) {
15173
+ return !await this.exists(path);
15174
+ }
15175
+ async read(path) {
15176
+ const response2 = await this.client.fetch((await this.head(path)).url);
15177
+ if (response2.status === 404) throw new FileNotFoundError(path);
15178
+ if (!response2.ok) throw new Error(`Vercel Blob answered ${response2.status} reading ${path}`);
15179
+ return response2;
15180
+ }
15181
+ async get(path) {
15182
+ return (await this.read(path)).text();
15183
+ }
15184
+ async getBytes(path) {
15185
+ return new Uint8Array(await (await this.read(path)).arrayBuffer());
15186
+ }
15187
+ async readStream(path) {
15188
+ const body = (await this.read(path)).body;
15189
+ return body ?? new ReadableStream({ start: (controller) => controller.close() });
15190
+ }
15191
+ async put(path, contents, options) {
15192
+ await this.store(path, contents, options?.mimeType ?? mimeFromExtension(posix2.extname(path)), options);
15193
+ return true;
15194
+ }
15195
+ async putFile(directory, file, options) {
15196
+ return this.putFileAs(directory, file, uniqueFileName(file.extension()), options);
15197
+ }
15198
+ /** Sends the upload's own type (its extension()), as the s3 disk does, never the type the client claimed. */
15199
+ async putFileAs(directory, file, name, options) {
15200
+ const path = joinDiskPath(directory, name);
15201
+ await this.store(path, new Uint8Array(await file.arrayBuffer()), options?.mimeType ?? mimeFromExtension(file.extension()), options);
15202
+ return path;
15203
+ }
15204
+ async store(path, body, contentType, options) {
15205
+ if (options?.visibility === "private") throw new UnsupportedOperationError("put", "Vercel Blob disks are public");
15206
+ await this.client.put(this.key(path), body, { ...CREATE, ...contentType === void 0 ? {} : { contentType } });
15207
+ }
15208
+ async delete(paths) {
15209
+ const list23 = Array.isArray(paths) ? paths : [paths];
15210
+ if (list23.length > 0) await this.client.del(list23.map((path) => this.key(path)));
15211
+ return true;
15212
+ }
15213
+ async copy(from, to) {
15214
+ try {
15215
+ await this.client.copy(this.key(from), this.key(to), CREATE);
15216
+ } catch (error) {
15217
+ if (isNotFound2(error)) throw new FileNotFoundError(from);
15218
+ throw error;
15219
+ }
15220
+ return true;
15221
+ }
15222
+ async move(from, to) {
15223
+ await this.copy(from, to);
15224
+ await this.delete(from);
15225
+ return true;
15226
+ }
15227
+ async size(path) {
15228
+ return (await this.head(path)).size;
15229
+ }
15230
+ async lastModified(path) {
15231
+ return new Date((await this.head(path)).uploadedAt);
15232
+ }
15233
+ async mimeType(path) {
15234
+ return (await this.head(path)).contentType || void 0;
15235
+ }
15236
+ url(path) {
15237
+ const base = this.config.url ?? blobStoreUrl(this.config.token);
15238
+ if (base === void 0) throw new UnsupportedOperationError("url", "set the disk's token (BLOB_READ_WRITE_TOKEN) or its url");
15239
+ return `${base.replace(/\/+$/, "")}/${encodeKey(this.key(path))}`;
15240
+ }
15241
+ /** A public store's files need no signature: the public URL. */
15242
+ async temporaryUrl(path, _expiresAt) {
15243
+ return this.url(path);
15244
+ }
15245
+ async files(directory = "", recursive = false) {
15246
+ const { blobs } = await this.list(directory, recursive);
15247
+ return blobs.map((pathname) => this.unroot(pathname)).sort();
15248
+ }
15249
+ async directories(directory = "", recursive = false) {
15250
+ if (!recursive) return (await this.list(directory, false)).folders.map((folder) => this.unroot(folder.replace(/\/+$/, ""))).sort();
15251
+ const found = /* @__PURE__ */ new Set();
15252
+ const start3 = this.key(directory).replace(/\/+$/, "");
15253
+ for (const pathname of (await this.list(directory, true)).blobs) {
15254
+ const parts = pathname.split("/").slice(0, -1);
15255
+ for (let i = 1; i <= parts.length; i++) {
15256
+ const folder = parts.slice(0, i).join("/");
15257
+ if (folder.length > start3.length && (start3 === "" || folder.startsWith(`${start3}/`))) found.add(this.unroot(folder));
15258
+ }
15259
+ }
15260
+ return [...found].sort();
15261
+ }
15262
+ /** Blob folders exist through their files, so there's nothing to create. */
15263
+ async makeDirectory(_path) {
15264
+ return true;
15265
+ }
15266
+ async deleteDirectory(path) {
15267
+ const { blobs } = await this.list(path, true);
15268
+ for (let i = 0; i < blobs.length; i += 1e3) await this.client.del(blobs.slice(i, i + 1e3));
15269
+ return true;
15270
+ }
15271
+ path(_path) {
15272
+ throw new UnsupportedOperationError("path", "vercel-blob disks have no local filesystem path");
15273
+ }
15274
+ /** Every page of a listing: the store's pathnames, and in folded mode its folders (`prefix/name/`). */
15275
+ async list(directory, recursive) {
15276
+ const base = this.key(directory).replace(/\/+$/, "");
15277
+ const prefix = base === "" ? void 0 : `${base}/`;
15278
+ const blobs = [];
15279
+ const folders = [];
15280
+ let cursor;
15281
+ do {
15282
+ const page = await this.client.list({ prefix, cursor, mode: recursive ? "expanded" : "folded" });
15283
+ blobs.push(...page.blobs.map((blob) => blob.pathname));
15284
+ folders.push(...page.folders ?? []);
15285
+ cursor = page.hasMore ? page.cursor : void 0;
15286
+ } while (cursor !== void 0);
15287
+ return { blobs, folders };
15288
+ }
15289
+ };
14608
15290
  var FilesystemManager2 = class {
14609
15291
  constructor(container) {
14610
15292
  this.container = container;
@@ -14617,6 +15299,15 @@ var FilesystemManager2 = class {
14617
15299
  })
14618
15300
  );
14619
15301
  this.drivers.set("s3", (config2) => new S3Filesystem(this.buildS3Client(config2), this.s3Config(config2)));
15302
+ this.drivers.set("vercel-blob", (config2) => {
15303
+ const token2 = config2.token ?? process.env.BLOB_READ_WRITE_TOKEN;
15304
+ return new VercelBlobFilesystem(vercelBlobClient(token2), {
15305
+ token: token2,
15306
+ url: config2.url,
15307
+ root: config2.root,
15308
+ visibility: config2.visibility
15309
+ });
15310
+ });
14620
15311
  }
14621
15312
  container;
14622
15313
  disks = /* @__PURE__ */ new Map();
@@ -14980,11 +15671,11 @@ var HashingServiceProvider = class extends ServiceProvider {
14980
15671
  import { existsSync as existsSync7, readdirSync as readdirSync5 } from "fs";
14981
15672
  import { join as join11, relative as relative5, sep as sep4 } from "path";
14982
15673
  import { createHash as createHash7 } from "crypto";
14983
- import { existsSync as existsSync23, readFileSync } from "fs";
15674
+ import { existsSync as existsSync24, readFileSync } from "fs";
14984
15675
  import { spawn as spawn2 } from "child_process";
14985
15676
  import { existsSync as existsSync33 } from "fs";
14986
15677
  import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
14987
- import { dirname as dirname5, isAbsolute as isAbsolute2, join as join24, relative as relative22 } from "path";
15678
+ import { dirname as dirname5, isAbsolute as isAbsolute2, join as join24, relative as relative23 } from "path";
14988
15679
  import { existsSync as existsSync43, readFileSync as readFileSync2 } from "fs";
14989
15680
  var Header = {
14990
15681
  INERTIA: "X-Inertia",
@@ -15862,7 +16553,7 @@ var InertiaMiddleware = class {
15862
16553
  if (typeof assetUrl === "string" && assetUrl !== "") return md5(assetUrl);
15863
16554
  const app2 = this.container.make(Application);
15864
16555
  const manifest = app2.publicPath("build/manifest.json");
15865
- return existsSync23(manifest) ? md5(readFileSync(manifest)) : null;
16556
+ return existsSync24(manifest) ? md5(readFileSync(manifest)) : null;
15866
16557
  }
15867
16558
  /** Props shared with every page. Override and spread `await super.share(request)` into your own. */
15868
16559
  async share(request2) {
@@ -15918,8 +16609,8 @@ var InertiaMiddleware = class {
15918
16609
  bag,
15919
16610
  Object.fromEntries(
15920
16611
  Object.entries(fields ?? {}).map(([field, messages]) => {
15921
- const list5 = Array.isArray(messages) ? messages.map(String) : [String(messages)];
15922
- return [field, this.withAllErrors ? list5 : list5[0] ?? ""];
16612
+ const list6 = Array.isArray(messages) ? messages.map(String) : [String(messages)];
16613
+ return [field, this.withAllErrors ? list6 : list6[0] ?? ""];
15923
16614
  })
15924
16615
  )
15925
16616
  ])
@@ -16143,7 +16834,7 @@ export class {{ class }} extends InertiaMiddleware {
16143
16834
  }
16144
16835
  }
16145
16836
  `;
16146
- var DEFAULT_EXCEPT_ROUTES = ["live.*"];
16837
+ var DEFAULT_EXCEPT_ROUTES = ["live.*", "vercel.*"];
16147
16838
  async function loadPageTypes() {
16148
16839
  const specifier = "@orieljs/vite/types";
16149
16840
  try {
@@ -16172,7 +16863,7 @@ function registerInertiaCommands(kernel, app2, gateway, options = {}) {
16172
16863
  await writeFile6(output, renderRoutesModule(table));
16173
16864
  await mkdir5(dirname5(server), { recursive: true });
16174
16865
  await writeFile6(server, renderServerRouteTypes(routeTable(app2.make(Router).getRoutes())));
16175
- this.info(`Route types for ${Object.keys(table).length} named routes written to ${relative22(app2.basePath(), output)}.`);
16866
+ this.info(`Route types for ${Object.keys(table).length} named routes written to ${relative23(app2.basePath(), output)}.`);
16176
16867
  return 0;
16177
16868
  }
16178
16869
  ).purpose("Generate the route table and route name types for the frontend");
@@ -16185,7 +16876,7 @@ function registerInertiaCommands(kernel, app2, gateway, options = {}) {
16185
16876
  const output = absolute(optionText(this.option("output")) ?? config2("inertia.types.pages", "resources/js/oriel/pages.d.ts"));
16186
16877
  const result = await module.generatePageTypes({ root: app2.basePath(), output });
16187
16878
  for (const warning of result.warnings) this.warn(warning);
16188
- this.info(`Page types for ${result.pages} pages written to ${relative22(app2.basePath(), result.output)}.`);
16879
+ this.info(`Page types for ${result.pages} pages written to ${relative23(app2.basePath(), result.output)}.`);
16189
16880
  return 0;
16190
16881
  }).purpose("Generate TypeScript types for the Inertia page props");
16191
16882
  kernel.command("inertia:middleware {name=HandleInertiaRequests : The middleware class name} {--force : Overwrite an existing file}", async function() {
@@ -16196,12 +16887,12 @@ function registerInertiaCommands(kernel, app2, gateway, options = {}) {
16196
16887
  }
16197
16888
  const file = app2.appPath(join24("Http", "Middleware", `${name}.ts`));
16198
16889
  if (existsSync33(file) && this.option("force") !== true) {
16199
- this.error(`Middleware [${relative22(app2.basePath(), file)}] already exists.`);
16890
+ this.error(`Middleware [${relative23(app2.basePath(), file)}] already exists.`);
16200
16891
  return 1;
16201
16892
  }
16202
16893
  await mkdir5(dirname5(file), { recursive: true });
16203
16894
  await writeFile6(file, MIDDLEWARE_STUB2.replace("{{ class }}", () => name));
16204
- this.info(`Middleware [${relative22(app2.basePath(), file)}] created successfully.`);
16895
+ this.info(`Middleware [${relative23(app2.basePath(), file)}] created successfully.`);
16205
16896
  return 0;
16206
16897
  }).purpose("Create a new Inertia middleware");
16207
16898
  kernel.command("inertia:start-ssr {--runtime= : The runtime that runs the SSR bundle (node or bun)}", async function() {
@@ -16400,7 +17091,7 @@ var InertiaServiceProvider = class extends ServiceProvider {
16400
17091
 
16401
17092
  // ../Log/dist/index.js
16402
17093
  import pino2 from "pino";
16403
- import { createWriteStream as createWriteStream2, mkdirSync as mkdirSync2, readdirSync as readdirSync6, rmSync } from "fs";
17094
+ import { createWriteStream as createWriteStream2, mkdirSync as mkdirSync3, readdirSync as readdirSync6, rmSync } from "fs";
16404
17095
  import { basename as basename2, dirname as dirname6, extname as extname3, join as join12 } from "path";
16405
17096
  import { Writable } from "stream";
16406
17097
  import "pino";
@@ -16529,7 +17220,7 @@ var DailyRotatingStream = class extends Writable {
16529
17220
  this.now = now5;
16530
17221
  this.dir = dirname6(basePath);
16531
17222
  this.stem = basename2(basePath, extname3(basePath) || void 0);
16532
- mkdirSync2(this.dir, { recursive: true });
17223
+ mkdirSync3(this.dir, { recursive: true });
16533
17224
  }
16534
17225
  days;
16535
17226
  now;
@@ -16940,8 +17631,8 @@ function normalizeAddress(recipient) {
16940
17631
  }
16941
17632
  function normalizeAddresses(recipients) {
16942
17633
  if (recipients === void 0) return [];
16943
- const list5 = Array.isArray(recipients) ? recipients : [recipients];
16944
- return list5.map(normalizeAddress);
17634
+ const list6 = Array.isArray(recipients) ? recipients : [recipients];
17635
+ return list6.map(normalizeAddress);
16945
17636
  }
16946
17637
  function formatAddress(address) {
16947
17638
  return address.name ? `${address.name} <${address.address}>` : address.address;
@@ -17242,7 +17933,7 @@ function copyShouldQueueFields(target, source) {
17242
17933
  }
17243
17934
  function recipientsHash(to = [], cc = [], bcc = []) {
17244
17935
  if (to.length + cc.length + bcc.length === 0) return void 0;
17245
- const addresses = (list5) => list5.map((recipient) => recipient.address);
17936
+ const addresses = (list6) => list6.map((recipient) => recipient.address);
17246
17937
  const recipients = JSON.stringify({ to: addresses(to), cc: addresses(cc), bcc: addresses(bcc) });
17247
17938
  return createHash8("sha1").update(recipients).digest("hex");
17248
17939
  }
@@ -17963,9 +18654,9 @@ function isQueueableNotification(notification) {
17963
18654
  }
17964
18655
  function forNotifiable(notification) {
17965
18656
  if (notification.id) return notification;
17966
- const copy = Object.assign(Object.create(Object.getPrototypeOf(notification)), notification);
17967
- copy.id = randomUUID23();
17968
- return copy;
18657
+ const copy2 = Object.assign(Object.create(Object.getPrototypeOf(notification)), notification);
18658
+ copy2.id = randomUUID23();
18659
+ return copy2;
17969
18660
  }
17970
18661
  function toNotifiableList(notifiables) {
17971
18662
  if (Array.isArray(notifiables)) return notifiables;
@@ -17996,12 +18687,12 @@ var Dispatcher3 = class {
17996
18687
  return channel;
17997
18688
  }
17998
18689
  async send(notifiables, notification) {
17999
- const list5 = toNotifiableList(notifiables);
18000
- if (list5.length === 0) return;
18690
+ const list6 = toNotifiableList(notifiables);
18691
+ if (list6.length === 0) return;
18001
18692
  if (isQueueableNotification(notification)) {
18002
18693
  if (!this.container.bound(BusDispatcher)) throw new NotificationQueueUnavailableError();
18003
18694
  const bus = this.container.make(BusDispatcher);
18004
- for (const notifiable of list5) {
18695
+ for (const notifiable of list6) {
18005
18696
  const own = forNotifiable(notification);
18006
18697
  for (const channel of notification.via(notifiable)) {
18007
18698
  await bus.dispatch(new SendQueuedNotifications([notifiable], own, [channel]));
@@ -18009,11 +18700,11 @@ var Dispatcher3 = class {
18009
18700
  }
18010
18701
  return;
18011
18702
  }
18012
- await this.sendNow(list5, notification);
18703
+ await this.sendNow(list6, notification);
18013
18704
  }
18014
18705
  async sendNow(notifiables, notification, channels) {
18015
- const list5 = toNotifiableList(notifiables);
18016
- for (const notifiable of list5) {
18706
+ const list6 = toNotifiableList(notifiables);
18707
+ for (const notifiable of list6) {
18017
18708
  const own = forNotifiable(notification);
18018
18709
  const names = channels ?? notification.via(notifiable);
18019
18710
  for (const name of names) {
@@ -18440,7 +19131,7 @@ async function restoreValue(value2, registry2) {
18440
19131
  }
18441
19132
  function normalizeBackoff(backoff) {
18442
19133
  if (typeof backoff === "number") return [backoff];
18443
- if (typeof backoff === "string" && backoff !== "") return backoff.split(",").map((part) => Number(part.trim()));
19134
+ if (typeof backoff === "string" && backoff !== "") return backoff.split(",").map((part2) => Number(part2.trim()));
18444
19135
  if (Array.isArray(backoff)) return backoff.map(Number);
18445
19136
  return void 0;
18446
19137
  }
@@ -19991,7 +20682,7 @@ function discoverJobs(directory, registry2) {
19991
20682
  return discoverClasses(directory, registry2, isJobClass);
19992
20683
  }
19993
20684
  var text3 = (value2) => optionalString2(value2) || void 0;
19994
- var list4 = (value2) => Array.isArray(value2) ? value2.map(String) : text3(value2) ? [String(value2)] : [];
20685
+ var list5 = (value2) => Array.isArray(value2) ? value2.map(String) : text3(value2) ? [String(value2)] : [];
19995
20686
  var number2 = (value2, fallback) => {
19996
20687
  const parsed = Number(value2);
19997
20688
  return value2 === void 0 || value2 === null || value2 === "" || Number.isNaN(parsed) ? fallback : parsed;
@@ -20116,7 +20807,7 @@ function registerQueueCommands(kernel, context) {
20116
20807
  '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}',
20117
20808
  "Retry a failed queue job",
20118
20809
  async (out) => {
20119
- const ids = await retryableIds(list4(out.argument("id")), text3(out.option("queue")));
20810
+ const ids = await retryableIds(list5(out.argument("id")), text3(out.option("queue")));
20120
20811
  if (ids.length === 0) {
20121
20812
  out.info("No retryable jobs found.");
20122
20813
  return 0;
@@ -20229,7 +20920,7 @@ var QueueServiceProvider = class extends ServiceProvider {
20229
20920
  };
20230
20921
 
20231
20922
  // ../Routing/dist/index.js
20232
- import { createHmac as createHmac4, timingSafeEqual as timingSafeEqual4 } from "crypto";
20923
+ import { createHmac as createHmac6, timingSafeEqual as timingSafeEqual6 } from "crypto";
20233
20924
  import { pathToFileURL as pathToFileURL7 } from "url";
20234
20925
  import { createHash as createHash10 } from "crypto";
20235
20926
  import { createHash as createHash23 } from "crypto";
@@ -20690,7 +21381,7 @@ async function dispatchRoute(router, request2, container) {
20690
21381
  }
20691
21382
  var ALL_VERBS = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
20692
21383
  function mergeGroup(parent, child) {
20693
- const prefix = [parent.prefix, child.prefix].map((part) => (part ?? "").replace(/^\/+|\/+$/g, "")).filter((part) => part !== "").join("/");
21384
+ const prefix = [parent.prefix, child.prefix].map((part2) => (part2 ?? "").replace(/^\/+|\/+$/g, "")).filter((part2) => part2 !== "").join("/");
20694
21385
  const merged = {
20695
21386
  middleware: [...parent.middleware ?? [], ...child.middleware ?? []]
20696
21387
  };
@@ -20845,7 +21536,7 @@ var BaseRouter = class {
20845
21536
  }
20846
21537
  const prefix = (group.prefix ?? "").replace(/^\/+|\/+$/g, "");
20847
21538
  const path = uri.replace(/^\/+|\/+$/g, "");
20848
- const route2 = new RouteDefinition(verbs, [prefix, path].filter((part) => part !== "").join("/"), action, {
21539
+ const route2 = new RouteDefinition(verbs, [prefix, path].filter((part2) => part2 !== "").join("/"), action, {
20849
21540
  ...group.as !== void 0 && { namePrefix: group.as },
20850
21541
  ...group.middleware !== void 0 && { middleware: group.middleware },
20851
21542
  ...group.controller !== void 0 && { controller: group.controller },
@@ -20994,20 +21685,20 @@ var BaseRouter = class {
20994
21685
  return this;
20995
21686
  }
20996
21687
  pushMiddlewareToGroup(group, middleware) {
20997
- const list5 = this.#groups[group] ??= [];
20998
- if (!list5.includes(middleware)) list5.push(middleware);
21688
+ const list6 = this.#groups[group] ??= [];
21689
+ if (!list6.includes(middleware)) list6.push(middleware);
20999
21690
  return this;
21000
21691
  }
21001
21692
  prependMiddlewareToGroup(group, middleware) {
21002
- const list5 = this.#groups[group] ??= [];
21003
- if (!list5.includes(middleware)) list5.unshift(middleware);
21693
+ const list6 = this.#groups[group] ??= [];
21694
+ if (!list6.includes(middleware)) list6.unshift(middleware);
21004
21695
  return this;
21005
21696
  }
21006
21697
  hasMiddlewareGroup(name) {
21007
21698
  return Object.hasOwn(this.#groups, name);
21008
21699
  }
21009
21700
  getMiddlewareGroups() {
21010
- return Object.fromEntries(Object.entries(this.#groups).map(([name, list5]) => [name, [...list5]]));
21701
+ return Object.fromEntries(Object.entries(this.#groups).map(([name, list6]) => [name, [...list6]]));
21011
21702
  }
21012
21703
  /** Every route, in registration order. */
21013
21704
  getRoutes() {
@@ -21070,12 +21761,12 @@ function routeKey(value2, field) {
21070
21761
  return typeof model.getRouteKey === "function" ? model.getRouteKey() : value2;
21071
21762
  }
21072
21763
  function sign(url2, key) {
21073
- return createHmac4("sha256", key).update(url2).digest("hex");
21764
+ return createHmac6("sha256", key).update(url2).digest("hex");
21074
21765
  }
21075
21766
  function safeEquals(expected, given) {
21076
21767
  const a = Buffer.from(expected);
21077
21768
  const b = Buffer.from(given);
21078
- return a.length === b.length && timingSafeEqual4(a, b);
21769
+ return a.length === b.length && timingSafeEqual6(a, b);
21079
21770
  }
21080
21771
  function availableAt(expiration) {
21081
21772
  return expiration instanceof Date ? Math.floor(expiration.getTime() / 1e3) : Math.floor(Date.now() / 1e3) + expiration;
@@ -21306,9 +21997,9 @@ function withHeaders2(response2, headers) {
21306
21997
  for (const [name, value2] of Object.entries(headers)) response2.headers.set(name, value2);
21307
21998
  return response2;
21308
21999
  } catch {
21309
- const copy = new Headers(response2.headers);
21310
- for (const [name, value2] of Object.entries(headers)) copy.set(name, value2);
21311
- return new Response(response2.body, { status: response2.status, statusText: response2.statusText, headers: copy });
22000
+ const copy2 = new Headers(response2.headers);
22001
+ for (const [name, value2] of Object.entries(headers)) copy2.set(name, value2);
22002
+ return new Response(response2.body, { status: response2.status, statusText: response2.statusText, headers: copy2 });
21312
22003
  }
21313
22004
  }
21314
22005
  var ThrottleRequests = class {
@@ -21446,8 +22137,8 @@ var RoutingServiceProvider = class extends ServiceProvider {
21446
22137
  import { mkdir as mkdir6, readdir as readdir3, readFile as readFile4, rename as rename3, rm as rm3, stat as stat3, writeFile as writeFile7 } from "fs/promises";
21447
22138
  import { join as join14 } from "path";
21448
22139
  import { Redis as Redis3 } from "ioredis";
21449
- import { randomInt } from "crypto";
21450
- import { timingSafeEqual as timingSafeEqual5 } from "crypto";
22140
+ import { randomInt as randomInt3 } from "crypto";
22141
+ import { timingSafeEqual as timingSafeEqual7 } from "crypto";
21451
22142
  var unique = (values) => [...new Set(values)];
21452
22143
  var Store = class {
21453
22144
  #id = "";
@@ -21515,8 +22206,8 @@ var Store = class {
21515
22206
  for (const [name, item] of Object.entries(values)) Arr.set(this.#attributes, name, item);
21516
22207
  }
21517
22208
  push(key, value2) {
21518
- const list5 = this.get(key, []);
21519
- this.put(key, [...Array.isArray(list5) ? list5 : [list5], value2]);
22209
+ const list6 = this.get(key, []);
22210
+ this.put(key, [...Array.isArray(list6) ? list6 : [list6], value2]);
21520
22211
  }
21521
22212
  increment(key, amount = 1) {
21522
22213
  const value2 = Number(this.get(key, 0)) + amount;
@@ -22004,7 +22695,7 @@ var StartSession = class {
22004
22695
  /** Laravel's lottery: on average lottery[0] requests out of lottery[1] clean up expired sessions. */
22005
22696
  async #collectGarbage(session2, config2) {
22006
22697
  const [chances, outOf] = config2.lottery ?? [2, 100];
22007
- if (session2.getHandler !== void 0 && randomInt(1, outOf + 1) <= chances) {
22698
+ if (session2.getHandler !== void 0 && randomInt3(1, outOf + 1) <= chances) {
22008
22699
  await session2.getHandler().gc((config2.lifetime ?? 120) * 60);
22009
22700
  }
22010
22701
  }
@@ -22016,7 +22707,7 @@ var StartSession = class {
22016
22707
  function equals3(known, user) {
22017
22708
  const a = Buffer.from(known);
22018
22709
  const b = Buffer.from(user);
22019
- return a.length === b.length && timingSafeEqual5(a, b);
22710
+ return a.length === b.length && timingSafeEqual7(a, b);
22020
22711
  }
22021
22712
  var VerifyCsrfToken = class {
22022
22713
  /** URIs (wildcards allowed) excluded from the check, in addition to config('http.csrf_except'). */
@@ -22190,7 +22881,7 @@ var ValidationErrorImpl = class extends Error {
22190
22881
  var ValidationException = class _ValidationException extends ValidationErrorImpl {
22191
22882
  /** Messages per field, each a string or a list. */
22192
22883
  static withMessages(messages) {
22193
- const errors2 = Object.fromEntries(Object.entries(messages).map(([field, list5]) => [field, typeof list5 === "string" ? [list5] : [...list5]]));
22884
+ const errors2 = Object.fromEntries(Object.entries(messages).map(([field, list6]) => [field, typeof list6 === "string" ? [list6] : [...list6]]));
22194
22885
  return new _ValidationException(errors2);
22195
22886
  }
22196
22887
  constructor(errors2, errorBag = "default", redirectTo2) {
@@ -23442,11 +24133,11 @@ var urlPatterns = /* @__PURE__ */ new Map();
23442
24133
  function isUrl(value2, protocols) {
23443
24134
  if (typeof value2 !== "string") return false;
23444
24135
  if (protocols.length === 0) return URL_RE.test(value2);
23445
- const list5 = protocols.join("|");
23446
- let pattern = urlPatterns.get(list5);
24136
+ const list6 = protocols.join("|");
24137
+ let pattern = urlPatterns.get(list6);
23447
24138
  if (pattern === void 0) {
23448
24139
  try {
23449
- pattern = urlPattern(list5);
24140
+ pattern = urlPattern(list6);
23450
24141
  } catch (error) {
23451
24142
  throw new InvalidRuleParameterError(
23452
24143
  "url",
@@ -23454,7 +24145,7 @@ function isUrl(value2, protocols) {
23454
24145
  error instanceof Error ? error.message : String(error)
23455
24146
  );
23456
24147
  }
23457
- urlPatterns.set(list5, pattern);
24148
+ urlPatterns.set(list6, pattern);
23458
24149
  }
23459
24150
  return pattern.test(value2);
23460
24151
  }
@@ -23880,11 +24571,11 @@ var sizeRules = {
23880
24571
  in: { validate: (ctx) => isListed(ctx.value, ctx.parameters, ctx.ruleNames), messageKey: () => "in" },
23881
24572
  not_in: { validate: (ctx) => !isListed(ctx.value, ctx.parameters, ctx.ruleNames), messageKey: () => "not_in" }
23882
24573
  };
23883
- function isListed(value2, list5, ruleNames) {
24574
+ function isListed(value2, list6, ruleNames) {
23884
24575
  if (Array.isArray(value2) && ruleNames.includes("array")) {
23885
- return value2.every((item) => (typeof item !== "object" || item === null) && list5.includes(String(item)));
24576
+ return value2.every((item) => (typeof item !== "object" || item === null) && list6.includes(String(item)));
23886
24577
  }
23887
- return (typeof value2 !== "object" || value2 === null) && list5.includes(String(value2));
24578
+ return (typeof value2 !== "object" || value2 === null) && list6.includes(String(value2));
23888
24579
  }
23889
24580
  var REGIONS = ["Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Australia", "Europe", "Indian", "Pacific"];
23890
24581
  var ZONE_RE = /^[A-Z][A-Za-z_-]*(?:\/[A-Z][A-Za-z0-9_+-]*)+$/;
@@ -24033,13 +24724,13 @@ var PasswordRule = class _PasswordRule {
24033
24724
  if (this.#numbers && !/\p{N}/u.test(value2)) say("password.numbers");
24034
24725
  }
24035
24726
  #copy() {
24036
- const copy = new _PasswordRule(this.#min);
24037
- copy.#max = this.#max;
24038
- copy.#letters = this.#letters;
24039
- copy.#mixedCase = this.#mixedCase;
24040
- copy.#numbers = this.#numbers;
24041
- copy.#symbols = this.#symbols;
24042
- return copy;
24727
+ const copy2 = new _PasswordRule(this.#min);
24728
+ copy2.#max = this.#max;
24729
+ copy2.#letters = this.#letters;
24730
+ copy2.#mixedCase = this.#mixedCase;
24731
+ copy2.#numbers = this.#numbers;
24732
+ copy2.#symbols = this.#symbols;
24733
+ return copy2;
24043
24734
  }
24044
24735
  };
24045
24736
  var BuiltinRuleObject = class {
@@ -24344,17 +25035,17 @@ function buildNested(flat) {
24344
25035
  const created = /* @__PURE__ */ new Set([root]);
24345
25036
  for (const [path, value2] of Object.entries(flat)) {
24346
25037
  const parts = path.split(".");
24347
- if (parts.some((part) => UNSAFE_KEYS.has(part))) continue;
25038
+ if (parts.some((part2) => UNSAFE_KEYS.has(part2))) continue;
24348
25039
  let current = root;
24349
- for (const part of parts.slice(0, -1)) {
24350
- const existing = Object.hasOwn(current, part) ? current[part] : void 0;
25040
+ for (const part2 of parts.slice(0, -1)) {
25041
+ const existing = Object.hasOwn(current, part2) ? current[part2] : void 0;
24351
25042
  if (typeof existing === "object" && existing !== null && created.has(existing)) {
24352
25043
  current = existing;
24353
25044
  continue;
24354
25045
  }
24355
25046
  const container = cloneContainer(existing);
24356
25047
  created.add(container);
24357
- current[part] = container;
25048
+ current[part2] = container;
24358
25049
  current = container;
24359
25050
  }
24360
25051
  current[parts.at(-1)] = value2;
@@ -24383,16 +25074,16 @@ function withoutPaths(data, paths) {
24383
25074
  const parts = path.split(".");
24384
25075
  const leaf = parts.pop();
24385
25076
  let node = root;
24386
- for (const part of parts) {
24387
- const child = Object.hasOwn(node, part) ? node[part] : void 0;
25077
+ for (const part2 of parts) {
25078
+ const child = Object.hasOwn(node, part2) ? node[part2] : void 0;
24388
25079
  if (!isPlainContainer(child)) {
24389
25080
  node = void 0;
24390
25081
  break;
24391
25082
  }
24392
- const copy = copies.has(child) ? child : Array.isArray(child) ? [...child] : { ...child };
24393
- copies.add(copy);
24394
- node[part] = copy;
24395
- node = copy;
25083
+ const copy2 = copies.has(child) ? child : Array.isArray(child) ? [...child] : { ...child };
25084
+ copies.add(copy2);
25085
+ node[part2] = copy2;
25086
+ node = copy2;
24396
25087
  }
24397
25088
  if (node === void 0 || !Object.hasOwn(node, leaf)) continue;
24398
25089
  if (Array.isArray(node)) node[Number(leaf)] = EXCLUDED;
@@ -24676,6 +25367,52 @@ var ValidationServiceProvider = class extends ServiceProvider {
24676
25367
  import { existsSync as existsSync9, readdirSync as readdirSync8, readFileSync as readFileSync3, realpathSync, statSync as statSync4 } from "fs";
24677
25368
  import { cp, mkdir as mkdir7, rm as rm4, writeFile as writeFile8 } from "fs/promises";
24678
25369
  import { basename as basename3, dirname as dirname7, join as join15, relative as relative6, resolve as resolve4 } from "path";
25370
+ import { CronExpressionParser as CronExpressionParser2 } from "cron-parser";
25371
+ import { timingSafeEqual as timingSafeEqual8 } from "crypto";
25372
+ import { CronExpressionParser as CronExpressionParser3 } from "cron-parser";
25373
+ var SCHEDULE_PATH = "/_oriel/schedule";
25374
+ var DEFAULT_CRON = "0 3 * * *";
25375
+ var LAST_RUN = "oriel:vercel:schedule:last";
25376
+ var LOCK = "oriel:vercel:schedule";
25377
+ var json2 = (body, status = 200) => Response.json(body, { status, headers: { "cache-control": "no-store" } });
25378
+ function authorized(header, secret) {
25379
+ const expected = Buffer.from(`Bearer ${secret}`);
25380
+ const given = Buffer.from(header ?? "");
25381
+ return given.length === expected.length && timingSafeEqual8(given, expected);
25382
+ }
25383
+ function previousTrigger(expression, now5) {
25384
+ const parsed = CronExpressionParser3.parse(expression, { currentDate: new Date(now5.getTime() + 6e4), tz: "UTC" });
25385
+ parsed.prev();
25386
+ return parsed.prev().toDate();
25387
+ }
25388
+ async function runVercelSchedule(authorization, options = {}) {
25389
+ const secret = process.env.CRON_SECRET;
25390
+ if (!secret) return new Response("Not Found", { status: 404 });
25391
+ if (!authorized(authorization, secret)) return json2({ error: "Unauthorized" }, 401);
25392
+ const cache2 = inject(CacheManager);
25393
+ const lock = cache2.lock(LOCK, 900);
25394
+ if (!await lock.get()) return json2({ ran: 0, failed: 0, skipped: "busy" });
25395
+ try {
25396
+ await inject(ConsoleKernel).bootstrap();
25397
+ const now5 = options.now?.() ?? /* @__PURE__ */ new Date();
25398
+ const last = await cache2.get(LAST_RUN);
25399
+ const from = last === void 0 ? previousTrigger(options.trigger ?? process.env.ORIEL_VERCEL_CRON ?? DEFAULT_CRON, now5) : new Date(last);
25400
+ const due = inject(Schedule).dueBetween(from, now5);
25401
+ let failed = 0;
25402
+ for (const event2 of due) {
25403
+ try {
25404
+ await event2.run();
25405
+ } catch (error) {
25406
+ failed++;
25407
+ await inject(ExceptionHandler).report(error);
25408
+ }
25409
+ }
25410
+ await cache2.forever(LAST_RUN, now5.toISOString());
25411
+ return json2({ ran: due.length, failed }, failed > 0 ? 500 : 200);
25412
+ } finally {
25413
+ await lock.release();
25414
+ }
25415
+ }
24679
25416
  var LAUNCHER = `// Oriel on Vercel: boots the app once per function instance and hands it each request.
24680
25417
  import { fileURLToPath } from 'node:url'
24681
25418
 
@@ -24758,8 +25495,10 @@ async function buildVercelOutput(app2, options = {}) {
24758
25495
  const vcConfig = { runtime: options.runtime ?? "nodejs22.x", handler: "index.mjs", launcherType: "Nodejs", shouldAddHelpers: false, supportsResponseStreaming: true };
24759
25496
  await writeFile8(join15(fn, ".vc-config.json"), `${JSON.stringify(vcConfig, null, 2)}
24760
25497
  `);
25498
+ const { crons, cronWarnings } = await scheduleCron(app2, options.cron);
24761
25499
  const config2 = {
24762
25500
  version: 3,
25501
+ ...crons === void 0 ? {} : { crons },
24763
25502
  routes: [
24764
25503
  // The built assets' names change with their content, so they're cached for a year, as serve() does.
24765
25504
  { src: "^/build/assets/(.*)$", headers: { "cache-control": "public, max-age=31536000, immutable" }, continue: true },
@@ -24778,7 +25517,26 @@ async function buildVercelOutput(app2, options = {}) {
24778
25517
  const warnings = platform === "linux" ? [] : [
24779
25518
  `Built on ${PLATFORM_NAMES[platform] ?? platform}: the function's native packages (such as esbuild, which tsx needs) only run on this kind of machine, and Vercel's functions run on Linux. Let Vercel run the build, or build on Linux.`
24780
25519
  ];
24781
- return { functionBytes, staticFiles: countFiles(staticFolder), warnings };
25520
+ return { functionBytes, staticFiles: countFiles(staticFolder), warnings: [...warnings, ...cronWarnings] };
25521
+ }
25522
+ async function scheduleCron(app2, cron) {
25523
+ if (cron === false) return { cronWarnings: [] };
25524
+ if (app2.bound(ConsoleKernel)) await app2.make(ConsoleKernel).bootstrap();
25525
+ if (!app2.bound(Schedule)) return { cronWarnings: [] };
25526
+ const schedule = app2.make(Schedule);
25527
+ if (schedule.events().length === 0) return { cronWarnings: [] };
25528
+ const expression = cron ?? DEFAULT_CRON;
25529
+ const fires = CronExpressionParser2.parse(expression, { tz: "UTC" });
25530
+ const start3 = fires.next().toDate();
25531
+ const end = fires.next().toDate();
25532
+ const middle = new Date((start3.getTime() + end.getTime()) / 2);
25533
+ const later = new Set(schedule.dueBetween(middle, end));
25534
+ const often = schedule.dueBetween(start3, middle).filter((event2) => later.has(event2));
25535
+ const plan = cron === void 0 ? " (once a day on Vercel's Hobby plan)" : "";
25536
+ return {
25537
+ crons: [{ path: SCHEDULE_PATH, schedule: expression }],
25538
+ cronWarnings: often.map((event2) => `"${event2.getSummaryForDisplay()}" (${event2.expression}) is due more often than the cron "${expression}", so it runs once per trigger${plan}.`)
25539
+ };
24782
25540
  }
24783
25541
  async function copyDependencies(base, fn) {
24784
25542
  const manifest = JSON.parse(readFileSync3(join15(base, "package.json"), "utf8"));
@@ -24823,11 +25581,19 @@ var megabytes = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)} MB`;
24823
25581
  var megabytes2 = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)} MB`;
24824
25582
  var VercelServiceProvider = class extends ServiceProvider {
24825
25583
  boot() {
25584
+ if (this.app.bound(Router)) {
25585
+ this.app.make(Router).get(SCHEDULE_PATH, () => {
25586
+ const request2 = inject(HttpRequest);
25587
+ return runVercelSchedule(request2.header("authorization"), { trigger: request2.header("x-vercel-cron-schedule") });
25588
+ }).name("vercel.schedule");
25589
+ }
24826
25590
  if (!this.app.bound(ConsoleKernel)) return;
24827
25591
  const app2 = this.app;
24828
- this.app.make(ConsoleKernel).command("vercel:build", async function() {
25592
+ this.app.make(ConsoleKernel).command("vercel:build {--cron= : When Vercel calls the scheduler, as a cron expression (UTC), or false for never}", async function() {
24829
25593
  try {
24830
- const { staticFiles, functionBytes, warnings } = await buildVercelOutput(app2);
25594
+ const option = this.option("cron");
25595
+ const cron = typeof option === "string" && option !== "" ? option === "false" ? false : option : void 0;
25596
+ const { staticFiles, functionBytes, warnings } = await buildVercelOutput(app2, { cron });
24831
25597
  const files3 = staticFiles === 1 ? "1 static file" : `${staticFiles} static files`;
24832
25598
  this.info(`Wrote .vercel/output: ${files3}, a function of ${megabytes2(functionBytes)}.`);
24833
25599
  for (const warning of warnings) this.warn(warning);
@@ -24851,7 +25617,7 @@ import { parse as parse2 } from "dotenv";
24851
25617
  var SECRET = /key|secret|password|token|credential|salt|private/i;
24852
25618
  var CREDENTIALS = /^[a-z][a-z0-9+.-]*:\/\/[^/?#\s]*@/i;
24853
25619
  var byName = (a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
24854
- var posix2 = (path) => path.split(sep5).join("/");
25620
+ var posix3 = (path) => path.split(sep5).join("/");
24855
25621
  function commandSection(all) {
24856
25622
  return Object.entries(all).map(([name, command]) => {
24857
25623
  const signature = parseSignature(command.signature);
@@ -24893,8 +25659,8 @@ function viewSection(paths, hints, extensions, basePath) {
24893
25659
  const extension = extensions.find((candidate) => item.name.endsWith(`.${candidate}`));
24894
25660
  if (extension === void 0) continue;
24895
25661
  const file = join16(item.parentPath, item.name);
24896
- const name = prefix + posix2(relative7(root, file)).slice(0, -(extension.length + 1)).split("/").join(".");
24897
- if (!views.has(name)) views.set(name, posix2(relative7(basePath, file)));
25662
+ const name = prefix + posix3(relative7(root, file)).slice(0, -(extension.length + 1)).split("/").join(".");
25663
+ if (!views.has(name)) views.set(name, posix3(relative7(basePath, file)));
24898
25664
  }
24899
25665
  };
24900
25666
  for (const path of paths) collect2(path, "");
@@ -24903,7 +25669,7 @@ function viewSection(paths, hints, extensions, basePath) {
24903
25669
  }
24904
25670
 
24905
25671
  // ide/IdeInfoCommand.ts
24906
- var posix3 = (path) => path.split(sep6).join("/");
25672
+ var posix4 = (path) => path.split(sep6).join("/");
24907
25673
  var read = (file) => existsSync11(file) ? readFileSync4(file, "utf8") : null;
24908
25674
  var IdeInfoCommand = class extends Command {
24909
25675
  signature = "ide:info {--pretty : Indent the JSON}";
@@ -24935,7 +25701,7 @@ function describe(app2, container) {
24935
25701
  const basePath = app2.basePath();
24936
25702
  const config2 = optional(ConfigRepository);
24937
25703
  const get = (key, fallback) => config2 ? config2.get(key, fallback) : fallback;
24938
- const relativeTo = (file) => posix3(relative8(basePath, file));
25704
+ const relativeTo = (file) => posix4(relative8(basePath, file));
24939
25705
  const configDirectory = app2.configPath();
24940
25706
  const configFiles = existsSync11(configDirectory) ? readdirSync10(configDirectory).filter((file) => /\.(ts|js|mjs)$/.test(file)).map((file) => relativeTo(join17(configDirectory, file))) : [];
24941
25707
  const factory = optional(ViewFactory);
@@ -25369,6 +26135,7 @@ export {
25369
26135
  QueuedMailSender,
25370
26136
  RESTART_CACHE_KEY,
25371
26137
  RateLimiter2 as RateLimiter,
26138
+ RecoveryCodeUsed,
25372
26139
  RedirectIfAuthenticated,
25373
26140
  RedisJob,
25374
26141
  RedisLock,
@@ -25395,6 +26162,7 @@ export {
25395
26162
  Rule,
25396
26163
  S3Filesystem,
25397
26164
  SCHEDULE_INTERRUPT_KEY,
26165
+ SESSION_URI,
25398
26166
  SSR_RENDER_FAILED,
25399
26167
  STORAGE_LINK_SIGNATURE,
25400
26168
  Schedule5 as Schedule,
@@ -25436,6 +26204,7 @@ export {
25436
26204
  SyncJob,
25437
26205
  SyncQueue,
25438
26206
  TEMPORARY_UPLOAD_DEFAULTS,
26207
+ TWO_FACTOR_COLUMNS_MIGRATION,
25439
26208
  TagsNotSupportedError,
25440
26209
  TemporaryFileNotFoundError,
25441
26210
  TemporaryUploadedFile,
@@ -25451,6 +26220,15 @@ export {
25451
26220
  TokenRepository,
25452
26221
  TrimStrings,
25453
26222
  TsxView,
26223
+ TwoFactor,
26224
+ TwoFactorAppDisabled,
26225
+ TwoFactorAppEnabled,
26226
+ TwoFactorAuthenticated,
26227
+ TwoFactorChallenge,
26228
+ TwoFactorChallenged,
26229
+ TwoFactorCode,
26230
+ TwoFactorFailed,
26231
+ TwoFactorManager,
25454
26232
  TypeGenerator,
25455
26233
  UNIQUE,
25456
26234
  UNIQUE_LOCK,
@@ -25486,6 +26264,7 @@ export {
25486
26264
  ValidationServiceProvider,
25487
26265
  Validator,
25488
26266
  ValidatorImpl,
26267
+ VercelBlobFilesystem,
25489
26268
  VercelServiceProvider,
25490
26269
  Verified,
25491
26270
  VerifyCsrfToken,