@geekmidas/constructs 10.0.0-alpha.102 → 10.0.0-alpha.104

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 (59) hide show
  1. package/dist/{HonoEndpointAdaptor-Cvhfj4gd.mjs → HonoEndpointAdaptor-RSvRnXSl.mjs} +17 -8
  2. package/dist/HonoEndpointAdaptor-RSvRnXSl.mjs.map +1 -0
  3. package/dist/{HonoEndpointAdaptor-C_FbrwlA.cjs → HonoEndpointAdaptor-eCKyEOl_.cjs} +19 -10
  4. package/dist/HonoEndpointAdaptor-eCKyEOl_.cjs.map +1 -0
  5. package/dist/adaptors/aws.cjs +31 -6
  6. package/dist/adaptors/aws.cjs.map +1 -1
  7. package/dist/adaptors/aws.d.cts +8 -0
  8. package/dist/adaptors/aws.d.cts.map +1 -1
  9. package/dist/adaptors/aws.d.mts +8 -0
  10. package/dist/adaptors/aws.d.mts.map +1 -1
  11. package/dist/adaptors/aws.mjs +29 -4
  12. package/dist/adaptors/aws.mjs.map +1 -1
  13. package/dist/adaptors/hono.cjs +1 -1
  14. package/dist/adaptors/hono.d.cts.map +1 -1
  15. package/dist/adaptors/hono.d.mts.map +1 -1
  16. package/dist/adaptors/hono.mjs +1 -1
  17. package/dist/adaptors/testing.cjs +15 -4
  18. package/dist/adaptors/testing.cjs.map +1 -1
  19. package/dist/adaptors/testing.d.cts +1 -1
  20. package/dist/adaptors/testing.d.cts.map +1 -1
  21. package/dist/adaptors/testing.d.mts +1 -1
  22. package/dist/adaptors/testing.d.mts.map +1 -1
  23. package/dist/adaptors/testing.mjs +14 -3
  24. package/dist/adaptors/testing.mjs.map +1 -1
  25. package/dist/credential-DYymbp_7.cjs +257 -0
  26. package/dist/credential-DYymbp_7.cjs.map +1 -0
  27. package/dist/credential-DfSAOOdY.mjs +222 -0
  28. package/dist/credential-DfSAOOdY.mjs.map +1 -0
  29. package/dist/credential.cjs +7 -155
  30. package/dist/credential.d.cts +2 -86
  31. package/dist/credential.d.mts +2 -86
  32. package/dist/credential.mjs +2 -152
  33. package/dist/external-api.cjs +3 -39
  34. package/dist/external-api.cjs.map +1 -1
  35. package/dist/external-api.d.cts +2 -2
  36. package/dist/external-api.d.mts +2 -2
  37. package/dist/external-api.mjs +1 -37
  38. package/dist/external-api.mjs.map +1 -1
  39. package/dist/fake-BcHTPz7N.d.mts +201 -0
  40. package/dist/fake-BcHTPz7N.d.mts.map +1 -0
  41. package/dist/fake-Dvion_Fh.d.cts +201 -0
  42. package/dist/fake-Dvion_Fh.d.cts.map +1 -0
  43. package/dist/{processAudits-DasOZjPl.cjs → rateLimit-BwHcMECR.cjs} +26 -5
  44. package/dist/rateLimit-BwHcMECR.cjs.map +1 -0
  45. package/dist/{processAudits-BRRVVHUB.mjs → rateLimit-DbOTsP01.mjs} +23 -2
  46. package/dist/rateLimit-DbOTsP01.mjs.map +1 -0
  47. package/package.json +31 -31
  48. package/dist/HonoEndpointAdaptor-C_FbrwlA.cjs.map +0 -1
  49. package/dist/HonoEndpointAdaptor-Cvhfj4gd.mjs.map +0 -1
  50. package/dist/credential.cjs.map +0 -1
  51. package/dist/credential.d.cts.map +0 -1
  52. package/dist/credential.d.mts.map +0 -1
  53. package/dist/credential.mjs.map +0 -1
  54. package/dist/external-api-MJ8id87V.d.mts +0 -105
  55. package/dist/external-api-MJ8id87V.d.mts.map +0 -1
  56. package/dist/external-api-O107yzZJ.d.cts +0 -105
  57. package/dist/external-api-O107yzZJ.d.cts.map +0 -1
  58. package/dist/processAudits-BRRVVHUB.mjs.map +0 -1
  59. package/dist/processAudits-DasOZjPl.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"external-api.mjs","names":[],"sources":["../src/external-api.ts"],"sourcesContent":["/**\n * `ExternalApi` — an HTTP API somebody else runs.\n *\n * Polar, Stripe, a payment gateway: nothing to provision, but an address that\n * changes by stage and credentials the provider issued for each one. Without a\n * construct every integration writes that switching by hand — an optional\n * `POLAR_URL`, a fake started somewhere, a client built in a service — and the\n * target cannot see any of it.\n *\n * ```ts\n * // constructs/polar.ts\n * export const polar = new ExternalApi('Polar', {\n * url: 'https://www.polaraccesslink.com',\n * credentials: z.object({ clientId: z.string(), clientSecret: z.string() }),\n * client: ({ url, credentials }) => new PolarClient(url, credentials),\n * });\n *\n * // test/fakes/polar.ts\n * export default fake.app(new Hono().post('/v3/oauth2/token', …), {\n * credentials: { clientId: 'fake', clientSecret: 'fake' },\n * });\n *\n * // in a handler — the real API deployed, the fake locally and in tests\n * .dependsOn([polar])\n * .handle(({ services }) => services.polar.exchangeCode(code))\n * ```\n *\n * Deployed, the URL is `url` — one for every stage, or one per stage name with\n * a `default` — and the credentials are the stage's `<ID>_CREDENTIALS`, one\n * JSON value read the way a `Credential`'s is. Locally and in tests both come\n * from the fake.\n *\n * **The construct never names its fake.** It is found by convention, at\n * `test/fakes/<id>.ts`, and read only by gkm and the generated test harness —\n * so a fake, and the recorded responses it answers from, cannot end up in a\n * deployed bundle however the bundler follows imports.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { Service, ServiceRegisterOptions } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { Consumable } from './construct-interface';\nimport { readCredentials } from './credential';\nimport type { DeployedStage, LocalStage } from './stages';\nimport { traceClient } from './tracing';\n\n/** What a credentials schema yields. */\ntype Value<TSchema extends StandardSchemaV1> =\n\tStandardSchemaV1.InferOutput<TSchema>;\n\n/**\n * Where an external API answers: one URL for every stage, or one per stage\n * name with `default` for any stage not listed.\n *\n * Keyed by the stages `gkm.config.ts` declares once `.gkm/stages.d.ts` exists,\n * so a key that names no stage — `prodution` — is a type error rather than a\n * URL nothing reads. Without the file any key goes, as before.\n */\nexport type ExternalApiUrl =\n\t| string\n\t| (string extends LocalStage | DeployedStage\n\t\t\t? Readonly<Record<string, string>>\n\t\t\t: { readonly [S in LocalStage | DeployedStage]?: string } & {\n\t\t\t\t\treadonly default?: string;\n\t\t\t\t});\n\nexport interface ExternalApiOptions<TSchema extends StandardSchemaV1, TClient> {\n\t/**\n\t * Where it answers deployed: one URL for every stage, or one per stage\n\t * name, with `default` for any stage not listed.\n\t */\n\turl: ExternalApiUrl;\n\t/** What the stage's `<ID>_CREDENTIALS` must look like. */\n\tcredentials: TSchema;\n\t/** What a handler is handed, built from where it answers and the credentials. */\n\tclient: (connection: {\n\t\turl: string;\n\t\tcredentials: Value<TSchema>;\n\t}) => TClient | Promise<TClient>;\n}\n\nexport class ExternalApi<\n\tTName extends string = string,\n\tTSchema extends StandardSchemaV1 = StandardSchemaV1,\n\tTClient = unknown,\n> implements Consumable<TName, TClient>\n{\n\t// `Consumable` for the reason `Credential` gives: a client generic in\n\t// `TClient` cannot satisfy `Construct`'s conditional service type.\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, TClient>;\n\n\t/** Read by both `declare()` and `connect()`, so the two cannot drift. */\n\tprivate readonly keys: { url: string; credentials: string };\n\n\t/** The promise, so concurrent registrations share one client. */\n\tprivate connected?: Promise<TClient>;\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly options: ExternalApiOptions<TSchema, TClient>,\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.keys = {\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\tcredentials: provideKey(canonical, 'credentials'),\n\t\t};\n\n\t\t// A field, not a getter: consumers cache services by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\tregister: (registerOptions) => {\n\t\t\t\tthis.connected ??= this.connect(registerOptions);\n\t\t\t\treturn this.connected;\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * What the stage's `<ID>_CREDENTIALS` must look like — the schema the value\n\t * is validated against where it is read. Public so a tool setting the value\n\t * (`gkm secrets:add`) can check it, and describe its fields, before it is\n\t * stored rather than at the first request.\n\t */\n\tget credentialsSchema(): TSchema {\n\t\treturn this.options.credentials;\n\t}\n\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'external-api',\n\t\t\t\tid: this.id,\n\t\t\t\t// The stage-keyed map is a record of URLs to the manifest, which\n\t\t\t\t// reads it by whatever stage it is resolving.\n\t\t\t\turl: this.options.url as Readonly<Record<string, string>> | string,\n\t\t\t\tprovides: [this.keys.url, this.keys.credentials],\n\t\t\t},\n\t\t];\n\t}\n\n\tprivate async connect(options: ServiceRegisterOptions): Promise<TClient> {\n\t\tconst { url } = options.envParser\n\t\t\t.create((get) => ({ url: get(this.keys.url).string() }))\n\t\t\t.parse();\n\t\tconst credentials = await readCredentials(\n\t\t\tthis.id,\n\t\t\tthis.keys.credentials,\n\t\t\tthis.options.credentials,\n\t\t\toptions.envParser,\n\t\t);\n\n\t\tconst client = await this.options.client({ url, credentials });\n\t\t// Each call a span carrying this API's name — the parent of the fetch\n\t\t// it makes, which is traced already.\n\t\treturn traceClient(this.id, client, {\n\t\t\t'server.address': hostOf(url),\n\t\t});\n\t}\n}\n\nfunction hostOf(url: string): string {\n\ttry {\n\t\treturn new URL(url).host;\n\t} catch {\n\t\treturn url;\n\t}\n}\n\n/** Anything that answers a request — a Hono app, or a bare `{ fetch }`. */\nexport interface FetchHandler {\n\tfetch(request: Request): Response | Promise<Response>;\n}\n\n/**\n * Marks a fake. A registered symbol, so a fake built by a second copy of this\n * package — a linked workspace, two versions in a lockfile — is still one.\n */\nconst FAKE = Symbol.for('@geekmidas/constructs/fake');\n\n/** What an external API's credentials look like going in, for a fake to supply. */\ntype CredentialsOf<TApi> =\n\tTApi extends ExternalApi<string, infer TSchema, unknown>\n\t\t? StandardSchemaV1.InferInput<TSchema>\n\t\t: unknown;\n\n/**\n * An external API's stand-in, as `test/fakes/<id>.ts` default-exports it.\n *\n * `credentials` are what the fake accepts, and what a local or test stage is\n * handed as `<ID>_CREDENTIALS` in place of the stage's own.\n */\nexport type Fake = {\n\treadonly [FAKE]: true;\n\treadonly credentials: unknown;\n} & (\n\t| {\n\t\t\treadonly kind: 'app';\n\t\t\t/** Served by gkm: in-process in a test, on its own port in `gkm dev`. */\n\t\t\treadonly handler: FetchHandler;\n\t }\n\t| {\n\t\t\treadonly kind: 'image';\n\t\t\t/** The provider's own local server, run as a container. */\n\t\t\treadonly image: string;\n\t\t\t/** The port it listens on inside the container. */\n\t\t\treadonly port: number;\n\t }\n);\n\n/**\n * Build the fake `test/fakes/<id>.ts` default-exports.\n *\n * Pass the API's type to have the credentials checked against its schema —\n * `fake.app<typeof polar>(…)`.\n */\nexport const fake = {\n\t/** A working implementation of the API, kept beside the tests that use it. */\n\tapp<TApi = unknown>(\n\t\thandler: FetchHandler,\n\t\toptions: { credentials: CredentialsOf<TApi> },\n\t): Fake {\n\t\treturn {\n\t\t\t[FAKE]: true,\n\t\t\tkind: 'app',\n\t\t\thandler,\n\t\t\tcredentials: options.credentials,\n\t\t};\n\t},\n\n\t/** A local server the provider publishes — `stripe/stripe-mock`. */\n\timage<TApi = unknown>(\n\t\timage: string,\n\t\toptions: { port: number; credentials: CredentialsOf<TApi> },\n\t): Fake {\n\t\treturn {\n\t\t\t[FAKE]: true,\n\t\t\tkind: 'image',\n\t\t\timage,\n\t\t\tport: options.port,\n\t\t\tcredentials: options.credentials,\n\t\t};\n\t},\n};\n\n/** Whether a value is a fake — what gkm asks of a `test/fakes` default export. */\nexport function isFake(value: unknown): value is Fake {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t(value as { [FAKE]?: unknown })[FAKE] === true\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuFA,IAAa,cAAb,MAKA;CAcmB;CAXlB;CACA;;CAGA;;CAGA;CAEA,YACC,IACA,SACC;EADgB,KAAA,UAAA;EAEjB,MAAM,YAAY,YAAY,EAAY;EAE1C,KAAK,KAAK;EACV,KAAK,OAAO;GACX,KAAK,WAAW,WAAW,KAAK;GAChC,aAAa,WAAW,WAAW,aAAa;EACjD;EAGA,KAAK,UAAU;GACd,aAAa,WAAW,SAAS;GACjC,WAAW,oBAAoB;IAC9B,KAAK,cAAc,KAAK,QAAQ,eAAe;IAC/C,OAAO,KAAK;GACb;EACD;CACD;;;;;;;CAQA,IAAI,oBAA6B;EAChC,OAAO,KAAK,QAAQ;CACrB;CAEA,UAAyB;EACxB,OAAO,CACN;GACC,MAAM;GACN,IAAI,KAAK;GAGT,KAAK,KAAK,QAAQ;GAClB,UAAU,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,WAAW;EAChD,CACD;CACD;CAEA,MAAc,QAAQ,SAAmD;EACxE,MAAM,EAAE,QAAQ,QAAQ,UACtB,QAAQ,SAAS,EAAE,KAAK,IAAI,KAAK,KAAK,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CACvD,MAAM;EACR,MAAM,cAAc,MAAM,gBACzB,KAAK,IACL,KAAK,KAAK,aACV,KAAK,QAAQ,aACb,QAAQ,SACT;EAEA,MAAM,SAAS,MAAM,KAAK,QAAQ,OAAO;GAAE;GAAK;EAAY,CAAC;EAG7D,OAAO,YAAY,KAAK,IAAI,QAAQ,EACnC,kBAAkB,OAAO,GAAG,EAC7B,CAAC;CACF;AACD;AAEA,SAAS,OAAO,KAAqB;CACpC,IAAI;EACH,OAAO,IAAI,IAAI,GAAG,CAAC,CAAC;CACrB,QAAQ;EACP,OAAO;CACR;AACD;;;;;AAWA,MAAM,OAAO,OAAO,IAAI,4BAA4B;;;;;;;AAsCpD,MAAa,OAAO;;CAEnB,IACC,SACA,SACO;EACP,OAAO;IACL,OAAO;GACR,MAAM;GACN;GACA,aAAa,QAAQ;EACtB;CACD;;CAGA,MACC,OACA,SACO;EACP,OAAO;IACL,OAAO;GACR,MAAM;GACN;GACA,MAAM,QAAQ;GACd,aAAa,QAAQ;EACtB;CACD;AACD;;AAGA,SAAgB,OAAO,OAA+B;CACrD,OACC,OAAO,UAAU,YACjB,UAAU,QACT,MAA+B,UAAU;AAE5C"}
