@geekmidas/constructs 10.0.0-alpha.83 → 10.0.0-alpha.84

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 (101) hide show
  1. package/dist/{Endpoint-ZJhv68qq.mjs → Endpoint-BG4UJ9oA.mjs} +8 -2
  2. package/dist/Endpoint-BG4UJ9oA.mjs.map +1 -0
  3. package/dist/{Endpoint-DJZkADYh.d.cts → Endpoint-BHdyT3wO.d.cts} +11 -3
  4. package/dist/Endpoint-BHdyT3wO.d.cts.map +1 -0
  5. package/dist/{Endpoint-Ba1PiyuJ.cjs → Endpoint-BoUg8ocM.cjs} +8 -2
  6. package/dist/Endpoint-BoUg8ocM.cjs.map +1 -0
  7. package/dist/{Endpoint-hcHSFhda.d.mts → Endpoint-Dg8TZMYX.d.mts} +11 -3
  8. package/dist/Endpoint-Dg8TZMYX.d.mts.map +1 -0
  9. package/dist/{EndpointFactory-C3QvXMk6.mjs → EndpointFactory-BDtsrEvJ.mjs} +18 -3
  10. package/dist/EndpointFactory-BDtsrEvJ.mjs.map +1 -0
  11. package/dist/{EndpointFactory-BkYuVg8W.d.cts → EndpointFactory-COJus3LO.d.cts} +15 -2
  12. package/dist/EndpointFactory-COJus3LO.d.cts.map +1 -0
  13. package/dist/{EndpointFactory-B_x2fXWQ.cjs → EndpointFactory-DJ7gXLpR.cjs} +18 -3
  14. package/dist/EndpointFactory-DJ7gXLpR.cjs.map +1 -0
  15. package/dist/{EndpointFactory-B5siFRcO.d.mts → EndpointFactory-DzYW1Gb8.d.mts} +15 -2
  16. package/dist/EndpointFactory-DzYW1Gb8.d.mts.map +1 -0
  17. package/dist/{HonoEndpointAdaptor-chgcjPmg.mjs → HonoEndpointAdaptor-PtPlbeKY.mjs} +2 -2
  18. package/dist/{HonoEndpointAdaptor-chgcjPmg.mjs.map → HonoEndpointAdaptor-PtPlbeKY.mjs.map} +1 -1
  19. package/dist/{HonoEndpointAdaptor-0yLvYunI.cjs → HonoEndpointAdaptor-zVzVLJEO.cjs} +2 -2
  20. package/dist/{HonoEndpointAdaptor-0yLvYunI.cjs.map → HonoEndpointAdaptor-zVzVLJEO.cjs.map} +1 -1
  21. package/dist/adaptors/aws.cjs +1 -1
  22. package/dist/adaptors/aws.cjs.map +1 -1
  23. package/dist/adaptors/aws.d.cts +2 -2
  24. package/dist/adaptors/aws.d.mts +2 -2
  25. package/dist/adaptors/aws.mjs +1 -1
  26. package/dist/adaptors/aws.mjs.map +1 -1
  27. package/dist/adaptors/hono.cjs +1 -1
  28. package/dist/adaptors/hono.d.cts +1 -1
  29. package/dist/adaptors/hono.d.mts +1 -1
  30. package/dist/adaptors/hono.mjs +1 -1
  31. package/dist/adaptors/testing.cjs +2 -2
  32. package/dist/adaptors/testing.d.cts +1 -1
  33. package/dist/adaptors/testing.d.mts +1 -1
  34. package/dist/adaptors/testing.mjs +2 -2
  35. package/dist/{auth-CuCozjsh.d.cts → auth-BPYUjY5T.d.cts} +8 -1
  36. package/dist/auth-BPYUjY5T.d.cts.map +1 -0
  37. package/dist/{auth-XtDcvQxV.d.mts → auth-CDmD16Ux.d.mts} +8 -1
  38. package/dist/auth-CDmD16Ux.d.mts.map +1 -0
  39. package/dist/auth.cjs +1 -0
  40. package/dist/auth.cjs.map +1 -1
  41. package/dist/auth.d.cts +1 -1
  42. package/dist/auth.d.mts +1 -1
  43. package/dist/auth.mjs +1 -0
  44. package/dist/auth.mjs.map +1 -1
  45. package/dist/endpoints/index.cjs +2 -2
  46. package/dist/endpoints/index.d.cts +2 -2
  47. package/dist/endpoints/index.d.mts +2 -2
  48. package/dist/endpoints/index.mjs +2 -2
  49. package/dist/index.d.cts +1 -1
  50. package/dist/index.d.mts +1 -1
  51. package/dist/{telemetry-DGcFlVpB.d.cts → lambdaTelemetry-8vnmVZQ5.d.cts} +2 -2
  52. package/dist/lambdaTelemetry-8vnmVZQ5.d.cts.map +1 -0
  53. package/dist/{telemetry-DGcFlVpB.d.mts → lambdaTelemetry-8vnmVZQ5.d.mts} +2 -2
  54. package/dist/lambdaTelemetry-8vnmVZQ5.d.mts.map +1 -0
  55. package/dist/rest-api.cjs +5 -1
  56. package/dist/rest-api.cjs.map +1 -1
  57. package/dist/rest-api.d.cts +15 -2
  58. package/dist/rest-api.d.cts.map +1 -1
  59. package/dist/rest-api.d.mts +15 -2
  60. package/dist/rest-api.d.mts.map +1 -1
  61. package/dist/rest-api.mjs +5 -1
  62. package/dist/rest-api.mjs.map +1 -1
  63. package/dist/site.cjs +1 -0
  64. package/dist/site.cjs.map +1 -1
  65. package/dist/site.d.cts +8 -0
  66. package/dist/site.d.cts.map +1 -1
  67. package/dist/site.d.mts +8 -0
  68. package/dist/site.d.mts.map +1 -1
  69. package/dist/site.mjs +1 -0
  70. package/dist/site.mjs.map +1 -1
  71. package/dist/telemetry-B48RLjyk.d.cts +40 -0
  72. package/dist/telemetry-B48RLjyk.d.cts.map +1 -0
  73. package/dist/telemetry-DRtT1_0A.d.mts +40 -0
  74. package/dist/telemetry-DRtT1_0A.d.mts.map +1 -0
  75. package/dist/telemetry.cjs +56 -0
  76. package/dist/telemetry.cjs.map +1 -0
  77. package/dist/telemetry.d.cts +2 -0
  78. package/dist/telemetry.d.mts +2 -0
  79. package/dist/telemetry.mjs +55 -0
  80. package/dist/telemetry.mjs.map +1 -0
  81. package/dist/worker.cjs +1 -0
  82. package/dist/worker.cjs.map +1 -1
  83. package/dist/worker.d.cts +7 -0
  84. package/dist/worker.d.cts.map +1 -1
  85. package/dist/worker.d.mts +7 -0
  86. package/dist/worker.d.mts.map +1 -1
  87. package/dist/worker.mjs +1 -0
  88. package/dist/worker.mjs.map +1 -1
  89. package/package.json +41 -31
  90. package/dist/Endpoint-Ba1PiyuJ.cjs.map +0 -1
  91. package/dist/Endpoint-DJZkADYh.d.cts.map +0 -1
  92. package/dist/Endpoint-ZJhv68qq.mjs.map +0 -1
  93. package/dist/Endpoint-hcHSFhda.d.mts.map +0 -1
  94. package/dist/EndpointFactory-B5siFRcO.d.mts.map +0 -1
  95. package/dist/EndpointFactory-B_x2fXWQ.cjs.map +0 -1
  96. package/dist/EndpointFactory-BkYuVg8W.d.cts.map +0 -1
  97. package/dist/EndpointFactory-C3QvXMk6.mjs.map +0 -1
  98. package/dist/auth-CuCozjsh.d.cts.map +0 -1
  99. package/dist/auth-XtDcvQxV.d.mts.map +0 -1
  100. package/dist/telemetry-DGcFlVpB.d.cts.map +0 -1
  101. package/dist/telemetry-DGcFlVpB.d.mts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"rest-api.cjs","names":["canonicalId","provideKey","DEFAULT_LOGGER","envParserFor","EndpointFactory","getSecurityScheme","edgeTo"],"sources":["../src/rest-api.ts"],"sourcesContent":["/**\n * `RestApi` — the application's own HTTP surface, declared.\n *\n * Everything an app serves already existed; what did not exist was a *node* for\n * it. Without one, three things that are properties of the graph had to be\n * written down somewhere else: which origins the API accepts, which origins the\n * auth server trusts, and which URL a frontend is built against. All three were\n * being derived by walking the workspace config for ports — a second mechanism,\n * running beside the graph and answering the same question worse, since it can\n * only ever describe what this repo happens to run.\n *\n * With the surface declared, all three become one edge read backwards: a\n * consumer depends on the API, and *that* is the CORS entry, the trusted origin,\n * and the build-time URL. Nothing enumerates its own callers.\n *\n * ```ts\n * export const api = new RestApi('Api', {\n * authorizers: ['session'],\n * defaultAuthorizer: 'session',\n * });\n * ```\n */\n\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\ttype Dependency,\n\tprovideKey,\n} from '@geekmidas/manifest';\nimport type { Telescope } from '@geekmidas/telescope';\nimport {\n\ttype Authenticator,\n\ttype Declarable,\n\tedgeTo,\n} from './construct-interface';\n\nexport type { Authenticator } from './construct-interface';\n\nimport {\n\ttype BuiltInSecuritySchemeId,\n\tgetSecurityScheme,\n} from './endpoints/Authorizer';\nimport { EndpointFactory } from './endpoints/EndpointFactory';\nimport { envParserFor } from './endpoints/surfaceEnv';\n\n/** The factory a surface builds its endpoints from. */\ntype Endpoints = EndpointFactory<[], '', Logger>;\n\nexport interface RestApiConfig<\n\t// `readonly []` rather than `readonly string[]`: a surface that declares no\n\t// authorizers of its own should narrow `defaultAuthorizer` to the built-ins\n\t// and `'none'`, not widen it back to any string.\n\tTAuthorizers extends readonly string[] = readonly [],\n> {\n\t/**\n\t * The app that serves this surface, relative to the workspace root —\n\t * `'apps/api'`, or `'.'` in a single-app project.\n\t *\n\t * Required. It was inferred from the id (`apps/<kebab-id>`, or the root\n\t * when no such directory existed), which meant a surface's home was\n\t * whatever happened to be on disk. It is where the app's `package.json`\n\t * and base `tsconfig.json` are, and which build serves this surface. It\n\t * does not change discovery: constructs still load from the workspace's\n\t * `constructs` glob.\n\t */\n\tpath: string;\n\t/**\n\t * The label it answers on under a stage's domain — `api` for\n\t * `api.myapp.com`, from `deploy.domains` — and under the project locally,\n\t * `api.myapp.localhost`. Absent, its id kebab-cased.\n\t */\n\tsubdomain?: string;\n\t/**\n\t * CORS tunables. The *origins* are never here — they are read off the\n\t * constructs that declared an edge to this surface, which is the whole point\n\t * of declaring one. These are the parts a graph cannot answer.\n\t */\n\tcors?: {\n\t\tmaxAge?: number;\n\t\tcredentials?: boolean;\n\t\tallowHeaders?: readonly string[];\n\t\texposeHeaders?: readonly string[];\n\t};\n\t/**\n\t * The authorizer names this surface exposes. Names only — what verifies a\n\t * request legitimately differs between local and deployed, so the mechanism\n\t * belongs to the target and never to portable code.\n\t */\n\tauthorizers?: TAuthorizers;\n\t/**\n\t * The authorizer applied to an endpoint that names none.\n\t *\n\t * Required, and `'none'` is a valid answer that has to be typed out. The\n\t * alternative is an API that ships open because a field was left off, which\n\t * is the one default worth refusing to have.\n\t *\n\t * It was called `default`, which said what it was to the type and nothing\n\t * to the reader: next to `authorizers: ['iam']`, a bare `default: 'none'`\n\t * could be defaulting the runtime or the target. The manifest has always\n\t * called it `defaultAuthorizer` and the factory `defaultAuthorizerName`;\n\t * this is the same fact under the same name.\n\t *\n\t * Typed from `authorizers`, so it can only name one this surface exposes —\n\t * plus a built-in scheme, and `'none'`. It was a bare `string`, which let\n\t * `defaultAuthorizer: 'jwtt'` compile and every endpoint on the surface\n\t * default to an authorizer that does not exist.\n\t */\n\tdefaultAuthorizer: TAuthorizers[number] | BuiltInSecuritySchemeId | 'none';\n\t/**\n\t * The logger every endpoint on this surface runs with.\n\t *\n\t * The actual logger, not a path to one. It used to be named in config as\n\t * `logger: './config/logger'`, because the build wrote an import into each\n\t * generated handler and a generator can print a specifier but not an object.\n\t * Endpoints built from the surface carry it, so there is nothing to print\n\t * and nothing to keep in step with a moved file.\n\t */\n\tlogger?: Logger;\n\t/**\n\t * The environment parser every endpoint on this surface runs with.\n\t *\n\t * Same reason as `logger`, and the same field it replaces —\n\t * `envParser: './config/env#envParser'`, a module path with an export name\n\t * appended, parsed by us, checked by nothing.\n\t */\n\tenvParser?: EnvironmentParser<{}>;\n\t/**\n\t * The Telescope instance this surface's entry mounts.\n\t *\n\t * The actual instance, for the reason `logger` is: it used to be named as\n\t * `telescope: './config/telescope#telescope'`, a module path the build\n\t * printed an import for. The entry already imports the surface.\n\t */\n\ttelescope?: Telescope;\n}\n\n/**\n * What is *not* here, and why.\n *\n * `dependsOn`, `database`, `auditor` and `.event(topic, …)` all inject a capability\n * into a handler. Putting one on the surface hands it to every route the\n * surface serves — a health check gets the database because a report needed it\n * — which is the thing least privilege forbids and the reason `.calls()` is not\n * spelled `dependsOn`. They belong to the endpoint, or to a branch —\n * `api.database(db)` — for a group of endpoints that genuinely share them. A\n * branch is a new factory; the surface config stays free of grants.\n *\n * `auth` is not here either: `.auth(construct)` already declares it, and a\n * second way to say the same thing is the duplication this whole model removes.\n *\n * What is left is what the *process* is rather than what a route may reach.\n */\n\nexport class RestApi<\n\tTName extends string = string,\n\tconst TAuthorizers extends readonly string[] = readonly [],\n> implements Declarable<TName>\n{\n\treadonly id: TName;\n\n\t/**\n\t * Declared once and read by both `declare()` and anything that consumes this\n\t * surface, so what the target publishes and what a consumer reads cannot\n\t * drift.\n\t */\n\treadonly keys: {\n\t\turl: string;\n\t\ttrustedOrigins: string;\n\t\tcookieDomain: string;\n\t};\n\n\t/**\n\t * The logger every endpoint on this surface runs with.\n\t *\n\t * Always defined — the surface's own if it was given one, the console\n\t * logger otherwise — so nothing downstream has to decide what to do about a\n\t * missing one. The generated entry reads it from here.\n\t */\n\treadonly logger: Logger;\n\n\t/**\n\t * The environment parser every endpoint on this surface runs with.\n\t *\n\t * Always defined, and normally the default: `process.env` merged with the\n\t * credentials `gkm dev`/`gkm exec` injected. That is what every\n\t * application's own `config/env.ts` was — boilerplate identical in every\n\t * project, and mandatory, which is a poor combination.\n\t */\n\treadonly envParser: EnvironmentParser<{}>;\n\n\t/** The Telescope instance, when this surface was given one. */\n\treadonly telescope?: Telescope;\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly config: RestApiConfig<TAuthorizers>,\n\t\t/** Internal: how `.calls()` carries edges into the copy it returns. */\n\t\tprivate readonly dependencies: readonly Dependency[] = [],\n\t\t/** Internal: how `.auth()` carries the authenticator into its copy. */\n\t\tprivate readonly authenticator?: Authenticator,\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\n\t\tthis.keys = {\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\ttrustedOrigins: provideKey(canonical, 'trustedOrigins'),\n\t\t\tcookieDomain: provideKey(canonical, 'cookieDomain'),\n\t\t};\n\n\t\tthis.logger = config.logger ?? DEFAULT_LOGGER;\n\t\tthis.telescope = config.telescope;\n\t\tthis.envParser = envParserFor(\n\t\t\tconfig.envParser\n\t\t\t\t? { id: canonical, envParser: config.envParser }\n\t\t\t\t: undefined,\n\t\t);\n\n\t\tthis.#endpoints = new EndpointFactory({\n\t\t\tdefaultLogger: this.logger,\n\t\t\t...(config.authorizers\n\t\t\t\t? {\n\t\t\t\t\t\t// With the scheme a built-in name stands for, as the factory's own\n\t\t\t\t\t\t// `.authorizers()` resolves it. Bare names reached the OpenAPI\n\t\t\t\t\t\t// document with no scheme, so a guarded endpoint read as public.\n\t\t\t\t\t\tavailableAuthorizers: config.authorizers.map((name) => {\n\t\t\t\t\t\t\tconst securityScheme = getSecurityScheme(name);\n\t\t\t\t\t\t\treturn securityScheme ? { name, securityScheme } : { name };\n\t\t\t\t\t\t}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t// `'none'` reaches the factory as no default at all, which is what\n\t\t\t// public means to an endpoint that names no authorizer.\n\t\t\t...(config.defaultAuthorizer && config.defaultAuthorizer !== 'none'\n\t\t\t\t? { defaultAuthorizerName: config.defaultAuthorizer }\n\t\t\t\t: {}),\n\t\t\tsurface: {\n\t\t\t\tid: this.id,\n\t\t\t\tenvParser: this.envParser,\n\t\t\t\t...(authenticator ? { auth: authenticator } : {}),\n\t\t\t},\n\t\t});\n\n\t\tconst endpoints = this.#endpoints;\n\t\tthis.get = endpoints.get.bind(endpoints);\n\t\tthis.post = endpoints.post.bind(endpoints);\n\t\tthis.put = endpoints.put.bind(endpoints);\n\t\tthis.patch = endpoints.patch.bind(endpoints);\n\t\tthis.delete = endpoints.delete.bind(endpoints);\n\t\tthis.options = endpoints.options.bind(endpoints);\n\t\tthis.database = endpoints.database.bind(endpoints);\n\t\tthis.session = endpoints.session.bind(endpoints);\n\t\tthis.auditor = endpoints.auditor.bind(endpoints);\n\t\tthis.actor = endpoints.actor.bind(endpoints);\n\t\tthis.authorizer = endpoints.authorizer.bind(endpoints);\n\t\tthis.authorize = endpoints.authorize.bind(endpoints);\n\t\tthis.rls = endpoints.rls.bind(endpoints);\n\t\tthis.route = endpoints.route.bind(endpoints);\n\t}\n\n\t/**\n\t * This surface's endpoint factory, which every method below starts from.\n\t *\n\t * Private: a surface *is* where endpoints are built, so there is no second\n\t * object to reach through first. `api.database(db)` said the same\n\t * thing as `api.database(db)` with one more word to learn.\n\t */\n\treadonly #endpoints: Endpoints;\n\n\t/** `api.get('/users')` — one route on this surface. */\n\treadonly get: Endpoints['get'];\n\treadonly post: Endpoints['post'];\n\treadonly put: Endpoints['put'];\n\treadonly patch: Endpoints['patch'];\n\treadonly delete: Endpoints['delete'];\n\treadonly options: Endpoints['options'];\n\n\t/**\n\t * A branch for a *group* of endpoints that shares what a single route would\n\t * otherwise repeat — a database, an auditor, a publisher, a session:\n\t *\n\t * ```ts\n\t * export const router = api.database(database);\n\t * export const createUser = router.post('/users').handle(({ db }) => …);\n\t * ```\n\t *\n\t * Each returns a new factory and leaves the surface as it was. That is the\n\t * whole difference from putting the same thing in the surface's config: a\n\t * group opts in, where the surface would grant it to every route it serves —\n\t * a health check handed the database because a profile endpoint needed it.\n\t *\n\t * `dependsOn` is deliberately not here. It is per endpoint, and on the\n\t * surface it would read as the API depending on something, which is what\n\t * `.calls()` and `.auth()` say instead.\n\t */\n\treadonly database: Endpoints['database'];\n\treadonly session: Endpoints['session'];\n\treadonly auditor: Endpoints['auditor'];\n\treadonly actor: Endpoints['actor'];\n\treadonly authorizer: Endpoints['authorizer'];\n\treadonly authorize: Endpoints['authorize'];\n\treadonly rls: Endpoints['rls'];\n\treadonly route: Endpoints['route'];\n\n\t/**\n\t * What authenticates this surface.\n\t *\n\t * An edge like `.calls()` — the auth server learns this origin, and the two\n\t * share a cookie domain — but a named one, because *who authenticates me* is\n\t * a different fact from *who I happen to call*, and only one of them decides\n\t * what a request is allowed to be. A surface with two of the first is a\n\t * mistake; two of the second is Tuesday.\n\t *\n\t * It declares the id, and keeps the construct: a session callback reads from\n\t * it as `auth` — `session(async ({ auth }) => auth.getSession())` — which\n\t * calls the construct's `verify(headers)`. That is the one thing every\n\t * provider shares, whether the server is mounted in this process, deployed\n\t * beside it, or an OIDC issuer somebody else runs.\n\t */\n\tauth(construct: Authenticator): RestApi<TName, TAuthorizers> {\n\t\treturn new RestApi<TName, TAuthorizers>(\n\t\t\tthis.id as ConstructName<TName>,\n\t\t\tthis.config,\n\t\t\t[...this.dependencies, edgeTo(construct)],\n\t\t\tconstruct,\n\t\t);\n\t}\n\n\t/**\n\t * Other surfaces this one calls.\n\t *\n\t * **Not `.dependsOn()`, and the difference matters.** An endpoint's\n\t * `.dependsOn()` injects a client into that handler and grants it exactly\n\t * what it named. This grants nothing and injects nothing: it records that\n\t * this API calls another surface, which is what puts this API's origin on\n\t * that surface's trusted-origin list.\n\t *\n\t * Spelling it `dependsOn` would invite the thing least privilege forbids —\n\t * every route on the surface receiving whatever the surface named.\n\t *\n\t * Immutable, like every other builder here.\n\t */\n\tcalls(constructs: readonly Declarable[]): RestApi<TName, TAuthorizers> {\n\t\treturn new RestApi<TName, TAuthorizers>(\n\t\t\tthis.id as ConstructName<TName>,\n\t\t\tthis.config,\n\t\t\t[...this.dependencies, ...constructs.map(edgeTo)],\n\t\t\tthis.authenticator,\n\t\t);\n\t}\n\n\t/**\n\t * One surface node, with its endpoints left to the build.\n\t *\n\t * The three provided keys are one fact about this surface and two about its\n\t * callers. Declaring the caller-derived pair here rather than having the\n\t * target invent them is what makes the contract checkable: a target that\n\t * resolves no origins is caught against this list instead of being noticed\n\t * when a browser is refused in production.\n\t */\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'rest-api',\n\t\t\t\tid: this.id,\n\t\t\t\tpath: this.config.path,\n\t\t\t\t...(this.config.subdomain ? { subdomain: this.config.subdomain } : {}),\n\t\t\t\t...(this.config.telescope ? { telescope: true } : {}),\n\t\t\t\t...(this.config.cors ? { cors: this.config.cors } : {}),\n\t\t\t\t...(this.authenticator ? { auth: this.authenticator.id } : {}),\n\t\t\t\t// Filled by the build, which already generates one handler per\n\t\t\t\t// endpoint and knows the path it wrote it to. A surface that\n\t\t\t\t// enumerates its own routes statically — an auth server's single\n\t\t\t\t// wildcard — puts them here instead.\n\t\t\t\tendpoints: [],\n\t\t\t\t...(this.dependencies.length ? { calls: this.dependencies } : {}),\n\t\t\t\t...(this.config.authorizers?.length\n\t\t\t\t\t? { authorizers: this.config.authorizers }\n\t\t\t\t\t: {}),\n\t\t\t\tdefaultAuthorizer: this.config.defaultAuthorizer,\n\t\t\t\tprovides: [\n\t\t\t\t\tthis.keys.url,\n\t\t\t\t\tthis.keys.trustedOrigins,\n\t\t\t\t\tthis.keys.cookieDomain,\n\t\t\t\t],\n\t\t\t},\n\t\t];\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"rest-api.cjs","names":["canonicalId","provideKey","DEFAULT_LOGGER","envParserFor","EndpointFactory","getSecurityScheme","edgeTo"],"sources":["../src/rest-api.ts"],"sourcesContent":["/**\n * `RestApi` — the application's own HTTP surface, declared.\n *\n * Everything an app serves already existed; what did not exist was a *node* for\n * it. Without one, three things that are properties of the graph had to be\n * written down somewhere else: which origins the API accepts, which origins the\n * auth server trusts, and which URL a frontend is built against. All three were\n * being derived by walking the workspace config for ports — a second mechanism,\n * running beside the graph and answering the same question worse, since it can\n * only ever describe what this repo happens to run.\n *\n * With the surface declared, all three become one edge read backwards: a\n * consumer depends on the API, and *that* is the CORS entry, the trusted origin,\n * and the build-time URL. Nothing enumerates its own callers.\n *\n * ```ts\n * export const api = new RestApi('Api', {\n * authorizers: ['session'],\n * defaultAuthorizer: 'session',\n * });\n * ```\n */\n\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\ttype Dependency,\n\tprovideKey,\n} from '@geekmidas/manifest';\nimport type { Telescope } from '@geekmidas/telescope';\nimport {\n\ttype Authenticator,\n\ttype Declarable,\n\tedgeTo,\n} from './construct-interface';\n\nexport type { Authenticator } from './construct-interface';\n\nimport {\n\ttype BuiltInSecuritySchemeId,\n\tgetSecurityScheme,\n} from './endpoints/Authorizer';\nimport { EndpointFactory } from './endpoints/EndpointFactory';\nimport { envParserFor } from './endpoints/surfaceEnv';\nimport type { Telemetry } from './telemetry';\n\n/** The factory a surface builds its endpoints from. */\ntype Endpoints = EndpointFactory<[], '', Logger>;\n\nexport interface RestApiConfig<\n\t// `readonly []` rather than `readonly string[]`: a surface that declares no\n\t// authorizers of its own should narrow `defaultAuthorizer` to the built-ins\n\t// and `'none'`, not widen it back to any string.\n\tTAuthorizers extends readonly string[] = readonly [],\n> {\n\t/**\n\t * The app that serves this surface, relative to the workspace root —\n\t * `'apps/api'`, or `'.'` in a single-app project.\n\t *\n\t * Required. It was inferred from the id (`apps/<kebab-id>`, or the root\n\t * when no such directory existed), which meant a surface's home was\n\t * whatever happened to be on disk. It is where the app's `package.json`\n\t * and base `tsconfig.json` are, and which build serves this surface. It\n\t * does not change discovery: constructs still load from the workspace's\n\t * `constructs` glob.\n\t */\n\tpath: string;\n\t/**\n\t * The label it answers on under a stage's domain — `api` for\n\t * `api.myapp.com`, from `deploy.domains` — and under the project locally,\n\t * `api.myapp.localhost`. Absent, its id kebab-cased.\n\t */\n\tsubdomain?: string;\n\t/**\n\t * CORS tunables. The *origins* are never here — they are read off the\n\t * constructs that declared an edge to this surface, which is the whole point\n\t * of declaring one. These are the parts a graph cannot answer.\n\t */\n\tcors?: {\n\t\tmaxAge?: number;\n\t\tcredentials?: boolean;\n\t\tallowHeaders?: readonly string[];\n\t\texposeHeaders?: readonly string[];\n\t};\n\t/**\n\t * The authorizer names this surface exposes. Names only — what verifies a\n\t * request legitimately differs between local and deployed, so the mechanism\n\t * belongs to the target and never to portable code.\n\t */\n\tauthorizers?: TAuthorizers;\n\t/**\n\t * The authorizer applied to an endpoint that names none.\n\t *\n\t * Required, and `'none'` is a valid answer that has to be typed out. The\n\t * alternative is an API that ships open because a field was left off, which\n\t * is the one default worth refusing to have.\n\t *\n\t * It was called `default`, which said what it was to the type and nothing\n\t * to the reader: next to `authorizers: ['iam']`, a bare `default: 'none'`\n\t * could be defaulting the runtime or the target. The manifest has always\n\t * called it `defaultAuthorizer` and the factory `defaultAuthorizerName`;\n\t * this is the same fact under the same name.\n\t *\n\t * Typed from `authorizers`, so it can only name one this surface exposes —\n\t * plus a built-in scheme, and `'none'`. It was a bare `string`, which let\n\t * `defaultAuthorizer: 'jwtt'` compile and every endpoint on the surface\n\t * default to an authorizer that does not exist.\n\t */\n\tdefaultAuthorizer: TAuthorizers[number] | BuiltInSecuritySchemeId | 'none';\n\t/**\n\t * The logger every endpoint on this surface runs with.\n\t *\n\t * The actual logger, not a path to one. It used to be named in config as\n\t * `logger: './config/logger'`, because the build wrote an import into each\n\t * generated handler and a generator can print a specifier but not an object.\n\t * Endpoints built from the surface carry it, so there is nothing to print\n\t * and nothing to keep in step with a moved file.\n\t */\n\tlogger?: Logger;\n\t/**\n\t * The environment parser every endpoint on this surface runs with.\n\t *\n\t * Same reason as `logger`, and the same field it replaces —\n\t * `envParser: './config/env#envParser'`, a module path with an export name\n\t * appended, parsed by us, checked by nothing.\n\t */\n\tenvParser?: EnvironmentParser<{}>;\n\t/**\n\t * The Telescope instance this surface's entry mounts.\n\t *\n\t * The actual instance, for the reason `logger` is: it used to be named as\n\t * `telescope: './config/telescope#telescope'`, a module path the build\n\t * printed an import for. The entry already imports the surface.\n\t */\n\ttelescope?: Telescope;\n\t/**\n\t * What this surface's process emits — the `Telemetry` construct, passed\n\t * like the logger: a fact about the process, not a grant to a route.\n\t *\n\t * An edge to the node, so the deploy hands this process the `OTEL_*` keys\n\t * its stage resolves, and the build fails without the OpenTelemetry\n\t * packages. Where the telemetry goes is never here: it is the deploy's,\n\t * per stage.\n\t */\n\ttelemetry?: Telemetry;\n}\n\n/**\n * What is *not* here, and why.\n *\n * `dependsOn`, `database`, `auditor` and `.event(topic, …)` all inject a capability\n * into a handler. Putting one on the surface hands it to every route the\n * surface serves — a health check gets the database because a report needed it\n * — which is the thing least privilege forbids and the reason `.calls()` is not\n * spelled `dependsOn`. They belong to the endpoint, or to a branch —\n * `api.database(db)` — for a group of endpoints that genuinely share them. A\n * branch is a new factory; the surface config stays free of grants.\n *\n * `auth` is not here either: `.auth(construct)` already declares it, and a\n * second way to say the same thing is the duplication this whole model removes.\n *\n * What is left is what the *process* is rather than what a route may reach.\n */\n\nexport class RestApi<\n\tTName extends string = string,\n\tconst TAuthorizers extends readonly string[] = readonly [],\n> implements Declarable<TName>\n{\n\treadonly id: TName;\n\n\t/**\n\t * Declared once and read by both `declare()` and anything that consumes this\n\t * surface, so what the target publishes and what a consumer reads cannot\n\t * drift.\n\t */\n\treadonly keys: {\n\t\turl: string;\n\t\ttrustedOrigins: string;\n\t\tcookieDomain: string;\n\t};\n\n\t/**\n\t * The logger every endpoint on this surface runs with.\n\t *\n\t * Always defined — the surface's own if it was given one, the console\n\t * logger otherwise — so nothing downstream has to decide what to do about a\n\t * missing one. The generated entry reads it from here.\n\t */\n\treadonly logger: Logger;\n\n\t/**\n\t * The environment parser every endpoint on this surface runs with.\n\t *\n\t * Always defined, and normally the default: `process.env` merged with the\n\t * credentials `gkm dev`/`gkm exec` injected. That is what every\n\t * application's own `config/env.ts` was — boilerplate identical in every\n\t * project, and mandatory, which is a poor combination.\n\t */\n\treadonly envParser: EnvironmentParser<{}>;\n\n\t/** The Telescope instance, when this surface was given one. */\n\treadonly telescope?: Telescope;\n\n\t/** What this surface's process emits, when it was given a `Telemetry`. */\n\treadonly telemetry?: Telemetry;\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly config: RestApiConfig<TAuthorizers>,\n\t\t/** Internal: how `.calls()` carries edges into the copy it returns. */\n\t\tprivate readonly dependencies: readonly Dependency[] = [],\n\t\t/** Internal: how `.auth()` carries the authenticator into its copy. */\n\t\tprivate readonly authenticator?: Authenticator,\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\n\t\tthis.keys = {\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\ttrustedOrigins: provideKey(canonical, 'trustedOrigins'),\n\t\t\tcookieDomain: provideKey(canonical, 'cookieDomain'),\n\t\t};\n\n\t\tthis.logger = config.logger ?? DEFAULT_LOGGER;\n\t\tthis.telescope = config.telescope;\n\t\tthis.telemetry = config.telemetry;\n\t\tthis.envParser = envParserFor(\n\t\t\tconfig.envParser\n\t\t\t\t? { id: canonical, envParser: config.envParser }\n\t\t\t\t: undefined,\n\t\t);\n\n\t\tthis.#endpoints = new EndpointFactory({\n\t\t\tdefaultLogger: this.logger,\n\t\t\t...(config.authorizers\n\t\t\t\t? {\n\t\t\t\t\t\t// With the scheme a built-in name stands for, as the factory's own\n\t\t\t\t\t\t// `.authorizers()` resolves it. Bare names reached the OpenAPI\n\t\t\t\t\t\t// document with no scheme, so a guarded endpoint read as public.\n\t\t\t\t\t\tavailableAuthorizers: config.authorizers.map((name) => {\n\t\t\t\t\t\t\tconst securityScheme = getSecurityScheme(name);\n\t\t\t\t\t\t\treturn securityScheme ? { name, securityScheme } : { name };\n\t\t\t\t\t\t}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t// `'none'` reaches the factory as no default at all, which is what\n\t\t\t// public means to an endpoint that names no authorizer.\n\t\t\t...(config.defaultAuthorizer && config.defaultAuthorizer !== 'none'\n\t\t\t\t? { defaultAuthorizerName: config.defaultAuthorizer }\n\t\t\t\t: {}),\n\t\t\tsurface: {\n\t\t\t\tid: this.id,\n\t\t\t\tenvParser: this.envParser,\n\t\t\t\t...(authenticator ? { auth: authenticator } : {}),\n\t\t\t},\n\t\t});\n\n\t\tconst endpoints = this.#endpoints;\n\t\tthis.get = endpoints.get.bind(endpoints);\n\t\tthis.post = endpoints.post.bind(endpoints);\n\t\tthis.put = endpoints.put.bind(endpoints);\n\t\tthis.patch = endpoints.patch.bind(endpoints);\n\t\tthis.delete = endpoints.delete.bind(endpoints);\n\t\tthis.options = endpoints.options.bind(endpoints);\n\t\tthis.database = endpoints.database.bind(endpoints);\n\t\tthis.session = endpoints.session.bind(endpoints);\n\t\tthis.auditor = endpoints.auditor.bind(endpoints);\n\t\tthis.actor = endpoints.actor.bind(endpoints);\n\t\tthis.authorizer = endpoints.authorizer.bind(endpoints);\n\t\tthis.authorize = endpoints.authorize.bind(endpoints);\n\t\tthis.rls = endpoints.rls.bind(endpoints);\n\t\tthis.route = endpoints.route.bind(endpoints);\n\t}\n\n\t/**\n\t * This surface's endpoint factory, which every method below starts from.\n\t *\n\t * Private: a surface *is* where endpoints are built, so there is no second\n\t * object to reach through first. `api.database(db)` said the same\n\t * thing as `api.database(db)` with one more word to learn.\n\t */\n\treadonly #endpoints: Endpoints;\n\n\t/** `api.get('/users')` — one route on this surface. */\n\treadonly get: Endpoints['get'];\n\treadonly post: Endpoints['post'];\n\treadonly put: Endpoints['put'];\n\treadonly patch: Endpoints['patch'];\n\treadonly delete: Endpoints['delete'];\n\treadonly options: Endpoints['options'];\n\n\t/**\n\t * A branch for a *group* of endpoints that shares what a single route would\n\t * otherwise repeat — a database, an auditor, a publisher, a session:\n\t *\n\t * ```ts\n\t * export const router = api.database(database);\n\t * export const createUser = router.post('/users').handle(({ db }) => …);\n\t * ```\n\t *\n\t * Each returns a new factory and leaves the surface as it was. That is the\n\t * whole difference from putting the same thing in the surface's config: a\n\t * group opts in, where the surface would grant it to every route it serves —\n\t * a health check handed the database because a profile endpoint needed it.\n\t *\n\t * `dependsOn` is deliberately not here. It is per endpoint, and on the\n\t * surface it would read as the API depending on something, which is what\n\t * `.calls()` and `.auth()` say instead.\n\t */\n\treadonly database: Endpoints['database'];\n\treadonly session: Endpoints['session'];\n\treadonly auditor: Endpoints['auditor'];\n\treadonly actor: Endpoints['actor'];\n\treadonly authorizer: Endpoints['authorizer'];\n\treadonly authorize: Endpoints['authorize'];\n\treadonly rls: Endpoints['rls'];\n\treadonly route: Endpoints['route'];\n\n\t/**\n\t * What authenticates this surface.\n\t *\n\t * An edge like `.calls()` — the auth server learns this origin, and the two\n\t * share a cookie domain — but a named one, because *who authenticates me* is\n\t * a different fact from *who I happen to call*, and only one of them decides\n\t * what a request is allowed to be. A surface with two of the first is a\n\t * mistake; two of the second is Tuesday.\n\t *\n\t * It declares the id, and keeps the construct: a session callback reads from\n\t * it as `auth` — `session(async ({ auth }) => auth.getSession())` — which\n\t * calls the construct's `verify(headers)`. That is the one thing every\n\t * provider shares, whether the server is mounted in this process, deployed\n\t * beside it, or an OIDC issuer somebody else runs.\n\t */\n\tauth(construct: Authenticator): RestApi<TName, TAuthorizers> {\n\t\treturn new RestApi<TName, TAuthorizers>(\n\t\t\tthis.id as ConstructName<TName>,\n\t\t\tthis.config,\n\t\t\t[...this.dependencies, edgeTo(construct)],\n\t\t\tconstruct,\n\t\t);\n\t}\n\n\t/**\n\t * Other surfaces this one calls.\n\t *\n\t * **Not `.dependsOn()`, and the difference matters.** An endpoint's\n\t * `.dependsOn()` injects a client into that handler and grants it exactly\n\t * what it named. This grants nothing and injects nothing: it records that\n\t * this API calls another surface, which is what puts this API's origin on\n\t * that surface's trusted-origin list.\n\t *\n\t * Spelling it `dependsOn` would invite the thing least privilege forbids —\n\t * every route on the surface receiving whatever the surface named.\n\t *\n\t * Immutable, like every other builder here.\n\t */\n\tcalls(constructs: readonly Declarable[]): RestApi<TName, TAuthorizers> {\n\t\treturn new RestApi<TName, TAuthorizers>(\n\t\t\tthis.id as ConstructName<TName>,\n\t\t\tthis.config,\n\t\t\t[...this.dependencies, ...constructs.map(edgeTo)],\n\t\t\tthis.authenticator,\n\t\t);\n\t}\n\n\t/**\n\t * One surface node, with its endpoints left to the build.\n\t *\n\t * The three provided keys are one fact about this surface and two about its\n\t * callers. Declaring the caller-derived pair here rather than having the\n\t * target invent them is what makes the contract checkable: a target that\n\t * resolves no origins is caught against this list instead of being noticed\n\t * when a browser is refused in production.\n\t */\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'rest-api',\n\t\t\t\tid: this.id,\n\t\t\t\tpath: this.config.path,\n\t\t\t\t...(this.config.subdomain ? { subdomain: this.config.subdomain } : {}),\n\t\t\t\t...(this.config.telescope ? { telescope: true } : {}),\n\t\t\t\t...(this.config.cors ? { cors: this.config.cors } : {}),\n\t\t\t\t...(this.authenticator ? { auth: this.authenticator.id } : {}),\n\t\t\t\t// Filled by the build, which already generates one handler per\n\t\t\t\t// endpoint and knows the path it wrote it to. A surface that\n\t\t\t\t// enumerates its own routes statically — an auth server's single\n\t\t\t\t// wildcard — puts them here instead.\n\t\t\t\tendpoints: [],\n\t\t\t\t...(this.dependencies.length ? { calls: this.dependencies } : {}),\n\t\t\t\t...(this.config.telemetry\n\t\t\t\t\t? { telemetry: this.config.telemetry.id }\n\t\t\t\t\t: {}),\n\t\t\t\t...(this.config.authorizers?.length\n\t\t\t\t\t? { authorizers: this.config.authorizers }\n\t\t\t\t\t: {}),\n\t\t\t\tdefaultAuthorizer: this.config.defaultAuthorizer,\n\t\t\t\tprovides: [\n\t\t\t\t\tthis.keys.url,\n\t\t\t\t\tthis.keys.trustedOrigins,\n\t\t\t\t\tthis.keys.cookieDomain,\n\t\t\t\t],\n\t\t\t},\n\t\t];\n\t}\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { i as Declarable, t as Authenticator } from "./construct-interface-B1AfqRl4.cjs";
