gemi 0.79.0 → 0.80.0

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.
Files changed (36) hide show
  1. package/dist/{chunk-3vtwbcwm.js → chunk-0vkd0v3b.js} +1 -1
  2. package/dist/chunk-9hbwvcxn.js.map +2 -2
  3. package/dist/{chunk-e8z3nyd5.js → chunk-azebyh90.js} +4 -4
  4. package/dist/{chunk-e8z3nyd5.js.map → chunk-azebyh90.js.map} +3 -3
  5. package/dist/chunk-cg469vsg.js +5 -0
  6. package/dist/{chunk-9z3y20ms.js.map → chunk-cg469vsg.js.map} +3 -3
  7. package/dist/{chunk-hg1fahzx.js → chunk-hjbe3jn2.js} +2 -2
  8. package/dist/{chunk-hg1fahzx.js.map → chunk-hjbe3jn2.js.map} +1 -1
  9. package/dist/{chunk-avhgqsc8.js → chunk-jvyj4hxd.js} +1 -1
  10. package/dist/chunk-yedycsnk.js +6 -0
  11. package/dist/chunk-yedycsnk.js.map +10 -0
  12. package/dist/database/DatabaseServiceProvider.d.ts +12 -0
  13. package/dist/database/DatabaseServiceProvider.d.ts.map +1 -1
  14. package/dist/database/index.js +1 -1
  15. package/dist/facades/index.js +1 -1
  16. package/dist/http/index.js +1 -1
  17. package/dist/i18n/index.js +1 -1
  18. package/dist/kernel/index.js +1 -1
  19. package/dist/server/Server.d.ts.map +1 -1
  20. package/dist/server/devReload.d.ts +68 -0
  21. package/dist/server/devReload.d.ts.map +1 -0
  22. package/dist/server/index.js +2 -2
  23. package/dist/server/index.js.map +5 -4
  24. package/dist/services/index.js +1 -1
  25. package/dist/services/redis/RedisManager.d.ts +5 -0
  26. package/dist/services/redis/RedisManager.d.ts.map +1 -1
  27. package/dist/services/redis/RedisServiceProvider.d.ts +6 -0
  28. package/dist/services/redis/RedisServiceProvider.d.ts.map +1 -1
  29. package/dist/support/ServiceProvider.d.ts +6 -0
  30. package/dist/support/ServiceProvider.d.ts.map +1 -1
  31. package/package.json +1 -1
  32. package/dist/chunk-3jyz4378.js +0 -6
  33. package/dist/chunk-3jyz4378.js.map +0 -10
  34. package/dist/chunk-9z3y20ms.js +0 -5
  35. /package/dist/{chunk-3vtwbcwm.js.map → chunk-0vkd0v3b.js.map} +0 -0
  36. /package/dist/{chunk-avhgqsc8.js.map → chunk-jvyj4hxd.js.map} +0 -0
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../server/Server.ts"],
3
+ "sources": ["../server/devReload.ts", "../server/Server.ts"],
4
4
  "sourcesContent": [
5
- "import { App } from \"../app\";\nimport { Kernel } from \"../kernel\";\nimport { projectRoot } from \"../support/discover\";\nimport {\n drain,\n installShutdownSignals,\n serveForShutdown,\n shutdownSettings,\n type ShutdownSettings,\n} from \"./shutdown\";\nimport { Instrumentation } from \"./types\";\nimport { watchEnv } from \"./watchEnv\";\n\nexport class Server {\n private app: App;\n private instrumentation: Instrumentation;\n private handleSignals: boolean;\n private server: Bun.Server<unknown> | undefined;\n private stopping: Promise<number> | undefined;\n private settings: ShutdownSettings | undefined;\n\n constructor(params: {\n kernel: new () => Kernel;\n instrumentation?: Instrumentation;\n /**\n * Drain on `SIGTERM`/`SIGINT` and exit — production only. `false` leaves\n * the signals to the app, which can call `stop()` itself.\n */\n handleSignals?: boolean;\n }) {\n this.app = new App({ kernel: params.kernel });\n this.handleSignals = params.handleSignals ?? true;\n this.instrumentation = serveForShutdown(\n params.instrumentation ??\n ((req: Request, next: (req: Request) => Promise<Response>) => next(req)),\n );\n }\n\n /**\n * Boots the application and starts listening. Resolves with the `Bun.Server`\n * once it is accepting requests.\n */\n async start(): Promise<Bun.Server<unknown>> {\n // Before the boot, not after: `httpDev`/`httpProd` set this too, but they\n // are imported below — after every provider's `boot()` has run — so a\n // service that resolves a path during boot used to read `undefined`\n // (`services/logging`, #423). Same rule `httpProd` computes it with, so\n // the assignment it makes a moment later is the same value.\n //\n // It doubles as the marker for \"this process is serving\": nothing else\n // sets `ROOT_DIR`, so a console command or a migration — which boot the\n // same providers through `runConsole` — can tell that it is not one, and\n // skip work that only a server should do at boot.\n process.env.ROOT_DIR = projectRoot();\n\n // Read once, ahead of the boot, so the signal handling below and the mode\n // that starts after the boot cannot disagree: whatever a provider's\n // `boot()` does to the environment, this process is the server it was\n // started as.\n const production = process.env.NODE_ENV === \"production\";\n\n if (production) {\n // Before the boot, not just before listening: the whole start-up is a\n // window in which a signal can arrive, and the slow part of it is the\n // boot — a connection pool, the config, the dictionaries. A rolling\n // update that `SIGTERM`s a pod two seconds in finds no listener\n // installed, and the default action for both signals is to kill the\n // process on the spot: no provider's `shutdown()`, no pool closed,\n // nothing flushed. Installing here makes that a drain like any other.\n // `stop()` handles a server that does not exist yet (nothing to close,\n // requests cannot be in flight) and `Application.shutdown` calls the\n // hooks of providers that registered but never booted, swallowing\n // whatever they throw — half a boot is exactly when a hook is most\n // likely to trip over something it expected `boot()` to have made.\n if (this.handleSignals) installShutdownSignals(() => this.stop());\n // Read now, so a bad value is warned about while the operator is\n // watching the deploy, not first at the shutdown it spoils — and so a\n // signal during the boot drains on the configured budget rather than on\n // whatever `stop()` reads at the time.\n this.settings = shutdownSettings();\n }\n\n // Phase two of the boot. `new App({ kernel })` already ran every provider's\n // synchronous `register()`; this awaits their `boot()` before the first\n // request is served.\n await this.app.waitForBoot();\n\n // Dynamic import so each mode only pulls in its own code: `httpDev` drags in\n // Vite (dev-only) and `httpProd` reads the built `dist/` manifests — neither\n // should load in the other environment.\n if (production) {\n const { httpProd } = await import(\"./httpProd.js\");\n this.server = await httpProd(this.app, this.instrumentation.bind(this));\n } else {\n // Dev only: reload `.env` into process.env on change so config edits take\n // effect without restarting the dev server (Bun reads `.env` only at\n // startup, even under `--hot`). No signal handling here: `bun --hot`\n // re-runs this on every reload, and a Ctrl+C in development should stop\n // the server now, not after a drain.\n watchEnv();\n const { httpDev } = await import(\"./httpDev.js\");\n this.server = await httpDev(this.app, this.instrumentation.bind(this));\n }\n return this.server;\n }\n\n /**\n * Stops the server gracefully and resolves with the exit code it earned: 0\n * when the in-flight requests drained and every provider shut down in time,\n * 1 otherwise. Does not exit the process — the signal handler does that.\n *\n * The order is `drain` in `./shutdown`: `isShuttingDown()` turns true, the\n * listener closes, in-flight requests finish under the grace period, then\n * every provider's `shutdown()` runs in reverse registration order.\n * Idempotent: a second call returns the first call's promise. Settings\n * default to the `GEMI_SHUTDOWN_*` environment variables.\n */\n stop(settings: Partial<ShutdownSettings> = {}): Promise<number> {\n this.stopping ??= drain({\n server: this.server,\n shutdownProviders: (options) => this.app.shutdown(options),\n settings: { ...(this.settings ?? shutdownSettings()), ...settings },\n });\n return this.stopping;\n }\n}\n"
5
+ "import type { Instrumentation } from \"./types\";\n\n/**\n * The application `gemi dev` is serving, kept across `bun --hot` reloads so\n * the next reload can shut it down (#652).\n *\n * A reload re-evaluates the whole module graph and `Server.start()` boots a\n * fresh application, with its own providers: its own database pool, Redis\n * client, scheduler and queue. Nothing used to stop the one it replaced, so\n * every save left a pool of idle Postgres connections behind (ten, Bun's\n * default) until the server's `max_connections` ran out for every process\n * sharing it.\n *\n * On `globalThis` under a registry symbol, like `__gemiVite`, because the\n * module holding it is re-evaluated too. What is read back after a reload is\n * therefore an instance of the *previous* graph's class, so it is typed by\n * the one method called on it and checked for that method before the call.\n */\nconst CURRENT = Symbol.for(\"gemi.server.devGeneration\");\n\nexport type RetireOptions = {\n /**\n * How long to wait for the requests the replaced application is still\n * serving before its providers shut down under them.\n */\n graceMs: number;\n /** The deadline `Application.shutdown` shares between the providers. */\n providerTimeoutMs: number;\n};\n\n/**\n * Ten seconds for the old application's requests, five for its providers.\n * Nothing waits on either but the old pool staying open a little longer. A\n * request that outlives the grace — a server-sent-events stream is open for as\n * long as its tab is — loses its database under it, which after a reload it\n * would soon anyway: the page is told to refetch against the new code.\n */\nexport const devRetireDefaults: RetireOptions = { graceMs: 10_000, providerTimeoutMs: 5_000 };\n\n/** The part of `App` a reload shuts down. */\ntype Shuttable = { shutdown(options?: { timeoutMs?: number }): Promise<unknown> };\n\ntype Retiring = { retire(options: RetireOptions): Promise<void> };\n\n/**\n * One application `gemi dev` served, with a count of the requests it is\n * serving so a reload can let them finish before shutting it down.\n *\n * Counted until the handler resolves with a `Response`, not until its body\n * has been sent. Wrapping every body to see its end — Vite's module responses\n * included — is a cost paid on each of the hundreds of requests a dev page\n * load makes, for the rare stream that spans a save. A query such a stream has\n * already sent still finishes: the pool's `close()` waits for it.\n */\nexport class DevGeneration {\n private inFlight = 0;\n private waiters = new Set<() => void>();\n\n constructor(private readonly app: Shuttable) {}\n\n /** Wraps `instrumentation` so every request through it is counted. */\n track(instrumentation: Instrumentation): Instrumentation {\n return async (req, next) => {\n this.inFlight++;\n try {\n return await instrumentation(req, next);\n } finally {\n this.inFlight--;\n if (this.inFlight === 0) this.wake();\n }\n };\n }\n\n /** Requests this application is serving right now. */\n get pending(): number {\n return this.inFlight;\n }\n\n /**\n * Waits up to `graceMs` for the requests in flight, then shuts the\n * application's providers down. Never rejects: it runs detached from the\n * reload, where a rejection would surface as an unhandled one and land in\n * the browser's error overlay, for a failure in code the save replaced.\n */\n async retire({ graceMs, providerTimeoutMs }: RetireOptions): Promise<void> {\n try {\n await this.idle(graceMs);\n // `.then` so a synchronous throw from `shutdown` lands in the catch too.\n await Promise.resolve().then(() => this.app.shutdown({ timeoutMs: providerTimeoutMs }));\n } catch (error) {\n reportFailure(error);\n }\n }\n\n private idle(timeoutMs: number): Promise<void> {\n if (this.inFlight === 0) return Promise.resolve();\n return new Promise((resolve) => {\n const done = () => {\n clearTimeout(timer);\n this.waiters.delete(done);\n resolve();\n };\n const timer = setTimeout(done, timeoutMs);\n this.waiters.add(done);\n });\n }\n\n private wake() {\n // Each `done` deletes itself, which a `Set` being iterated allows.\n for (const done of this.waiters) done();\n }\n}\n\n/**\n * Records `next` as the application `gemi dev` is serving and retires the one\n * it replaced, if any. Call it once `next` is serving — after `Bun.serve` has\n * swapped the fetch handler — so that no new request reaches the application\n * being shut down, and the ones already on it can finish.\n *\n * The caller does not wait for the returned retirement: the reload is done,\n * and the old application's requests and providers finishing is no reason to\n * hold up the new one. The promise is for tests, and never rejects.\n */\nexport function replaceDevGeneration(\n next: DevGeneration,\n options: RetireOptions = devRetireDefaults,\n): Promise<void> {\n const global = globalThis as { [CURRENT]?: Partial<Retiring> };\n const previous = global[CURRENT];\n global[CURRENT] = next;\n if (!previous || previous === next || typeof previous.retire !== \"function\") {\n return Promise.resolve();\n }\n const retire = previous.retire.bind(previous);\n // Guarded like `retire` itself, because this one is the previous graph's code.\n return Promise.resolve()\n .then(() => retire(options))\n .catch(reportFailure);\n}\n\nfunction reportFailure(error: unknown) {\n console.error(\"[gemi] Shutting down the application a reload replaced failed:\", error);\n}\n\n/** Test seam: forget the application a previous test left as current. */\nexport function resetDevGeneration() {\n delete (globalThis as { [CURRENT]?: unknown })[CURRENT];\n}\n",
6
+ "import { App } from \"../app\";\nimport { Kernel } from \"../kernel\";\nimport { projectRoot } from \"../support/discover\";\nimport { DevGeneration, devRetireDefaults, replaceDevGeneration } from \"./devReload\";\nimport {\n drain,\n installShutdownSignals,\n serveForShutdown,\n shutdownSettings,\n type ShutdownSettings,\n} from \"./shutdown\";\nimport { Instrumentation } from \"./types\";\nimport { watchEnv } from \"./watchEnv\";\n\nexport class Server {\n private app: App;\n private instrumentation: Instrumentation;\n private handleSignals: boolean;\n private server: Bun.Server<unknown> | undefined;\n private stopping: Promise<number> | undefined;\n private settings: ShutdownSettings | undefined;\n\n constructor(params: {\n kernel: new () => Kernel;\n instrumentation?: Instrumentation;\n /**\n * Drain on `SIGTERM`/`SIGINT` and exit — production only. `false` leaves\n * the signals to the app, which can call `stop()` itself.\n */\n handleSignals?: boolean;\n }) {\n this.app = new App({ kernel: params.kernel });\n this.handleSignals = params.handleSignals ?? true;\n this.instrumentation = serveForShutdown(\n params.instrumentation ??\n ((req: Request, next: (req: Request) => Promise<Response>) => next(req)),\n );\n }\n\n /**\n * Boots the application and starts listening. Resolves with the `Bun.Server`\n * once it is accepting requests.\n */\n async start(): Promise<Bun.Server<unknown>> {\n // Before the boot, not after: `httpDev`/`httpProd` set this too, but they\n // are imported below — after every provider's `boot()` has run — so a\n // service that resolves a path during boot used to read `undefined`\n // (`services/logging`, #423). Same rule `httpProd` computes it with, so\n // the assignment it makes a moment later is the same value.\n //\n // It doubles as the marker for \"this process is serving\": nothing else\n // sets `ROOT_DIR`, so a console command or a migration — which boot the\n // same providers through `runConsole` — can tell that it is not one, and\n // skip work that only a server should do at boot.\n process.env.ROOT_DIR = projectRoot();\n\n // Read once, ahead of the boot, so the signal handling below and the mode\n // that starts after the boot cannot disagree: whatever a provider's\n // `boot()` does to the environment, this process is the server it was\n // started as.\n const production = process.env.NODE_ENV === \"production\";\n\n if (production) {\n // Before the boot, not just before listening: the whole start-up is a\n // window in which a signal can arrive, and the slow part of it is the\n // boot — a connection pool, the config, the dictionaries. A rolling\n // update that `SIGTERM`s a pod two seconds in finds no listener\n // installed, and the default action for both signals is to kill the\n // process on the spot: no provider's `shutdown()`, no pool closed,\n // nothing flushed. Installing here makes that a drain like any other.\n // `stop()` handles a server that does not exist yet (nothing to close,\n // requests cannot be in flight) and `Application.shutdown` calls the\n // hooks of providers that registered but never booted, swallowing\n // whatever they throw — half a boot is exactly when a hook is most\n // likely to trip over something it expected `boot()` to have made.\n if (this.handleSignals) installShutdownSignals(() => this.stop());\n // Read now, so a bad value is warned about while the operator is\n // watching the deploy, not first at the shutdown it spoils — and so a\n // signal during the boot drains on the configured budget rather than on\n // whatever `stop()` reads at the time.\n this.settings = shutdownSettings();\n }\n\n // Phase two of the boot. `new App({ kernel })` already ran every provider's\n // synchronous `register()`; this awaits their `boot()` before the first\n // request is served.\n try {\n await this.app.waitForBoot();\n } catch (error) {\n // A `bun --hot` reload whose boot threw: the previous application keeps\n // serving, and this one never will, so whatever its providers opened\n // before the throw (the feature flags' warm-up, when they are on, has\n // opened the database pool by then) is closed now rather than left open\n // for good (#652). Production exits on a failed boot, which closes\n // everything anyway.\n if (!production) void this.app.shutdown({ timeoutMs: devRetireDefaults.providerTimeoutMs });\n throw error;\n }\n\n // Dynamic import so each mode only pulls in its own code: `httpDev` drags in\n // Vite (dev-only) and `httpProd` reads the built `dist/` manifests — neither\n // should load in the other environment.\n if (production) {\n const { httpProd } = await import(\"./httpProd.js\");\n this.server = await httpProd(this.app, this.instrumentation.bind(this));\n } else {\n // Dev only: reload `.env` into process.env on change so config edits take\n // effect without restarting the dev server (Bun reads `.env` only at\n // startup, even under `--hot`). No signal handling here: `bun --hot`\n // re-runs this on every reload, and a Ctrl+C in development should stop\n // the server now, not after a drain.\n watchEnv();\n const { httpDev } = await import(\"./httpDev.js\");\n const generation = new DevGeneration(this.app);\n this.server = await httpDev(this.app, generation.track(this.instrumentation.bind(this)));\n // Only now, with the new fetch handler live, has the application this\n // reload replaced stopped receiving requests; it is shut down once the\n // ones it is serving finish (#652). Not awaited, so the reload is not\n // held up by it.\n void replaceDevGeneration(generation);\n }\n return this.server;\n }\n\n /**\n * Stops the server gracefully and resolves with the exit code it earned: 0\n * when the in-flight requests drained and every provider shut down in time,\n * 1 otherwise. Does not exit the process — the signal handler does that.\n *\n * The order is `drain` in `./shutdown`: `isShuttingDown()` turns true, the\n * listener closes, in-flight requests finish under the grace period, then\n * every provider's `shutdown()` runs in reverse registration order.\n * Idempotent: a second call returns the first call's promise. Settings\n * default to the `GEMI_SHUTDOWN_*` environment variables.\n */\n stop(settings: Partial<ShutdownSettings> = {}): Promise<number> {\n this.stopping ??= drain({\n server: this.server,\n shutdownProviders: (options) => this.app.shutdown(options),\n settings: { ...(this.settings ?? shutdownSettings()), ...settings },\n });\n return this.stopping;\n }\n}\n"
6
7
  ],