1
+ {"version":3,"file":"external-api.mjs","names":[],"sources":["../src/external-api.ts"],"sourcesContent":["/**\n * `ExternalApi` — an HTTP API somebody else runs.\n *\n * Polar, Stripe, a payment gateway: nothing to provision, but an address that\n * changes by stage and credentials the provider issued for each one. Without a\n * construct every integration writes that switching by hand — an optional\n * `POLAR_URL`, a fake started somewhere, a client built in a service — and the\n * target cannot see any of it.\n *\n * ```ts\n * // constructs/polar.ts\n * export const polar = new ExternalApi('Polar', {\n * url: 'https://www.polaraccesslink.com',\n * credentials: z.object({ clientId: z.string(), clientSecret: z.string() }),\n * client: ({ url, credentials }) => new PolarClient(url, credentials),\n * });\n *\n * // test/fakes/polar.ts\n * export default fake.app(new Hono().post('/v3/oauth2/token', …), {\n * credentials: { clientId: 'fake', clientSecret: 'fake' },\n * });\n *\n * // in a handler — the real API deployed, the fake locally and in tests\n * .dependsOn([polar])\n * .handle(({ services }) => services.polar.exchangeCode(code))\n * ```\n *\n * Deployed, the URL is `url` — one for every stage, or one per stage name with\n * a `default` — and the credentials are the stage's `<ID>_CREDENTIALS`, one\n * JSON value read the way a `Credential`'s is. Locally and in tests both come\n * from the fake.\n *\n * **The construct never names its fake.** It is found by convention, at\n * `test/fakes/<id>.ts`, and read only by gkm and the generated test harness —\n * so a fake, and the recorded responses it answers from, cannot end up in a\n * deployed bundle however the bundler follows imports.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { Service, ServiceRegisterOptions } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { Consumable } from './construct-interface';\nimport { readCredentials } from './credential';\nimport type { DeployedStage, LocalStage } from './stages';\nimport { traceClient } from './tracing';\n\n/** What a credentials schema yields. */\ntype Value<TSchema extends StandardSchemaV1> =\n\tStandardSchemaV1.InferOutput<TSchema>;\n\n/**\n * Where an external API answers: one URL for every stage, or one per stage\n * name with `default` for any stage not listed.\n *\n * Keyed by the stages `gkm.config.ts` declares once `.gkm/stages.d.ts` exists,\n * so a key that names no stage — `prodution` — is a type error rather than a\n * URL nothing reads. Without the file any key goes, as before.\n */\nexport type ExternalApiUrl =\n\t| string\n\t| (string extends LocalStage | DeployedStage\n\t\t\t? Readonly<Record<string, string>>\n\t\t\t: { readonly [S in LocalStage | DeployedStage]?: string } & {\n\t\t\t\t\treadonly default?: string;\n\t\t\t\t});\n\nexport interface ExternalApiOptions<TSchema extends StandardSchemaV1, TClient> {\n\t/**\n\t * Where it answers deployed: one URL for every stage, or one per stage\n\t * name, with `default` for any stage not listed.\n\t */\n\turl: ExternalApiUrl;\n\t/** What the stage's `<ID>_CREDENTIALS` must look like. */\n\tcredentials: TSchema;\n\t/** What a handler is handed, built from where it answers and the credentials. */\n\tclient: (connection: {\n\t\turl: string;\n\t\tcredentials: Value<TSchema>;\n\t}) => TClient | Promise<TClient>;\n}\n\nexport class ExternalApi<\n\tTName extends string = string,\n\tTSchema extends StandardSchemaV1 = StandardSchemaV1,\n\tTClient = unknown,\n> implements Consumable<TName, TClient>\n{\n\t// `Consumable` for the reason `Credential` gives: a client generic in\n\t// `TClient` cannot satisfy `Construct`'s conditional service type.\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, TClient>;\n\n\t/** Read by both `declare()` and `connect()`, so the two cannot drift. */\n\tprivate readonly keys: { url: string; credentials: string };\n\n\t/** The promise, so concurrent registrations share one client. */\n\tprivate connected?: Promise<TClient>;\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly options: ExternalApiOptions<TSchema, TClient>,\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.keys = {\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\tcredentials: provideKey(canonical, 'credentials'),\n\t\t};\n\n\t\t// A field, not a getter: consumers cache services by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\tregister: (registerOptions) => {\n\t\t\t\tthis.connected ??= this.connect(registerOptions);\n\t\t\t\treturn this.connected;\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * What the stage's `<ID>_CREDENTIALS` must look like — the schema the value\n\t * is validated against where it is read. Public so a tool setting the value\n\t * (`gkm secrets:add`) can check it, and describe its fields, before it is\n\t * stored rather than at the first request.\n\t */\n\tget credentialsSchema(): TSchema {\n\t\treturn this.options.credentials;\n\t}\n\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'external-api',\n\t\t\t\tid: this.id,\n\t\t\t\t// The stage-keyed map is a record of URLs to the manifest, which\n\t\t\t\t// reads it by whatever stage it is resolving.\n\t\t\t\turl: this.options.url as Readonly<Record<string, string>> | string,\n\t\t\t\tprovides: [this.keys.url, this.keys.credentials],\n\t\t\t},\n\t\t];\n\t}\n\n\tprivate async connect(options: ServiceRegisterOptions): Promise<TClient> {\n\t\tconst { url } = options.envParser\n\t\t\t.create((get) => ({ url: get(this.keys.url).string() }))\n\t\t\t.parse();\n\t\tconst credentials = await readCredentials(\n\t\t\tthis.id,\n\t\t\tthis.keys.credentials,\n\t\t\tthis.options.credentials,\n\t\t\toptions.envParser,\n\t\t);\n\n\t\tconst client = await this.options.client({ url, credentials });\n\t\t// Each call a span carrying this API's name — the parent of the fetch\n\t\t// it makes, which is traced already.\n\t\treturn traceClient(this.id, client, {\n\t\t\t'server.address': hostOf(url),\n\t\t});\n\t}\n}\n\nfunction hostOf(url: string): string {\n\ttry {\n\t\treturn new URL(url).host;\n\t} catch {\n\t\treturn url;\n\t}\n}\n\nexport {\n\ttype Fake,\n\ttype FetchHandler,\n\tfake,\n\tisFake,\n} from './fake';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuFA,IAAa,cAAb,MAKA;CAcmB;CAXlB;CACA;;CAGA;;CAGA;CAEA,YACC,IACA,SACC;EADgB,KAAA,UAAA;EAEjB,MAAM,YAAY,YAAY,EAAY;EAE1C,KAAK,KAAK;EACV,KAAK,OAAO;GACX,KAAK,WAAW,WAAW,KAAK;GAChC,aAAa,WAAW,WAAW,aAAa;EACjD;EAGA,KAAK,UAAU;GACd,aAAa,WAAW,SAAS;GACjC,WAAW,oBAAoB;IAC9B,KAAK,cAAc,KAAK,QAAQ,eAAe;IAC/C,OAAO,KAAK;GACb;EACD;CACD;;;;;;;CAQA,IAAI,oBAA6B;EAChC,OAAO,KAAK,QAAQ;CACrB;CAEA,UAAyB;EACxB,OAAO,CACN;GACC,MAAM;GACN,IAAI,KAAK;GAGT,KAAK,KAAK,QAAQ;GAClB,UAAU,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,WAAW;EAChD,CACD;CACD;CAEA,MAAc,QAAQ,SAAmD;EACxE,MAAM,EAAE,QAAQ,QAAQ,UACtB,QAAQ,SAAS,EAAE,KAAK,IAAI,KAAK,KAAK,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CACvD,MAAM;EACR,MAAM,cAAc,MAAM,gBACzB,KAAK,IACL,KAAK,KAAK,aACV,KAAK,QAAQ,aACb,QAAQ,SACT;EAEA,MAAM,SAAS,MAAM,KAAK,QAAQ,OAAO;GAAE;GAAK;EAAY,CAAC;EAG7D,OAAO,YAAY,KAAK,IAAI,QAAQ,EACnC,kBAAkB,OAAO,GAAG,EAC7B,CAAC;CACF;AACD;AAEA,SAAS,OAAO,KAAqB;CACpC,IAAI;EACH,OAAO,IAAI,IAAI,GAAG,CAAC,CAAC;CACrB,QAAQ;EACP,OAAO;CACR;AACD"}
@@ -0,0 +1,201 @@
1
+ import { t as StandardSchemaV1 } from "./index-CeEtxDmK.mjs";
2
+ import { r as Consumable } from "./construct-interface-DluK3_da.mjs";
3
+ import { n as DeployedStage, r as LocalStage } from "./stages-BSCF19o2.mjs";
4
+ import { ConstructName, Declaration } from "@geekmidas/manifest";
5
+ import { Service, ServiceRegisterOptions } from "@geekmidas/services";
6
+ //#region src/credential.d.ts
7
+ /** What a credential's schema yields. */
8
+ type Value$1<TSchema extends StandardSchemaV1> = StandardSchemaV1.InferOutput<TSchema>;
9
+ interface CredentialOptions<TSchema extends StandardSchemaV1> {
10
+ /**
11
+ * What the credential must look like.
12
+ *
13
+ * Validated at registration, so a malformed or half-set credential fails
14
+ * when the process starts rather than on the first request that needs the
15
+ * one field somebody forgot.
16
+ */
17
+ schema: TSchema;
18
+ /**
19
+ * Re-resolve on every registration instead of once per process.
20
+ *
21
+ * Off by default: a credential is resolved once and reused, because the
22
+ * alternative is a fetch on every request. The cost of that default is that
23
+ * a rotated credential needs a restart, which is the right trade for a value
24
+ * that changes a few times a year — and this is the escape hatch for when it
25
+ * is not.
26
+ */
27
+ refresh?: boolean;
28
+ }
29
+ declare class Credential<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1> implements Consumable<TName, Value$1<TSchema>> {
30
+ private readonly options;
31
+ readonly id: TName;
32
+ readonly service: Service<Uncapitalize<TName>, Value$1<TSchema>>;
33
+ /**
34
+ * Declared once and read by both `declare()` and `connect()`, so the key the
35
+ * target publishes and the key the client reads cannot drift.
36
+ */
37
+ private readonly keys;
38
+ /**
39
+ * The resolved value, held as the *promise* rather than the value.
40
+ *
41
+ * Caching the promise is what makes two concurrent registrations share one
42
+ * resolution: caching the value would let both start before either finished,
43
+ * which is a duplicated fetch every cold start rather than never.
44
+ */
45
+ private resolved?;
46
+ constructor(id: ConstructName<TName>, options: CredentialOptions<TSchema>);
47
+ /**
48
+ * What the stage's `<ID>_CREDENTIALS` must look like — the schema the value
49
+ * is validated against where it is read. Public so a tool setting the value
50
+ * (`gkm secrets:add`) can check it, and describe its fields, before it is
51
+ * stored rather than at the first request.
52
+ */
53
+ get credentialsSchema(): TSchema;
54
+ declare(): Declaration[];
55
+ private connect;
56
+ private resolve;
57
+ }
58
+ /**
59
+ * A credentials key, read and validated against its schema.
60
+ *
61
+ * Shared with every construct that holds a third party's credentials — an
62
+ * `ExternalApi` reads its own the same way — so the shape a stage stores and
63
+ * the error a bad value raises are the same everywhere.
64
+ *
65
+ * @throws {MalformedCredential} when the value is not what the schema says.
66
+ */
67
+ declare function readCredentials<TSchema extends StandardSchemaV1>(id: string, key: string, schema: TSchema, envParser: ServiceRegisterOptions['envParser']): Promise<Value$1<TSchema>>;
68
+ /**
69
+ * The raw environment value as the thing the schema should see.
70
+ *
71
+ * JSON when it is JSON, and the string itself when it is not — a credential
72
+ * with a single opaque field is ordinary, and requiring `"…"` around it would
73
+ * be ceremony that every operator setting one by hand would get wrong once.
74
+ *
75
+ * Exported so a tool checking a value before it is stored reads it exactly as
76
+ * the process that uses it will.
77
+ */
78
+ declare function decodeCredentials(raw: string): unknown;
79
+ /** A credential was set, and is not what its schema says it should be. */
80
+ declare class MalformedCredential extends Error {
81
+ readonly id: string;
82
+ readonly key: string;
83
+ readonly issues: unknown;
84
+ constructor(id: string, key: string, issues: unknown);
85
+ }
86
+ //#endregion
87
+ //#region src/external-api.d.ts
88
+ /** What a credentials schema yields. */
89
+ type Value<TSchema extends StandardSchemaV1> = StandardSchemaV1.InferOutput<TSchema>;
90
+ /**
91
+ * Where an external API answers: one URL for every stage, or one per stage
92
+ * name with `default` for any stage not listed.
93
+ *
94
+ * Keyed by the stages `gkm.config.ts` declares once `.gkm/stages.d.ts` exists,
95
+ * so a key that names no stage — `prodution` — is a type error rather than a
96
+ * URL nothing reads. Without the file any key goes, as before.
97
+ */
98
+ type ExternalApiUrl = string | (string extends LocalStage | DeployedStage ? Readonly<Record<string, string>> : { readonly [S in LocalStage | DeployedStage]?: string; } & {
99
+ readonly default?: string;
100
+ });
101
+ interface ExternalApiOptions<TSchema extends StandardSchemaV1, TClient> {
102
+ /**
103
+ * Where it answers deployed: one URL for every stage, or one per stage
104
+ * name, with `default` for any stage not listed.
105
+ */
106
+ url: ExternalApiUrl;
107
+ /** What the stage's `<ID>_CREDENTIALS` must look like. */
108
+ credentials: TSchema;
109
+ /** What a handler is handed, built from where it answers and the credentials. */
110
+ client: (connection: {
111
+ url: string;
112
+ credentials: Value<TSchema>;
113
+ }) => TClient | Promise<TClient>;
114
+ }
115
+ declare class ExternalApi<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1, TClient = unknown> implements Consumable<TName, TClient> {
116
+ private readonly options;
117
+ readonly id: TName;
118
+ readonly service: Service<Uncapitalize<TName>, TClient>;
119
+ /** Read by both `declare()` and `connect()`, so the two cannot drift. */
120
+ private readonly keys;
121
+ /** The promise, so concurrent registrations share one client. */
122
+ private connected?;
123
+ constructor(id: ConstructName<TName>, options: ExternalApiOptions<TSchema, TClient>);
124
+ /**
125
+ * What the stage's `<ID>_CREDENTIALS` must look like — the schema the value
126
+ * is validated against where it is read. Public so a tool setting the value
127
+ * (`gkm secrets:add`) can check it, and describe its fields, before it is
128
+ * stored rather than at the first request.
129
+ */
130
+ get credentialsSchema(): TSchema;
131
+ declare(): Declaration[];
132
+ private connect;
133
+ }
134
+ //#endregion
135
+ //#region src/fake.d.ts
136
+ /**
137
+ * Marks a fake. A registered symbol, so a fake built by a second copy of this
138
+ * package — a linked workspace, two versions in a lockfile — is still one.
139
+ */
140
+ declare const FAKE: unique symbol;
141
+ /** Anything that answers a request — a Hono app, or a bare `{ fetch }`. */
142
+ interface FetchHandler {
143
+ fetch(request: Request): Response | Promise<Response>;
144
+ }
145
+ /**
146
+ * What a construct's credentials look like going in, for a fake to supply —
147
+ * an external API's `credentials` schema, or a credential's `schema`.
148
+ */
149
+ type CredentialsOf<TConstruct> = TConstruct extends ExternalApi<string, infer TSchema, unknown> ? StandardSchemaV1.InferInput<TSchema> : TConstruct extends Credential<string, infer TSchema> ? StandardSchemaV1.InferInput<TSchema> : unknown;
150
+ /**
151
+ * A third party's stand-in, as `test/fakes/<id>.ts` default-exports it.
152
+ *
153
+ * `credentials` are what a local or test stage is handed as `<ID>_CREDENTIALS`
154
+ * in place of the stage's own: what an external API's fake accepts, or a
155
+ * credential's test value.
156
+ */
157
+ type Fake = {
158
+ readonly [FAKE]: true;
159
+ readonly credentials: unknown;
160
+ } & ({
161
+ readonly kind: 'app';
162
+ /** Served by gkm: in-process in a test, on its own port in `gkm dev`. */
163
+ readonly handler: FetchHandler;
164
+ } | {
165
+ readonly kind: 'image';
166
+ /** The provider's own local server, run as a container. */
167
+ readonly image: string;
168
+ /** The port it listens on inside the container. */
169
+ readonly port: number;
170
+ } | {
171
+ /** A `Credential`'s test value — nothing to serve. */
172
+ readonly kind: 'credential';
173
+ });
174
+ /**
175
+ * Build the fake `test/fakes/<id>.ts` default-exports.
176
+ *
177
+ * Pass the construct's type to have the value checked against its schema —
178
+ * `fake.app<typeof polar>(…)`, `fake.credential<typeof stripe>(…)`.
179
+ */
180
+ declare const fake: {
181
+ /** A working implementation of an external API, kept beside the tests that use it. */
182
+ app<TApi = unknown>(handler: FetchHandler, options: {
183
+ credentials: CredentialsOf<TApi>;
184
+ }): Fake;
185
+ /** A local server an external API's provider publishes — `stripe/stripe-mock`. */
186
+ image<TApi = unknown>(image: string, options: {
187
+ port: number;
188
+ credentials: CredentialsOf<TApi>;
189
+ }): Fake;
190
+ /**
191
+ * A `Credential`'s value on the test stage and under `gkm dev --fake`, as
192
+ * its schema takes it — a string, or the object a JSON credential holds.
193
+ * It is validated where it is read, like the real one.
194
+ */
195
+ credential<TCredential = unknown>(credentials: CredentialsOf<TCredential>): Fake;
196
+ };
197
+ /** Whether a value is a fake — what gkm asks of a `test/fakes` default export. */
198
+ declare function isFake(value: unknown): value is Fake;
199
+ //#endregion
200
+ export { ExternalApi as a, Credential as c, decodeCredentials as d, readCredentials as f, isFake as i, CredentialOptions as l, FetchHandler as n, ExternalApiOptions as o, fake as r, ExternalApiUrl as s, Fake as t, MalformedCredential as u };
201
+ //# sourceMappingURL=fake-BcHTPz7N.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-BcHTPz7N.d.mts","names":["Value"],"sources":["../src/credential.ts","../src/external-api.ts","../src/fake.ts"],"mappings":";;;;;;;KAiEKA,QAAM,gBAAgB,oBAC1B,iBAAiB,YAAY;UAEb,kBAAkB,gBAAgB;;;;;;;;EAQlD,QAAQ;;;;;;;;;;EAUR;;cAGY,WACZ,+BACA,gBAAgB,mBAAmB,6BACvB,WAAW,OAAOA,QAAM;mBA6BlB;WApBT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQA,QAAM;;;;;mBAMpC;;;;;;;;UAST;EAER,YACC,IAAI,cAAc,QACD,SAAS,kBAAkB;;;;;;;MAoBzC,qBAAqB;EAIzB,WAAW;UAUH;UAQA;;;;;;;;;;;iBAmBa,gBAAgB,gBAAgB,kBACrD,YACA,aACA,QAAQ,SACR,WAAW,sCACT,QAAQA,QAAM;;;;;;;;;;;iBAsBD,kBAAkB;;cAoBrB,4BAA4B;WAE9B;WACA;WACA;EAHV,YACU,YACA,aACA;;;;;KCpLN,MAAM,gBAAgB,oBAC1B,iBAAiB,YAAY;;;;;;;;;KAUlB,0CAEO,aAAa,gBAC3B,SAAS,sCACG,KAAK,aAAa;WACrB;;UAGG,mBAAmB,gBAAgB,kBAAkB;;;;;EAKrE,KAAK;;EAEL,aAAa;;EAEb,SAAS;IACR;IACA,aAAa,MAAM;QACd,UAAU,QAAQ;;cAGZ,YACZ,+BACA,gBAAgB,mBAAmB,kBACnC,8BACY,WAAW,OAAO;mBAeZ;WAXT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ;;mBAG9B;;UAGT;EAER,YACC,IAAI,cAAc,QACD,SAAS,mBAAmB,SAAS;;;;;;;MA0BnD,qBAAqB;EAIzB,WAAW;UAaG;;;;;;;;cCjHT;;UAGW;EAChB,MAAM,SAAS,UAAU,WAAW,QAAQ;;;;;;KAOxC,cAAc,cAClB,mBAAmB,0BAA0B,oBAC1C,iBAAiB,WAAW,WAC5B,mBAAmB,yBAAyB,WAC3C,iBAAiB,WAAW;;;;;;;;KAUrB;YACD;WACD;;WAGE;;WAEA,SAAS;;WAGT;;WAEA;;WAEA;;;WAIA;;;;;;;;cAUC;;EAEZ,IAAI,gBAAc,SACR,cAAY;IACV,aAAa,cAAc;MACpC;;EAUH,MAAM,gBAAc,eACN;IACF;IAAc,aAAa,cAAc;MAClD;;;;;;EAeH,WAAW,uBAAqB,aAClB,cAAc,eACzB;;;iBAMY,OAAO,iBAAiB,SAAS"}
@@ -0,0 +1,201 @@
1
+ import { t as StandardSchemaV1 } from "./index-CeEtxDmK.cjs";
2
+ import { r as Consumable } from "./construct-interface-B1AfqRl4.cjs";
3
+ import { n as DeployedStage, r as LocalStage } from "./stages-BSCF19o2.cjs";
4
+ import { Service, ServiceRegisterOptions } from "@geekmidas/services";
5
+ import { ConstructName, Declaration } from "@geekmidas/manifest";
6
+ //#region src/credential.d.ts
7
+ /** What a credential's schema yields. */
8
+ type Value$1<TSchema extends StandardSchemaV1> = StandardSchemaV1.InferOutput<TSchema>;
9
+ interface CredentialOptions<TSchema extends StandardSchemaV1> {
10
+ /**
11
+ * What the credential must look like.
12
+ *
13
+ * Validated at registration, so a malformed or half-set credential fails
14
+ * when the process starts rather than on the first request that needs the
15
+ * one field somebody forgot.
16
+ */
17
+ schema: TSchema;
18
+ /**
19
+ * Re-resolve on every registration instead of once per process.
20
+ *
21
+ * Off by default: a credential is resolved once and reused, because the
22
+ * alternative is a fetch on every request. The cost of that default is that
23
+ * a rotated credential needs a restart, which is the right trade for a value
24
+ * that changes a few times a year — and this is the escape hatch for when it
25
+ * is not.
26
+ */
27
+ refresh?: boolean;
28
+ }
29
+ declare class Credential<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1> implements Consumable<TName, Value$1<TSchema>> {
30
+ private readonly options;
31
+ readonly id: TName;
32
+ readonly service: Service<Uncapitalize<TName>, Value$1<TSchema>>;
33
+ /**
34
+ * Declared once and read by both `declare()` and `connect()`, so the key the
35
+ * target publishes and the key the client reads cannot drift.
36
+ */
37
+ private readonly keys;
38
+ /**
39
+ * The resolved value, held as the *promise* rather than the value.
40
+ *
41
+ * Caching the promise is what makes two concurrent registrations share one
42
+ * resolution: caching the value would let both start before either finished,
43
+ * which is a duplicated fetch every cold start rather than never.
44
+ */
45
+ private resolved?;
46
+ constructor(id: ConstructName<TName>, options: CredentialOptions<TSchema>);
47
+ /**
48
+ * What the stage's `<ID>_CREDENTIALS` must look like — the schema the value
49
+ * is validated against where it is read. Public so a tool setting the value
50
+ * (`gkm secrets:add`) can check it, and describe its fields, before it is
51
+ * stored rather than at the first request.
52
+ */
53
+ get credentialsSchema(): TSchema;
54
+ declare(): Declaration[];
55
+ private connect;
56
+ private resolve;
57
+ }
58
+ /**
59
+ * A credentials key, read and validated against its schema.
60
+ *
61
+ * Shared with every construct that holds a third party's credentials — an
62
+ * `ExternalApi` reads its own the same way — so the shape a stage stores and
63
+ * the error a bad value raises are the same everywhere.
64
+ *
65
+ * @throws {MalformedCredential} when the value is not what the schema says.
66
+ */
67
+ declare function readCredentials<TSchema extends StandardSchemaV1>(id: string, key: string, schema: TSchema, envParser: ServiceRegisterOptions['envParser']): Promise<Value$1<TSchema>>;
68
+ /**
69
+ * The raw environment value as the thing the schema should see.
70
+ *
71
+ * JSON when it is JSON, and the string itself when it is not — a credential
72
+ * with a single opaque field is ordinary, and requiring `"…"` around it would
73
+ * be ceremony that every operator setting one by hand would get wrong once.
74
+ *
75
+ * Exported so a tool checking a value before it is stored reads it exactly as
76
+ * the process that uses it will.
77
+ */
78
+ declare function decodeCredentials(raw: string): unknown;
79
+ /** A credential was set, and is not what its schema says it should be. */
80
+ declare class MalformedCredential extends Error {
81
+ readonly id: string;
82
+ readonly key: string;
83
+ readonly issues: unknown;
84
+ constructor(id: string, key: string, issues: unknown);
85
+ }
86
+ //#endregion
87
+ //#region src/external-api.d.ts
88
+ /** What a credentials schema yields. */
89
+ type Value<TSchema extends StandardSchemaV1> = StandardSchemaV1.InferOutput<TSchema>;
90
+ /**
91
+ * Where an external API answers: one URL for every stage, or one per stage
92
+ * name with `default` for any stage not listed.
93
+ *
94
+ * Keyed by the stages `gkm.config.ts` declares once `.gkm/stages.d.ts` exists,
95
+ * so a key that names no stage — `prodution` — is a type error rather than a
96
+ * URL nothing reads. Without the file any key goes, as before.
97
+ */
98
+ type ExternalApiUrl = string | (string extends LocalStage | DeployedStage ? Readonly<Record<string, string>> : { readonly [S in LocalStage | DeployedStage]?: string; } & {
99
+ readonly default?: string;
100
+ });
101
+ interface ExternalApiOptions<TSchema extends StandardSchemaV1, TClient> {
102
+ /**
103
+ * Where it answers deployed: one URL for every stage, or one per stage
104
+ * name, with `default` for any stage not listed.
105
+ */
106
+ url: ExternalApiUrl;
107
+ /** What the stage's `<ID>_CREDENTIALS` must look like. */
108
+ credentials: TSchema;
109
+ /** What a handler is handed, built from where it answers and the credentials. */
110
+ client: (connection: {
111
+ url: string;
112
+ credentials: Value<TSchema>;
113
+ }) => TClient | Promise<TClient>;
114
+ }
115
+ declare class ExternalApi<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1, TClient = unknown> implements Consumable<TName, TClient> {
116
+ private readonly options;
117
+ readonly id: TName;
118
+ readonly service: Service<Uncapitalize<TName>, TClient>;
119
+ /** Read by both `declare()` and `connect()`, so the two cannot drift. */
120
+ private readonly keys;
121
+ /** The promise, so concurrent registrations share one client. */
122
+ private connected?;
123
+ constructor(id: ConstructName<TName>, options: ExternalApiOptions<TSchema, TClient>);
124
+ /**
125
+ * What the stage's `<ID>_CREDENTIALS` must look like — the schema the value
126
+ * is validated against where it is read. Public so a tool setting the value
127
+ * (`gkm secrets:add`) can check it, and describe its fields, before it is
128
+ * stored rather than at the first request.
129
+ */
130
+ get credentialsSchema(): TSchema;
131
+ declare(): Declaration[];
132
+ private connect;
133
+ }
134
+ //#endregion
135
+ //#region src/fake.d.ts
136
+ /**
137
+ * Marks a fake. A registered symbol, so a fake built by a second copy of this
138
+ * package — a linked workspace, two versions in a lockfile — is still one.
139
+ */
140
+ declare const FAKE: unique symbol;
141
+ /** Anything that answers a request — a Hono app, or a bare `{ fetch }`. */
142
+ interface FetchHandler {
143
+ fetch(request: Request): Response | Promise<Response>;
144
+ }
145
+ /**
146
+ * What a construct's credentials look like going in, for a fake to supply —
147
+ * an external API's `credentials` schema, or a credential's `schema`.
148
+ */
149
+ type CredentialsOf<TConstruct> = TConstruct extends ExternalApi<string, infer TSchema, unknown> ? StandardSchemaV1.InferInput<TSchema> : TConstruct extends Credential<string, infer TSchema> ? StandardSchemaV1.InferInput<TSchema> : unknown;
150
+ /**
151
+ * A third party's stand-in, as `test/fakes/<id>.ts` default-exports it.
152
+ *
153
+ * `credentials` are what a local or test stage is handed as `<ID>_CREDENTIALS`
154
+ * in place of the stage's own: what an external API's fake accepts, or a
155
+ * credential's test value.
156
+ */
157
+ type Fake = {
158
+ readonly [FAKE]: true;
159
+ readonly credentials: unknown;
160
+ } & ({
161
+ readonly kind: 'app';
162
+ /** Served by gkm: in-process in a test, on its own port in `gkm dev`. */
163
+ readonly handler: FetchHandler;
164
+ } | {
165
+ readonly kind: 'image';
166
+ /** The provider's own local server, run as a container. */
167
+ readonly image: string;
168
+ /** The port it listens on inside the container. */
169
+ readonly port: number;
170
+ } | {
171
+ /** A `Credential`'s test value — nothing to serve. */
172
+ readonly kind: 'credential';
173
+ });
174
+ /**
175
+ * Build the fake `test/fakes/<id>.ts` default-exports.
176
+ *
177
+ * Pass the construct's type to have the value checked against its schema —
178
+ * `fake.app<typeof polar>(…)`, `fake.credential<typeof stripe>(…)`.
179
+ */
180
+ declare const fake: {
181
+ /** A working implementation of an external API, kept beside the tests that use it. */
182
+ app<TApi = unknown>(handler: FetchHandler, options: {
183
+ credentials: CredentialsOf<TApi>;
184
+ }): Fake;
185
+ /** A local server an external API's provider publishes — `stripe/stripe-mock`. */
186
+ image<TApi = unknown>(image: string, options: {
187
+ port: number;
188
+ credentials: CredentialsOf<TApi>;
189
+ }): Fake;
190
+ /**
191
+ * A `Credential`'s value on the test stage and under `gkm dev --fake`, as
192
+ * its schema takes it — a string, or the object a JSON credential holds.
193
+ * It is validated where it is read, like the real one.
194
+ */
195
+ credential<TCredential = unknown>(credentials: CredentialsOf<TCredential>): Fake;
196
+ };
197
+ /** Whether a value is a fake — what gkm asks of a `test/fakes` default export. */
198
+ declare function isFake(value: unknown): value is Fake;
199
+ //#endregion
200
+ export { ExternalApi as a, Credential as c, decodeCredentials as d, readCredentials as f, isFake as i, CredentialOptions as l, FetchHandler as n, ExternalApiOptions as o, fake as r, ExternalApiUrl as s, Fake as t, MalformedCredential as u };
201
+ //# sourceMappingURL=fake-Dvion_Fh.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-Dvion_Fh.d.cts","names":["Value"],"sources":["../src/credential.ts","../src/external-api.ts","../src/fake.ts"],"mappings":";;;;;;;KAiEKA,QAAM,gBAAgB,oBAC1B,iBAAiB,YAAY;UAEb,kBAAkB,gBAAgB;;;;;;;;EAQlD,QAAQ;;;;;;;;;;EAUR;;cAGY,WACZ,+BACA,gBAAgB,mBAAmB,6BACvB,WAAW,OAAOA,QAAM;mBA6BlB;WApBT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQA,QAAM;;;;;mBAMpC;;;;;;;;UAST;EAER,YACC,IAAI,cAAc,QACD,SAAS,kBAAkB;;;;;;;MAoBzC,qBAAqB;EAIzB,WAAW;UAUH;UAQA;;;;;;;;;;;iBAmBa,gBAAgB,gBAAgB,kBACrD,YACA,aACA,QAAQ,SACR,WAAW,sCACT,QAAQA,QAAM;;;;;;;;;;;iBAsBD,kBAAkB;;cAoBrB,4BAA4B;WAE9B;WACA;WACA;EAHV,YACU,YACA,aACA;;;;;KCpLN,MAAM,gBAAgB,oBAC1B,iBAAiB,YAAY;;;;;;;;;KAUlB,0CAEO,aAAa,gBAC3B,SAAS,sCACG,KAAK,aAAa;WACrB;;UAGG,mBAAmB,gBAAgB,kBAAkB;;;;;EAKrE,KAAK;;EAEL,aAAa;;EAEb,SAAS;IACR;IACA,aAAa,MAAM;QACd,UAAU,QAAQ;;cAGZ,YACZ,+BACA,gBAAgB,mBAAmB,kBACnC,8BACY,WAAW,OAAO;mBAeZ;WAXT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ;;mBAG9B;;UAGT;EAER,YACC,IAAI,cAAc,QACD,SAAS,mBAAmB,SAAS;;;;;;;MA0BnD,qBAAqB;EAIzB,WAAW;UAaG;;;;;;;;cCjHT;;UAGW;EAChB,MAAM,SAAS,UAAU,WAAW,QAAQ;;;;;;KAOxC,cAAc,cAClB,mBAAmB,0BAA0B,oBAC1C,iBAAiB,WAAW,WAC5B,mBAAmB,yBAAyB,WAC3C,iBAAiB,WAAW;;;;;;;;KAUrB;YACD;WACD;;WAGE;;WAEA,SAAS;;WAGT;;WAEA;;WAEA;;;WAIA;;;;;;;;cAUC;;EAEZ,IAAI,gBAAc,SACR,cAAY;IACV,aAAa,cAAc;MACpC;;EAUH,MAAM,gBAAc,eACN;IACF;IAAc,aAAa,cAAc;MAClD;;;;;;EAeH,WAAW,uBAAqB,aAClB,cAAc,eACzB;;;iBAMY,OAAO,iBAAiB,SAAS"}
@@ -126,22 +126,43 @@ async function executeWithAuditTransaction(auditContext, handler, onComplete, op
126
126
  return response;
127
127
  }
128
128
  //#endregion
129
+ //#region src/endpoints/rateLimit.ts
130
+ /**
131
+ * An endpoint's `.rateLimit()`, checked the one way every adaptor checks it —
132
+ * Hono, Lambda and the test adaptor — after the request is authorized.
133
+ *
134
+ * Under the limit, it answers the headers the response carries:
135
+ * `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (and
136
+ * `Retry-After` on the last request the window allows).
137
+ *
138
+ * @throws {TooManyRequestsError} over the limit — `@geekmidas/errors`' 429,
139
+ * whose `headers` hold `Retry-After` (seconds until the window resets) and the
140
+ * same `X-RateLimit-*`, which every adaptor sets on the error response.
141
+ */
142
+ async function enforceRateLimit(config, ctx) {
143
+ const { checkRateLimit, getRateLimitHeaders } = await loadRateLimit();
144
+ const info = await checkRateLimit(config, ctx);
145
+ const headers = {};
146
+ for (const [key, value] of Object.entries(getRateLimitHeaders(info, config))) if (value !== void 0) headers[key] = value;
147
+ return headers;
148
+ }
149
+ //#endregion
129
150
  Object.defineProperty(exports, "createAuditContext", {
130
151
  enumerable: true,
131
152
  get: function() {
132
153
  return createAuditContext;
133
154
  }
134
155
  });
135
- Object.defineProperty(exports, "executeWithAuditTransaction", {
156
+ Object.defineProperty(exports, "enforceRateLimit", {
136
157
  enumerable: true,
137
158
  get: function() {
138
- return executeWithAuditTransaction;
159
+ return enforceRateLimit;
139
160
  }
140
161
  });
141
- Object.defineProperty(exports, "loadRateLimit", {
162
+ Object.defineProperty(exports, "executeWithAuditTransaction", {
142
163
  enumerable: true,
143
164
  get: function() {
144
- return loadRateLimit;
165
+ return executeWithAuditTransaction;
145
166
  }
146
167
  });
147
168
  Object.defineProperty(exports, "loadRls", {
@@ -151,4 +172,4 @@ Object.defineProperty(exports, "loadRls", {
151
172
  }
152
173
  });
153
174
 
154
- //# sourceMappingURL=processAudits-DasOZjPl.cjs.map
175
+ //# sourceMappingURL=rateLimit-BwHcMECR.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimit-BwHcMECR.cjs","names":[],"sources":["../src/endpoints/optionalPeers.ts","../src/endpoints/processAudits.ts","../src/endpoints/rateLimit.ts"],"sourcesContent":["/**\n * The optional peers an endpoint needs only for a feature it opted into.\n *\n * `@geekmidas/audit`, `@geekmidas/rate-limit` and `@geekmidas/db` are optional\n * peers of this package: an app that audits nothing, limits nothing and sets\n * no row-level security need not install them. The adaptor every endpoint is\n * served by used to import them at the top, so serving any endpoint needed\n * them all — and a production bundle of an app without them failed to build.\n * They are loaded here, when an endpoint that uses one is served, which is\n * also when the app declaring that endpoint has installed it: an auditor's\n * storage, a rate limit's store and an RLS policy's database come from these\n * packages.\n *\n * The `try` is load-bearing beyond the error it names: a bundler leaves an\n * import it cannot resolve to run time when the import is guarded, and fails\n * the build when it is not.\n */\n\n/** An endpoint uses a feature whose optional peer is not installed. */\nexport class OptionalPeerMissing extends Error {\n\tconstructor(\n\t\treadonly packageName: string,\n\t\treadonly feature: string,\n\t\tcause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${feature} needs '${packageName}', which could not be loaded. ` +\n\t\t\t\t`Add it to the dependencies of the app that serves this endpoint.`,\n\t\t\t{ cause },\n\t\t);\n\t\tthis.name = 'OptionalPeerMissing';\n\t}\n}\n\n/** `@geekmidas/audit`, for an endpoint with an auditor. */\nexport async function loadAudit(): Promise<typeof import('@geekmidas/audit')> {\n\ttry {\n\t\treturn await import('@geekmidas/audit');\n\t} catch (cause) {\n\t\tthrow new OptionalPeerMissing(\n\t\t\t'@geekmidas/audit',\n\t\t\t'An endpoint with an auditor',\n\t\t\tcause,\n\t\t);\n\t}\n}\n\n/** `@geekmidas/rate-limit`, for an endpoint with a rate limit. */\nexport async function loadRateLimit(): Promise<\n\ttypeof import('@geekmidas/rate-limit')\n> {\n\ttry {\n\t\treturn await import('@geekmidas/rate-limit');\n\t} catch (cause) {\n\t\tthrow new OptionalPeerMissing(\n\t\t\t'@geekmidas/rate-limit',\n\t\t\t'An endpoint with a rate limit',\n\t\t\tcause,\n\t\t);\n\t}\n}\n\n/** `@geekmidas/db/rls`, for an endpoint with row-level security. */\nexport async function loadRls(): Promise<typeof import('@geekmidas/db/rls')> {\n\ttry {\n\t\treturn await import('@geekmidas/db/rls');\n\t} catch (cause) {\n\t\tthrow new OptionalPeerMissing(\n\t\t\t'@geekmidas/db',\n\t\t\t'An endpoint with row-level security',\n\t\t\tcause,\n\t\t);\n\t}\n}\n","import type {\n\tAuditActor,\n\tAuditableAction,\n\tAuditor,\n\tAuditStorage,\n} from '@geekmidas/audit';\nimport type { Logger } from '@geekmidas/logger';\nimport type { InferStandardSchema } from '@geekmidas/schema';\nimport type { Service, ServiceDiscovery } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { ActorExtractor, MappedAudit } from './audit';\nimport type { CookieFn, Endpoint, HeaderFn } from './Endpoint';\nimport { loadAudit } from './optionalPeers';\n\n/**\n * Process declarative audit definitions after successful endpoint execution.\n * Similar to publishConstructEvents for events.\n *\n * @param endpoint - The endpoint with audit configuration\n * @param response - The handler response to generate audit payloads from\n * @param serviceDiscovery - Service discovery for registering audit storage\n * @param logger - Logger for debug/error messages\n * @param ctx - Request context (session, headers, cookies, services)\n * @param existingAuditor - Optional existing auditor instance (e.g., from handler context).\n * If provided, uses this auditor (with its stored transaction).\n * If not provided, creates a new auditor.\n */\nexport async function processEndpointAudits<\n\tTServices extends Service[] = [],\n\tTSession = unknown,\n\tTLogger extends Logger = Logger,\n\tOutSchema extends StandardSchemaV1 | undefined = undefined,\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n\tTAuditStorageServiceName extends string = string,\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n>(\n\tendpoint: Endpoint<\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tOutSchema,\n\t\tTServices,\n\t\tTLogger,\n\t\tTSession,\n\t\tTAuditStorage,\n\t\tTAuditStorageServiceName,\n\t\tTAuditAction\n\t>,\n\tresponse: InferStandardSchema<OutSchema>,\n\tserviceDiscovery: ServiceDiscovery<any>,\n\tlogger: TLogger,\n\tctx: {\n\t\tsession: TSession;\n\t\theader: HeaderFn;\n\t\tcookie: CookieFn;\n\t\tservices: Record<string, unknown>;\n\t},\n\texistingAuditor?: Auditor<TAuditAction>,\n): Promise<void> {\n\ttry {\n\t\tconst audits = endpoint.audits as MappedAudit<TAuditAction, OutSchema>[];\n\n\t\t// If we have an existing auditor (from handler context), we need to flush\n\t\t// any manual audits it collected, even if there are no declarative audits\n\t\tconst hasExistingRecords =\n\t\t\texistingAuditor && existingAuditor.getRecords().length > 0;\n\n\t\t// Skip if no declarative audits and no existing records to flush\n\t\tif (!audits?.length && !hasExistingRecords) {\n\t\t\tlogger.debug('No audits to process');\n\t\t\treturn;\n\t\t}\n\n\t\t// If no auditor storage service and we have things to process, warn\n\t\tif (!endpoint.auditorStorageService) {\n\t\t\tif (hasExistingRecords || audits?.length) {\n\t\t\t\tlogger.warn('No auditor storage service available');\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Get or create auditor\n\t\tlet auditor: Auditor<TAuditAction>;\n\n\t\tif (existingAuditor) {\n\t\t\t// Use existing auditor (preserves stored transaction and manual audits)\n\t\t\tauditor = existingAuditor;\n\t\t\tlogger.debug('Using existing auditor from handler context');\n\t\t} else {\n\t\t\t// Create new auditor (backward compatibility)\n\t\t\tconst services = await serviceDiscovery.register([\n\t\t\t\tendpoint.auditorStorageService,\n\t\t\t]);\n\t\t\tconst storage = services[\n\t\t\t\tendpoint.auditorStorageService.serviceName\n\t\t\t] as AuditStorage;\n\n\t\t\t// Extract actor if configured\n\t\t\tlet actor: AuditActor = { id: 'system', type: 'system' };\n\t\t\tif (endpoint.actorExtractor) {\n\t\t\t\ttry {\n\t\t\t\t\tactor = await (\n\t\t\t\t\t\tendpoint.actorExtractor as ActorExtractor<\n\t\t\t\t\t\t\tTServices,\n\t\t\t\t\t\t\tTSession,\n\t\t\t\t\t\t\tTLogger\n\t\t\t\t\t\t>\n\t\t\t\t\t)({\n\t\t\t\t\t\tservices: ctx.services as any,\n\t\t\t\t\t\tsession: ctx.session,\n\t\t\t\t\t\theader: ctx.header,\n\t\t\t\t\t\tcookie: ctx.cookie,\n\t\t\t\t\t\tlogger,\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger.error(error as Error, 'Failed to extract actor for audits');\n\t\t\t\t\t// Continue with system actor\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst { DefaultAuditor } = await loadAudit();\n\t\t\tauditor = new DefaultAuditor<TAuditAction>({\n\t\t\t\tactor,\n\t\t\t\tstorage,\n\t\t\t\tmetadata: {\n\t\t\t\t\tendpoint: endpoint.route,\n\t\t\t\t\tmethod: endpoint.method,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\t// Process each declarative audit\n\t\tif (audits?.length) {\n\t\t\tfor (const audit of audits) {\n\t\t\t\tlogger.debug({ audit: audit.type }, 'Processing declarative audit');\n\n\t\t\t\t// Check when condition\n\t\t\t\tif (audit.when && !audit.when(response as any)) {\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t{ audit: audit.type },\n\t\t\t\t\t\t'Audit skipped due to when condition',\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Extract payload\n\t\t\t\tconst payload = audit.payload(response as any);\n\n\t\t\t\t// Extract entityId if configured\n\t\t\t\tconst entityId = audit.entityId?.(response as any);\n\n\t\t\t\t// Record the audit\n\t\t\t\tauditor.audit(audit.type as any, payload as any, {\n\t\t\t\t\ttable: audit.table,\n\t\t\t\t\tentityId,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Flush audits to storage\n\t\t// Note: If existingAuditor has a stored transaction (via setTransaction),\n\t\t// flush() will use it automatically\n\t\tconst recordCount = auditor.getRecords().length;\n\t\tif (recordCount > 0) {\n\t\t\t// Check if auditor has a stored transaction (for logging purposes)\n\t\t\tconst trx =\n\t\t\t\t'getTransaction' in auditor\n\t\t\t\t\t? (auditor as { getTransaction(): unknown }).getTransaction()\n\t\t\t\t\t: undefined;\n\t\t\tlogger.debug(\n\t\t\t\t{ auditCount: recordCount, hasTransaction: !!trx },\n\t\t\t\t'Flushing audits',\n\t\t\t);\n\t\t\tawait auditor.flush();\n\t\t}\n\t} catch (error) {\n\t\tlogger.error(error as Error, 'Failed to process audits');\n\t\t// Don't rethrow - audit failures shouldn't fail the request\n\t}\n}\n\n/**\n * Context for audit-aware handler execution.\n */\nexport interface AuditExecutionContext<\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n> {\n\t/** The auditor instance for recording audits */\n\tauditor: Auditor<TAuditAction>;\n\t/** The audit storage instance */\n\tstorage: AuditStorage;\n}\n\n/**\n * Create audit context for handler execution.\n * Returns the auditor and storage for use in the handler.\n *\n * @param endpoint - The endpoint with audit configuration\n * @param serviceDiscovery - Service discovery for getting audit storage\n * @param logger - Logger for debug/error messages\n * @param ctx - Request context for actor extraction\n * @returns Audit context with auditor and storage, or undefined if not configured\n */\nexport async function createAuditContext<\n\tTServices extends Service[] = [],\n\tTSession = unknown,\n\tTLogger extends Logger = Logger,\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n\tTAuditStorageServiceName extends string = string,\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n\tTDatabase = undefined,\n\tTDatabaseServiceName extends string = string,\n>(\n\tendpoint: Endpoint<\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tTServices,\n\t\tTLogger,\n\t\tTSession,\n\t\tTAuditStorage,\n\t\tTAuditStorageServiceName,\n\t\tTAuditAction,\n\t\tTDatabase,\n\t\tTDatabaseServiceName\n\t>,\n\tserviceDiscovery: ServiceDiscovery<any>,\n\tlogger: TLogger,\n\tctx: {\n\t\tsession: TSession;\n\t\theader: HeaderFn;\n\t\tcookie: CookieFn;\n\t\tservices: Record<string, unknown>;\n\t},\n): Promise<AuditExecutionContext<TAuditAction> | undefined> {\n\tif (!endpoint.auditorStorageService) {\n\t\treturn undefined;\n\t}\n\n\tconst services = await serviceDiscovery.register([\n\t\tendpoint.auditorStorageService,\n\t]);\n\tconst storage = services[\n\t\tendpoint.auditorStorageService.serviceName\n\t] as AuditStorage;\n\n\t// Extract actor if configured\n\tlet actor: AuditActor = { id: 'system', type: 'system' };\n\tif (endpoint.actorExtractor) {\n\t\ttry {\n\t\t\tactor = await (\n\t\t\t\tendpoint.actorExtractor as ActorExtractor<TServices, TSession, TLogger>\n\t\t\t)({\n\t\t\t\tservices: ctx.services as any,\n\t\t\t\tsession: ctx.session,\n\t\t\t\theader: ctx.header,\n\t\t\t\tcookie: ctx.cookie,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tlogger.error(error as Error, 'Failed to extract actor for audits');\n\t\t}\n\t}\n\n\tconst { DefaultAuditor } = await loadAudit();\n\tconst auditor = new DefaultAuditor<TAuditAction>({\n\t\tactor,\n\t\tstorage,\n\t\tmetadata: {\n\t\t\tendpoint: endpoint.route,\n\t\t\tmethod: endpoint.method,\n\t\t},\n\t});\n\n\treturn { auditor, storage };\n}\n\n/**\n * Options for executeWithAuditTransaction.\n */\nexport interface ExecuteWithAuditTransactionOptions {\n\t/**\n\t * Database connection to use for the transaction.\n\t * If this is already a transaction, it will be reused instead of creating a nested one.\n\t * If not provided, the storage's internal database is used.\n\t */\n\tdb?: unknown;\n}\n\n/**\n * Execute a handler with automatic audit transaction support.\n * If the audit storage provides a withTransaction method, wraps execution\n * in a transaction so audits are atomic with handler's database operations.\n *\n * This is database-agnostic - each storage implementation provides its own\n * transaction handling based on the underlying database (Kysely, Drizzle, etc.).\n *\n * If the db parameter is provided and is already a transaction, the storage\n * will reuse it instead of creating a nested transaction (similar to\n * packages/db/src/kysely.ts#withTransaction).\n *\n * @param auditContext - The audit context from createAuditContext\n * @param handler - The handler function to execute (receives auditor)\n * @param onComplete - Called after handler with response, to process declarative audits\n * @param options - Optional configuration including database connection\n * @returns The handler result\n */\nexport async function executeWithAuditTransaction<\n\tT,\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n>(\n\tauditContext: AuditExecutionContext<TAuditAction> | undefined,\n\thandler: (auditor?: Auditor<TAuditAction>) => Promise<T>,\n\tonComplete?: (response: T, auditor: Auditor<TAuditAction>) => Promise<void>,\n\toptions?: ExecuteWithAuditTransactionOptions,\n): Promise<T> {\n\t// No audit context - just run handler\n\tif (!auditContext) {\n\t\treturn handler(undefined);\n\t}\n\n\tconst { auditor, storage } = auditContext;\n\n\t// Check if storage provides a transaction wrapper\n\tif (storage.withTransaction) {\n\t\t// Wrap in transaction - audits are atomic with handler operations\n\t\t// The storage's withTransaction handles setTransaction and flush\n\t\t// Pass db so existing transactions are reused\n\t\treturn storage.withTransaction(\n\t\t\tauditor,\n\t\t\tasync () => {\n\t\t\t\tconst response = await handler(auditor);\n\n\t\t\t\t// Process declarative audits within the transaction\n\t\t\t\tif (onComplete) {\n\t\t\t\t\tawait onComplete(response, auditor);\n\t\t\t\t}\n\n\t\t\t\treturn response;\n\t\t\t},\n\t\t\toptions?.db,\n\t\t);\n\t}\n\n\t// No transaction support - run handler and flush audits after\n\tconst response = await handler(auditor);\n\n\tif (onComplete) {\n\t\tawait onComplete(response, auditor);\n\t}\n\n\t// Flush audits (no transaction)\n\tawait auditor.flush();\n\n\treturn response;\n}\n","import type { RateLimitConfig, RateLimitContext } from '@geekmidas/rate-limit';\nimport { loadRateLimit } from './optionalPeers';\n\n/**\n * An endpoint's `.rateLimit()`, checked the one way every adaptor checks it —\n * Hono, Lambda and the test adaptor — after the request is authorized.\n *\n * Under the limit, it answers the headers the response carries:\n * `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (and\n * `Retry-After` on the last request the window allows).\n *\n * @throws {TooManyRequestsError} over the limit — `@geekmidas/errors`' 429,\n * whose `headers` hold `Retry-After` (seconds until the window resets) and the\n * same `X-RateLimit-*`, which every adaptor sets on the error response.\n */\nexport async function enforceRateLimit(\n\tconfig: RateLimitConfig,\n\tctx: RateLimitContext<any, any, any>,\n): Promise<Record<string, string>> {\n\tconst { checkRateLimit, getRateLimitHeaders } = await loadRateLimit();\n\tconst info = await checkRateLimit(config, ctx);\n\n\tconst headers: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(\n\t\tgetRateLimitHeaders(info, config),\n\t)) {\n\t\tif (value !== undefined) headers[key] = value;\n\t}\n\treturn headers;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmBA,IAAa,sBAAb,cAAyC,MAAM;CAEpC;CACA;CAFV,YACC,aACA,SACA,OACC;EACD,MACC,GAAG,QAAQ,UAAU,YAAY,iGAEjC,EAAE,MAAM,CACT;EARS,KAAA,cAAA;EACA,KAAA,UAAA;EAQT,KAAK,OAAO;CACb;AACD;;AAGA,eAAsB,YAAwD;CAC7E,IAAI;EACH,OAAO,MAAM,OAAO;CACrB,SAAS,OAAO;EACf,MAAM,IAAI,oBACT,oBACA,+BACA,KACD;CACD;AACD;;AAGA,eAAsB,gBAEpB;CACD,IAAI;EACH,OAAO,MAAM,OAAO;CACrB,SAAS,OAAO;EACf,MAAM,IAAI,oBACT,yBACA,iCACA,KACD;CACD;AACD;;AAGA,eAAsB,UAAuD;CAC5E,IAAI;EACH,OAAO,MAAM,OAAO;CACrB,SAAS,OAAO;EACf,MAAM,IAAI,oBACT,iBACA,uCACA,KACD;CACD;AACD;;;;;;;;;;;;;ACwIA,eAAsB,mBAarB,UAcA,kBACA,QACA,KAM2D;CAC3D,IAAI,CAAC,SAAS,uBACb;CAMD,MAAM,WAAU,MAHO,iBAAiB,SAAS,CAChD,SAAS,qBACV,CAAC,EAAA,CAEA,SAAS,sBAAsB;CAIhC,IAAI,QAAoB;EAAE,IAAI;EAAU,MAAM;CAAS;CACvD,IAAI,SAAS,gBACZ,IAAI;EACH,QAAQ,MACP,SAAS,eACR;GACD,UAAU,IAAI;GACd,SAAS,IAAI;GACb,QAAQ,IAAI;GACZ,QAAQ,IAAI;GACZ;EACD,CAAC;CACF,SAAS,OAAO;EACf,OAAO,MAAM,OAAgB,oCAAoC;CAClE;CAGD,MAAM,EAAE,mBAAmB,MAAM,UAAU;CAU3C,OAAO;EAAE,SAAA,IATW,eAA6B;GAChD;GACA;GACA,UAAU;IACT,UAAU,SAAS;IACnB,QAAQ,SAAS;GAClB;EACD,CAEe;EAAG;CAAQ;AAC3B;;;;;;;;;;;;;;;;;;;AAgCA,eAAsB,4BAOrB,cACA,SACA,YACA,SACa;CAEb,IAAI,CAAC,cACJ,OAAO,QAAQ,KAAA,CAAS;CAGzB,MAAM,EAAE,SAAS,YAAY;CAG7B,IAAI,QAAQ,iBAIX,OAAO,QAAQ,gBACd,SACA,YAAY;EACX,MAAM,WAAW,MAAM,QAAQ,OAAO;EAGtC,IAAI,YACH,MAAM,WAAW,UAAU,OAAO;EAGnC,OAAO;CACR,GACA,SAAS,EACV;CAID,MAAM,WAAW,MAAM,QAAQ,OAAO;CAEtC,IAAI,YACH,MAAM,WAAW,UAAU,OAAO;CAInC,MAAM,QAAQ,MAAM;CAEpB,OAAO;AACR;;;;;;;;;;;;;;;ACjWA,eAAsB,iBACrB,QACA,KACkC;CAClC,MAAM,EAAE,gBAAgB,wBAAwB,MAAM,cAAc;CACpE,MAAM,OAAO,MAAM,eAAe,QAAQ,GAAG;CAE7C,MAAM,UAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QACjC,oBAAoB,MAAM,MAAM,CACjC,GACC,IAAI,UAAU,KAAA,GAAW,QAAQ,OAAO;CAEzC,OAAO;AACR"}
@@ -126,6 +126,27 @@ async function executeWithAuditTransaction(auditContext, handler, onComplete, op
126
126
  return response;
127
127
  }
128
128
  //#endregion
129
- export { loadRls as i, executeWithAuditTransaction as n, loadRateLimit as r, createAuditContext as t };
129
+ //#region src/endpoints/rateLimit.ts
130
+ /**
131
+ * An endpoint's `.rateLimit()`, checked the one way every adaptor checks it —
132
+ * Hono, Lambda and the test adaptor — after the request is authorized.
133
+ *
134
+ * Under the limit, it answers the headers the response carries:
135
+ * `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (and
136
+ * `Retry-After` on the last request the window allows).
137
+ *
138
+ * @throws {TooManyRequestsError} over the limit — `@geekmidas/errors`' 429,
139
+ * whose `headers` hold `Retry-After` (seconds until the window resets) and the
140
+ * same `X-RateLimit-*`, which every adaptor sets on the error response.
141
+ */
142
+ async function enforceRateLimit(config, ctx) {
143
+ const { checkRateLimit, getRateLimitHeaders } = await loadRateLimit();
144
+ const info = await checkRateLimit(config, ctx);
145
+ const headers = {};
146
+ for (const [key, value] of Object.entries(getRateLimitHeaders(info, config))) if (value !== void 0) headers[key] = value;
147
+ return headers;
148
+ }
149
+ //#endregion
150
+ export { loadRls as i, createAuditContext as n, executeWithAuditTransaction as r, enforceRateLimit as t };
130
151
 
131
- //# sourceMappingURL=processAudits-BRRVVHUB.mjs.map
152
+ //# sourceMappingURL=rateLimit-DbOTsP01.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimit-DbOTsP01.mjs","names":[],"sources":["../src/endpoints/optionalPeers.ts","../src/endpoints/processAudits.ts","../src/endpoints/rateLimit.ts"],"sourcesContent":["/**\n * The optional peers an endpoint needs only for a feature it opted into.\n *\n * `@geekmidas/audit`, `@geekmidas/rate-limit` and `@geekmidas/db` are optional\n * peers of this package: an app that audits nothing, limits nothing and sets\n * no row-level security need not install them. The adaptor every endpoint is\n * served by used to import them at the top, so serving any endpoint needed\n * them all — and a production bundle of an app without them failed to build.\n * They are loaded here, when an endpoint that uses one is served, which is\n * also when the app declaring that endpoint has installed it: an auditor's\n * storage, a rate limit's store and an RLS policy's database come from these\n * packages.\n *\n * The `try` is load-bearing beyond the error it names: a bundler leaves an\n * import it cannot resolve to run time when the import is guarded, and fails\n * the build when it is not.\n */\n\n/** An endpoint uses a feature whose optional peer is not installed. */\nexport class OptionalPeerMissing extends Error {\n\tconstructor(\n\t\treadonly packageName: string,\n\t\treadonly feature: string,\n\t\tcause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${feature} needs '${packageName}', which could not be loaded. ` +\n\t\t\t\t`Add it to the dependencies of the app that serves this endpoint.`,\n\t\t\t{ cause },\n\t\t);\n\t\tthis.name = 'OptionalPeerMissing';\n\t}\n}\n\n/** `@geekmidas/audit`, for an endpoint with an auditor. */\nexport async function loadAudit(): Promise<typeof import('@geekmidas/audit')> {\n\ttry {\n\t\treturn await import('@geekmidas/audit');\n\t} catch (cause) {\n\t\tthrow new OptionalPeerMissing(\n\t\t\t'@geekmidas/audit',\n\t\t\t'An endpoint with an auditor',\n\t\t\tcause,\n\t\t);\n\t}\n}\n\n/** `@geekmidas/rate-limit`, for an endpoint with a rate limit. */\nexport async function loadRateLimit(): Promise<\n\ttypeof import('@geekmidas/rate-limit')\n> {\n\ttry {\n\t\treturn await import('@geekmidas/rate-limit');\n\t} catch (cause) {\n\t\tthrow new OptionalPeerMissing(\n\t\t\t'@geekmidas/rate-limit',\n\t\t\t'An endpoint with a rate limit',\n\t\t\tcause,\n\t\t);\n\t}\n}\n\n/** `@geekmidas/db/rls`, for an endpoint with row-level security. */\nexport async function loadRls(): Promise<typeof import('@geekmidas/db/rls')> {\n\ttry {\n\t\treturn await import('@geekmidas/db/rls');\n\t} catch (cause) {\n\t\tthrow new OptionalPeerMissing(\n\t\t\t'@geekmidas/db',\n\t\t\t'An endpoint with row-level security',\n\t\t\tcause,\n\t\t);\n\t}\n}\n","import type {\n\tAuditActor,\n\tAuditableAction,\n\tAuditor,\n\tAuditStorage,\n} from '@geekmidas/audit';\nimport type { Logger } from '@geekmidas/logger';\nimport type { InferStandardSchema } from '@geekmidas/schema';\nimport type { Service, ServiceDiscovery } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { ActorExtractor, MappedAudit } from './audit';\nimport type { CookieFn, Endpoint, HeaderFn } from './Endpoint';\nimport { loadAudit } from './optionalPeers';\n\n/**\n * Process declarative audit definitions after successful endpoint execution.\n * Similar to publishConstructEvents for events.\n *\n * @param endpoint - The endpoint with audit configuration\n * @param response - The handler response to generate audit payloads from\n * @param serviceDiscovery - Service discovery for registering audit storage\n * @param logger - Logger for debug/error messages\n * @param ctx - Request context (session, headers, cookies, services)\n * @param existingAuditor - Optional existing auditor instance (e.g., from handler context).\n * If provided, uses this auditor (with its stored transaction).\n * If not provided, creates a new auditor.\n */\nexport async function processEndpointAudits<\n\tTServices extends Service[] = [],\n\tTSession = unknown,\n\tTLogger extends Logger = Logger,\n\tOutSchema extends StandardSchemaV1 | undefined = undefined,\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n\tTAuditStorageServiceName extends string = string,\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n>(\n\tendpoint: Endpoint<\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tOutSchema,\n\t\tTServices,\n\t\tTLogger,\n\t\tTSession,\n\t\tTAuditStorage,\n\t\tTAuditStorageServiceName,\n\t\tTAuditAction\n\t>,\n\tresponse: InferStandardSchema<OutSchema>,\n\tserviceDiscovery: ServiceDiscovery<any>,\n\tlogger: TLogger,\n\tctx: {\n\t\tsession: TSession;\n\t\theader: HeaderFn;\n\t\tcookie: CookieFn;\n\t\tservices: Record<string, unknown>;\n\t},\n\texistingAuditor?: Auditor<TAuditAction>,\n): Promise<void> {\n\ttry {\n\t\tconst audits = endpoint.audits as MappedAudit<TAuditAction, OutSchema>[];\n\n\t\t// If we have an existing auditor (from handler context), we need to flush\n\t\t// any manual audits it collected, even if there are no declarative audits\n\t\tconst hasExistingRecords =\n\t\t\texistingAuditor && existingAuditor.getRecords().length > 0;\n\n\t\t// Skip if no declarative audits and no existing records to flush\n\t\tif (!audits?.length && !hasExistingRecords) {\n\t\t\tlogger.debug('No audits to process');\n\t\t\treturn;\n\t\t}\n\n\t\t// If no auditor storage service and we have things to process, warn\n\t\tif (!endpoint.auditorStorageService) {\n\t\t\tif (hasExistingRecords || audits?.length) {\n\t\t\t\tlogger.warn('No auditor storage service available');\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Get or create auditor\n\t\tlet auditor: Auditor<TAuditAction>;\n\n\t\tif (existingAuditor) {\n\t\t\t// Use existing auditor (preserves stored transaction and manual audits)\n\t\t\tauditor = existingAuditor;\n\t\t\tlogger.debug('Using existing auditor from handler context');\n\t\t} else {\n\t\t\t// Create new auditor (backward compatibility)\n\t\t\tconst services = await serviceDiscovery.register([\n\t\t\t\tendpoint.auditorStorageService,\n\t\t\t]);\n\t\t\tconst storage = services[\n\t\t\t\tendpoint.auditorStorageService.serviceName\n\t\t\t] as AuditStorage;\n\n\t\t\t// Extract actor if configured\n\t\t\tlet actor: AuditActor = { id: 'system', type: 'system' };\n\t\t\tif (endpoint.actorExtractor) {\n\t\t\t\ttry {\n\t\t\t\t\tactor = await (\n\t\t\t\t\t\tendpoint.actorExtractor as ActorExtractor<\n\t\t\t\t\t\t\tTServices,\n\t\t\t\t\t\t\tTSession,\n\t\t\t\t\t\t\tTLogger\n\t\t\t\t\t\t>\n\t\t\t\t\t)({\n\t\t\t\t\t\tservices: ctx.services as any,\n\t\t\t\t\t\tsession: ctx.session,\n\t\t\t\t\t\theader: ctx.header,\n\t\t\t\t\t\tcookie: ctx.cookie,\n\t\t\t\t\t\tlogger,\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger.error(error as Error, 'Failed to extract actor for audits');\n\t\t\t\t\t// Continue with system actor\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst { DefaultAuditor } = await loadAudit();\n\t\t\tauditor = new DefaultAuditor<TAuditAction>({\n\t\t\t\tactor,\n\t\t\t\tstorage,\n\t\t\t\tmetadata: {\n\t\t\t\t\tendpoint: endpoint.route,\n\t\t\t\t\tmethod: endpoint.method,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\t// Process each declarative audit\n\t\tif (audits?.length) {\n\t\t\tfor (const audit of audits) {\n\t\t\t\tlogger.debug({ audit: audit.type }, 'Processing declarative audit');\n\n\t\t\t\t// Check when condition\n\t\t\t\tif (audit.when && !audit.when(response as any)) {\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t{ audit: audit.type },\n\t\t\t\t\t\t'Audit skipped due to when condition',\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Extract payload\n\t\t\t\tconst payload = audit.payload(response as any);\n\n\t\t\t\t// Extract entityId if configured\n\t\t\t\tconst entityId = audit.entityId?.(response as any);\n\n\t\t\t\t// Record the audit\n\t\t\t\tauditor.audit(audit.type as any, payload as any, {\n\t\t\t\t\ttable: audit.table,\n\t\t\t\t\tentityId,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Flush audits to storage\n\t\t// Note: If existingAuditor has a stored transaction (via setTransaction),\n\t\t// flush() will use it automatically\n\t\tconst recordCount = auditor.getRecords().length;\n\t\tif (recordCount > 0) {\n\t\t\t// Check if auditor has a stored transaction (for logging purposes)\n\t\t\tconst trx =\n\t\t\t\t'getTransaction' in auditor\n\t\t\t\t\t? (auditor as { getTransaction(): unknown }).getTransaction()\n\t\t\t\t\t: undefined;\n\t\t\tlogger.debug(\n\t\t\t\t{ auditCount: recordCount, hasTransaction: !!trx },\n\t\t\t\t'Flushing audits',\n\t\t\t);\n\t\t\tawait auditor.flush();\n\t\t}\n\t} catch (error) {\n\t\tlogger.error(error as Error, 'Failed to process audits');\n\t\t// Don't rethrow - audit failures shouldn't fail the request\n\t}\n}\n\n/**\n * Context for audit-aware handler execution.\n */\nexport interface AuditExecutionContext<\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n> {\n\t/** The auditor instance for recording audits */\n\tauditor: Auditor<TAuditAction>;\n\t/** The audit storage instance */\n\tstorage: AuditStorage;\n}\n\n/**\n * Create audit context for handler execution.\n * Returns the auditor and storage for use in the handler.\n *\n * @param endpoint - The endpoint with audit configuration\n * @param serviceDiscovery - Service discovery for getting audit storage\n * @param logger - Logger for debug/error messages\n * @param ctx - Request context for actor extraction\n * @returns Audit context with auditor and storage, or undefined if not configured\n */\nexport async function createAuditContext<\n\tTServices extends Service[] = [],\n\tTSession = unknown,\n\tTLogger extends Logger = Logger,\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n\tTAuditStorageServiceName extends string = string,\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n\tTDatabase = undefined,\n\tTDatabaseServiceName extends string = string,\n>(\n\tendpoint: Endpoint<\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tTServices,\n\t\tTLogger,\n\t\tTSession,\n\t\tTAuditStorage,\n\t\tTAuditStorageServiceName,\n\t\tTAuditAction,\n\t\tTDatabase,\n\t\tTDatabaseServiceName\n\t>,\n\tserviceDiscovery: ServiceDiscovery<any>,\n\tlogger: TLogger,\n\tctx: {\n\t\tsession: TSession;\n\t\theader: HeaderFn;\n\t\tcookie: CookieFn;\n\t\tservices: Record<string, unknown>;\n\t},\n): Promise<AuditExecutionContext<TAuditAction> | undefined> {\n\tif (!endpoint.auditorStorageService) {\n\t\treturn undefined;\n\t}\n\n\tconst services = await serviceDiscovery.register([\n\t\tendpoint.auditorStorageService,\n\t]);\n\tconst storage = services[\n\t\tendpoint.auditorStorageService.serviceName\n\t] as AuditStorage;\n\n\t// Extract actor if configured\n\tlet actor: AuditActor = { id: 'system', type: 'system' };\n\tif (endpoint.actorExtractor) {\n\t\ttry {\n\t\t\tactor = await (\n\t\t\t\tendpoint.actorExtractor as ActorExtractor<TServices, TSession, TLogger>\n\t\t\t)({\n\t\t\t\tservices: ctx.services as any,\n\t\t\t\tsession: ctx.session,\n\t\t\t\theader: ctx.header,\n\t\t\t\tcookie: ctx.cookie,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tlogger.error(error as Error, 'Failed to extract actor for audits');\n\t\t}\n\t}\n\n\tconst { DefaultAuditor } = await loadAudit();\n\tconst auditor = new DefaultAuditor<TAuditAction>({\n\t\tactor,\n\t\tstorage,\n\t\tmetadata: {\n\t\t\tendpoint: endpoint.route,\n\t\t\tmethod: endpoint.method,\n\t\t},\n\t});\n\n\treturn { auditor, storage };\n}\n\n/**\n * Options for executeWithAuditTransaction.\n */\nexport interface ExecuteWithAuditTransactionOptions {\n\t/**\n\t * Database connection to use for the transaction.\n\t * If this is already a transaction, it will be reused instead of creating a nested one.\n\t * If not provided, the storage's internal database is used.\n\t */\n\tdb?: unknown;\n}\n\n/**\n * Execute a handler with automatic audit transaction support.\n * If the audit storage provides a withTransaction method, wraps execution\n * in a transaction so audits are atomic with handler's database operations.\n *\n * This is database-agnostic - each storage implementation provides its own\n * transaction handling based on the underlying database (Kysely, Drizzle, etc.).\n *\n * If the db parameter is provided and is already a transaction, the storage\n * will reuse it instead of creating a nested transaction (similar to\n * packages/db/src/kysely.ts#withTransaction).\n *\n * @param auditContext - The audit context from createAuditContext\n * @param handler - The handler function to execute (receives auditor)\n * @param onComplete - Called after handler with response, to process declarative audits\n * @param options - Optional configuration including database connection\n * @returns The handler result\n */\nexport async function executeWithAuditTransaction<\n\tT,\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n>(\n\tauditContext: AuditExecutionContext<TAuditAction> | undefined,\n\thandler: (auditor?: Auditor<TAuditAction>) => Promise<T>,\n\tonComplete?: (response: T, auditor: Auditor<TAuditAction>) => Promise<void>,\n\toptions?: ExecuteWithAuditTransactionOptions,\n): Promise<T> {\n\t// No audit context - just run handler\n\tif (!auditContext) {\n\t\treturn handler(undefined);\n\t}\n\n\tconst { auditor, storage } = auditContext;\n\n\t// Check if storage provides a transaction wrapper\n\tif (storage.withTransaction) {\n\t\t// Wrap in transaction - audits are atomic with handler operations\n\t\t// The storage's withTransaction handles setTransaction and flush\n\t\t// Pass db so existing transactions are reused\n\t\treturn storage.withTransaction(\n\t\t\tauditor,\n\t\t\tasync () => {\n\t\t\t\tconst response = await handler(auditor);\n\n\t\t\t\t// Process declarative audits within the transaction\n\t\t\t\tif (onComplete) {\n\t\t\t\t\tawait onComplete(response, auditor);\n\t\t\t\t}\n\n\t\t\t\treturn response;\n\t\t\t},\n\t\t\toptions?.db,\n\t\t);\n\t}\n\n\t// No transaction support - run handler and flush audits after\n\tconst response = await handler(auditor);\n\n\tif (onComplete) {\n\t\tawait onComplete(response, auditor);\n\t}\n\n\t// Flush audits (no transaction)\n\tawait auditor.flush();\n\n\treturn response;\n}\n","import type { RateLimitConfig, RateLimitContext } from '@geekmidas/rate-limit';\nimport { loadRateLimit } from './optionalPeers';\n\n/**\n * An endpoint's `.rateLimit()`, checked the one way every adaptor checks it —\n * Hono, Lambda and the test adaptor — after the request is authorized.\n *\n * Under the limit, it answers the headers the response carries:\n * `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (and\n * `Retry-After` on the last request the window allows).\n *\n * @throws {TooManyRequestsError} over the limit — `@geekmidas/errors`' 429,\n * whose `headers` hold `Retry-After` (seconds until the window resets) and the\n * same `X-RateLimit-*`, which every adaptor sets on the error response.\n */\nexport async function enforceRateLimit(\n\tconfig: RateLimitConfig,\n\tctx: RateLimitContext<any, any, any>,\n): Promise<Record<string, string>> {\n\tconst { checkRateLimit, getRateLimitHeaders } = await loadRateLimit();\n\tconst info = await checkRateLimit(config, ctx);\n\n\tconst headers: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(\n\t\tgetRateLimitHeaders(info, config),\n\t)) {\n\t\tif (value !== undefined) headers[key] = value;\n\t}\n\treturn headers;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmBA,IAAa,sBAAb,cAAyC,MAAM;CAEpC;CACA;CAFV,YACC,aACA,SACA,OACC;EACD,MACC,GAAG,QAAQ,UAAU,YAAY,iGAEjC,EAAE,MAAM,CACT;EARS,KAAA,cAAA;EACA,KAAA,UAAA;EAQT,KAAK,OAAO;CACb;AACD;;AAGA,eAAsB,YAAwD;CAC7E,IAAI;EACH,OAAO,MAAM,OAAO;CACrB,SAAS,OAAO;EACf,MAAM,IAAI,oBACT,oBACA,+BACA,KACD;CACD;AACD;;AAGA,eAAsB,gBAEpB;CACD,IAAI;EACH,OAAO,MAAM,OAAO;CACrB,SAAS,OAAO;EACf,MAAM,IAAI,oBACT,yBACA,iCACA,KACD;CACD;AACD;;AAGA,eAAsB,UAAuD;CAC5E,IAAI;EACH,OAAO,MAAM,OAAO;CACrB,SAAS,OAAO;EACf,MAAM,IAAI,oBACT,iBACA,uCACA,KACD;CACD;AACD;;;;;;;;;;;;;ACwIA,eAAsB,mBAarB,UAcA,kBACA,QACA,KAM2D;CAC3D,IAAI,CAAC,SAAS,uBACb;CAMD,MAAM,WAAU,MAHO,iBAAiB,SAAS,CAChD,SAAS,qBACV,CAAC,EAAA,CAEA,SAAS,sBAAsB;CAIhC,IAAI,QAAoB;EAAE,IAAI;EAAU,MAAM;CAAS;CACvD,IAAI,SAAS,gBACZ,IAAI;EACH,QAAQ,MACP,SAAS,eACR;GACD,UAAU,IAAI;GACd,SAAS,IAAI;GACb,QAAQ,IAAI;GACZ,QAAQ,IAAI;GACZ;EACD,CAAC;CACF,SAAS,OAAO;EACf,OAAO,MAAM,OAAgB,oCAAoC;CAClE;CAGD,MAAM,EAAE,mBAAmB,MAAM,UAAU;CAU3C,OAAO;EAAE,SAAA,IATW,eAA6B;GAChD;GACA;GACA,UAAU;IACT,UAAU,SAAS;IACnB,QAAQ,SAAS;GAClB;EACD,CAEe;EAAG;CAAQ;AAC3B;;;;;;;;;;;;;;;;;;;AAgCA,eAAsB,4BAOrB,cACA,SACA,YACA,SACa;CAEb,IAAI,CAAC,cACJ,OAAO,QAAQ,KAAA,CAAS;CAGzB,MAAM,EAAE,SAAS,YAAY;CAG7B,IAAI,QAAQ,iBAIX,OAAO,QAAQ,gBACd,SACA,YAAY;EACX,MAAM,WAAW,MAAM,QAAQ,OAAO;EAGtC,IAAI,YACH,MAAM,WAAW,UAAU,OAAO;EAGnC,OAAO;CACR,GACA,SAAS,EACV;CAID,MAAM,WAAW,MAAM,QAAQ,OAAO;CAEtC,IAAI,YACH,MAAM,WAAW,UAAU,OAAO;CAInC,MAAM,QAAQ,MAAM;CAEpB,OAAO;AACR;;;;;;;;;;;;;;;ACjWA,eAAsB,iBACrB,QACA,KACkC;CAClC,MAAM,EAAE,gBAAgB,wBAAwB,MAAM,cAAc;CACpE,MAAM,OAAO,MAAM,eAAe,QAAQ,GAAG;CAE7C,MAAM,UAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QACjC,oBAAoB,MAAM,MAAM,CACjC,GACC,IAAI,UAAU,KAAA,GAAW,QAAQ,OAAO;CAEzC,OAAO;AACR"}