2
- import { T as BuiltInSecuritySchemeId } from "./Endpoint-DJZkADYh.cjs";
3
- import { t as EndpointFactory } from "./EndpointFactory-BkYuVg8W.cjs";
2
+ import { n as Telemetry } from "./telemetry-B48RLjyk.cjs";
3
+ import { T as BuiltInSecuritySchemeId } from "./Endpoint-BHdyT3wO.cjs";
4
+ import { t as EndpointFactory } from "./EndpointFactory-COJus3LO.cjs";
4
5
  import { EnvironmentParser } from "@geekmidas/envkit";
5
6
  import { Logger } from "@geekmidas/logger";
6
7
  import { ConstructName, Declaration, Dependency } from "@geekmidas/manifest";
@@ -89,6 +90,16 @@ export interface RestApiConfig<TAuthorizers extends readonly string[] = readonly
89
90
  * printed an import for. The entry already imports the surface.
90
91
  */
91
92
  telescope?: Telescope;
93
+ /**
94
+ * What this surface's process emits — the `Telemetry` construct, passed
95
+ * like the logger: a fact about the process, not a grant to a route.
96
+ *
97
+ * An edge to the node, so the deploy hands this process the `OTEL_*` keys
98
+ * its stage resolves, and the build fails without the OpenTelemetry
99
+ * packages. Where the telemetry goes is never here: it is the deploy's,
100
+ * per stage.
101
+ */
102
+ telemetry?: Telemetry;
92
103
  }