7
- "mappings": ";wSAaO,CAAM,JAAO,JACV,DACA,WACA,cACA,OACA,SACA,SAER,WAAW,CAAC,EAQT,CACD,KAAK,IAAM,IAAI,EAAI,CAAE,OAAQ,EAAO,MAAO,CAAC,EAC5C,KAAK,cAAgB,EAAO,eAAiB,GAC7C,KAAK,gBAAkB,EACrB,EAAO,kBACJ,CAAC,EAAc,IAA8C,EAAK,CAAG,EAC1E,OAOI,MAAK,EAAiC,CAW1C,QAAQ,IAAI,SAAW,EAAY,EAMnC,IAAM,EAAa,mBAAyB,aAE5C,GAAI,EAAY,CAad,GAAI,KAAK,cAAe,EAAuB,IAAM,KAAK,KAAK,CAAC,EAKhE,KAAK,SAAW,EAAiB,EAWnC,GALA,MAAM,KAAK,IAAI,YAAY,EAKvB,EAAY,CACd,IAAQ,YAAa,KAAa,gCAClC,KAAK,OAAS,MAAM,EAAS,KAAK,IAAK,KAAK,gBAAgB,KAAK,IAAI,CAAC,EACjE,KAML,EAAS,EACT,IAAQ,WAAY,KAAa,gCACjC,KAAK,OAAS,MAAM,EAAQ,KAAK,IAAK,KAAK,gBAAgB,KAAK,IAAI,CAAC,EAEvE,OAAO,KAAK,OAcd,IAAI,CAAC,EAAsC,CAAC,EAAoB,CAM9D,OALA,KAAK,WAAa,EAAM,CACtB,OAAQ,KAAK,OACb,kBAAmB,CAAC,IAAY,KAAK,IAAI,SAAS,CAAO,EACzD,SAAU,IAAM,KAAK,UAAY,EAAiB,KAAO,CAAS,CACpE,CAAC,EACM,KAAK,SAEhB",
8
- "debugId": "E11DF1C1D352FDB464756E2164756E21",
8
+ "mappings": ";wSAkBA,DAAM,HAAU,EAAO,DAAI,sBAA2B,EAmBzC,EAAmC,CAAE,QAAS,IAAQ,kBAAmB,IAAM,EAiBrF,MAAM,CAAc,CAII,IAHrB,SAAW,EACX,QAAU,IAAI,IAEtB,WAAW,CAAkB,EAAgB,CAAhB,WAG7B,KAAK,CAAC,EAAmD,CACvD,MAAO,OAAO,EAAK,IAAS,CAC1B,KAAK,WACL,GAAI,CACF,OAAO,MAAM,EAAgB,EAAK,CAAI,SACtC,CAEA,GADA,KAAK,WACD,KAAK,WAAa,EAAG,KAAK,KAAK,OAMrC,QAAO,EAAW,CACpB,OAAO,KAAK,cASR,OAAM,EAAG,UAAS,qBAAmD,CACzE,GAAI,CACF,MAAM,KAAK,KAAK,CAAO,EAEvB,MAAM,QAAQ,QAAQ,EAAE,KAAK,IAAM,KAAK,IAAI,SAAS,CAAE,UAAW,CAAkB,CAAC,CAAC,EACtF,MAAO,EAAO,CACd,EAAc,CAAK,GAIf,IAAI,CAAC,EAAkC,CAC7C,GAAI,KAAK,WAAa,EAAG,OAAO,QAAQ,QAAQ,EAChD,OAAO,IAAI,QAAQ,CAAC,IAAY,CAC9B,IAAM,EAAO,IAAM,CACjB,aAAa,CAAK,EAClB,KAAK,QAAQ,OAAO,CAAI,EACxB,EAAQ,GAEJ,EAAQ,WAAW,EAAM,CAAS,EACxC,KAAK,QAAQ,IAAI,CAAI,EACtB,EAGK,IAAI,EAAG,CAEb,QAAW,KAAQ,KAAK,QAAS,EAAK,EAE1C,CAYO,SAAS,CAAoB,CAClC,EACA,EAAyB,EACV,CACf,IAAM,EAAS,WACT,EAAW,EAAO,GAExB,GADA,EAAO,GAAW,EACd,CAAC,GAAY,IAAa,GAAQ,OAAO,EAAS,SAAW,WAC/D,OAAO,QAAQ,QAAQ,EAEzB,IAAM,EAAS,EAAS,OAAO,KAAK,CAAQ,EAE5C,OAAO,QAAQ,QAAQ,EACpB,KAAK,IAAM,EAAO,CAAO,CAAC,EAC1B,MAAM,CAAa,EAGxB,SAAS,CAAa,CAAC,EAAgB,CACrC,QAAQ,MAAM,iEAAkE,CAAK,EC/HhF,MAAM,CAAO,CACV,IACA,gBACA,cACA,OACA,SACA,SAER,WAAW,CAAC,EAQT,CACD,KAAK,IAAM,IAAI,EAAI,CAAE,OAAQ,EAAO,MAAO,CAAC,EAC5C,KAAK,cAAgB,EAAO,eAAiB,GAC7C,KAAK,gBAAkB,EACrB,EAAO,kBACJ,CAAC,EAAc,IAA8C,EAAK,CAAG,EAC1E,OAOI,MAAK,EAAiC,CAW1C,QAAQ,IAAI,SAAW,EAAY,EAMnC,IAAM,EAAa,mBAAyB,aAE5C,GAAI,EAAY,CAad,GAAI,KAAK,cAAe,EAAuB,IAAM,KAAK,KAAK,CAAC,EAKhE,KAAK,SAAW,EAAiB,EAMnC,GAAI,CACF,MAAM,KAAK,IAAI,YAAY,EAC3B,MAAO,EAAO,CAOd,GAAI,CAAC,EAAiB,KAAK,IAAI,SAAS,CAAE,UAAW,EAAkB,iBAAkB,CAAC,EAC1F,MAAM,EAMR,GAAI,EAAY,CACd,IAAQ,YAAa,KAAa,gCAClC,KAAK,OAAS,MAAM,EAAS,KAAK,IAAK,KAAK,gBAAgB,KAAK,IAAI,CAAC,EACjE,KAML,EAAS,EACT,IAAQ,WAAY,KAAa,gCAC3B,EAAa,IAAI,EAAc,KAAK,GAAG,EAC7C,KAAK,OAAS,MAAM,EAAQ,KAAK,IAAK,EAAW,MAAM,KAAK,gBAAgB,KAAK,IAAI,CAAC,CAAC,EAKlF,EAAqB,CAAU,EAEtC,OAAO,KAAK,OAcd,IAAI,CAAC,EAAsC,CAAC,EAAoB,CAM9D,OALA,KAAK,WAAa,EAAM,CACtB,OAAQ,KAAK,OACb,kBAAmB,CAAC,IAAY,KAAK,IAAI,SAAS,CAAO,EACzD,SAAU,IAAM,KAAK,UAAY,EAAiB,KAAO,CAAS,CACpE,CAAC,EACM,KAAK,SAEhB",
9
+ "debugId": "AB09AEB7DB33E5DB64756E2164756E21",
9
10
  "names": []
10
11
  }
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- import{Ya as mW}from"../chunk-zytmxty5.js";import{Ab as AW,Bb as yW,Cb as VW,Db as gW,Eb as cW,Fb as EW,Za as u6,_a as BW,ab as xW,bb as NW,cb as LW,db as DW,eb as FW,fb as OW,gb as HW,hb as U,ib as UW,jb as SW,kb as fW,lb as RW,mb as kW,nb as _W,ob as CW,pb as bW,qb as vW,rb as IW,sb as TW,tb as i4,ub as o4,vb as y1,wb as qW,xb as hW,yb as PW,zb as wW}from"../chunk-9z3y20ms.js";import"../chunk-zbhqa2z7.js";import{Ib as o6,Jb as s6,Kb as a6,Lb as t6,Mb as r6,Nb as e6,Ob as WW,Pb as YW,Qb as JW,Rb as ZW,Sb as GW,Tb as $W,Ub as QW,Vb as KW,Wb as XW,Xb as zW}from"../chunk-1qwkhbc4.js";import{$b as n6,Yb as d6,Zb as p6,_b as l6,ac as i6}from"../chunk-b941c34r.js";import{fc as MW,gc as jW}from"../chunk-0kpvbnz0.js";import"../chunk-9hbwvcxn.js";import{$c as f6,Cc as $6,Cd as A6,Ed as y6,Ic as B6,Mc as N1,Oc as x6,Qc as M6,Rc as j6,Sc as N6,Tc as L6,Uc as D6,Vc as F6,Wc as O6,Xc as m4,Yc as H6,Zc as U6,_c as S6,ad as R6,ed as k6,fd as _6,gd as C6,hd as b6,id as v6,jd as I6,kd as T6,ld as q6,md as h6,oc as C0,od as P6,tc as r0,uc as e0,vc as W6,vd as w6,wc as Y6,xc as J6,yc as Z6,zc as G6}from"../chunk-e8z3nyd5.js";import"../chunk-34s3tfgk.js";import"../chunk-q0nqypca.js";import{Te as A1,Ue as P1,Ve as W1,hf as V,if as P,mf as Y1,nf as Q6,of as K6,pf as X6,qf as z6}from"../chunk-4h7z32zp.js";import"../chunk-gzs2y282.js";import"../chunk-1712rv0r.js";import"../chunk-bdfdjnht.js";import"../chunk-jcq0tbdv.js";import"../chunk-axxbkm30.js";import{hg as V6,ig as g6,jg as c6,kg as E6,lg as m6}from"../chunk-sp4wwg7s.js";import"../chunk-qjgfrsw7.js";import{qg as g}from"../chunk-989rzqd6.js";import"../chunk-0da5t7vs.js";import"../chunk-fvn78g6k.js";import"../chunk-e97kq9tn.js";import"../chunk-yaz93haz.js";import"../chunk-7yhsy039.js";import{Kg as d4}from"../chunk-xzp2ptj5.js";import{Buffer as u4}from"buffer";class p4 extends Y1{client;config;constructor(...W){super();this.config=W}async connect(){let W=await import("../chunk-rkx6qhwg.js");return this.client??=new W.S3Client(...this.config),{sdk:W,client:this.client}}async put(W,{signal:Y}={}){Y?.throwIfAborted();let J,Z,G,$=process.env.BUCKET_NAME;if(W instanceof Blob)J=W,G=`${Bun.randomUUIDv7()}.${W.type.split("/")[1].split(";")[0]}`,Z=W.type;else J=W.body,G=W.name,$=W.bucket??$,Z=W.contentType||(J instanceof Blob||J instanceof File?J.type:void 0);Z=Z||void 0;let Q=J instanceof u4?J:J instanceof Blob||J instanceof File?u4.from(await J.arrayBuffer()):"",{sdk:K,client:z}=await this.connect();return Y?.throwIfAborted(),await z.send(new K.PutObjectCommand({Bucket:$,Key:G,Body:Q,ContentType:Z}),{abortSignal:Y}),G}async delete(W){let Y=typeof W==="string"?W:W.name,J=(typeof W==="string"?void 0:W.bucket)??process.env.BUCKET_NAME;if(!Y)throw Error("Object name has to be specified");let{sdk:Z,client:G}=await this.connect();try{await G.send(new Z.DeleteObjectCommand({Bucket:J,Key:Y}))}catch($){if($?.name==="NoSuchKey"||$?.$metadata?.httpStatusCode===404)return;throw $}}async list(W){let{sdk:Y,client:J}=await this.connect();return await J.send(new Y.ListObjectsV2Command({Bucket:process.env.BUCKET_NAME,Prefix:W}))}async fetch(W){let Y=process.env.BUCKET_NAME,J;if(typeof W==="string")J=W;else Y=W.bucket??Y,J=W.name;if(!J)throw Error("Object name has to be specified");let{sdk:Z,client:G}=await this.connect(),$=await G.send(new Z.GetObjectCommand({Bucket:Y,Key:J}));return new Response($.Body.transformToWebStream(),{headers:{"Content-Type":$.ContentType,"Content-Length":$.ContentLength.toString(),"Cache-Control":"private, max-age=12000, must-revalidate","Last-Modified":$.LastModified?.toUTCString()||"",ETag:$.ETag||""}})}async read(W){let Y=typeof W==="string"?{name:W}:W,J=Y.bucket??process.env.BUCKET_NAME,{name:Z,range:G=null}=Y;if(!Z)throw Error("Object name has to be specified");let{sdk:$,client:Q}=await this.connect(),K;try{K=await Q.send(new $.GetObjectCommand({Bucket:J,Key:Z,Range:G?P1(G):void 0}))}catch(M){if(M?.name==="InvalidRange"||M?.$metadata?.httpStatusCode===416)throw new V(await this.size({name:Z,bucket:J}));if(M?.name==="NoSuchKey"||M?.$metadata?.httpStatusCode===404)throw new P(Z);throw M}let z=W1(K.ContentRange),X=Boolean(z)&&K.$metadata?.httpStatusCode===206,x=z?.total??K.ContentLength??0;return{body:K.Body.transformToWebStream(),start:z?.start??0,end:z?.end??x-1,total:x,partial:X,type:K.ContentType??"application/octet-stream",etag:K.ETag||void 0,lastModified:K.LastModified,name:Z}}async size(W){let Y=typeof W==="string"?W:W.name,J=(typeof W==="string"?void 0:W.bucket)??process.env.BUCKET_NAME,{sdk:Z,client:G}=await this.connect();try{return(await G.send(new Z.HeadObjectCommand({Bucket:J,Key:Y}))).ContentLength??0}catch($){if($?.name==="NotFound"||$?.$metadata?.httpStatusCode===404)throw new P(Y);throw $}}}import{Buffer as l4}from"buffer";import{Readable as T0}from"stream";function w1(W){return W?.statusCode===404||W?.details?.errorCode==="BlobNotFound"||W?.code==="BlobNotFound"}function q0(W){return W?.statusCode===416||W?.details?.errorCode==="InvalidRange"||W?.code==="InvalidRange"}class n4 extends Y1{config;serviceClientPromise=null;constructor(W={}){super();this.config=W}async serviceClient(){if(this.config.serviceClient)return this.config.serviceClient;return this.serviceClientPromise??=(async()=>{let W;try{W=await import("@azure/storage-blob")}catch{throw Error("AzureBlobDriver requires the '@azure/storage-blob' package. Install it with `bun add @azure/storage-blob`, or pass a pre-built `serviceClient`.")}let Y=this.config.connectionString??process.env.AZURE_STORAGE_CONNECTION_STRING;if(this.config.url)return new W.BlobServiceClient(this.config.url,this.config.credential);if(Y)return W.BlobServiceClient.fromConnectionString(Y);throw Error("AzureBlobDriver needs a connection string (AZURE_STORAGE_CONNECTION_STRING), a `url`, or a `serviceClient`.")})(),this.serviceClientPromise}async blob(W,Y){if(!W)throw Error("Object name has to be specified");let J=Y??this.config.container??process.env.BUCKET_NAME;if(!J)throw Error("AzureBlobDriver needs a container name, from `bucket`, the `container` config, or BUCKET_NAME.");return(await this.serviceClient()).getContainerClient(J).getBlockBlobClient(W)}async put(W,{signal:Y}={}){Y?.throwIfAborted();let J,Z,G,$;if(W instanceof Blob)J=W,G=`${Bun.randomUUIDv7()}.${W.type.split("/")[1].split(";")[0]}`,Z=W.type;else J=W.body,G=W.name,$=W.bucket,Z=W.contentType??(J instanceof Blob||J instanceof File?J.type:void 0);let Q=J instanceof l4?J:l4.from(await J.arrayBuffer()),K=await this.blob(G,$);return Y?.throwIfAborted(),await K.uploadData(Q,{blobHTTPHeaders:Z?{blobContentType:Z}:void 0,abortSignal:Y}),G}async list(W){let Y=this.config.container??process.env.BUCKET_NAME,Z=(await this.serviceClient()).getContainerClient(Y),G=[];for await(let $ of Z.listBlobsFlat({prefix:W}))G.push($.name);return G}async fetch(W){let Y=await this.read(typeof W==="string"?{name:W}:{name:W.name,bucket:W.bucket});return new Response(Y.body,{headers:{"Content-Type":Y.type,"Content-Length":String(Y.total),"Cache-Control":"private, max-age=12000, must-revalidate","Last-Modified":Y.lastModified?.toUTCString()??"",ETag:Y.etag??""}})}async read(W){let Y=typeof W==="string"?{name:W}:W,{name:J,bucket:Z,range:G=null}=Y,$=await this.blob(J,Z),Q,K,z=G?.kind==="suffix"||G?.kind==="offset"&&G.end===null&&G.start===0;if(G&&z){let k=await this.size({name:J,bucket:Z}),e=A1(G,k);if(!e)throw new V(k);Q=e.start,K=e.length}else if(G?.kind==="offset")Q=G.start,K=G.end===null?void 0:G.end-G.start+1;let X;try{X=await $.download(Q,K)}catch(k){if(q0(k))throw new V(await this.size({name:J,bucket:Z}));if(w1(k))throw new P(J);throw k}let x=W1(X.contentRange),M=Boolean(x),R=x?.total??X.contentLength??0;return{body:await h0(X),start:x?.start??0,end:x?.end??R-1,total:R,partial:M,type:X.contentType??"application/octet-stream",etag:X.etag||void 0,lastModified:X.lastModified,name:J}}async size(W){let Y=typeof W==="string"?W:W.name,J=typeof W==="string"?void 0:W.bucket,Z=await this.blob(Y,J);try{return(await Z.getProperties()).contentLength??0}catch(G){if(w1(G))throw new P(Y);throw G}}async delete(W){let Y=typeof W==="string"?W:W.name,J=typeof W==="string"?void 0:W.bucket,Z=await this.blob(Y,J);try{await Z.delete()}catch(G){if(w1(G))return;throw G}}}async function h0(W){if(W.blobBody)return await W.blobBody;if(W.readableStreamBody)return T0.toWeb(W.readableStreamBody);return null}var P0=`
2
+ import{Ya as mW}from"../chunk-zytmxty5.js";import{Ab as AW,Bb as yW,Cb as VW,Db as gW,Eb as cW,Fb as EW,Za as u6,_a as BW,ab as xW,bb as NW,cb as LW,db as DW,eb as FW,fb as OW,gb as HW,hb as U,ib as UW,jb as SW,kb as fW,lb as RW,mb as kW,nb as _W,ob as CW,pb as bW,qb as vW,rb as IW,sb as TW,tb as i4,ub as o4,vb as y1,wb as qW,xb as hW,yb as PW,zb as wW}from"../chunk-cg469vsg.js";import"../chunk-zbhqa2z7.js";import{Ib as o6,Jb as s6,Kb as a6,Lb as t6,Mb as r6,Nb as e6,Ob as WW,Pb as YW,Qb as JW,Rb as ZW,Sb as GW,Tb as $W,Ub as QW,Vb as KW,Wb as XW,Xb as zW}from"../chunk-1qwkhbc4.js";import{$b as n6,Yb as d6,Zb as p6,_b as l6,ac as i6}from"../chunk-b941c34r.js";import{fc as MW,gc as jW}from"../chunk-0kpvbnz0.js";import"../chunk-9hbwvcxn.js";import{$c as f6,Cc as $6,Cd as A6,Ed as y6,Ic as B6,Mc as N1,Oc as x6,Qc as M6,Rc as j6,Sc as N6,Tc as L6,Uc as D6,Vc as F6,Wc as O6,Xc as m4,Yc as H6,Zc as U6,_c as S6,ad as R6,ed as k6,fd as _6,gd as C6,hd as b6,id as v6,jd as I6,kd as T6,ld as q6,md as h6,oc as C0,od as P6,tc as r0,uc as e0,vc as W6,vd as w6,wc as Y6,xc as J6,yc as Z6,zc as G6}from"../chunk-azebyh90.js";import"../chunk-34s3tfgk.js";import"../chunk-q0nqypca.js";import{Te as A1,Ue as P1,Ve as W1,hf as V,if as P,mf as Y1,nf as Q6,of as K6,pf as X6,qf as z6}from"../chunk-4h7z32zp.js";import"../chunk-gzs2y282.js";import"../chunk-1712rv0r.js";import"../chunk-bdfdjnht.js";import"../chunk-jcq0tbdv.js";import"../chunk-axxbkm30.js";import{hg as V6,ig as g6,jg as c6,kg as E6,lg as m6}from"../chunk-sp4wwg7s.js";import"../chunk-qjgfrsw7.js";import{qg as g}from"../chunk-989rzqd6.js";import"../chunk-0da5t7vs.js";import"../chunk-fvn78g6k.js";import"../chunk-e97kq9tn.js";import"../chunk-yaz93haz.js";import"../chunk-7yhsy039.js";import{Kg as d4}from"../chunk-xzp2ptj5.js";import{Buffer as u4}from"buffer";class p4 extends Y1{client;config;constructor(...W){super();this.config=W}async connect(){let W=await import("../chunk-rkx6qhwg.js");return this.client??=new W.S3Client(...this.config),{sdk:W,client:this.client}}async put(W,{signal:Y}={}){Y?.throwIfAborted();let J,Z,G,$=process.env.BUCKET_NAME;if(W instanceof Blob)J=W,G=`${Bun.randomUUIDv7()}.${W.type.split("/")[1].split(";")[0]}`,Z=W.type;else J=W.body,G=W.name,$=W.bucket??$,Z=W.contentType||(J instanceof Blob||J instanceof File?J.type:void 0);Z=Z||void 0;let Q=J instanceof u4?J:J instanceof Blob||J instanceof File?u4.from(await J.arrayBuffer()):"",{sdk:K,client:z}=await this.connect();return Y?.throwIfAborted(),await z.send(new K.PutObjectCommand({Bucket:$,Key:G,Body:Q,ContentType:Z}),{abortSignal:Y}),G}async delete(W){let Y=typeof W==="string"?W:W.name,J=(typeof W==="string"?void 0:W.bucket)??process.env.BUCKET_NAME;if(!Y)throw Error("Object name has to be specified");let{sdk:Z,client:G}=await this.connect();try{await G.send(new Z.DeleteObjectCommand({Bucket:J,Key:Y}))}catch($){if($?.name==="NoSuchKey"||$?.$metadata?.httpStatusCode===404)return;throw $}}async list(W){let{sdk:Y,client:J}=await this.connect();return await J.send(new Y.ListObjectsV2Command({Bucket:process.env.BUCKET_NAME,Prefix:W}))}async fetch(W){let Y=process.env.BUCKET_NAME,J;if(typeof W==="string")J=W;else Y=W.bucket??Y,J=W.name;if(!J)throw Error("Object name has to be specified");let{sdk:Z,client:G}=await this.connect(),$=await G.send(new Z.GetObjectCommand({Bucket:Y,Key:J}));return new Response($.Body.transformToWebStream(),{headers:{"Content-Type":$.ContentType,"Content-Length":$.ContentLength.toString(),"Cache-Control":"private, max-age=12000, must-revalidate","Last-Modified":$.LastModified?.toUTCString()||"",ETag:$.ETag||""}})}async read(W){let Y=typeof W==="string"?{name:W}:W,J=Y.bucket??process.env.BUCKET_NAME,{name:Z,range:G=null}=Y;if(!Z)throw Error("Object name has to be specified");let{sdk:$,client:Q}=await this.connect(),K;try{K=await Q.send(new $.GetObjectCommand({Bucket:J,Key:Z,Range:G?P1(G):void 0}))}catch(M){if(M?.name==="InvalidRange"||M?.$metadata?.httpStatusCode===416)throw new V(await this.size({name:Z,bucket:J}));if(M?.name==="NoSuchKey"||M?.$metadata?.httpStatusCode===404)throw new P(Z);throw M}let z=W1(K.ContentRange),X=Boolean(z)&&K.$metadata?.httpStatusCode===206,x=z?.total??K.ContentLength??0;return{body:K.Body.transformToWebStream(),start:z?.start??0,end:z?.end??x-1,total:x,partial:X,type:K.ContentType??"application/octet-stream",etag:K.ETag||void 0,lastModified:K.LastModified,name:Z}}async size(W){let Y=typeof W==="string"?W:W.name,J=(typeof W==="string"?void 0:W.bucket)??process.env.BUCKET_NAME,{sdk:Z,client:G}=await this.connect();try{return(await G.send(new Z.HeadObjectCommand({Bucket:J,Key:Y}))).ContentLength??0}catch($){if($?.name==="NotFound"||$?.$metadata?.httpStatusCode===404)throw new P(Y);throw $}}}import{Buffer as l4}from"buffer";import{Readable as T0}from"stream";function w1(W){return W?.statusCode===404||W?.details?.errorCode==="BlobNotFound"||W?.code==="BlobNotFound"}function q0(W){return W?.statusCode===416||W?.details?.errorCode==="InvalidRange"||W?.code==="InvalidRange"}class n4 extends Y1{config;serviceClientPromise=null;constructor(W={}){super();this.config=W}async serviceClient(){if(this.config.serviceClient)return this.config.serviceClient;return this.serviceClientPromise??=(async()=>{let W;try{W=await import("@azure/storage-blob")}catch{throw Error("AzureBlobDriver requires the '@azure/storage-blob' package. Install it with `bun add @azure/storage-blob`, or pass a pre-built `serviceClient`.")}let Y=this.config.connectionString??process.env.AZURE_STORAGE_CONNECTION_STRING;if(this.config.url)return new W.BlobServiceClient(this.config.url,this.config.credential);if(Y)return W.BlobServiceClient.fromConnectionString(Y);throw Error("AzureBlobDriver needs a connection string (AZURE_STORAGE_CONNECTION_STRING), a `url`, or a `serviceClient`.")})(),this.serviceClientPromise}async blob(W,Y){if(!W)throw Error("Object name has to be specified");let J=Y??this.config.container??process.env.BUCKET_NAME;if(!J)throw Error("AzureBlobDriver needs a container name, from `bucket`, the `container` config, or BUCKET_NAME.");return(await this.serviceClient()).getContainerClient(J).getBlockBlobClient(W)}async put(W,{signal:Y}={}){Y?.throwIfAborted();let J,Z,G,$;if(W instanceof Blob)J=W,G=`${Bun.randomUUIDv7()}.${W.type.split("/")[1].split(";")[0]}`,Z=W.type;else J=W.body,G=W.name,$=W.bucket,Z=W.contentType??(J instanceof Blob||J instanceof File?J.type:void 0);let Q=J instanceof l4?J:l4.from(await J.arrayBuffer()),K=await this.blob(G,$);return Y?.throwIfAborted(),await K.uploadData(Q,{blobHTTPHeaders:Z?{blobContentType:Z}:void 0,abortSignal:Y}),G}async list(W){let Y=this.config.container??process.env.BUCKET_NAME,Z=(await this.serviceClient()).getContainerClient(Y),G=[];for await(let $ of Z.listBlobsFlat({prefix:W}))G.push($.name);return G}async fetch(W){let Y=await this.read(typeof W==="string"?{name:W}:{name:W.name,bucket:W.bucket});return new Response(Y.body,{headers:{"Content-Type":Y.type,"Content-Length":String(Y.total),"Cache-Control":"private, max-age=12000, must-revalidate","Last-Modified":Y.lastModified?.toUTCString()??"",ETag:Y.etag??""}})}async read(W){let Y=typeof W==="string"?{name:W}:W,{name:J,bucket:Z,range:G=null}=Y,$=await this.blob(J,Z),Q,K,z=G?.kind==="suffix"||G?.kind==="offset"&&G.end===null&&G.start===0;if(G&&z){let k=await this.size({name:J,bucket:Z}),e=A1(G,k);if(!e)throw new V(k);Q=e.start,K=e.length}else if(G?.kind==="offset")Q=G.start,K=G.end===null?void 0:G.end-G.start+1;let X;try{X=await $.download(Q,K)}catch(k){if(q0(k))throw new V(await this.size({name:J,bucket:Z}));if(w1(k))throw new P(J);throw k}let x=W1(X.contentRange),M=Boolean(x),R=x?.total??X.contentLength??0;return{body:await h0(X),start:x?.start??0,end:x?.end??R-1,total:R,partial:M,type:X.contentType??"application/octet-stream",etag:X.etag||void 0,lastModified:X.lastModified,name:J}}async size(W){let Y=typeof W==="string"?W:W.name,J=typeof W==="string"?void 0:W.bucket,Z=await this.blob(Y,J);try{return(await Z.getProperties()).contentLength??0}catch(G){if(w1(G))throw new P(Y);throw G}}async delete(W){let Y=typeof W==="string"?W:W.name,J=typeof W==="string"?void 0:W.bucket,Z=await this.blob(Y,J);try{await Z.delete()}catch(G){if(w1(G))return;throw G}}}async function h0(W){if(W.blobBody)return await W.blobBody;if(W.readableStreamBody)return T0.toWeb(W.readableStreamBody);return null}var P0=`
3
3
  local currentKey = KEYS[1]