93
104
  /**
94
105
  * What is *not* here, and why.
@@ -143,6 +154,8 @@ export declare class RestApi<TName extends string = string, const TAuthorizers e
143
154
  readonly envParser: EnvironmentParser<{}>;
144
155
  /** The Telescope instance, when this surface was given one. */
145
156
  readonly telescope?: Telescope;
157
+ /** What this surface's process emits, when it was given a `Telemetry`. */
158
+ readonly telemetry?: Telemetry;
146
159
  constructor(id: ConstructName<TName>, config: RestApiConfig<TAuthorizers>,
147
160
  /** Internal: how `.calls()` carries edges into the copy it returns. */
148
161
  dependencies?: readonly Dependency[],
@@ -1 +1 @@
1
- {"version":3,"file":"rest-api.d.cts","names":[],"sources":["../src/rest-api.ts"],"mappings":";;;;;;;;;KAkDK,YAAY,wBAAwB;iBAExB,cAIhB;;;;;;;;;;;;EAaA;;;;;;EAMA;;;;;;EAMA;IACC;IACA;IACA;IACA;;;;;;;EAOD,cAAc;;;;;;;;;;;;;;;;;;;EAmBd,mBAAmB,uBAAuB;;;;;;;;;;EAU1C,SAAS;;;;;;;;EAQT,YAAY;;;;;;;;EAQZ,YAAY;;;;;;;;;;;;;;;;;;qBAoBA,QACZ,qCACM,iEACM,WAAW;;mBAuCL;;mBAEA;;mBAEA;WAzCT,IAAI;;;;;;WAOJ;IACR;IACA;IACA;;;;;;;;;WAUQ,QAAQ;;;;;;;;;WAUR,WAAW;;WAGX,YAAY;EAErB,YACC,IAAI,cAAc,QACD,QAAQ,cAAc,eAEtB;;EAAA,wBAAuB,cAEvB;;EAAA,gBAAgB;;WAwEzB,KAAK;WACL,MAAM;WACN,KAAK;WACL,OAAO;WACP,QAAQ;WACR,SAAS;;;;;;;;;;;;;;;;;;;WAoBT,UAAU;WACV,SAAS;WACT,SAAS;WACT,OAAO;WACP,YAAY;WACZ,WAAW;WACX,KAAK;WACL,OAAO;;;;;;;;;;;;;;;;EAiBhB,KAAK,WAAW,gBAAgB,QAAQ,OAAO;;;;;;;;;;;;;;;EAuB/C,MAAM,qBAAqB,eAAe,QAAQ,OAAO;;;;;;;;;;EAkBzD,WAAW"}
1
+ {"version":3,"file":"rest-api.d.cts","names":[],"sources":["../src/rest-api.ts"],"mappings":";;;;;;;;;;KAmDK,YAAY,wBAAwB;iBAExB,cAIhB;;;;;;;;;;;;EAaA;;;;;;EAMA;;;;;;EAMA;IACC;IACA;IACA;IACA;;;;;;;EAOD,cAAc;;;;;;;;;;;;;;;;;;;EAmBd,mBAAmB,uBAAuB;;;;;;;;;;EAU1C,SAAS;;;;;;;;EAQT,YAAY;;;;;;;;EAQZ,YAAY;;;;;;;;;;EAUZ,YAAY;;;;;;;;;;;;;;;;;;qBAoBA,QACZ,qCACM,iEACM,WAAW;;mBA0CL;;mBAEA;;mBAEA;WA5CT,IAAI;;;;;;WAOJ;IACR;IACA;IACA;;;;;;;;;WAUQ,QAAQ;;;;;;;;;WAUR,WAAW;;WAGX,YAAY;;WAGZ,YAAY;EAErB,YACC,IAAI,cAAc,QACD,QAAQ,cAAc,eAEtB;;EAAA,wBAAuB,cAEvB;;EAAA,gBAAgB;;WAyEzB,KAAK;WACL,MAAM;WACN,KAAK;WACL,OAAO;WACP,QAAQ;WACR,SAAS;;;;;;;;;;;;;;;;;;;WAoBT,UAAU;WACV,SAAS;WACT,SAAS;WACT,OAAO;WACP,YAAY;WACZ,WAAW;WACX,KAAK;WACL,OAAO;;;;;;;;;;;;;;;;EAiBhB,KAAK,WAAW,gBAAgB,QAAQ,OAAO;;;;;;;;;;;;;;;EAuB/C,MAAM,qBAAqB,eAAe,QAAQ,OAAO;;;;;;;;;;EAkBzD,WAAW"}
@@ -1,6 +1,7 @@
1
1
  import { i as Declarable, t as Authenticator } from "./construct-interface-DluK3_da.mjs";
2
- import { T as BuiltInSecuritySchemeId } from "./Endpoint-hcHSFhda.mjs";
3
- import { t as EndpointFactory } from "./EndpointFactory-B5siFRcO.mjs";
2
+ import { n as Telemetry } from "./telemetry-DRtT1_0A.mjs";
3
+ import { T as BuiltInSecuritySchemeId } from "./Endpoint-Dg8TZMYX.mjs";
4
+ import { t as EndpointFactory } from "./EndpointFactory-DzYW1Gb8.mjs";
4
5
  import { ConstructName, Declaration, Dependency } from "@geekmidas/manifest";
5
6
  import { EnvironmentParser } from "@geekmidas/envkit";
6
7
  import { Logger } from "@geekmidas/logger";
@@ -89,6 +90,16 @@ export interface RestApiConfig<TAuthorizers extends readonly string[] = readonly
89
90
  * printed an import for. The entry already imports the surface.
90
91
  */
91
92
  telescope?: Telescope;
93
+ /**
94
+ * What this surface's process emits — the `Telemetry` construct, passed
95
+ * like the logger: a fact about the process, not a grant to a route.
96
+ *
97
+ * An edge to the node, so the deploy hands this process the `OTEL_*` keys
98
+ * its stage resolves, and the build fails without the OpenTelemetry
99
+ * packages. Where the telemetry goes is never here: it is the deploy's,
100
+ * per stage.
101
+ */
102
+ telemetry?: Telemetry;
92
103
  }
93
104
  /**
94
105
  * What is *not* here, and why.
@@ -143,6 +154,8 @@ export declare class RestApi<TName extends string = string, const TAuthorizers e
143
154
  readonly envParser: EnvironmentParser<{}>;
144
155
  /** The Telescope instance, when this surface was given one. */
145
156
  readonly telescope?: Telescope;