4
4
  local previousKey = KEYS[2]
5
5
  local limit = tonumber(ARGV[1])
@@ -22,5 +22,10 @@ export declare class RedisManager {
22
22
  * property` instead, which is not a change this was meant to make.
23
23
  */
24
24
  set client(client: RedisClient);
25
+ /**
26
+ * Closes the client, when one was built; a later `client` builds a new one.
27
+ * Does not build one to close it.
28
+ */
29
+ close(): void;
25
30
  }
26
31
  //# sourceMappingURL=RedisManager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RedisManager.d.ts","sourceRoot":"","sources":["../../../services/redis/RedisManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,KAAK,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAsB5C,qBAAa,YAAY;IAKJ,MAAM,EAAE,WAAW;IAJtC,MAAM,CAAC,KAAK,SAAW;IAEvB,OAAO,CAAC,QAAQ,CAA4B;gBAEzB,MAAM,GAAE,WAAgB;IAE3C;;;;;;OAMG;IACH,IAAI,MAAM,IAAI,WAAW,CAKxB;IAED;;;;;;;OAOG;IACH,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,EAE7B;CACF"}
1
+ {"version":3,"file":"RedisManager.d.ts","sourceRoot":"","sources":["../../../services/redis/RedisManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,KAAK,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAsB5C,qBAAa,YAAY;IAKJ,MAAM,EAAE,WAAW;IAJtC,MAAM,CAAC,KAAK,SAAW;IAEvB,OAAO,CAAC,QAAQ,CAA4B;gBAEzB,MAAM,GAAE,WAAgB;IAE3C;;;;;;OAMG;IACH,IAAI,MAAM,IAAI,WAAW,CAKxB;IAED;;;;;;;OAOG;IACH,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,EAE7B;IAED;;;OAGG;IACH,KAAK,IAAI,IAAI;CAKd"}
@@ -1,5 +1,11 @@
1
1
  import { ServiceProvider } from "../../support/ServiceProvider";
2
2
  export declare class RedisServiceProvider extends ServiceProvider {
3
3
  register(): void;
4
+ /**
5
+ * Closes the client, if anything built one. Under `gemi dev` this is what
6
+ * keeps each `bun --hot` reload from leaving a Redis connection behind; see
7
+ * `DatabaseServiceProvider.shutdown`.
8
+ */
9
+ shutdown(): void;
4
10
  }
5
11
  //# sourceMappingURL=RedisServiceProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RedisServiceProvider.d.ts","sourceRoot":"","sources":["../../../services/redis/RedisServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAKhE,qBAAa,oBAAqB,SAAQ,eAAe;IACvD,QAAQ;CAYT"}
1
+ {"version":3,"file":"RedisServiceProvider.d.ts","sourceRoot":"","sources":["../../../services/redis/RedisServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAKhE,qBAAa,oBAAqB,SAAQ,eAAe;IACvD,QAAQ;IAaR;;;;OAIG;IACH,QAAQ;CAIT"}
@@ -13,6 +13,12 @@ import type { Application } from "../foundation/Application";
13
13
  * abandoned and the next begins, and one that throws is logged and does not
14
14
  * stop the rest. See `Application.shutdown`.
15
15
  *
16
+ * Under `gemi dev` it also runs on every `bun --hot` reload, for the
17
+ * application the reload replaced, once the new one is serving and the old
18
+ * one's requests have finished (see `server/devReload.ts`). A hook that closes
19
+ * something kept on `globalThis` across reloads would close it for the new
20
+ * application too.
21
+ *
16
22
  * `timeoutMs` is what is left of that shared deadline when this provider is
17
23
  * reached. A provider that waits for something — jobs finishing, a queue
18
24
  * draining — should bound its own wait by a little less than this, so that it
@@ -1 +1 @@
1
- {"version":3,"file":"ServiceProvider.d.ts","sourceRoot":"","sources":["../../support/ServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,8BAAsB,eAAe;IACvB,SAAS,CAAC,GAAG,EAAE,WAAW;gBAAhB,GAAG,EAAE,WAAW;IAEtC,QAAQ,IAAI,IAAI;IAEhB,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE5B,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAChE"}
1
+ {"version":3,"file":"ServiceProvider.d.ts","sourceRoot":"","sources":["../../support/ServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,8BAAsB,eAAe;IACvB,SAAS,CAAC,GAAG,EAAE,WAAW;gBAAhB,GAAG,EAAE,WAAW;IAEtC,QAAQ,IAAI,IAAI;IAEhB,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE5B,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAChE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gemi",
3
- "version": "0.79.0",
3
+ "version": "0.80.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Enes Tufekci <enes@gemijs.dev>",
@@ -1,6 +0,0 @@
1
- // @bun
2
- import{hc as G}from"./chunk-9hbwvcxn.js";import{Je as F}from"./chunk-q0nqypca.js";import{vf as E}from"./chunk-1712rv0r.js";import{Lf as B,Yf as z}from"./chunk-jcq0tbdv.js";class J extends G{register(){this.app.singleton(z,()=>{let j=new z(E(B(),this.app.config.get("database",{})));return K(j),j})}}function K(j){if(Bun.env.NODE_ENV!=="development")return;for(let x of j.connectionNames){let A=j.connection(x);if(F(A.dialect))continue;let H=x===j.name?"DATABASE_URL":`The "${x}" database connection`;console.warn(`[gemi] ${H} points at ${A.dialect}, which the gemi ORM `+"does not implement \u2014 only sqlite and postgres are. The connection "+`works, so raw SQL through DB.query / DB.sql and transactions are fine, but every model operation will raise UnsupportedDialectError.
3
- (development only)`)}}
4
- export{J as ya};
5
-
6
- //# debugId=9328D61DA42EA3AC64756E2164756E21
@@ -1,10 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../database/DatabaseServiceProvider.ts"],
4
- "sourcesContent": [
5
- "import { ormSupports } from \"../orm/dialect\";\nimport { ServiceProvider } from \"../support/ServiceProvider\";\nimport { withDefaults } from \"../support/withDefaults\";\nimport { databaseConfigDefaults, type DatabaseConfig } from \"./config\";\nimport { DatabaseManager } from \"./DatabaseManager\";\n\nexport class DatabaseServiceProvider extends ServiceProvider {\n register() {\n this.app.singleton(\n DatabaseManager,\n () => {\n const database = new DatabaseManager(\n withDefaults(\n databaseConfigDefaults(),\n this.app.config.get<DatabaseConfig>(\"database\", {}),\n ),\n );\n\n warnIfOrmUnavailable(database);\n return database;\n },\n );\n }\n}\n\n/**\n * Say it at boot, not on the first model query.\n *\n * `DatabaseManager` connects to MySQL and MariaDB without complaint — Bun's\n * client speaks them — and the ORM's compiler has no dialect for either. So a\n * deploy pointed at one starts, passes a health check, serves whatever does not\n * touch a model, and raises `UnsupportedDialectError` on the first `findMany`.\n * That is the latest and most expensive moment to learn it.\n *\n * A **warning rather than a throw**, deliberately: raw SQL through `DB.query`\n * and `DB.sql` genuinely works on those dialects, and so do transactions.\n * Refusing to construct the manager would break code that is correct today to\n * pre-empt an error the ORM already raises clearly. The supported set is a\n * property of the query compiler, not of the connection.\n *\n * Development only, for the same reason the slow-transaction warning is: it is\n * a diagnostic aimed at whoever is choosing the database, and it should not\n * add noise to a production log every time the container starts.\n */\nfunction warnIfOrmUnavailable(database: DatabaseManager): void {\n if (process.env.NODE_ENV !== \"development\") return;\n\n // Every connection, not only the default one. A named connection is a whole\n // pool an application will run model queries on — `Model.on(\"analytics\")` —\n // so it can be pointed at MySQL exactly as the default can, and warning about\n // only one of them would make the check pass while the query that raises is\n // on the other.\n for (const name of database.connectionNames) {\n const connection = database.connection(name);\n if (ormSupports(connection.dialect)) continue;\n\n const source =\n name === database.name\n ? \"DATABASE_URL\"\n : `The \"${name}\" database connection`;\n\n console.warn(\n `[gemi] ${source} points at ${connection.dialect}, which the gemi ORM ` +\n `does not implement — only sqlite and postgres are. The connection ` +\n `works, so raw SQL through DB.query / DB.sql and transactions are ` +\n `fine, but every model operation will raise ` +\n `UnsupportedDialectError.\\n(development only)`,\n );\n }\n}\n"
6
- ],
7
- "mappings": ";8LAMO,AAAM,IAAgC,LAAgB,CAC3D,QAAQ,EAAG,CACT,KAAK,IAAI,UACP,EACA,IAAM,CACJ,IAAM,EAAW,IAAI,EACnB,EACE,EAAuB,EACvB,KAAK,IAAI,OAAO,IAAoB,WAAY,CAAC,CAAC,CACpD,CACF,EAGA,OADA,EAAqB,CAAQ,EACtB,EAEX,EAEJ,CAqBA,SAAS,CAAoB,CAAC,EAAiC,CAC7D,GAAI,mBAAyB,cAAe,OAO5C,QAAW,KAAQ,EAAS,gBAAiB,CAC3C,IAAM,EAAa,EAAS,WAAW,CAAI,EAC3C,GAAI,EAAY,EAAW,OAAO,EAAG,SAErC,IAAM,EACJ,IAAS,EAAS,KACd,eACA,QAAQ,yBAEd,QAAQ,KACN,UAAU,eAAoB,EAAW,+BACvC,0EACA;AAAA,mBAGJ",
8
- "debugId": "9328D61DA42EA3AC64756E2164756E21",
9
- "names": []
10
- }
@@ -1,5 +0,0 @@
1
- // @bun
2
- import{Gb as y}from"./chunk-zbhqa2z7.js";import{Pb as w,Rb as M,Tb as WZ,Ub as GZ,Vb as YZ,Xb as BZ}from"./chunk-1qwkhbc4.js";import{$b as e,_b as a,ac as J,bc as ZZ,cc as $Z,ec as HZ}from"./chunk-b941c34r.js";import{hc as C}from"./chunk-9hbwvcxn.js";import{Cc as l,Cd as N,Dd as b,Ed as v,Ic as k,Jc as R,Mc as t,Oc as n,Xc as CZ,Yc as h,ad as D,id as u,jd as f,od as d,uc as PZ,vd as S}from"./chunk-e8z3nyd5.js";import{Md as zZ,Nd as m}from"./chunk-34s3tfgk.js";import{qf as p}from"./chunk-4h7z32zp.js";import{vf as U}from"./chunk-1712rv0r.js";import{Bf as UZ}from"./chunk-bdfdjnht.js";import{lg as c}from"./chunk-sp4wwg7s.js";import{qg as j}from"./chunk-989rzqd6.js";import{rg as XZ}from"./chunk-0da5t7vs.js";async function EZ(Z){try{if(!await Z.shouldRun.call(Z))return}catch($){console.error(`Error evaluating shouldRun for cron job ${Z.name}:`,$);return}try{await Z.onTick.call(Z)}catch($){console.error(`Error executing cron job ${Z.name}:`,$)}try{await Z.callback.call(Z)}catch($){console.error(`Error in cron job ${Z.name}:`,$)}try{await Z.onComplete.call(Z)}catch($){console.error(`Error completing cron job ${Z.name}:`,$)}}class A{static token="scheduler";handles=[];resolved;inFlight=new Map;stopped=!1;constructor(Z){this.resolved=Z.jobs}get jobs(){return[...this.resolved]}useJobs(Z){this.resolved=Z}start(Z=($)=>$()){if(this.stopped=!1,this.resolved.length===0)return;let $=globalThis.__gemiCronJobs??=new Map,H=new Map;for(let G of this.resolved){let V=new G;if(!V.name){console.error(`Cron job must have a name \u2014 ${G.name||"an anonymous class"} has none, so it is not scheduled. If this is a base class that other jobs extend, move it out of the discovered directory: \`abstract\` does not survive to runtime, so a class under app/cron is indistinguishable from a job.`);continue}if(!V.cron){console.error(`Cron job must have an expression. Job: ${V.name}`);continue}let X=H.get(V.name);if(X!==void 0){console.error(`Two cron jobs claim the name "${V.name}" \u2014 ${X} is scheduled and ${G.name||"an anonymous class"} is not. A name is the scheduler's key, so only one job can hold it. Rename one.`);continue}H.set(V.name,G.name||"an anonymous class"),$.get(V.name)?.stop();let Y=Bun.cron(V.cron,()=>{if(this.stopped)return;return this.track(V.name,()=>Z(()=>EZ(V)))});$.set(V.name,Y),this.handles.push(Y)}}stop(){this.stopped=!0;for(let Z of this.handles)Z.stop();this.handles=[]}async drain(Z=1/0){this.stop();let $,H=new Promise((G)=>{if(Z!==1/0)$=setTimeout(G,Z)});return await Promise.race([Promise.allSettled([...this.inFlight.values()].map((G)=>G.done)),H]),clearTimeout($),{unfinished:[...this.inFlight.values()].map((G)=>({...G.tick}))}}get running(){return this.inFlight.size}track(Z,$){let H={},G=Promise.resolve().then($).finally(()=>this.inFlight.delete(H));return this.inFlight.set(H,{tick:{name:Z,startedAt:new Date},done:G}),G}}import{isMainThread as SZ}from"worker_threads";var TZ=100,RZ=(Z)=>Math.min(TZ,Math.floor(Z/10));class DZ extends C{register(){this.app.singleton(J,()=>new J(this.app.config.get("queue",{}),{application:this.app}))}async boot(){let Z=this.app.config.get("queue",{});if(Z.jobs!==void 0)return;let{jobsDir:$}=U(a(),Z);this.app.make(J).useJobs(await GZ($))}async shutdown(Z){if(!this.app.resolved(J))return;let $=this.app.make(J);if($.running>0)console.log(`[gemi] Shutting down: waiting for ${$.running} running queued job(s).`);let H=Z?.timeoutMs,{unfinished:G}=await $.drain(H===void 0?1/0:Math.max(0,H-RZ(H)));if(G.length>0)console.error("[gemi] Queued jobs still running at shutdown: "+G.map((V)=>`${V.name} (${V.id})`).join(", "))}}function _3(Z){if(MZ(Z),Bun.env.NODE_ENV!=="production")return kZ(Z);if(VZ(Z))return;if(!ZZ())return;if(!Z.bound(J))return;Z.make(J).start()}function VZ(Z){let $=Z.config.get("queue",{}).driver??"memory";return $==="memory"||$ instanceof e}function jZ(Z){if(VZ(Z))return!0;if(typeof Z.config.get("queue",{}).driver!=="function"||!Z.bound(J))return!1;try{return!Z.make(J).durable}catch{return!1}}function MZ(Z){let $=process.env.GEMI_QUEUE_CLAIM;if($===void 0||HZ())return;if(process.env.ROOT_DIR===void 0||!SZ)return;if($Z()){if(!jZ(Z))return;console.warn('[gemi] GEMI_QUEUE_CLAIM=off is ignored: the memory queue driver keeps jobs in the process that dispatched them, so no worker could run them. This server runs its own jobs. Use a driver other processes can read, such as "database", to hand jobs to `gemi queue:work`.')}else if($.trim().toLowerCase()!=="on")console.warn(`[gemi] GEMI_QUEUE_CLAIM="${$}" is not "off" or "on", so this server claims jobs. Set it to "off" to leave them to \`gemi queue:work\`.`)}function kZ(Z){let $=globalThis.__gemiDevQueue;if(!$)return;if(globalThis.__gemiDevQueue=void 0,$.stop(),Z.bound(J))Z.make(J).start()}class fZ extends C{register(){this.app.singleton(v,()=>new v(this.app.config.get("auth",{})))}}var yZ=100,gZ=(Z)=>Math.min(yZ,Math.floor(Z/10));class bZ extends C{register(){this.app.singleton(A,()=>new A(U(w(),this.app.config.get("schedule",{}))))}async boot(){let Z=this.app.make(A),$=this.app.config.get("schedule",{});if($.jobs===void 0){let{jobsDir:G}=U(w(),$);Z.useJobs(await YZ(G))}if(process.env.GEMI_NO_SCHEDULE==="1")return;let H=this.app;Z.start((G)=>XZ.run(H,G))}async shutdown(Z){if(!this.app.resolved(A))return;let $=this.app.make(A);if($.running>0)console.log(`[gemi] Shutting down: waiting for ${$.running} running cron tick(s).`);let H=Z?.timeoutMs,{unfinished:G}=await $.drain(H===void 0?1/0:Math.max(0,H-gZ(H)));if(G.length>0)console.error("[gemi] Cron jobs still running at shutdown: "+G.map((V)=>`${V.name} (started ${V.startedAt.toISOString()})`).join(", "))}}class x extends CZ{modelName;constructor(Z="FeatureFlag"){super();this.modelName=Z}get available(){return zZ(this.modelName)}async load(){if(!this.available)throw new h(this.modelName);let Z=m(this.modelName);return await Z.asSystem(()=>Z.findMany())}async write(Z,$,H){if(!this.available)throw new h(this.modelName);let G=m(this.modelName),V=this.hasActorColumn(G),X={active:$};if(V)X.updatedBy=H.actor;return await G.asSystem(()=>G.upsert({where:{key:Z},create:{key:Z,...X},update:X})),{actorRecorded:V}}hasActorColumn(Z){let $=Z?.$schema?.fields?.updatedBy;return $!==void 0&&$.type==="String"}}function k3(Z){return Z}function KZ(){return{features:void 0,enabled:!0,source:new x,model:"FeatureFlag",ttl:30,context:void 0,onEvaluate:void 0,maxClientFlags:200}}class vZ extends C{register(){this.app.singleton(D,()=>{let Z=this.app.config.get("features",{}),$=U(KZ(),Z);if(Z?.model!==void 0&&Z?.source===void 0)$.source=new x(Z.model);else if(Z?.model!==void 0&&Z.source instanceof x&&Z.source.modelName!==Z.model)R.warning(`app/config/features.ts sets \`model: "${Z.model}"\` and a source reading "${Z.source.modelName}". The source wins; drop \`model\` to silence this.`);return new D($,(H)=>R.warning(H))})}async boot(){let Z=this.app.make(D);if(!Z.config.enabled)return;try{await Z.refresh()}catch($){R.error(`Could not warm the feature flag cache at boot. ${$ instanceof Error?$.message:String($)}`)}}}class wZ extends C{register(){this.app.singleton(c,()=>new c(this.app.config.get("mail",{})))}}function mZ(Z){return`listener:${Z}`}function IZ(Z,$){hZ(Z);let{maxAttempts:H,backoff:G,worker:V}=$,X=Z.event.name,Y=class extends WZ{maxAttempts=H;backoff=G;worker=V;async run(...z){let[K,B]=cZ(z),I=j(Q).rehydrate(K,B);await new Z().handle(I)}onFail(z){console.error(`The listener ${Z.name} threw while handling ${X} off the queue. The queue will retry it up to ${H} times in total; nothing the dispatcher did is waiting on it.`,z)}onDeadletter(z){console.error(`The listener ${Z.name} failed every one of its ${H} attempts at ${X} and has been dead-lettered, so this side effect did not happen and will not be retried.`,z)}};return Object.defineProperty(Y,"name",{value:mZ(Z.name),writable:!0}),Y}function hZ(Z){if(Object.getOwnPropertyDescriptor(Z,"name")?.writable)return;throw Error(`The listener ${Z.name} sets \`queued = true\` but does not declare \`static name\`, so it would be registered with the queue `+"under its class name \u2014 which a production build renames while the "+`payload that names it does not. Add: static name = "${Z.name}";`)}function cZ(Z){return typeof Z[0]==="string"?Z:Z[0]}class Q{static token="events";listeners={};events={};warnedFor=new Set;warnedAboutWaiting=new Set;config;constructor(Z={}){this.config=U(M(),Z),this.useListeners(this.config.listeners)}useListeners(Z){this.config.listeners=Z,this.listeners={},this.events={};let $=new Set;for(let H of Z){if(!H.event){console.error(`Listener ${H.name} does not declare \`static event\`, so there is no event name to register it under and its handle will never run. Add: static event = SomeEvent;`);continue}if($.has(H.name)){console.error(`Two event listeners are named "${H.name}" \u2014 the first is `+"registered and this one is not. A listener's name identifies it across the framework, so only one class can hold it. Rename one.");continue}$.add(H.name);let G=H.event.name,V=new H;this.events[G]??=H.event,(this.listeners[G]??=[]).push({listener:H,job:V.queued?IZ(H,V):null})}}get queuedListenerJobs(){return Object.values(this.listeners).flat().flatMap(({job:Z})=>Z?[Z]:[])}get registeredListeners(){return[...this.config.listeners]}dispatch(Z,$){if(this.deferToCommit(Z,$,!1))return;this.runListeners(Z,$)}async dispatchAndWait(Z,$){if(this.deferToCommit(Z,$,!0))return;return this.runListeners(Z,$)}deferToCommit(Z,$,H){let{name:G,afterCommit:V}=Z.constructor;if(!V)return!1;let X=UZ(()=>this.runListeners(Z,$));if(X&&H)this.warnNothingIsWaitedFor(G);return X}async runListeners(Z,$){let H=Z.constructor.name,G=this.listeners[H];if(!G?.length){this.warnNothingIsListening(H);return}for(let{listener:V,job:X}of G){if(X){this.enqueue(X,V,H,$);continue}try{await new V().handle(Z)}catch(Y){console.error(`The listener ${V.name} threw while handling ${H}. The `+"listeners after it still ran \u2014 a listener's failure is its own.",Y)}}}rehydrate(Z,$=[]){let H=this.events[Z];if(!H)throw Error(`Cannot rebuild the event "${Z}" that came off the queue: no registered listener binds to an event of that name, so there is no class to construct. A queued listener runs in an application that `+"discovered its own listeners \u2014 check that this process sees the "+`same app/listeners as the one that dispatched, and that the event declares \`static name = "${Z}"\`.`);return new H(...$)}enqueue(Z,$,H,G){if(G===void 0){console.error(`The listener ${$.name} is queued, but ${H} reached the dispatcher without its constructor arguments, so there is nothing for the queue to rebuild it from and the listener was skipped. Dispatch through ${H}.dispatch(...) or ${H}.dispatchAndWait(...), which carry them.`);return}let V=(X)=>console.error(`The listener ${$.name} could not be queued for ${H}, so it did not run and will not be retried. The listeners after it still ran.`,X);try{j(J).push(Z,JSON.stringify([H,G]),{reportFailure:!1}).catch(V)}catch(X){V(X)}}warnNothingIsWaitedFor(Z){if(Bun.env.NODE_ENV==="production")return;if(this.warnedAboutWaiting.has(Z))return;this.warnedAboutWaiting.add(Z),console.warn(`[gemi] ${Z}.dispatchAndWait() was called inside a transaction and ${Z} declares \`static afterCommit = true\`, so it resolved `+"immediately with no listener having run \u2014 they run when the "+"transaction commits. Await the transaction instead, or drop afterCommit if the caller needs the side effects first. Development only.")}warnNothingIsListening(Z){if(Bun.env.NODE_ENV==="production")return;if(this.warnedFor.has(Z))return;this.warnedFor.add(Z),console.warn(`[gemi] ${Z} was dispatched and nothing is listening for it. A listener registers under the name its event declares, so check that app/listeners holds a Listener with \`static event = ${Z}\` and that ${Z} declares \`static name = "${Z}"\`. Development only.`)}}class pZ extends C{register(){this.app.singleton(Q,()=>new Q(this.app.config.get("events",{})))}async boot(){let Z=this.app.make(Q),$=this.app.config.get("events",{});if($.listeners===void 0){let{listenersDir:V}=U(M(),$);Z.useListeners(await BZ(V))}let H=Z.queuedListenerJobs;if(H.length===0)return;let G=this.app.make(J);for(let V of H)G.registerJob(V)}}class uZ extends C{register(){this.app.singleton(p,()=>new p(this.app.config.get("filesystem",{})))}}class dZ extends C{register(){this.app.singleton(u,()=>new u(this.app.config.get("image",{})))}}class lZ extends C{register(){this.app.instance(d,new d)}}function q4(Z){return Z}function JZ(){return{maxFileSize:10485760,onLogFileClosed:()=>{},onLogCreated:()=>{}}}class sZ extends C{register(){this.app.singleton(k,()=>new k(U(JZ(),this.app.config.get("log",{}))))}async boot(){if(!process.env.ROOT_DIR)return;await this.app.make(k).boot()}}class iZ extends C{register(){this.app.singleton(f,()=>new f(U(PZ(),this.app.config.get("middleware",{}))))}boot(){this.app.make(f).assertGlobalMiddleware()}}function j4(Z){return Z}function _Z(){return{channels:{}}}class oZ extends C{register(){this.app.singleton(l,()=>new l(U(_Z(),this.app.config.get("broadcast",{}))))}}class s{}function OZ(Z,$){return Math.floor(Z/$)*$}function nZ(Z,$){if($<=0)return 0;return Math.min(1,Math.max(0,($-Z)/$))}function i(Z,$,H,G){return $*nZ(H,G)+Z}function tZ(Z){let{current:$,previous:H,elapsed:G,window:V,limit:X,cost:Y}=Z,z=X-Y;if(z<0)return V;let K=Math.max(0,V-G);if(H>0){let I=V*(1-(z-$)/H);if(I<=V)return Math.max(0,Math.ceil(I-G))}if($<=z)return K;if($<=0)return K;let B=V*(1-z/$);return K+Math.max(0,Math.ceil(B))}function qZ(Z){let{allowed:$,current:H,previous:G,now:V,elapsed:X,window:Y,limit:z,cost:K}=Z;if($){let I=i(H,G,X,Y);return{allowed:!0,limit:z,remaining:Math.max(0,Math.floor(z-I)),resetAt:V-X+Y,retryAfter:0}}let B=tZ({current:H,previous:G,elapsed:X,window:Y,limit:z,cost:K});return{allowed:!1,limit:z,remaining:0,resetAt:V+B,retryAfter:B}}class o extends s{buckets=new Map;maxKeys;constructor(Z={}){super();this.maxKeys=Math.max(1,Z.maxKeys??1e5)}consume(Z){let{key:$,limit:H,window:G}=Z,V=Z.cost??1,X=Date.now(),Y=OZ(X,G),z=X-Y,K=this.roll($,G,Y),I=i(K.current,K.previous,z,G)+V<=H;if(I)K.current+=V;if(this.buckets.delete($),this.buckets.set($,K),this.buckets.size>this.maxKeys)this.evict(X);return qZ({allowed:I,current:K.current,previous:K.previous,now:X,elapsed:z,window:G,limit:H,cost:V})}clear(){this.buckets.clear()}get size(){return this.buckets.size}roll(Z,$,H){let G=this.buckets.get(Z);if(!G||G.window!==$)return{window:$,windowStart:H,current:0,previous:0};if(G.windowStart===H)return G;if(G.windowStart+$===H)return G.previous=G.current,G.current=0,G.windowStart=H,G;return G.previous=0,G.current=0,G.windowStart=H,G}evict(Z){for(let[$,H]of this.buckets)if(Z-H.windowStart>=H.window*2)this.buckets.delete($);for(let $ of this.buckets.keys()){if(this.buckets.size<=this.maxKeys)break;this.buckets.delete($)}}}function u4(Z){return Z}function LZ(){return{driver:new o,limit:1000,window:60}}class rZ extends C{register(){this.app.singleton(n,()=>new n(U(LZ(),this.app.config.get("ratelimiter",{}))))}}function t4(Z){return Z}function AZ(){return{url:process.env.REDIS_URL,options:void 0}}class aZ extends C{register(){this.app.singleton(t,()=>new t(U(AZ(),this.app.config.get("redis",{}))))}}class P extends Error{status;constructor(Z,$){super(Z);this.status=$;this.name="McpToolError"}}var eZ=/^[A-Za-z0-9_-]{1,64}$/,Z3=/:([A-Za-z_][A-Za-z0-9_]*)([?*+]?)/g,QZ=4000,r=1e5,$3=/^(text\/|application\/([\w.+-]*\+)?(json|xml))/i;class E{dispatcher;static token="router.mcp";plans=new Map;constructor(Z,$){this.dispatcher=$;for(let[H,G]of Object.entries(Z.routes??{}))this.plans.set(H,this.plan(H,G))}list(Z,$){return xZ(Z),this.descriptors($)}descriptors(Z){let $=[];for(let{descriptor:H}of this.plans.values()){if(Z?.names&&!Z.names.includes(H.name))continue;if(Z?.tags&&!H.tags.some((G)=>Z.tags.includes(G)))continue;$.push(H)}return $}async execute(Z,$,H,G){xZ(Z);let V=this.plans.get($);if(!V)throw Error(`McpRegistry: there is no tool named "${$}".`);let{descriptor:X}=V,Y=X.inputSchema.safeParse(H);if(Y.ok===!1)throw new P(`Invalid arguments for "${$}": ${Y.errors.join(", ")}`);let z=Y.value,K=await this.fillPath(V,z,Z.req),{path:B}=this.dispatcher.getRouteHandlerAndParams(new Request(`http://gemi.internal/api${K}`));if(B!==X.url)throw new P(`"${$}" has nothing at ${K}.`,404);let I={};for(let L of V.jsonKeys)if(z[L]!==void 0)I[L]=z[L];let _,O="";if(V.fileFields.length>0)_=await this.formData(V,z,I,G);else if(X.method==="GET")O=X3(I);else if(V.jsonKeys.length>0)_=I;let F;try{F=await this.dispatcher.dispatchAs(Z.req,X.method,O?`${K}?${O}`:K,_)}catch(L){throw console.error(`[gemi/mcp] Dispatching "${$}" failed:`,L),new P(`"${$}" failed on the server.`,500)}return await Y3($,F)}plan(Z,$){if(!eZ.test(Z))throw Error(`McpRouter: "${Z}" is not a valid tool name. A routes key is the tool's name as a model and an MCP client see it, used verbatim \u2014 letters, digits, "_" and "-", at most 64.`);let H=`McpRouter: "${Z}" (${$?.method} ${$?.url})`;if(!($ instanceof Object)||$.__internal_brand!=="McpRoute")throw Error(`McpRouter: "${Z}" is not a route. Declare it with this.fromApiRoute(...).`);let{method:G,url:V,meta:X}=$;if(V.startsWith("/__gemi__"))throw Error(`${H} is a framework route, not an app route.`);let Y=this.dispatcher.flatRoutes[V]?.[G];if(!Y)throw Error(`${H} names no route of this app. Expose a route by the path and verb it is mounted at.`);let z=[...V.matchAll(Z3)].map(([,W,q])=>({name:W,modifier:q})),K=X.params??{};for(let W of z)if(!(W.name in K))throw Error(`${H}: the path param "${W.name}" must be bound or declared "input".`);for(let[W,q]of Object.entries(K)){if(!z.some((T)=>T.name===W))throw Error(`${H}: "${W}" in params is not a param of the url.`);NZ(H,`params.${W}`,q)}let B=[],I;if(X.input){if(I=X.input.toJSONSchema(),I.type!=="object"||!I.properties)throw Error(`${H}: input must be an s.object(...), the fields of the request body.`);B=Object.keys(I.properties)}let _=Object.entries(X.files??{}).map(([W,q])=>({name:W,binder:q}));for(let W of _)NZ(H,`files.${W.name}`,W.binder);if(_.length>0&&G==="GET")throw Error(`${H}: a GET has no body to carry a file.`);let O={},F=new Map;for(let W of z){let q=K[W.name];if(q==="input"){let T=y.string().describe(`The ":${W.name}" segment of the url.`);O[W.name]=W.modifier==="?"||W.modifier==="*"?T.optional():T}else F.set(W.name,q)}for(let W of _)if(W.binder==="input")O[W.name]=y.string().describe("The id of an attachment the user uploaded or a tool produced (gemi_att_\u2026), whose file is sent as this field.");let L=[...Object.keys(O),..._.map((W)=>W.name)];for(let W of new Set(L))if(B.includes(W))throw Error(`${H}: "${W}" is both an input field and a param or file, and the model can only send one.`);return{descriptor:Object.freeze({name:Z,description:X.description,method:G,url:V,inputSchema:G3(X.input,I,y.object(O)),annotations:H3(G),tags:Object.freeze([...X.tags??[]]),requiresApproval:X.requiresApproval===!0,...Y.source?{source:Y.source}:{}}),params:z,paramBinders:F,fileFields:_,jsonKeys:B}}async fillPath(Z,$,H){let G=Z.descriptor.url;for(let V of Z.params){let X=Z.paramBinders.get(V.name),Y;if(X){if(Y=await this.bind(Z,`the param "${V.name}"`,()=>X(H)),Y===void 0||Y===null||Y==="")throw console.error(`[gemi/mcp] The binder for "${V.name}" of "${Z.descriptor.name}" returned ${JSON.stringify(Y)}.`),new P(`"${Z.descriptor.name}" failed on the server.`,500)}else Y=$[V.name];let z=`:${V.name}${V.modifier}`;if(Y===void 0||Y===null||Y===""){if(V.modifier!=="?"&&V.modifier!=="*")throw new P(`"${V.name}" of "${Z.descriptor.name}" must not be empty.`);G=G.replace(`/${z}`,"");continue}G=G.replace(z,V3(Z.descriptor.name,V,String(Y)))}return G}async formData(Z,$,H,G){let V=Z.descriptor.name;if(!G)throw Error(`McpRegistry: "${V}" sends a file, which is resolved through the tool context's attachments, and none was passed to execute().`);let X=new FormData;for(let[Y,z]of Object.entries(H))FZ(X,Y,z);for(let Y of Z.fileFields){let z=Y.binder==="input"?$[Y.name]:await this.bind(Z,`the file "${Y.name}"`,()=>Y.binder(G));if(typeof z!=="string"||z==="")throw new P(`"${V}" needs a file for "${Y.name}", and this turn has none. Ask the user to attach one.`);X.append(Y.name,await G.attachments.file(z))}return X}async bind(Z,$,H){try{return await H()}catch(G){throw console.error(`[gemi/mcp] Binding ${$} of "${Z.descriptor.name}" failed:`,G),new P(`"${Z.descriptor.name}" failed on the server.`,500)}}}function xZ(Z){if(Z?.kind!=="local")throw Error("McpRegistry: only local callers are implemented. A remote caller needs a bearer-token resolver, which v1 does not have \u2014 and until it does there is no remote surface to call from.")}function NZ(Z,$,H){if(H!=="input"&&typeof H!=="function")throw Error(`${Z}: ${$} must be a function or "input".`)}function H3(Z){switch(Z){case"GET":return Object.freeze({readOnlyHint:!0});case"DELETE":return Object.freeze({destructiveHint:!0});case"PUT":return Object.freeze({idempotentHint:!0});default:return Object.freeze({})}}function G3(Z,$,H){let G=H.toJSONSchema(),V={...$??{},type:"object",properties:{...$?.properties??{},...G.properties},required:[...$?.required??[],...G.required??[]],additionalProperties:!1},X=(Y)=>{if(typeof Y!=="object"||Y===null||Array.isArray(Y))return{ok:!1,errors:["expected an object"]};let z=[],K={};if(Z){let I=Z.safeParse(Y);if(I.ok===!1)z.push(...I.errors);else K={...I.value}}let B=H.safeParse(Y);if(B.ok===!1)z.push(...B.errors);else K={...K,...B.value};return z.length>0?{ok:!1,errors:z}:{ok:!0,value:K}};return{toJSONSchema:()=>V,safeParse:X,parse(Y){let z=X(Y);if(z.ok===!1)throw Error(z.errors.join(", "));return z.value}}}function V3(Z,$,H){let G=$.modifier==="*"||$.modifier==="+"?H.split("/"):[H];if(G.some((V)=>V==="."||V===".."))throw new P(`"${H}" is not a valid value for "${$.name}" of "${Z}".`);return G.map(encodeURIComponent).join("/")}function FZ(Z,$,H){if(H===void 0||H===null)return;if(Array.isArray(H)){for(let G of H)FZ(Z,$,G);return}Z.append($,typeof H==="object"?JSON.stringify(H):String(H))}function X3(Z){let $=new URLSearchParams,H=(G,V)=>{if(V===void 0||V===null)return;if(Array.isArray(V)){for(let X of V)H(G,X);return}$.append(G,typeof V==="object"?JSON.stringify(V):String(V))};for(let[G,V]of Object.entries(Z))H(G,V);return $.toString()}async function Y3(Z,$){if($.status>=200&&$.status<300){let G=$.headers.get("Content-Type");if(G&&!$3.test(G)){let X=(await $.arrayBuffer()).byteLength;return`"${Z}" answered with ${X} bytes of ${G}, which is not shown.`}let V=await $.text();if(V==="")return null;if(V.length>r)return`${V.slice(0,r)}\u2026 [cut at ${r} of ${V.length} characters]`;try{return JSON.parse(V)}catch{return V}}let H=await $.text();if($.status>=400&&$.status<500){let G=H.length>QZ?`${H.slice(0,QZ)}\u2026`:H;throw new P(`"${Z}" was refused with ${$.status}${G?`: ${G}`:"."}`,$.status)}if($.status<500)console.error(`[gemi/mcp] "${Z}" answered ${$.status}${$.headers.get("Location")?` to ${$.headers.get("Location")}`:""}, which a tool call cannot follow.`);throw new P(`"${Z}" failed on the server.`,$.status)}function g(Z,$){let H=Z.get($);if(!H)throw Error(`Missing "${$}" configuration. Route dispatching needs a root router \u2014 declare it in "app/config/route.ts".`);return H}class z3 extends C{register(){this.app.singleton(N,()=>new N(g(this.app.config,"route.api"))),this.app.singleton(S,()=>new S(g(this.app.config,"route.view"))),this.app.singleton(b,()=>new b({api:this.app.make(N),view:this.app.make(S)},{api:g(this.app.config,"route.api"),view:g(this.app.config,"route.view"),domains:this.app.config.get("route.domains")})),this.app.singleton(E,()=>{let Z=this.app.config.get("route.mcp");if(!Z?.router)throw Error('No MCP router is configured. Declare one as `mcp: { router }` in "app/config/route.ts".');return new E(new Z.router,this.app.make(N))})}boot(){if(this.app.make(N),this.app.make(S),this.app.make(b),this.app.config.get("route.mcp"))this.app.make(E)}}
3
- export{A as Za,DZ as _a,_3 as $a,fZ as ab,bZ as bb,x as cb,k3 as db,KZ as eb,vZ as fb,wZ as gb,Q as hb,pZ as ib,uZ as jb,dZ as kb,lZ as lb,q4 as mb,JZ as nb,sZ as ob,iZ as pb,j4 as qb,_Z as rb,oZ as sb,OZ as tb,qZ as ub,s as vb,o as wb,u4 as xb,LZ as yb,rZ as zb,t4 as Ab,AZ as Bb,aZ as Cb,P as Db,E as Eb,z3 as Fb};
4
-
5
- //# debugId=18D267EAD674954E64756E2164756E21