157
+ /** What this surface's process emits, when it was given a `Telemetry`. */
158
+ readonly telemetry?: Telemetry;
146
159
  constructor(id: ConstructName<TName>, config: RestApiConfig<TAuthorizers>,
147
160
  /** Internal: how `.calls()` carries edges into the copy it returns. */
148
161
  dependencies?: readonly Dependency[],
@@ -1 +1 @@
1
- {"version":3,"file":"rest-api.d.mts","names":[],"sources":["../src/rest-api.ts"],"mappings":";;;;;;;;;KAkDK,YAAY,wBAAwB;iBAExB,cAIhB;;;;;;;;;;;;EAaA;;;;;;EAMA;;;;;;EAMA;IACC;IACA;IACA;IACA;;;;;;;EAOD,cAAc;;;;;;;;;;;;;;;;;;;EAmBd,mBAAmB,uBAAuB;;;;;;;;;;EAU1C,SAAS;;;;;;;;EAQT,YAAY;;;;;;;;EAQZ,YAAY;;;;;;;;;;;;;;;;;;qBAoBA,QACZ,qCACM,iEACM,WAAW;;mBAuCL;;mBAEA;;mBAEA;WAzCT,IAAI;;;;;;WAOJ;IACR;IACA;IACA;;;;;;;;;WAUQ,QAAQ;;;;;;;;;WAUR,WAAW;;WAGX,YAAY;EAErB,YACC,IAAI,cAAc,QACD,QAAQ,cAAc,eAEtB;;EAAA,wBAAuB,cAEvB;;EAAA,gBAAgB;;WAwEzB,KAAK;WACL,MAAM;WACN,KAAK;WACL,OAAO;WACP,QAAQ;WACR,SAAS;;;;;;;;;;;;;;;;;;;WAoBT,UAAU;WACV,SAAS;WACT,SAAS;WACT,OAAO;WACP,YAAY;WACZ,WAAW;WACX,KAAK;WACL,OAAO;;;;;;;;;;;;;;;;EAiBhB,KAAK,WAAW,gBAAgB,QAAQ,OAAO;;;;;;;;;;;;;;;EAuB/C,MAAM,qBAAqB,eAAe,QAAQ,OAAO;;;;;;;;;;EAkBzD,WAAW"}
1
+ {"version":3,"file":"rest-api.d.mts","names":[],"sources":["../src/rest-api.ts"],"mappings":";;;;;;;;;;KAmDK,YAAY,wBAAwB;iBAExB,cAIhB;;;;;;;;;;;;EAaA;;;;;;EAMA;;;;;;EAMA;IACC;IACA;IACA;IACA;;;;;;;EAOD,cAAc;;;;;;;;;;;;;;;;;;;EAmBd,mBAAmB,uBAAuB;;;;;;;;;;EAU1C,SAAS;;;;;;;;EAQT,YAAY;;;;;;;;EAQZ,YAAY;;;;;;;;;;EAUZ,YAAY;;;;;;;;;;;;;;;;;;qBAoBA,QACZ,qCACM,iEACM,WAAW;;mBA0CL;;mBAEA;;mBAEA;WA5CT,IAAI;;;;;;WAOJ;IACR;IACA;IACA;;;;;;;;;WAUQ,QAAQ;;;;;;;;;WAUR,WAAW;;WAGX,YAAY;;WAGZ,YAAY;EAErB,YACC,IAAI,cAAc,QACD,QAAQ,cAAc,eAEtB;;EAAA,wBAAuB,cAEvB;;EAAA,gBAAgB;;WAyEzB,KAAK;WACL,MAAM;WACN,KAAK;WACL,OAAO;WACP,QAAQ;WACR,SAAS;;;;;;;;;;;;;;;;;;;WAoBT,UAAU;WACV,SAAS;WACT,SAAS;WACT,OAAO;WACP,YAAY;WACZ,WAAW;WACX,KAAK;WACL,OAAO;;;;;;;;;;;;;;;;EAiBhB,KAAK,WAAW,gBAAgB,QAAQ,OAAO;;;;;;;;;;;;;;;EAuB/C,MAAM,qBAAqB,eAAe,QAAQ,OAAO;;;;;;;;;;EAkBzD,WAAW"}
package/dist/rest-api.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { edgeTo } from "./construct-interface.mjs";
2
- import { o as getSecurityScheme, t as EndpointFactory } from "./EndpointFactory-C3QvXMk6.mjs";
2
+ import { o as getSecurityScheme, t as EndpointFactory } from "./EndpointFactory-BDtsrEvJ.mjs";
3
3
  import { n as envParserFor } from "./surfaceEnv-wMT5QzgZ.mjs";
4
4
  import { canonicalId, provideKey } from "@geekmidas/manifest";
5
5
  import { DEFAULT_LOGGER } from "@geekmidas/logger/console";
@@ -50,6 +50,8 @@ var RestApi = class RestApi {
50
50
  envParser;
51
51
  /** The Telescope instance, when this surface was given one. */
52
52
  telescope;
53
+ /** What this surface's process emits, when it was given a `Telemetry`. */
54
+ telemetry;
53
55
  constructor(id, config, dependencies = [], authenticator) {
54
56
  this.config = config;
55
57
  this.dependencies = dependencies;
@@ -63,6 +65,7 @@ var RestApi = class RestApi {
63
65
  };
64
66
  this.logger = config.logger ?? DEFAULT_LOGGER;
65
67
  this.telescope = config.telescope;
68
+ this.telemetry = config.telemetry;
66
69
  this.envParser = envParserFor(config.envParser ? {
67
70
  id: canonical,
68
71
  envParser: config.envParser
@@ -195,6 +198,7 @@ var RestApi = class RestApi {
195
198
  ...this.authenticator ? { auth: this.authenticator.id } : {},
196
199
  endpoints: [],
197
200
  ...this.dependencies.length ? { calls: this.dependencies } : {},
201
+ ...this.config.telemetry ? { telemetry: this.config.telemetry.id } : {},
198
202
  ...this.config.authorizers?.length ? { authorizers: this.config.authorizers } : {},
199
203
  defaultAuthorizer: this.config.defaultAuthorizer,
200
204
  provides: [
@@ -1 +1 @@
1
- {"version":3,"file":"rest-api.mjs","names":[],"sources":["../src/rest-api.ts"],"sourcesContent":["/**\n * `RestApi` — the application's own HTTP surface, declared.\n *\n * Everything an app serves already existed; what did not exist was a *node* for\n * it. Without one, three things that are properties of the graph had to be\n * written down somewhere else: which origins the API accepts, which origins the\n * auth server trusts, and which URL a frontend is built against. All three were\n * being derived by walking the workspace config for ports — a second mechanism,\n * running beside the graph and answering the same question worse, since it can\n * only ever describe what this repo happens to run.\n *\n * With the surface declared, all three become one edge read backwards: a\n * consumer depends on the API, and *that* is the CORS entry, the trusted origin,\n * and the build-time URL. Nothing enumerates its own callers.\n *\n * ```ts\n * export const api = new RestApi('Api', {\n * authorizers: ['session'],\n * defaultAuthorizer: 'session',\n * });\n * ```\n */\n\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\ttype Dependency,\n\tprovideKey,\n} from '@geekmidas/manifest';\nimport type { Telescope } from '@geekmidas/telescope';\nimport {\n\ttype Authenticator,\n\ttype Declarable,\n\tedgeTo,\n} from './construct-interface';\n\nexport type { Authenticator } from './construct-interface';\n\nimport {\n\ttype BuiltInSecuritySchemeId,\n\tgetSecurityScheme,\n} from './endpoints/Authorizer';\nimport { EndpointFactory } from './endpoints/EndpointFactory';\nimport { envParserFor } from './endpoints/surfaceEnv';\n\n/** The factory a surface builds its endpoints from. */\ntype Endpoints = EndpointFactory<[], '', Logger>;\n\nexport interface RestApiConfig<\n\t// `readonly []` rather than `readonly string[]`: a surface that declares no\n\t// authorizers of its own should narrow `defaultAuthorizer` to the built-ins\n\t// and `'none'`, not widen it back to any string.\n\tTAuthorizers extends readonly string[] = readonly [],\n> {\n\t/**\n\t * The app that serves this surface, relative to the workspace root —\n\t * `'apps/api'`, or `'.'` in a single-app project.\n\t *\n\t * Required. It was inferred from the id (`apps/<kebab-id>`, or the root\n\t * when no such directory existed), which meant a surface's home was\n\t * whatever happened to be on disk. It is where the app's `package.json`\n\t * and base `tsconfig.json` are, and which build serves this surface. It\n\t * does not change discovery: constructs still load from the workspace's\n\t * `constructs` glob.\n\t */\n\tpath: string;\n\t/**\n\t * The label it answers on under a stage's domain — `api` for\n\t * `api.myapp.com`, from `deploy.domains` — and under the project locally,\n\t * `api.myapp.localhost`. Absent, its id kebab-cased.\n\t */\n\tsubdomain?: string;\n\t/**\n\t * CORS tunables. The *origins* are never here — they are read off the\n\t * constructs that declared an edge to this surface, which is the whole point\n\t * of declaring one. These are the parts a graph cannot answer.\n\t */\n\tcors?: {\n\t\tmaxAge?: number;\n\t\tcredentials?: boolean;\n\t\tallowHeaders?: readonly string[];\n\t\texposeHeaders?: readonly string[];\n\t};\n\t/**\n\t * The authorizer names this surface exposes. Names only — what verifies a\n\t * request legitimately differs between local and deployed, so the mechanism\n\t * belongs to the target and never to portable code.\n\t */\n\tauthorizers?: TAuthorizers;\n\t/**\n\t * The authorizer applied to an endpoint that names none.\n\t *\n\t * Required, and `'none'` is a valid answer that has to be typed out. The\n\t * alternative is an API that ships open because a field was left off, which\n\t * is the one default worth refusing to have.\n\t *\n\t * It was called `default`, which said what it was to the type and nothing\n\t * to the reader: next to `authorizers: ['iam']`, a bare `default: 'none'`\n\t * could be defaulting the runtime or the target. The manifest has always\n\t * called it `defaultAuthorizer` and the factory `defaultAuthorizerName`;\n\t * this is the same fact under the same name.\n\t *\n\t * Typed from `authorizers`, so it can only name one this surface exposes —\n\t * plus a built-in scheme, and `'none'`. It was a bare `string`, which let\n\t * `defaultAuthorizer: 'jwtt'` compile and every endpoint on the surface\n\t * default to an authorizer that does not exist.\n\t */\n\tdefaultAuthorizer: TAuthorizers[number] | BuiltInSecuritySchemeId | 'none';\n\t/**\n\t * The logger every endpoint on this surface runs with.\n\t *\n\t * The actual logger, not a path to one. It used to be named in config as\n\t * `logger: './config/logger'`, because the build wrote an import into each\n\t * generated handler and a generator can print a specifier but not an object.\n\t * Endpoints built from the surface carry it, so there is nothing to print\n\t * and nothing to keep in step with a moved file.\n\t */\n\tlogger?: Logger;\n\t/**\n\t * The environment parser every endpoint on this surface runs with.\n\t *\n\t * Same reason as `logger`, and the same field it replaces —\n\t * `envParser: './config/env#envParser'`, a module path with an export name\n\t * appended, parsed by us, checked by nothing.\n\t */\n\tenvParser?: EnvironmentParser<{}>;\n\t/**\n\t * The Telescope instance this surface's entry mounts.\n\t *\n\t * The actual instance, for the reason `logger` is: it used to be named as\n\t * `telescope: './config/telescope#telescope'`, a module path the build\n\t * printed an import for. The entry already imports the surface.\n\t */\n\ttelescope?: Telescope;\n}\n\n/**\n * What is *not* here, and why.\n *\n * `dependsOn`, `database`, `auditor` and `.event(topic, …)` all inject a capability\n * into a handler. Putting one on the surface hands it to every route the\n * surface serves — a health check gets the database because a report needed it\n * — which is the thing least privilege forbids and the reason `.calls()` is not\n * spelled `dependsOn`. They belong to the endpoint, or to a branch —\n * `api.database(db)` — for a group of endpoints that genuinely share them. A\n * branch is a new factory; the surface config stays free of grants.\n *\n * `auth` is not here either: `.auth(construct)` already declares it, and a\n * second way to say the same thing is the duplication this whole model removes.\n *\n * What is left is what the *process* is rather than what a route may reach.\n */\n\nexport class RestApi<\n\tTName extends string = string,\n\tconst TAuthorizers extends readonly string[] = readonly [],\n> implements Declarable<TName>\n{\n\treadonly id: TName;\n\n\t/**\n\t * Declared once and read by both `declare()` and anything that consumes this\n\t * surface, so what the target publishes and what a consumer reads cannot\n\t * drift.\n\t */\n\treadonly keys: {\n\t\turl: string;\n\t\ttrustedOrigins: string;\n\t\tcookieDomain: string;\n\t};\n\n\t/**\n\t * The logger every endpoint on this surface runs with.\n\t *\n\t * Always defined — the surface's own if it was given one, the console\n\t * logger otherwise — so nothing downstream has to decide what to do about a\n\t * missing one. The generated entry reads it from here.\n\t */\n\treadonly logger: Logger;\n\n\t/**\n\t * The environment parser every endpoint on this surface runs with.\n\t *\n\t * Always defined, and normally the default: `process.env` merged with the\n\t * credentials `gkm dev`/`gkm exec` injected. That is what every\n\t * application's own `config/env.ts` was — boilerplate identical in every\n\t * project, and mandatory, which is a poor combination.\n\t */\n\treadonly envParser: EnvironmentParser<{}>;\n\n\t/** The Telescope instance, when this surface was given one. */\n\treadonly telescope?: Telescope;\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly config: RestApiConfig<TAuthorizers>,\n\t\t/** Internal: how `.calls()` carries edges into the copy it returns. */\n\t\tprivate readonly dependencies: readonly Dependency[] = [],\n\t\t/** Internal: how `.auth()` carries the authenticator into its copy. */\n\t\tprivate readonly authenticator?: Authenticator,\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\n\t\tthis.keys = {\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\ttrustedOrigins: provideKey(canonical, 'trustedOrigins'),\n\t\t\tcookieDomain: provideKey(canonical, 'cookieDomain'),\n\t\t};\n\n\t\tthis.logger = config.logger ?? DEFAULT_LOGGER;\n\t\tthis.telescope = config.telescope;\n\t\tthis.envParser = envParserFor(\n\t\t\tconfig.envParser\n\t\t\t\t? { id: canonical, envParser: config.envParser }\n\t\t\t\t: undefined,\n\t\t);\n\n\t\tthis.#endpoints = new EndpointFactory({\n\t\t\tdefaultLogger: this.logger,\n\t\t\t...(config.authorizers\n\t\t\t\t? {\n\t\t\t\t\t\t// With the scheme a built-in name stands for, as the factory's own\n\t\t\t\t\t\t// `.authorizers()` resolves it. Bare names reached the OpenAPI\n\t\t\t\t\t\t// document with no scheme, so a guarded endpoint read as public.\n\t\t\t\t\t\tavailableAuthorizers: config.authorizers.map((name) => {\n\t\t\t\t\t\t\tconst securityScheme = getSecurityScheme(name);\n\t\t\t\t\t\t\treturn securityScheme ? { name, securityScheme } : { name };\n\t\t\t\t\t\t}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t// `'none'` reaches the factory as no default at all, which is what\n\t\t\t// public means to an endpoint that names no authorizer.\n\t\t\t...(config.defaultAuthorizer && config.defaultAuthorizer !== 'none'\n\t\t\t\t? { defaultAuthorizerName: config.defaultAuthorizer }\n\t\t\t\t: {}),\n\t\t\tsurface: {\n\t\t\t\tid: this.id,\n\t\t\t\tenvParser: this.envParser,\n\t\t\t\t...(authenticator ? { auth: authenticator } : {}),\n\t\t\t},\n\t\t});\n\n\t\tconst endpoints = this.#endpoints;\n\t\tthis.get = endpoints.get.bind(endpoints);\n\t\tthis.post = endpoints.post.bind(endpoints);\n\t\tthis.put = endpoints.put.bind(endpoints);\n\t\tthis.patch = endpoints.patch.bind(endpoints);\n\t\tthis.delete = endpoints.delete.bind(endpoints);\n\t\tthis.options = endpoints.options.bind(endpoints);\n\t\tthis.database = endpoints.database.bind(endpoints);\n\t\tthis.session = endpoints.session.bind(endpoints);\n\t\tthis.auditor = endpoints.auditor.bind(endpoints);\n\t\tthis.actor = endpoints.actor.bind(endpoints);\n\t\tthis.authorizer = endpoints.authorizer.bind(endpoints);\n\t\tthis.authorize = endpoints.authorize.bind(endpoints);\n\t\tthis.rls = endpoints.rls.bind(endpoints);\n\t\tthis.route = endpoints.route.bind(endpoints);\n\t}\n\n\t/**\n\t * This surface's endpoint factory, which every method below starts from.\n\t *\n\t * Private: a surface *is* where endpoints are built, so there is no second\n\t * object to reach through first. `api.database(db)` said the same\n\t * thing as `api.database(db)` with one more word to learn.\n\t */\n\treadonly #endpoints: Endpoints;\n\n\t/** `api.get('/users')` — one route on this surface. */\n\treadonly get: Endpoints['get'];\n\treadonly post: Endpoints['post'];\n\treadonly put: Endpoints['put'];\n\treadonly patch: Endpoints['patch'];\n\treadonly delete: Endpoints['delete'];\n\treadonly options: Endpoints['options'];\n\n\t/**\n\t * A branch for a *group* of endpoints that shares what a single route would\n\t * otherwise repeat — a database, an auditor, a publisher, a session:\n\t *\n\t * ```ts\n\t * export const router = api.database(database);\n\t * export const createUser = router.post('/users').handle(({ db }) => …);\n\t * ```\n\t *\n\t * Each returns a new factory and leaves the surface as it was. That is the\n\t * whole difference from putting the same thing in the surface's config: a\n\t * group opts in, where the surface would grant it to every route it serves —\n\t * a health check handed the database because a profile endpoint needed it.\n\t *\n\t * `dependsOn` is deliberately not here. It is per endpoint, and on the\n\t * surface it would read as the API depending on something, which is what\n\t * `.calls()` and `.auth()` say instead.\n\t */\n\treadonly database: Endpoints['database'];\n\treadonly session: Endpoints['session'];\n\treadonly auditor: Endpoints['auditor'];\n\treadonly actor: Endpoints['actor'];\n\treadonly authorizer: Endpoints['authorizer'];\n\treadonly authorize: Endpoints['authorize'];\n\treadonly rls: Endpoints['rls'];\n\treadonly route: Endpoints['route'];\n\n\t/**\n\t * What authenticates this surface.\n\t *\n\t * An edge like `.calls()` — the auth server learns this origin, and the two\n\t * share a cookie domain — but a named one, because *who authenticates me* is\n\t * a different fact from *who I happen to call*, and only one of them decides\n\t * what a request is allowed to be. A surface with two of the first is a\n\t * mistake; two of the second is Tuesday.\n\t *\n\t * It declares the id, and keeps the construct: a session callback reads from\n\t * it as `auth` — `session(async ({ auth }) => auth.getSession())` — which\n\t * calls the construct's `verify(headers)`. That is the one thing every\n\t * provider shares, whether the server is mounted in this process, deployed\n\t * beside it, or an OIDC issuer somebody else runs.\n\t */\n\tauth(construct: Authenticator): RestApi<TName, TAuthorizers> {\n\t\treturn new RestApi<TName, TAuthorizers>(\n\t\t\tthis.id as ConstructName<TName>,\n\t\t\tthis.config,\n\t\t\t[...this.dependencies, edgeTo(construct)],\n\t\t\tconstruct,\n\t\t);\n\t}\n\n\t/**\n\t * Other surfaces this one calls.\n\t *\n\t * **Not `.dependsOn()`, and the difference matters.** An endpoint's\n\t * `.dependsOn()` injects a client into that handler and grants it exactly\n\t * what it named. This grants nothing and injects nothing: it records that\n\t * this API calls another surface, which is what puts this API's origin on\n\t * that surface's trusted-origin list.\n\t *\n\t * Spelling it `dependsOn` would invite the thing least privilege forbids —\n\t * every route on the surface receiving whatever the surface named.\n\t *\n\t * Immutable, like every other builder here.\n\t */\n\tcalls(constructs: readonly Declarable[]): RestApi<TName, TAuthorizers> {\n\t\treturn new RestApi<TName, TAuthorizers>(\n\t\t\tthis.id as ConstructName<TName>,\n\t\t\tthis.config,\n\t\t\t[...this.dependencies, ...constructs.map(edgeTo)],\n\t\t\tthis.authenticator,\n\t\t);\n\t}\n\n\t/**\n\t * One surface node, with its endpoints left to the build.\n\t *\n\t * The three provided keys are one fact about this surface and two about its\n\t * callers. Declaring the caller-derived pair here rather than having the\n\t * target invent them is what makes the contract checkable: a target that\n\t * resolves no origins is caught against this list instead of being noticed\n\t * when a browser is refused in production.\n\t */\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'rest-api',\n\t\t\t\tid: this.id,\n\t\t\t\tpath: this.config.path,\n\t\t\t\t...(this.config.subdomain ? { subdomain: this.config.subdomain } : {}),\n\t\t\t\t...(this.config.telescope ? { telescope: true } : {}),\n\t\t\t\t...(this.config.cors ? { cors: this.config.cors } : {}),\n\t\t\t\t...(this.authenticator ? { auth: this.authenticator.id } : {}),\n\t\t\t\t// Filled by the build, which already generates one handler per\n\t\t\t\t// endpoint and knows the path it wrote it to. A surface that\n\t\t\t\t// enumerates its own routes statically — an auth server's single\n\t\t\t\t// wildcard — puts them here instead.\n\t\t\t\tendpoints: [],\n\t\t\t\t...(this.dependencies.length ? { calls: this.dependencies } : {}),\n\t\t\t\t...(this.config.authorizers?.length\n\t\t\t\t\t? { authorizers: this.config.authorizers }\n\t\t\t\t\t: {}),\n\t\t\t\tdefaultAuthorizer: this.config.defaultAuthorizer,\n\t\t\t\tprovides: [\n\t\t\t\t\tthis.keys.url,\n\t\t\t\t\tthis.keys.trustedOrigins,\n\t\t\t\t\tthis.keys.cookieDomain,\n\t\t\t\t],\n\t\t\t},\n\t\t];\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"rest-api.mjs","names":[],"sources":["../src/rest-api.ts"],"sourcesContent":["/**\n * `RestApi` — the application's own HTTP surface, declared.\n *\n * Everything an app serves already existed; what did not exist was a *node* for\n * it. Without one, three things that are properties of the graph had to be\n * written down somewhere else: which origins the API accepts, which origins the\n * auth server trusts, and which URL a frontend is built against. All three were\n * being derived by walking the workspace config for ports — a second mechanism,\n * running beside the graph and answering the same question worse, since it can\n * only ever describe what this repo happens to run.\n *\n * With the surface declared, all three become one edge read backwards: a\n * consumer depends on the API, and *that* is the CORS entry, the trusted origin,\n * and the build-time URL. Nothing enumerates its own callers.\n *\n * ```ts\n * export const api = new RestApi('Api', {\n * authorizers: ['session'],\n * defaultAuthorizer: 'session',\n * });\n * ```\n */\n\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\ttype Dependency,\n\tprovideKey,\n} from '@geekmidas/manifest';\nimport type { Telescope } from '@geekmidas/telescope';\nimport {\n\ttype Authenticator,\n\ttype Declarable,\n\tedgeTo,\n} from './construct-interface';\n\nexport type { Authenticator } from './construct-interface';\n\nimport {\n\ttype BuiltInSecuritySchemeId,\n\tgetSecurityScheme,\n} from './endpoints/Authorizer';\nimport { EndpointFactory } from './endpoints/EndpointFactory';\nimport { envParserFor } from './endpoints/surfaceEnv';\nimport type { Telemetry } from './telemetry';\n\n/** The factory a surface builds its endpoints from. */\ntype Endpoints = EndpointFactory<[], '', Logger>;\n\nexport interface RestApiConfig<\n\t// `readonly []` rather than `readonly string[]`: a surface that declares no\n\t// authorizers of its own should narrow `defaultAuthorizer` to the built-ins\n\t// and `'none'`, not widen it back to any string.\n\tTAuthorizers extends readonly string[] = readonly [],\n> {\n\t/**\n\t * The app that serves this surface, relative to the workspace root —\n\t * `'apps/api'`, or `'.'` in a single-app project.\n\t *\n\t * Required. It was inferred from the id (`apps/<kebab-id>`, or the root\n\t * when no such directory existed), which meant a surface's home was\n\t * whatever happened to be on disk. It is where the app's `package.json`\n\t * and base `tsconfig.json` are, and which build serves this surface. It\n\t * does not change discovery: constructs still load from the workspace's\n\t * `constructs` glob.\n\t */\n\tpath: string;\n\t/**\n\t * The label it answers on under a stage's domain — `api` for\n\t * `api.myapp.com`, from `deploy.domains` — and under the project locally,\n\t * `api.myapp.localhost`. Absent, its id kebab-cased.\n\t */\n\tsubdomain?: string;\n\t/**\n\t * CORS tunables. The *origins* are never here — they are read off the\n\t * constructs that declared an edge to this surface, which is the whole point\n\t * of declaring one. These are the parts a graph cannot answer.\n\t */\n\tcors?: {\n\t\tmaxAge?: number;\n\t\tcredentials?: boolean;\n\t\tallowHeaders?: readonly string[];\n\t\texposeHeaders?: readonly string[];\n\t};\n\t/**\n\t * The authorizer names this surface exposes. Names only — what verifies a\n\t * request legitimately differs between local and deployed, so the mechanism\n\t * belongs to the target and never to portable code.\n\t */\n\tauthorizers?: TAuthorizers;\n\t/**\n\t * The authorizer applied to an endpoint that names none.\n\t *\n\t * Required, and `'none'` is a valid answer that has to be typed out. The\n\t * alternative is an API that ships open because a field was left off, which\n\t * is the one default worth refusing to have.\n\t *\n\t * It was called `default`, which said what it was to the type and nothing\n\t * to the reader: next to `authorizers: ['iam']`, a bare `default: 'none'`\n\t * could be defaulting the runtime or the target. The manifest has always\n\t * called it `defaultAuthorizer` and the factory `defaultAuthorizerName`;\n\t * this is the same fact under the same name.\n\t *\n\t * Typed from `authorizers`, so it can only name one this surface exposes —\n\t * plus a built-in scheme, and `'none'`. It was a bare `string`, which let\n\t * `defaultAuthorizer: 'jwtt'` compile and every endpoint on the surface\n\t * default to an authorizer that does not exist.\n\t */\n\tdefaultAuthorizer: TAuthorizers[number] | BuiltInSecuritySchemeId | 'none';\n\t/**\n\t * The logger every endpoint on this surface runs with.\n\t *\n\t * The actual logger, not a path to one. It used to be named in config as\n\t * `logger: './config/logger'`, because the build wrote an import into each\n\t * generated handler and a generator can print a specifier but not an object.\n\t * Endpoints built from the surface carry it, so there is nothing to print\n\t * and nothing to keep in step with a moved file.\n\t */\n\tlogger?: Logger;\n\t/**\n\t * The environment parser every endpoint on this surface runs with.\n\t *\n\t * Same reason as `logger`, and the same field it replaces —\n\t * `envParser: './config/env#envParser'`, a module path with an export name\n\t * appended, parsed by us, checked by nothing.\n\t */\n\tenvParser?: EnvironmentParser<{}>;\n\t/**\n\t * The Telescope instance this surface's entry mounts.\n\t *\n\t * The actual instance, for the reason `logger` is: it used to be named as\n\t * `telescope: './config/telescope#telescope'`, a module path the build\n\t * printed an import for. The entry already imports the surface.\n\t */\n\ttelescope?: Telescope;\n\t/**\n\t * What this surface's process emits — the `Telemetry` construct, passed\n\t * like the logger: a fact about the process, not a grant to a route.\n\t *\n\t * An edge to the node, so the deploy hands this process the `OTEL_*` keys\n\t * its stage resolves, and the build fails without the OpenTelemetry\n\t * packages. Where the telemetry goes is never here: it is the deploy's,\n\t * per stage.\n\t */\n\ttelemetry?: Telemetry;\n}\n\n/**\n * What is *not* here, and why.\n *\n * `dependsOn`, `database`, `auditor` and `.event(topic, …)` all inject a capability\n * into a handler. Putting one on the surface hands it to every route the\n * surface serves — a health check gets the database because a report needed it\n * — which is the thing least privilege forbids and the reason `.calls()` is not\n * spelled `dependsOn`. They belong to the endpoint, or to a branch —\n * `api.database(db)` — for a group of endpoints that genuinely share them. A\n * branch is a new factory; the surface config stays free of grants.\n *\n * `auth` is not here either: `.auth(construct)` already declares it, and a\n * second way to say the same thing is the duplication this whole model removes.\n *\n * What is left is what the *process* is rather than what a route may reach.\n */\n\nexport class RestApi<\n\tTName extends string = string,\n\tconst TAuthorizers extends readonly string[] = readonly [],\n> implements Declarable<TName>\n{\n\treadonly id: TName;\n\n\t/**\n\t * Declared once and read by both `declare()` and anything that consumes this\n\t * surface, so what the target publishes and what a consumer reads cannot\n\t * drift.\n\t */\n\treadonly keys: {\n\t\turl: string;\n\t\ttrustedOrigins: string;\n\t\tcookieDomain: string;\n\t};\n\n\t/**\n\t * The logger every endpoint on this surface runs with.\n\t *\n\t * Always defined — the surface's own if it was given one, the console\n\t * logger otherwise — so nothing downstream has to decide what to do about a\n\t * missing one. The generated entry reads it from here.\n\t */\n\treadonly logger: Logger;\n\n\t/**\n\t * The environment parser every endpoint on this surface runs with.\n\t *\n\t * Always defined, and normally the default: `process.env` merged with the\n\t * credentials `gkm dev`/`gkm exec` injected. That is what every\n\t * application's own `config/env.ts` was — boilerplate identical in every\n\t * project, and mandatory, which is a poor combination.\n\t */\n\treadonly envParser: EnvironmentParser<{}>;\n\n\t/** The Telescope instance, when this surface was given one. */\n\treadonly telescope?: Telescope;\n\n\t/** What this surface's process emits, when it was given a `Telemetry`. */\n\treadonly telemetry?: Telemetry;\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly config: RestApiConfig<TAuthorizers>,\n\t\t/** Internal: how `.calls()` carries edges into the copy it returns. */\n\t\tprivate readonly dependencies: readonly Dependency[] = [],\n\t\t/** Internal: how `.auth()` carries the authenticator into its copy. */\n\t\tprivate readonly authenticator?: Authenticator,\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\n\t\tthis.keys = {\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\ttrustedOrigins: provideKey(canonical, 'trustedOrigins'),\n\t\t\tcookieDomain: provideKey(canonical, 'cookieDomain'),\n\t\t};\n\n\t\tthis.logger = config.logger ?? DEFAULT_LOGGER;\n\t\tthis.telescope = config.telescope;\n\t\tthis.telemetry = config.telemetry;\n\t\tthis.envParser = envParserFor(\n\t\t\tconfig.envParser\n\t\t\t\t? { id: canonical, envParser: config.envParser }\n\t\t\t\t: undefined,\n\t\t);\n\n\t\tthis.#endpoints = new EndpointFactory({\n\t\t\tdefaultLogger: this.logger,\n\t\t\t...(config.authorizers\n\t\t\t\t? {\n\t\t\t\t\t\t// With the scheme a built-in name stands for, as the factory's own\n\t\t\t\t\t\t// `.authorizers()` resolves it. Bare names reached the OpenAPI\n\t\t\t\t\t\t// document with no scheme, so a guarded endpoint read as public.\n\t\t\t\t\t\tavailableAuthorizers: config.authorizers.map((name) => {\n\t\t\t\t\t\t\tconst securityScheme = getSecurityScheme(name);\n\t\t\t\t\t\t\treturn securityScheme ? { name, securityScheme } : { name };\n\t\t\t\t\t\t}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t// `'none'` reaches the factory as no default at all, which is what\n\t\t\t// public means to an endpoint that names no authorizer.\n\t\t\t...(config.defaultAuthorizer && config.defaultAuthorizer !== 'none'\n\t\t\t\t? { defaultAuthorizerName: config.defaultAuthorizer }\n\t\t\t\t: {}),\n\t\t\tsurface: {\n\t\t\t\tid: this.id,\n\t\t\t\tenvParser: this.envParser,\n\t\t\t\t...(authenticator ? { auth: authenticator } : {}),\n\t\t\t},\n\t\t});\n\n\t\tconst endpoints = this.#endpoints;\n\t\tthis.get = endpoints.get.bind(endpoints);\n\t\tthis.post = endpoints.post.bind(endpoints);\n\t\tthis.put = endpoints.put.bind(endpoints);\n\t\tthis.patch = endpoints.patch.bind(endpoints);\n\t\tthis.delete = endpoints.delete.bind(endpoints);\n\t\tthis.options = endpoints.options.bind(endpoints);\n\t\tthis.database = endpoints.database.bind(endpoints);\n\t\tthis.session = endpoints.session.bind(endpoints);\n\t\tthis.auditor = endpoints.auditor.bind(endpoints);\n\t\tthis.actor = endpoints.actor.bind(endpoints);\n\t\tthis.authorizer = endpoints.authorizer.bind(endpoints);\n\t\tthis.authorize = endpoints.authorize.bind(endpoints);\n\t\tthis.rls = endpoints.rls.bind(endpoints);\n\t\tthis.route = endpoints.route.bind(endpoints);\n\t}\n\n\t/**\n\t * This surface's endpoint factory, which every method below starts from.\n\t *\n\t * Private: a surface *is* where endpoints are built, so there is no second\n\t * object to reach through first. `api.database(db)` said the same\n\t * thing as `api.database(db)` with one more word to learn.\n\t */\n\treadonly #endpoints: Endpoints;\n\n\t/** `api.get('/users')` — one route on this surface. */\n\treadonly get: Endpoints['get'];\n\treadonly post: Endpoints['post'];\n\treadonly put: Endpoints['put'];\n\treadonly patch: Endpoints['patch'];\n\treadonly delete: Endpoints['delete'];\n\treadonly options: Endpoints['options'];\n\n\t/**\n\t * A branch for a *group* of endpoints that shares what a single route would\n\t * otherwise repeat — a database, an auditor, a publisher, a session:\n\t *\n\t * ```ts\n\t * export const router = api.database(database);\n\t * export const createUser = router.post('/users').handle(({ db }) => …);\n\t * ```\n\t *\n\t * Each returns a new factory and leaves the surface as it was. That is the\n\t * whole difference from putting the same thing in the surface's config: a\n\t * group opts in, where the surface would grant it to every route it serves —\n\t * a health check handed the database because a profile endpoint needed it.\n\t *\n\t * `dependsOn` is deliberately not here. It is per endpoint, and on the\n\t * surface it would read as the API depending on something, which is what\n\t * `.calls()` and `.auth()` say instead.\n\t */\n\treadonly database: Endpoints['database'];\n\treadonly session: Endpoints['session'];\n\treadonly auditor: Endpoints['auditor'];\n\treadonly actor: Endpoints['actor'];\n\treadonly authorizer: Endpoints['authorizer'];\n\treadonly authorize: Endpoints['authorize'];\n\treadonly rls: Endpoints['rls'];\n\treadonly route: Endpoints['route'];\n\n\t/**\n\t * What authenticates this surface.\n\t *\n\t * An edge like `.calls()` — the auth server learns this origin, and the two\n\t * share a cookie domain — but a named one, because *who authenticates me* is\n\t * a different fact from *who I happen to call*, and only one of them decides\n\t * what a request is allowed to be. A surface with two of the first is a\n\t * mistake; two of the second is Tuesday.\n\t *\n\t * It declares the id, and keeps the construct: a session callback reads from\n\t * it as `auth` — `session(async ({ auth }) => auth.getSession())` — which\n\t * calls the construct's `verify(headers)`. That is the one thing every\n\t * provider shares, whether the server is mounted in this process, deployed\n\t * beside it, or an OIDC issuer somebody else runs.\n\t */\n\tauth(construct: Authenticator): RestApi<TName, TAuthorizers> {\n\t\treturn new RestApi<TName, TAuthorizers>(\n\t\t\tthis.id as ConstructName<TName>,\n\t\t\tthis.config,\n\t\t\t[...this.dependencies, edgeTo(construct)],\n\t\t\tconstruct,\n\t\t);\n\t}\n\n\t/**\n\t * Other surfaces this one calls.\n\t *\n\t * **Not `.dependsOn()`, and the difference matters.** An endpoint's\n\t * `.dependsOn()` injects a client into that handler and grants it exactly\n\t * what it named. This grants nothing and injects nothing: it records that\n\t * this API calls another surface, which is what puts this API's origin on\n\t * that surface's trusted-origin list.\n\t *\n\t * Spelling it `dependsOn` would invite the thing least privilege forbids —\n\t * every route on the surface receiving whatever the surface named.\n\t *\n\t * Immutable, like every other builder here.\n\t */\n\tcalls(constructs: readonly Declarable[]): RestApi<TName, TAuthorizers> {\n\t\treturn new RestApi<TName, TAuthorizers>(\n\t\t\tthis.id as ConstructName<TName>,\n\t\t\tthis.config,\n\t\t\t[...this.dependencies, ...constructs.map(edgeTo)],\n\t\t\tthis.authenticator,\n\t\t);\n\t}\n\n\t/**\n\t * One surface node, with its endpoints left to the build.\n\t *\n\t * The three provided keys are one fact about this surface and two about its\n\t * callers. Declaring the caller-derived pair here rather than having the\n\t * target invent them is what makes the contract checkable: a target that\n\t * resolves no origins is caught against this list instead of being noticed\n\t * when a browser is refused in production.\n\t */\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'rest-api',\n\t\t\t\tid: this.id,\n\t\t\t\tpath: this.config.path,\n\t\t\t\t...(this.config.subdomain ? { subdomain: this.config.subdomain } : {}),\n\t\t\t\t...(this.config.telescope ? { telescope: true } : {}),\n\t\t\t\t...(this.config.cors ? { cors: this.config.cors } : {}),\n\t\t\t\t...(this.authenticator ? { auth: this.authenticator.id } : {}),\n\t\t\t\t// Filled by the build, which already generates one handler per\n\t\t\t\t// endpoint and knows the path it wrote it to. A surface that\n\t\t\t\t// enumerates its own routes statically — an auth server's single\n\t\t\t\t// wildcard — puts them here instead.\n\t\t\t\tendpoints: [],\n\t\t\t\t...(this.dependencies.length ? { calls: this.dependencies } : {}),\n\t\t\t\t...(this.config.telemetry\n\t\t\t\t\t? { telemetry: this.config.telemetry.id }\n\t\t\t\t\t: {}),\n\t\t\t\t...(this.config.authorizers?.length\n\t\t\t\t\t? { authorizers: this.config.authorizers }\n\t\t\t\t\t: {}),\n\t\t\t\tdefaultAuthorizer: this.config.defaultAuthorizer,\n\t\t\t\tprovides: [\n\t\t\t\t\tthis.keys.url,\n\t\t\t\t\tthis.keys.trustedOrigins,\n\t\t\t\t\tthis.keys.cookieDomain,\n\t\t\t\t],\n\t\t\t},\n\t\t];\n\t}\n}\n"],"mappings":"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"}
package/dist/site.cjs CHANGED
@@ -63,6 +63,7 @@ var StaticSite = class StaticSite {
63
63
  },
64
64
  ...this.config.root ? { root: true } : {},
65
65
  dependencies: this.dependencies,
66
+ ...this.config.telemetry ? { telemetry: this.config.telemetry.id } : {},
66
67
  provides: [this.keys.url]
67
68
  }];
68
69
  }
package/dist/site.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"site.cjs","names":["canonicalId","provideKey","edgeTo"],"sources":["../src/site.ts"],"sourcesContent":["/**\n * `StaticSite` — a frontend, declared like anything else.\n *\n * The point is not that a site needs provisioning; it is that a site is the\n * *consumer* whose edges make four hand-maintained lists derivable. Declaring it\n * is what turns \"which origins does the API allow\" from something a person keeps\n * in step into something the graph already knows.\n *\n * ```ts\n * export const console = new StaticSite('Console', { path: 'apps/console' })\n * .dependsOn([api, auth]);\n * ```\n *\n * It has no `.service`. A site is not a process that registers clients — it is\n * built, and the values it needs are inlined or fetched by its own framework —\n * so the edge is {@link Declarable} rather than {@link Consumable}, and the\n * asymmetry is the honest one: a site consumes addresses, not connections.\n *\n * Only the static variant ships. SSR deploy shapes are unstable and each one is\n * its own maintenance, so they follow rather than arrive together. A mobile app\n * is not a site — it is reached by a scheme, not a URL — and is its own\n * construct: see `MobileApp`.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\ttype Dependency,\n\tprovideKey,\n\ttype SiteDeclaration,\n} from '@geekmidas/manifest';\nimport { type Declarable, edgeTo } from './construct-interface';\n\n/**\n * Where a site's source lives, and the little else a layout can differ by.\n *\n * Only `path` is required: `new StaticSite('Web', { path: 'apps/web' })` is a\n * complete site.\n */\nexport interface StaticSiteConfig {\n\t/**\n\t * Where its source lives, relative to the workspace root — `'apps/web'`.\n\t *\n\t * Required, the same field `RestApi` and `BetterAuth` take. It was\n\t * inferred from the id, which made a site's home whatever was on disk.\n\t */\n\tpath: string;\n\t/**\n\t * The label it answers on under a stage's domain — `api` for\n\t * `api.myapp.com`, from `deploy.domains` — and under the project locally,\n\t * `api.myapp.localhost`. Absent, its id kebab-cased.\n\t */\n\tsubdomain?: string;\n\t/**\n\t * The port it answers on locally.\n\t *\n\t * Normally omitted: ports are assigned in a stable order, so adding a site\n\t * does not renumber the ones already running.\n\t */\n\tport?: number;\n\t/**\n\t * Modules to import when sniffing which env vars this site reads.\n\t *\n\t * A frontend's values are inlined at build time, so the build has to know\n\t * which ones it reads before it reads them.\n\t */\n\tconfig?: { client?: string; server?: string };\n\t/**\n\t * Whether the base domain points at this site.\n\t *\n\t * A project with one site needs this no more than a project with one\n\t * database needs to say which database. It matters when there are several\n\t * and none is called `web`: the base domain then belongs to whichever site\n\t * says so, rather than to whichever file the glob happened to reach first.\n\t */\n\troot?: boolean;\n\t/**\n\t * Which framework builds it.\n\t *\n\t * It selects how values are delivered — `VITE_`, `NEXT_PUBLIC_`, a fetched\n\t * `config.json` — and never which values there are. One neutral name from the\n\t * construct, one serialisation per framework, the same rule as everywhere\n\t * else.\n\t */\n\tvariant?: SiteDeclaration['variant'];\n}\n\nexport class StaticSite<TName extends string = string>\n\timplements Declarable<TName>\n{\n\treadonly id: TName;\n\treadonly keys: { url: string };\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly config: StaticSiteConfig,\n\t\t/**\n\t\t * Internal: how `.dependsOn()` carries edges into the copy it returns.\n\t\t * Written as a parameter rather than a mutable field so the builder can\n\t\t * stay immutable without a clone method, the same shape\n\t\t * `KyselyDatabase` uses for its derived forms.\n\t\t */\n\t\tprivate readonly dependencies: readonly Dependency[] = [],\n\t) {\n\t\tthis.id = canonicalId(id as string) as TName;\n\t\tthis.keys = { url: provideKey(this.id, 'url') };\n\t}\n\n\t/**\n\t * What this site calls.\n\t *\n\t * Immutable, like every other builder here: it returns a new site rather than\n\t * mutating this one, so a module that exports both a base and a variant of it\n\t * cannot have the second silently change the first.\n\t *\n\t * Takes {@link Declarable} rather than {@link Consumable} because a site's\n\t * edge is a build-time link and not a service injection — requiring a client\n\t * would exclude exactly the surfaces a frontend actually depends on.\n\t */\n\tdependsOn(constructs: readonly Declarable[]): StaticSite<TName> {\n\t\treturn new StaticSite<TName>(this.id as ConstructName<TName>, this.config, [\n\t\t\t...this.dependencies,\n\t\t\t...constructs.map(edgeTo),\n\t\t]);\n\t}\n\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'site',\n\t\t\t\tid: this.id,\n\t\t\t\tvariant: this.config.variant ?? 'static',\n\t\t\t\t...(this.config.subdomain ? { subdomain: this.config.subdomain } : {}),\n\t\t\t\tapp: {\n\t\t\t\t\tpath: this.config.path,\n\t\t\t\t\t...(this.config.port ? { port: this.config.port } : {}),\n\t\t\t\t\t...(this.config.config ? { config: this.config.config } : {}),\n\t\t\t\t},\n\t\t\t\t...(this.config.root ? { root: true } : {}),\n\t\t\t\tdependencies: this.dependencies,\n\t\t\t\tprovides: [this.keys.url],\n\t\t\t},\n\t\t];\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAwFA,IAAa,aAAb,MAAa,WAEb;CAMmB;CAOA;CAZlB;CACA;CAEA,YACC,IACA,QAOA,eAAuD,CAAC,GACvD;EARgB,KAAA,SAAA;EAOA,KAAA,eAAA;EAEjB,KAAK,MAAA,GAAKA,oBAAAA,YAAAA,CAAY,EAAY;EAClC,KAAK,OAAO,EAAE,MAAA,GAAKC,oBAAAA,WAAAA,CAAW,KAAK,IAAI,KAAK,EAAE;CAC/C;;;;;;;;;;;;CAaA,UAAU,YAAsD;EAC/D,OAAO,IAAI,WAAkB,KAAK,IAA4B,KAAK,QAAQ,CAC1E,GAAG,KAAK,cACR,GAAG,WAAW,IAAIC,4BAAAA,MAAM,CACzB,CAAC;CACF;CAEA,UAAyB;EACxB,OAAO,CACN;GACC,MAAM;GACN,IAAI,KAAK;GACT,SAAS,KAAK,OAAO,WAAW;GAChC,GAAI,KAAK,OAAO,YAAY,EAAE,WAAW,KAAK,OAAO,UAAU,IAAI,CAAC;GACpE,KAAK;IACJ,MAAM,KAAK,OAAO;IAClB,GAAI,KAAK,OAAO,OAAO,EAAE,MAAM,KAAK,OAAO,KAAK,IAAI,CAAC;IACrD,GAAI,KAAK,OAAO,SAAS,EAAE,QAAQ,KAAK,OAAO,OAAO,IAAI,CAAC;GAC5D;GACA,GAAI,KAAK,OAAO,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;GACzC,cAAc,KAAK;GACnB,UAAU,CAAC,KAAK,KAAK,GAAG;EACzB,CACD;CACD;AACD"}
1
+ {"version":3,"file":"site.cjs","names":["canonicalId","provideKey","edgeTo"],"sources":["../src/site.ts"],"sourcesContent":["/**\n * `StaticSite` — a frontend, declared like anything else.\n *\n * The point is not that a site needs provisioning; it is that a site is the\n * *consumer* whose edges make four hand-maintained lists derivable. Declaring it\n * is what turns \"which origins does the API allow\" from something a person keeps\n * in step into something the graph already knows.\n *\n * ```ts\n * export const console = new StaticSite('Console', { path: 'apps/console' })\n * .dependsOn([api, auth]);\n * ```\n *\n * It has no `.service`. A site is not a process that registers clients — it is\n * built, and the values it needs are inlined or fetched by its own framework —\n * so the edge is {@link Declarable} rather than {@link Consumable}, and the\n * asymmetry is the honest one: a site consumes addresses, not connections.\n *\n * Only the static variant ships. SSR deploy shapes are unstable and each one is\n * its own maintenance, so they follow rather than arrive together. A mobile app\n * is not a site — it is reached by a scheme, not a URL — and is its own\n * construct: see `MobileApp`.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\ttype Dependency,\n\tprovideKey,\n\ttype SiteDeclaration,\n} from '@geekmidas/manifest';\nimport { type Declarable, edgeTo } from './construct-interface';\nimport type { Telemetry } from './telemetry';\n\n/**\n * Where a site's source lives, and the little else a layout can differ by.\n *\n * Only `path` is required: `new StaticSite('Web', { path: 'apps/web' })` is a\n * complete site.\n */\nexport interface StaticSiteConfig {\n\t/**\n\t * Where its source lives, relative to the workspace root — `'apps/web'`.\n\t *\n\t * Required, the same field `RestApi` and `BetterAuth` take. It was\n\t * inferred from the id, which made a site's home whatever was on disk.\n\t */\n\tpath: string;\n\t/**\n\t * The label it answers on under a stage's domain — `api` for\n\t * `api.myapp.com`, from `deploy.domains` — and under the project locally,\n\t * `api.myapp.localhost`. Absent, its id kebab-cased.\n\t */\n\tsubdomain?: string;\n\t/**\n\t * The port it answers on locally.\n\t *\n\t * Normally omitted: ports are assigned in a stable order, so adding a site\n\t * does not renumber the ones already running.\n\t */\n\tport?: number;\n\t/**\n\t * Modules to import when sniffing which env vars this site reads.\n\t *\n\t * A frontend's values are inlined at build time, so the build has to know\n\t * which ones it reads before it reads them.\n\t */\n\tconfig?: { client?: string; server?: string };\n\t/**\n\t * Whether the base domain points at this site.\n\t *\n\t * A project with one site needs this no more than a project with one\n\t * database needs to say which database. It matters when there are several\n\t * and none is called `web`: the base domain then belongs to whichever site\n\t * says so, rather than to whichever file the glob happened to reach first.\n\t */\n\troot?: boolean;\n\t/**\n\t * Which framework builds it.\n\t *\n\t * It selects how values are delivered — `VITE_`, `NEXT_PUBLIC_`, a fetched\n\t * `config.json` — and never which values there are. One neutral name from the\n\t * construct, one serialisation per framework, the same rule as everywhere\n\t * else.\n\t */\n\tvariant?: SiteDeclaration['variant'];\n\t/**\n\t * What the site emits — the `Telemetry` construct. An edge to the node and\n\t * nothing more for now: a site is handed none of its server keys, and the\n\t * browser half (propagation from the generated client, then a browser SDK\n\t * exporting through the API) builds on this edge.\n\t */\n\ttelemetry?: Telemetry;\n}\n\nexport class StaticSite<TName extends string = string>\n\timplements Declarable<TName>\n{\n\treadonly id: TName;\n\treadonly keys: { url: string };\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly config: StaticSiteConfig,\n\t\t/**\n\t\t * Internal: how `.dependsOn()` carries edges into the copy it returns.\n\t\t * Written as a parameter rather than a mutable field so the builder can\n\t\t * stay immutable without a clone method, the same shape\n\t\t * `KyselyDatabase` uses for its derived forms.\n\t\t */\n\t\tprivate readonly dependencies: readonly Dependency[] = [],\n\t) {\n\t\tthis.id = canonicalId(id as string) as TName;\n\t\tthis.keys = { url: provideKey(this.id, 'url') };\n\t}\n\n\t/**\n\t * What this site calls.\n\t *\n\t * Immutable, like every other builder here: it returns a new site rather than\n\t * mutating this one, so a module that exports both a base and a variant of it\n\t * cannot have the second silently change the first.\n\t *\n\t * Takes {@link Declarable} rather than {@link Consumable} because a site's\n\t * edge is a build-time link and not a service injection — requiring a client\n\t * would exclude exactly the surfaces a frontend actually depends on.\n\t */\n\tdependsOn(constructs: readonly Declarable[]): StaticSite<TName> {\n\t\treturn new StaticSite<TName>(this.id as ConstructName<TName>, this.config, [\n\t\t\t...this.dependencies,\n\t\t\t...constructs.map(edgeTo),\n\t\t]);\n\t}\n\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'site',\n\t\t\t\tid: this.id,\n\t\t\t\tvariant: this.config.variant ?? 'static',\n\t\t\t\t...(this.config.subdomain ? { subdomain: this.config.subdomain } : {}),\n\t\t\t\tapp: {\n\t\t\t\t\tpath: this.config.path,\n\t\t\t\t\t...(this.config.port ? { port: this.config.port } : {}),\n\t\t\t\t\t...(this.config.config ? { config: this.config.config } : {}),\n\t\t\t\t},\n\t\t\t\t...(this.config.root ? { root: true } : {}),\n\t\t\t\tdependencies: this.dependencies,\n\t\t\t\t...(this.config.telemetry\n\t\t\t\t\t? { telemetry: this.config.telemetry.id }\n\t\t\t\t\t: {}),\n\t\t\t\tprovides: [this.keys.url],\n\t\t\t},\n\t\t];\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAgGA,IAAa,aAAb,MAAa,WAEb;CAMmB;CAOA;CAZlB;CACA;CAEA,YACC,IACA,QAOA,eAAuD,CAAC,GACvD;EARgB,KAAA,SAAA;EAOA,KAAA,eAAA;EAEjB,KAAK,MAAA,GAAKA,oBAAAA,YAAAA,CAAY,EAAY;EAClC,KAAK,OAAO,EAAE,MAAA,GAAKC,oBAAAA,WAAAA,CAAW,KAAK,IAAI,KAAK,EAAE;CAC/C;;;;;;;;;;;;CAaA,UAAU,YAAsD;EAC/D,OAAO,IAAI,WAAkB,KAAK,IAA4B,KAAK,QAAQ,CAC1E,GAAG,KAAK,cACR,GAAG,WAAW,IAAIC,4BAAAA,MAAM,CACzB,CAAC;CACF;CAEA,UAAyB;EACxB,OAAO,CACN;GACC,MAAM;GACN,IAAI,KAAK;GACT,SAAS,KAAK,OAAO,WAAW;GAChC,GAAI,KAAK,OAAO,YAAY,EAAE,WAAW,KAAK,OAAO,UAAU,IAAI,CAAC;GACpE,KAAK;IACJ,MAAM,KAAK,OAAO;IAClB,GAAI,KAAK,OAAO,OAAO,EAAE,MAAM,KAAK,OAAO,KAAK,IAAI,CAAC;IACrD,GAAI,KAAK,OAAO,SAAS,EAAE,QAAQ,KAAK,OAAO,OAAO,IAAI,CAAC;GAC5D;GACA,GAAI,KAAK,OAAO,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;GACzC,cAAc,KAAK;GACnB,GAAI,KAAK,OAAO,YACb,EAAE,WAAW,KAAK,OAAO,UAAU,GAAG,IACtC,CAAC;GACJ,UAAU,CAAC,KAAK,KAAK,GAAG;EACzB,CACD;CACD;AACD"}
package/dist/site.d.cts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { i as Declarable } from "./construct-interface-B1AfqRl4.cjs";
2
+ import { n as Telemetry } from "./telemetry-B48RLjyk.cjs";
2
3
  import { ConstructName, Declaration, Dependency, SiteDeclaration } from "@geekmidas/manifest";
3
4
  //#region src/site.d.ts
4
5
  /**
@@ -56,6 +57,13 @@ export interface StaticSiteConfig {
56
57
  * else.
57
58
  */
58
59
  variant?: SiteDeclaration['variant'];
60
+ /**
61
+ * What the site emits — the `Telemetry` construct. An edge to the node and
62
+ * nothing more for now: a site is handed none of its server keys, and the
63
+ * browser half (propagation from the generated client, then a browser SDK
64
+ * exporting through the API) builds on this edge.
65
+ */
66
+ telemetry?: Telemetry;
59
67
  }
60
68
  export declare class StaticSite<TName extends string = string> implements Declarable<TName> {
61
69
  private readonly config;
@@ -1 +1 @@
1
- {"version":3,"file":"site.d.cts","names":[],"sources":["../src/site.ts"],"mappings":";;;;;;;;;iBAwCiB;;;;;;;EAOhB;;;;;;EAMA;;;;;;;EAOA;;;;;;;EAOA;IAAW;IAAiB;;;;;;;;;;EAS5B;;;;;;;;;EASA,UAAU;;qBAGE,WAAW,0CACZ,WAAW;mBAOJ;;;;;;;mBAOA;WAZT,IAAI;WACJ;IAAQ;;EAEjB,YACC,IAAI,cAAc,QACD,QAAQ,kBAOR;;;;;;;EAAA,wBAAuB;;;;;;;;;;;;EAiBzC,UAAU,qBAAqB,eAAe,WAAW;EAOzD,WAAW"}
1
+ {"version":3,"file":"site.d.cts","names":[],"sources":["../src/site.ts"],"mappings":";;;;;;;;;;iBAyCiB;;;;;;;EAOhB;;;;;;EAMA;;;;;;;EAOA;;;;;;;EAOA;IAAW;IAAiB;;;;;;;;;;EAS5B;;;;;;;;;EASA,UAAU;;;;;;;EAOV,YAAY;;qBAGA,WAAW,0CACZ,WAAW;mBAOJ;;;;;;;mBAOA;WAZT,IAAI;WACJ;IAAQ;;EAEjB,YACC,IAAI,cAAc,QACD,QAAQ,kBAOR;;;;;;;EAAA,wBAAuB;;;;;;;;;;;;EAiBzC,UAAU,qBAAqB,eAAe,WAAW;EAOzD,WAAW"}
package/dist/site.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { i as Declarable } from "./construct-interface-DluK3_da.mjs";
2
+ import { n as Telemetry } from "./telemetry-DRtT1_0A.mjs";
2
3
  import { ConstructName, Declaration, Dependency, SiteDeclaration } from "@geekmidas/manifest";
3
4
  //#region src/site.d.ts
4
5
  /**
@@ -56,6 +57,13 @@ export interface StaticSiteConfig {
56
57
  * else.
57
58
  */
58
59
  variant?: SiteDeclaration['variant'];
60
+ /**
61
+ * What the site emits — the `Telemetry` construct. An edge to the node and
62
+ * nothing more for now: a site is handed none of its server keys, and the
63
+ * browser half (propagation from the generated client, then a browser SDK
64
+ * exporting through the API) builds on this edge.
65
+ */
66
+ telemetry?: Telemetry;
59
67
  }
60
68
  export declare class StaticSite<TName extends string = string> implements Declarable<TName> {
61
69
  private readonly config;
@@ -1 +1 @@
1
- {"version":3,"file":"site.d.mts","names":[],"sources":["../src/site.ts"],"mappings":";;;;;;;;;iBAwCiB;;;;;;;EAOhB;;;;;;EAMA;;;;;;;EAOA;;;;;;;EAOA;IAAW;IAAiB;;;;;;;;;;EAS5B;;;;;;;;;EASA,UAAU;;qBAGE,WAAW,0CACZ,WAAW;mBAOJ;;;;;;;mBAOA;WAZT,IAAI;WACJ;IAAQ;;EAEjB,YACC,IAAI,cAAc,QACD,QAAQ,kBAOR;;;;;;;EAAA,wBAAuB;;;;;;;;;;;;EAiBzC,UAAU,qBAAqB,eAAe,WAAW;EAOzD,WAAW"}
1
+ {"version":3,"file":"site.d.mts","names":[],"sources":["../src/site.ts"],"mappings":";;;;;;;;;;iBAyCiB;;;;;;;EAOhB;;;;;;EAMA;;;;;;;EAOA;;;;;;;EAOA;IAAW;IAAiB;;;;;;;;;;EAS5B;;;;;;;;;EASA,UAAU;;;;;;;EAOV,YAAY;;qBAGA,WAAW,0CACZ,WAAW;mBAOJ;;;;;;;mBAOA;WAZT,IAAI;WACJ;IAAQ;;EAEjB,YACC,IAAI,cAAc,QACD,QAAQ,kBAOR;;;;;;;EAAA,wBAAuB;;;;;;;;;;;;EAiBzC,UAAU,qBAAqB,eAAe,WAAW;EAOzD,WAAW"}
package/dist/site.mjs CHANGED
@@ -62,6 +62,7 @@ var StaticSite = class StaticSite {
62
62
  },
63
63
  ...this.config.root ? { root: true } : {},
64
64
  dependencies: this.dependencies,
65
+ ...this.config.telemetry ? { telemetry: this.config.telemetry.id } : {},
65
66
  provides: [this.keys.url]
66
67
  }];
67
68
  }
package/dist/site.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"site.mjs","names":[],"sources":["../src/site.ts"],"sourcesContent":["/**\n * `StaticSite` — a frontend, declared like anything else.\n *\n * The point is not that a site needs provisioning; it is that a site is the\n * *consumer* whose edges make four hand-maintained lists derivable. Declaring it\n * is what turns \"which origins does the API allow\" from something a person keeps\n * in step into something the graph already knows.\n *\n * ```ts\n * export const console = new StaticSite('Console', { path: 'apps/console' })\n * .dependsOn([api, auth]);\n * ```\n *\n * It has no `.service`. A site is not a process that registers clients — it is\n * built, and the values it needs are inlined or fetched by its own framework —\n * so the edge is {@link Declarable} rather than {@link Consumable}, and the\n * asymmetry is the honest one: a site consumes addresses, not connections.\n *\n * Only the static variant ships. SSR deploy shapes are unstable and each one is\n * its own maintenance, so they follow rather than arrive together. A mobile app\n * is not a site — it is reached by a scheme, not a URL — and is its own\n * construct: see `MobileApp`.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\ttype Dependency,\n\tprovideKey,\n\ttype SiteDeclaration,\n} from '@geekmidas/manifest';\nimport { type Declarable, edgeTo } from './construct-interface';\n\n/**\n * Where a site's source lives, and the little else a layout can differ by.\n *\n * Only `path` is required: `new StaticSite('Web', { path: 'apps/web' })` is a\n * complete site.\n */\nexport interface StaticSiteConfig {\n\t/**\n\t * Where its source lives, relative to the workspace root — `'apps/web'`.\n\t *\n\t * Required, the same field `RestApi` and `BetterAuth` take. It was\n\t * inferred from the id, which made a site's home whatever was on disk.\n\t */\n\tpath: string;\n\t/**\n\t * The label it answers on under a stage's domain — `api` for\n\t * `api.myapp.com`, from `deploy.domains` — and under the project locally,\n\t * `api.myapp.localhost`. Absent, its id kebab-cased.\n\t */\n\tsubdomain?: string;\n\t/**\n\t * The port it answers on locally.\n\t *\n\t * Normally omitted: ports are assigned in a stable order, so adding a site\n\t * does not renumber the ones already running.\n\t */\n\tport?: number;\n\t/**\n\t * Modules to import when sniffing which env vars this site reads.\n\t *\n\t * A frontend's values are inlined at build time, so the build has to know\n\t * which ones it reads before it reads them.\n\t */\n\tconfig?: { client?: string; server?: string };\n\t/**\n\t * Whether the base domain points at this site.\n\t *\n\t * A project with one site needs this no more than a project with one\n\t * database needs to say which database. It matters when there are several\n\t * and none is called `web`: the base domain then belongs to whichever site\n\t * says so, rather than to whichever file the glob happened to reach first.\n\t */\n\troot?: boolean;\n\t/**\n\t * Which framework builds it.\n\t *\n\t * It selects how values are delivered — `VITE_`, `NEXT_PUBLIC_`, a fetched\n\t * `config.json` — and never which values there are. One neutral name from the\n\t * construct, one serialisation per framework, the same rule as everywhere\n\t * else.\n\t */\n\tvariant?: SiteDeclaration['variant'];\n}\n\nexport class StaticSite<TName extends string = string>\n\timplements Declarable<TName>\n{\n\treadonly id: TName;\n\treadonly keys: { url: string };\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly config: StaticSiteConfig,\n\t\t/**\n\t\t * Internal: how `.dependsOn()` carries edges into the copy it returns.\n\t\t * Written as a parameter rather than a mutable field so the builder can\n\t\t * stay immutable without a clone method, the same shape\n\t\t * `KyselyDatabase` uses for its derived forms.\n\t\t */\n\t\tprivate readonly dependencies: readonly Dependency[] = [],\n\t) {\n\t\tthis.id = canonicalId(id as string) as TName;\n\t\tthis.keys = { url: provideKey(this.id, 'url') };\n\t}\n\n\t/**\n\t * What this site calls.\n\t *\n\t * Immutable, like every other builder here: it returns a new site rather than\n\t * mutating this one, so a module that exports both a base and a variant of it\n\t * cannot have the second silently change the first.\n\t *\n\t * Takes {@link Declarable} rather than {@link Consumable} because a site's\n\t * edge is a build-time link and not a service injection — requiring a client\n\t * would exclude exactly the surfaces a frontend actually depends on.\n\t */\n\tdependsOn(constructs: readonly Declarable[]): StaticSite<TName> {\n\t\treturn new StaticSite<TName>(this.id as ConstructName<TName>, this.config, [\n\t\t\t...this.dependencies,\n\t\t\t...constructs.map(edgeTo),\n\t\t]);\n\t}\n\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'site',\n\t\t\t\tid: this.id,\n\t\t\t\tvariant: this.config.variant ?? 'static',\n\t\t\t\t...(this.config.subdomain ? { subdomain: this.config.subdomain } : {}),\n\t\t\t\tapp: {\n\t\t\t\t\tpath: this.config.path,\n\t\t\t\t\t...(this.config.port ? { port: this.config.port } : {}),\n\t\t\t\t\t...(this.config.config ? { config: this.config.config } : {}),\n\t\t\t\t},\n\t\t\t\t...(this.config.root ? { root: true } : {}),\n\t\t\t\tdependencies: this.dependencies,\n\t\t\t\tprovides: [this.keys.url],\n\t\t\t},\n\t\t];\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAwFA,IAAa,aAAb,MAAa,WAEb;CAMmB;CAOA;CAZlB;CACA;CAEA,YACC,IACA,QAOA,eAAuD,CAAC,GACvD;EARgB,KAAA,SAAA;EAOA,KAAA,eAAA;EAEjB,KAAK,KAAK,YAAY,EAAY;EAClC,KAAK,OAAO,EAAE,KAAK,WAAW,KAAK,IAAI,KAAK,EAAE;CAC/C;;;;;;;;;;;;CAaA,UAAU,YAAsD;EAC/D,OAAO,IAAI,WAAkB,KAAK,IAA4B,KAAK,QAAQ,CAC1E,GAAG,KAAK,cACR,GAAG,WAAW,IAAI,MAAM,CACzB,CAAC;CACF;CAEA,UAAyB;EACxB,OAAO,CACN;GACC,MAAM;GACN,IAAI,KAAK;GACT,SAAS,KAAK,OAAO,WAAW;GAChC,GAAI,KAAK,OAAO,YAAY,EAAE,WAAW,KAAK,OAAO,UAAU,IAAI,CAAC;GACpE,KAAK;IACJ,MAAM,KAAK,OAAO;IAClB,GAAI,KAAK,OAAO,OAAO,EAAE,MAAM,KAAK,OAAO,KAAK,IAAI,CAAC;IACrD,GAAI,KAAK,OAAO,SAAS,EAAE,QAAQ,KAAK,OAAO,OAAO,IAAI,CAAC;GAC5D;GACA,GAAI,KAAK,OAAO,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;GACzC,cAAc,KAAK;GACnB,UAAU,CAAC,KAAK,KAAK,GAAG;EACzB,CACD;CACD;AACD"}
1
+ {"version":3,"file":"site.mjs","names":[],"sources":["../src/site.ts"],"sourcesContent":["/**\n * `StaticSite` — a frontend, declared like anything else.\n *\n * The point is not that a site needs provisioning; it is that a site is the\n * *consumer* whose edges make four hand-maintained lists derivable. Declaring it\n * is what turns \"which origins does the API allow\" from something a person keeps\n * in step into something the graph already knows.\n *\n * ```ts\n * export const console = new StaticSite('Console', { path: 'apps/console' })\n * .dependsOn([api, auth]);\n * ```\n *\n * It has no `.service`. A site is not a process that registers clients — it is\n * built, and the values it needs are inlined or fetched by its own framework —\n * so the edge is {@link Declarable} rather than {@link Consumable}, and the\n * asymmetry is the honest one: a site consumes addresses, not connections.\n *\n * Only the static variant ships. SSR deploy shapes are unstable and each one is\n * its own maintenance, so they follow rather than arrive together. A mobile app\n * is not a site — it is reached by a scheme, not a URL — and is its own\n * construct: see `MobileApp`.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\ttype Dependency,\n\tprovideKey,\n\ttype SiteDeclaration,\n} from '@geekmidas/manifest';\nimport { type Declarable, edgeTo } from './construct-interface';\nimport type { Telemetry } from './telemetry';\n\n/**\n * Where a site's source lives, and the little else a layout can differ by.\n *\n * Only `path` is required: `new StaticSite('Web', { path: 'apps/web' })` is a\n * complete site.\n */\nexport interface StaticSiteConfig {\n\t/**\n\t * Where its source lives, relative to the workspace root — `'apps/web'`.\n\t *\n\t * Required, the same field `RestApi` and `BetterAuth` take. It was\n\t * inferred from the id, which made a site's home whatever was on disk.\n\t */\n\tpath: string;\n\t/**\n\t * The label it answers on under a stage's domain — `api` for\n\t * `api.myapp.com`, from `deploy.domains` — and under the project locally,\n\t * `api.myapp.localhost`. Absent, its id kebab-cased.\n\t */\n\tsubdomain?: string;\n\t/**\n\t * The port it answers on locally.\n\t *\n\t * Normally omitted: ports are assigned in a stable order, so adding a site\n\t * does not renumber the ones already running.\n\t */\n\tport?: number;\n\t/**\n\t * Modules to import when sniffing which env vars this site reads.\n\t *\n\t * A frontend's values are inlined at build time, so the build has to know\n\t * which ones it reads before it reads them.\n\t */\n\tconfig?: { client?: string; server?: string };\n\t/**\n\t * Whether the base domain points at this site.\n\t *\n\t * A project with one site needs this no more than a project with one\n\t * database needs to say which database. It matters when there are several\n\t * and none is called `web`: the base domain then belongs to whichever site\n\t * says so, rather than to whichever file the glob happened to reach first.\n\t */\n\troot?: boolean;\n\t/**\n\t * Which framework builds it.\n\t *\n\t * It selects how values are delivered — `VITE_`, `NEXT_PUBLIC_`, a fetched\n\t * `config.json` — and never which values there are. One neutral name from the\n\t * construct, one serialisation per framework, the same rule as everywhere\n\t * else.\n\t */\n\tvariant?: SiteDeclaration['variant'];\n\t/**\n\t * What the site emits — the `Telemetry` construct. An edge to the node and\n\t * nothing more for now: a site is handed none of its server keys, and the\n\t * browser half (propagation from the generated client, then a browser SDK\n\t * exporting through the API) builds on this edge.\n\t */\n\ttelemetry?: Telemetry;\n}\n\nexport class StaticSite<TName extends string = string>\n\timplements Declarable<TName>\n{\n\treadonly id: TName;\n\treadonly keys: { url: string };\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly config: StaticSiteConfig,\n\t\t/**\n\t\t * Internal: how `.dependsOn()` carries edges into the copy it returns.\n\t\t * Written as a parameter rather than a mutable field so the builder can\n\t\t * stay immutable without a clone method, the same shape\n\t\t * `KyselyDatabase` uses for its derived forms.\n\t\t */\n\t\tprivate readonly dependencies: readonly Dependency[] = [],\n\t) {\n\t\tthis.id = canonicalId(id as string) as TName;\n\t\tthis.keys = { url: provideKey(this.id, 'url') };\n\t}\n\n\t/**\n\t * What this site calls.\n\t *\n\t * Immutable, like every other builder here: it returns a new site rather than\n\t * mutating this one, so a module that exports both a base and a variant of it\n\t * cannot have the second silently change the first.\n\t *\n\t * Takes {@link Declarable} rather than {@link Consumable} because a site's\n\t * edge is a build-time link and not a service injection — requiring a client\n\t * would exclude exactly the surfaces a frontend actually depends on.\n\t */\n\tdependsOn(constructs: readonly Declarable[]): StaticSite<TName> {\n\t\treturn new StaticSite<TName>(this.id as ConstructName<TName>, this.config, [\n\t\t\t...this.dependencies,\n\t\t\t...constructs.map(edgeTo),\n\t\t]);\n\t}\n\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'site',\n\t\t\t\tid: this.id,\n\t\t\t\tvariant: this.config.variant ?? 'static',\n\t\t\t\t...(this.config.subdomain ? { subdomain: this.config.subdomain } : {}),\n\t\t\t\tapp: {\n\t\t\t\t\tpath: this.config.path,\n\t\t\t\t\t...(this.config.port ? { port: this.config.port } : {}),\n\t\t\t\t\t...(this.config.config ? { config: this.config.config } : {}),\n\t\t\t\t},\n\t\t\t\t...(this.config.root ? { root: true } : {}),\n\t\t\t\tdependencies: this.dependencies,\n\t\t\t\t...(this.config.telemetry\n\t\t\t\t\t? { telemetry: this.config.telemetry.id }\n\t\t\t\t\t: {}),\n\t\t\t\tprovides: [this.keys.url],\n\t\t\t},\n\t\t];\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAgGA,IAAa,aAAb,MAAa,WAEb;CAMmB;CAOA;CAZlB;CACA;CAEA,YACC,IACA,QAOA,eAAuD,CAAC,GACvD;EARgB,KAAA,SAAA;EAOA,KAAA,eAAA;EAEjB,KAAK,KAAK,YAAY,EAAY;EAClC,KAAK,OAAO,EAAE,KAAK,WAAW,KAAK,IAAI,KAAK,EAAE;CAC/C;;;;;;;;;;;;CAaA,UAAU,YAAsD;EAC/D,OAAO,IAAI,WAAkB,KAAK,IAA4B,KAAK,QAAQ,CAC1E,GAAG,KAAK,cACR,GAAG,WAAW,IAAI,MAAM,CACzB,CAAC;CACF;CAEA,UAAyB;EACxB,OAAO,CACN;GACC,MAAM;GACN,IAAI,KAAK;GACT,SAAS,KAAK,OAAO,WAAW;GAChC,GAAI,KAAK,OAAO,YAAY,EAAE,WAAW,KAAK,OAAO,UAAU,IAAI,CAAC;GACpE,KAAK;IACJ,MAAM,KAAK,OAAO;IAClB,GAAI,KAAK,OAAO,OAAO,EAAE,MAAM,KAAK,OAAO,KAAK,IAAI,CAAC;IACrD,GAAI,KAAK,OAAO,SAAS,EAAE,QAAQ,KAAK,OAAO,OAAO,IAAI,CAAC;GAC5D;GACA,GAAI,KAAK,OAAO,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;GACzC,cAAc,KAAK;GACnB,GAAI,KAAK,OAAO,YACb,EAAE,WAAW,KAAK,OAAO,UAAU,GAAG,IACtC,CAAC;GACJ,UAAU,CAAC,KAAK,KAAK,GAAG;EACzB,CACD;CACD;AACD"}
@@ -0,0 +1,40 @@
1
+ import { i as Declarable } from "./construct-interface-B1AfqRl4.cjs";
2
+ import { ConstructName, Declaration } from "@geekmidas/manifest";
3
+ //#region src/telemetry.d.ts
4
+ interface TelemetryConfig {
5
+ /**
6
+ * Request paths no span is recorded for, on every surface that uses this —
7
+ * health checks, by default the only noise worth naming. A trailing `*`
8
+ * matches a prefix. One route on one surface says so itself:
9
+ * `.telemetry({ ignore: true })`.
10
+ */
11
+ ignorePaths?: readonly string[];
12
+ /**
13
+ * Resource attributes every span and log record carries —
14
+ * `{ 'service.namespace': 'shop' }`. The service's name is not one of
15
+ * them: it is the app's, set per process.
16
+ */
17
+ attributes?: Readonly<Record<string, string>>;
18
+ }
19
+ declare class Telemetry<TName extends string = string> implements Declarable<TName> {
20
+ readonly id: TName;
21
+ readonly ignorePaths: readonly string[];
22
+ readonly attributes: Readonly<Record<string, string>>;
23
+ constructor(id: ConstructName<TName>, config?: TelemetryConfig);
24
+ /**
25
+ * One node, providing OpenTelemetry's own variables. No address of its
26
+ * own: where the telemetry goes is the deploy's, so it is resolved by the
27
+ * target for each stage, never declared here.
28
+ */
29
+ declare(): Declaration[];
30
+ }
31
+ /** Per-route telemetry, on the endpoint builder — `.telemetry({ ignore: true })`. */
32
+ interface EndpointTelemetry {
33
+ /** Record no span for this route. */
34
+ ignore?: boolean;
35
+ /** Attributes this route's request span carries. */
36
+ attributes?: Readonly<Record<string, string>>;
37
+ }
38
+ //#endregion
39
+ export { Telemetry as n, TelemetryConfig as r, EndpointTelemetry as t };
40
+ //# sourceMappingURL=telemetry-B48RLjyk.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-B48RLjyk.d.cts","names":[],"sources":["../src/telemetry.ts"],"mappings":";;;UAkCiB;;;;;;;EAOhB;;;;;;EAMA,aAAa,SAAS;;cAGV,UAAU,0CACX,WAAW;WAEb,IAAI;WACJ;WACA,YAAY,SAAS;EAE9B,YAAY,IAAI,cAAc,QAAQ,SAAQ;;;;;;EAW9C,WAAW;;;UAgBK;;EAEhB;;EAEA,aAAa,SAAS"}
@@ -0,0 +1,40 @@
1
+ import { i as Declarable } from "./construct-interface-DluK3_da.mjs";
2
+ import { ConstructName, Declaration } from "@geekmidas/manifest";
3
+ //#region src/telemetry.d.ts
4
+ interface TelemetryConfig {
5
+ /**
6
+ * Request paths no span is recorded for, on every surface that uses this —
7
+ * health checks, by default the only noise worth naming. A trailing `*`
8
+ * matches a prefix. One route on one surface says so itself:
9
+ * `.telemetry({ ignore: true })`.
10
+ */
11
+ ignorePaths?: readonly string[];
12
+ /**
13
+ * Resource attributes every span and log record carries —
14
+ * `{ 'service.namespace': 'shop' }`. The service's name is not one of
15
+ * them: it is the app's, set per process.
16
+ */
17
+ attributes?: Readonly<Record<string, string>>;
18
+ }
19
+ declare class Telemetry<TName extends string = string> implements Declarable<TName> {
20
+ readonly id: TName;
21
+ readonly ignorePaths: readonly string[];
22
+ readonly attributes: Readonly<Record<string, string>>;
23
+ constructor(id: ConstructName<TName>, config?: TelemetryConfig);
24
+ /**
25
+ * One node, providing OpenTelemetry's own variables. No address of its
26
+ * own: where the telemetry goes is the deploy's, so it is resolved by the
27
+ * target for each stage, never declared here.
28
+ */
29
+ declare(): Declaration[];
30
+ }
31
+ /** Per-route telemetry, on the endpoint builder — `.telemetry({ ignore: true })`. */
32
+ interface EndpointTelemetry {
33
+ /** Record no span for this route. */
34
+ ignore?: boolean;
35
+ /** Attributes this route's request span carries. */
36
+ attributes?: Readonly<Record<string, string>>;
37
+ }
38
+ //#endregion
39
+ export { Telemetry as n, TelemetryConfig as r, EndpointTelemetry as t };
40
+ //# sourceMappingURL=telemetry-DRtT1_0A.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-DRtT1_0A.d.mts","names":[],"sources":["../src/telemetry.ts"],"mappings":";;;UAkCiB;;;;;;;EAOhB;;;;;;EAMA,aAAa,SAAS;;cAGV,UAAU,0CACX,WAAW;WAEb,IAAI;WACJ;WACA,YAAY,SAAS;EAE9B,YAAY,IAAI,cAAc,QAAQ,SAAQ;;;;;;EAW9C,WAAW;;;UAgBK;;EAEhB;;EAEA,aAAa,SAAS"}
@@ -0,0 +1,56 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _geekmidas_manifest = require("@geekmidas/manifest");
3
+ //#region src/telemetry.ts
4
+ /**
5
+ * `Telemetry` — what an application emits, declared.
6
+ *
7
+ * Traces and logs over OTLP, from every process that is given it. The
8
+ * construct says *what* is emitted — which requests are noise, which
9
+ * attributes every span carries — and never where it goes or how much of it:
10
+ * the provider and the sample rate are the deploy's, per stage
11
+ * (`deploy.telemetry` in `gkm.config.ts`), and locally `gkm dev` runs
12
+ * OpenObserve and sends everything there.
13
+ *
14
+ * ```ts
15
+ * export const telemetry = new Telemetry('Telemetry', {
16
+ * ignorePaths: ['/health', '/ready'],
17
+ * attributes: { 'service.namespace': 'shop' },
18
+ * });
19
+ *
20
+ * // passed like the logger: a fact about the process
21
+ * new RestApi('Api', { path: 'apps/api', defaultAuthorizer: 'none', telemetry });
22
+ * new Worker('Jobs', { logger, telemetry });
23
+ * ```
24
+ *
25
+ * Each process given it is an edge to this node, so the `OTEL_*` keys it
26
+ * provides reach exactly those processes, and the build of each one fails
27
+ * without the OpenTelemetry packages it needs.
28
+ */
29
+ var Telemetry = class {
30
+ id;
31
+ ignorePaths;
32
+ attributes;
33
+ constructor(id, config = {}) {
34
+ this.id = (0, _geekmidas_manifest.canonicalId)(id);
35
+ this.ignorePaths = [...config.ignorePaths ?? []];
36
+ this.attributes = { ...config.attributes };
37
+ }
38
+ /**
39
+ * One node, providing OpenTelemetry's own variables. No address of its
40
+ * own: where the telemetry goes is the deploy's, so it is resolved by the
41
+ * target for each stage, never declared here.
42
+ */
43
+ declare() {
44
+ return [{
45
+ kind: "telemetry",
46
+ id: this.id,
47
+ ...this.ignorePaths.length ? { ignorePaths: this.ignorePaths } : {},
48
+ ...Object.keys(this.attributes).length ? { attributes: this.attributes } : {},
49
+ provides: [..._geekmidas_manifest.TELEMETRY_KEYS]
50
+ }];
51
+ }
52
+ };
53
+ //#endregion
54
+ exports.Telemetry = Telemetry;
55
+
56
+ //# sourceMappingURL=telemetry.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.cjs","names":["canonicalId","TELEMETRY_KEYS"],"sources":["../src/telemetry.ts"],"sourcesContent":["/**\n * `Telemetry` — what an application emits, declared.\n *\n * Traces and logs over OTLP, from every process that is given it. The\n * construct says *what* is emitted — which requests are noise, which\n * attributes every span carries — and never where it goes or how much of it:\n * the provider and the sample rate are the deploy's, per stage\n * (`deploy.telemetry` in `gkm.config.ts`), and locally `gkm dev` runs\n * OpenObserve and sends everything there.\n *\n * ```ts\n * export const telemetry = new Telemetry('Telemetry', {\n * ignorePaths: ['/health', '/ready'],\n * attributes: { 'service.namespace': 'shop' },\n * });\n *\n * // passed like the logger: a fact about the process\n * new RestApi('Api', { path: 'apps/api', defaultAuthorizer: 'none', telemetry });\n * new Worker('Jobs', { logger, telemetry });\n * ```\n *\n * Each process given it is an edge to this node, so the `OTEL_*` keys it\n * provides reach exactly those processes, and the build of each one fails\n * without the OpenTelemetry packages it needs.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\tTELEMETRY_KEYS,\n} from '@geekmidas/manifest';\nimport type { Declarable } from './construct-interface';\n\nexport interface TelemetryConfig {\n\t/**\n\t * Request paths no span is recorded for, on every surface that uses this —\n\t * health checks, by default the only noise worth naming. A trailing `*`\n\t * matches a prefix. One route on one surface says so itself:\n\t * `.telemetry({ ignore: true })`.\n\t */\n\tignorePaths?: readonly string[];\n\t/**\n\t * Resource attributes every span and log record carries —\n\t * `{ 'service.namespace': 'shop' }`. The service's name is not one of\n\t * them: it is the app's, set per process.\n\t */\n\tattributes?: Readonly<Record<string, string>>;\n}\n\nexport class Telemetry<TName extends string = string>\n\timplements Declarable<TName>\n{\n\treadonly id: TName;\n\treadonly ignorePaths: readonly string[];\n\treadonly attributes: Readonly<Record<string, string>>;\n\n\tconstructor(id: ConstructName<TName>, config: TelemetryConfig = {}) {\n\t\tthis.id = canonicalId(id as string) as TName;\n\t\tthis.ignorePaths = [...(config.ignorePaths ?? [])];\n\t\tthis.attributes = { ...config.attributes };\n\t}\n\n\t/**\n\t * One node, providing OpenTelemetry's own variables. No address of its\n\t * own: where the telemetry goes is the deploy's, so it is resolved by the\n\t * target for each stage, never declared here.\n\t */\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'telemetry',\n\t\t\t\tid: this.id,\n\t\t\t\t...(this.ignorePaths.length ? { ignorePaths: this.ignorePaths } : {}),\n\t\t\t\t...(Object.keys(this.attributes).length\n\t\t\t\t\t? { attributes: this.attributes }\n\t\t\t\t\t: {}),\n\t\t\t\tprovides: [...TELEMETRY_KEYS],\n\t\t\t},\n\t\t];\n\t}\n}\n\n/** Per-route telemetry, on the endpoint builder — `.telemetry({ ignore: true })`. */\nexport interface EndpointTelemetry {\n\t/** Record no span for this route. */\n\tignore?: boolean;\n\t/** Attributes this route's request span carries. */\n\tattributes?: Readonly<Record<string, string>>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,IAAa,YAAb,MAEA;CACC;CACA;CACA;CAEA,YAAY,IAA0B,SAA0B,CAAC,GAAG;EACnE,KAAK,MAAA,GAAKA,oBAAAA,YAAAA,CAAY,EAAY;EAClC,KAAK,cAAc,CAAC,GAAI,OAAO,eAAe,CAAC,CAAE;EACjD,KAAK,aAAa,EAAE,GAAG,OAAO,WAAW;CAC1C;;;;;;CAOA,UAAyB;EACxB,OAAO,CACN;GACC,MAAM;GACN,IAAI,KAAK;GACT,GAAI,KAAK,YAAY,SAAS,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;GACnE,GAAI,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,SAC9B,EAAE,YAAY,KAAK,WAAW,IAC9B,CAAC;GACJ,UAAU,CAAC,GAAGC,oBAAAA,cAAc;EAC7B,CACD;CACD;AACD"}
@@ -0,0 +1,2 @@
1
+ import { n as Telemetry, r as TelemetryConfig, t as EndpointTelemetry } from "./telemetry-B48RLjyk.cjs";
2
+ export { EndpointTelemetry, Telemetry, TelemetryConfig };