@geekmidas/manifest 10.0.0-alpha.99 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,1345 +1,32 @@
1
- //#region src/declaration.d.ts
2
- /**
3
- * The construct manifest — the contract between what an application declares
4
- * and what a target adapter provisions.
5
- *
6
- * Kinds are added here as each one lands, not up front: a declaration for a
7
- * construct nobody has built yet is a guess that the implementation will
8
- * contradict. See `docs/design/constructs-paradigm.md`.
9
- */
10
- /**
11
- * A construct's canonical id — PascalCase, unique within the manifest.
12
- *
13
- * Inputs canonicalise, so `uploads`, `Uploads`, `user_uploads`, and
14
- * `user-uploads` are the *same* id rather than four that collide. Everything
15
- * else derives from it: the service key is its `Uncapitalize`, the env prefix
16
- * its SCREAMING_SNAKE form, the cloud name its kebab form scoped by stage and
17
- * app.
18
- */
19
- type ConstructId = string;
20
- type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
21
- /**
22
- * Constrains a construct name at the point it is written.
23
- *
24
- * Resolves to the name itself when valid, and otherwise to a string explaining
25
- * why — so the compiler reports *"not assignable to type 'a construct name
26
- * cannot start with a digit'"* rather than the unhelpful `never`.
27
- *
28
- * Only the cases a template-literal type can see are caught here; `canonicalId`
29
- * enforces the rest at runtime, which is also what covers JavaScript callers.
30
- *
31
- * @example new ObjectStorage('Uploads') // ok
32
- * @example new ObjectStorage('2fa') // a construct name cannot start with a digit
33
- */
34
- type ConstructName<S extends string> = S extends '' ? 'a construct name cannot be empty' : S extends `${Digit}${string}` ? 'a construct name cannot start with a digit' : S;
35
- /** Shared by every declaration. */
36
- interface Node {
37
- id: ConstructId;
38
- /**
39
- * Env keys this construct resolves onto anything that depends on it.
40
- * Names only — the values are composed by the adapter from the provisioned
41
- * resource's own attributes.
42
- */
43
- provides?: readonly string[];
44
- /**
45
- * Env keys this construct needs. Derivable from `dependencies`, so it is an
46
- * assertion rather than an input: the adapter composes env from the edges and
47
- * checks the result against this, which catches app/infra drift at synth.
48
- */
49
- requires?: readonly string[];
50
- }
51
- /**
52
- * A dependency edge. Records only *what* is depended on — never permissions.
53
- * From one edge the framework derives env and the runtime binding; a target
54
- * adapter separately derives cloud access.
55
- */
56
- interface Dependency<TTarget extends ConstructId = ConstructId> {
57
- /**
58
- * The {@link ConstructId} of the consumed construct. Left open here because a
59
- * declaration is written before the manifest that contains it; once assembled,
60
- * `IdsOf` narrows it and the build's reference-integrity check enforces it.
61
- */
62
- target: TTarget;
63
- kind: DeclarationKind;
64
- }
65
- /** Anything with a handler. */
66
- interface Fn extends Node {
67
- handler: string;
68
- dependencies: readonly Dependency[];
69
- }
70
- /** Blob storage. `--target=aws` provisions a bucket. */
71
- interface ObjectsDeclaration extends Node {
72
- kind: 'objects';
73
- versioned?: boolean;
74
- }
75
- /**
76
- * A domain that serves a bucket's objects.
77
- *
78
- * Its own construct rather than a flag on the bucket, because three things it
79
- * has to express are not properties of a bucket: a surface can front several
80
- * origins, a bucket can have several surfaces over it, and issuing a
81
- * certificate and writing a DNS record is a domain lifecycle that has no
82
- * business living inside an `objects` provisioner. It shares its infrastructure
83
- * with a static site rather than with storage — a site is the same
84
- * distribution over a build output instead of over live contents.
85
- *
86
- * It derives from the bucket by `of`, which costs the one thing the flag gave
87
- * for free: the bucket alone no longer says whether it is served, and finding
88
- * out means finding whoever points at it. That is answered the way every other
89
- * derivation is — the reference check at manifest build, so an unresolvable
90
- * origin is a build failure and `gkm` can name, for any bucket, the surfaces
91
- * over it.
92
- *
93
- * Private by default. `open` is an exception list, because a bucket where
94
- * forgetting a flag publishes user uploads is the wrong default — and paths
95
- * rather than per-object flags, because a path pattern is what the
96
- * infrastructure actually enforces and a per-object ACL is a thing nobody
97
- * audits.
98
- *
99
- * "Open" never means the bucket is world-readable. It means the server serves
100
- * that path without a signature; the bucket is private in both cases.
101
- */
102
- interface FileServerDeclaration extends Node {
103
- kind: 'file-server';
104
- /** The bucket whose objects it serves. */
105
- of: ConstructId;
106
- /**
107
- * Paths served without a signature — everything else requires one.
108
- *
109
- * Globs, matched most-literally by the infrastructure: a CDN keys its
110
- * behaviours off path patterns, and a bucket policy names prefixes.
111
- */
112
- open?: readonly string[];
113
- }
114
- /**
115
- * Outbound email.
116
- *
117
- * Provides one `smtp://` URL and nothing else, because email is delivered over
118
- * SMTP whatever the provider — Mailpit locally, SES through its SMTP interface,
119
- * Resend and Postmark through theirs. There is no `provider` field here for the
120
- * same reason there is no `ses://` scheme: which service delivers the mail
121
- * differs between dev and prod, so by this design's own test it is stage-varying
122
- * config rather than a structural fact about the app.
123
- *
124
- * What *is* structural is only that the app sends mail at all. The sending
125
- * domain is not: it is `myapp.test` locally and `example.com` deployed, so it
126
- * fails the same test the provider does and resolves at deploy alongside every
127
- * other address.
128
- */
129
- interface EmailDeclaration extends Node {
130
- kind: 'email';
131
- }
132
- /**
133
- * A logical database, its schema, and the roles that reach it.
134
- *
135
- * Provides one key — the *runtime* role's URL. The owner URL exists but is
136
- * deliberately absent from `provides`: it is wired by the adapter straight into
137
- * the migrator and seeder this construct declares, so no edge in any manifest
138
- * can name it and nothing else can be granted it by mistake.
139
- */
140
- /**
141
- * The Postgres major versions this toolbox provisions.
142
- *
143
- * A union rather than a string, because the two targets that read it are not
144
- * equally forgiving: locally it becomes a container tag, where a typo yields a
145
- * confusing pull failure, and on AWS it becomes an engine version, where a
146
- * wrong value fails partway through a deploy. Both are better as a compile
147
- * error.
148
- *
149
- * The union is what this toolbox knows how to name, not a promise that every
150
- * target offers every one of them. A deployed stage is limited by the engine
151
- * catalogue in its region, so check before pinning an unusual version:
152
- *
153
- * ```
154
- * aws rds describe-db-engine-versions --engine postgres \
155
- * --query 'DBEngineVersions[].EngineVersion' --output text
156
- * ```
157
- *
158
- * RDS offered 11 through 18 in `eu-west-1` when this was written. 11 and 12 are
159
- * left out because they are past upstream end-of-life: still provisionable, but
160
- * not something to make easy to reach for.
161
- */
162
- type PostgresVersion = 13 | 14 | 15 | 16 | 17 | 18;
163
- /**
164
- * The version used when a database names none.
165
- *
166
- * The point is not which number this is but that there is only one of them.
167
- * Local ran 18 while Aurora provisioned its own default of 17.7, and nothing in
168
- * any declaration recorded the difference — a stage could behave differently
169
- * from a developer's machine for a reason neither could see. Both now read
170
- * this.
171
- */
172
- export declare const DEFAULT_POSTGRES_VERSION: PostgresVersion;
173
- interface DatabaseDeclaration extends Node {
174
- kind: 'database';
175
- engine?: 'postgres';
176
- /**
177
- * The engine's major version, read by every target that provisions one.
178
- *
179
- * Declared rather than configured per target, because a version set in a
180
- * compose file and a version set in a deploy config are two statements of
181
- * one fact — and they had already drifted apart, silently, by a major.
182
- *
183
- * Defaults to {@link DEFAULT_POSTGRES_VERSION}.
184
- */
185
- version?: PostgresVersion;
186
- /**
187
- * The schema, pinned on both roles' `search_path`. Names the role the schema
188
- * plays rather than restating the database's own name, so `app` reads
189
- * correctly beside `auth` and `pgboss`.
190
- */
191
- schema?: string;
192
- /**
193
- * Whether to provision the owner/runtime role split. Off falls back to the
194
- * cluster's master credential in both URLs — a deliberate downgrade, not a
195
- * default. See `roles: false` in the design doc.
196
- */
197
- roles?: boolean;
198
- }
199
- /**
200
- * A read-only endpoint on an existing database or schema.
201
- *
202
- * Provisions no cluster of its own — `of` names the parent it reads from.
203
- * Read-only is enforced by the role's grants rather than by which endpoint it
204
- * resolves to, so falling back to the writer where no replica exists stays safe.
205
- */
206
- interface DatabaseReaderDeclaration extends Node {
207
- kind: 'database-reader';
208
- of: ConstructId;
209
- }
210
- /**
211
- * A second schema inside an existing database, with its own role(s) and URL.
212
- *
213
- * The mechanism behind tenancy: the parent's role holds no grant on these
214
- * tables at all. pg-boss is an instance of this rather than a special case.
215
- */
216
- interface DatabaseSchemaDeclaration extends Node {
217
- kind: 'database-schema';
218
- of: ConstructId;
219
- schema: string;
220
- }
221
- /**
222
- * A generated secret — a signing key, a token, anything with no address.
223
- *
224
- * It provides a value rather than a URL, which is why it is a node of its own
225
- * instead of a field on whatever needs it: the thing that generates it, the
226
- * thing that stores it, and the thing that reads it are three different systems
227
- * deployed, and one derived string locally.
228
- */
229
- interface SecretDeclaration extends Node {
230
- kind: 'secret';
231
- }
232
- /**
233
- * A third-party credential with a shape.
234
- *
235
- * Distinct from {@link SecretDeclaration} by *lifecycle*, which is the only
236
- * distinction worth having two kinds for. A secret is generated and rotated by
237
- * the platform — `gkm secrets`, `sst secret set` — and is one opaque string
238
- * whose name is its key. A credential is issued by someone else, arrives with
239
- * several fields, and is validated on the way in: a Stripe key pair, an OAuth
240
- * client, a webhook signing secret.
241
- *
242
- * It provides one key holding a JSON object, rather than one key per field.
243
- * That is what a secret manager actually stores, and it is also the only shape
244
- * that works with an arbitrary StandardSchema — the spec has no introspection
245
- * API, so enumerating a schema's fields means reaching into one library's
246
- * internals and being wrong for every other.
247
- */
248
- interface CredentialDeclaration extends Node {
249
- kind: 'credential';
250
- }
251
- /**
252
- * A key that encrypts what the application stores.
253
- *
254
- * It provides one URL, and the scheme picks the cipher the way a queue's
255
- * connection string picks its broker: a keyring the process holds
256
- * (`aes256gcm://`) locally and on a server, a KMS key (`kms://`) on AWS. The
257
- * value is the platform's to generate and the app never names a cipher.
258
- *
259
- * A node rather than a field on whatever encrypts, for the reason a secret is:
260
- * what holds the key, what grants access to it, and what uses it are three
261
- * systems deployed — and an edge to it is what grants a function the key.
262
- */
263
- interface EncryptionDeclaration extends Node {
264
- kind: 'encryption';
265
- }
266
- /**
267
- * An HTTP API somebody else runs — Polar, Stripe, a payment gateway.
268
- *
269
- * Provisions nothing, like {@link OidcDeclaration}: the API already exists. What
270
- * makes it a node rather than a URL somebody writes into `.env` is that its
271
- * address changes by stage — the real one deployed, a fake locally and in
272
- * tests — and switching it is the target's job, not each app's.
273
- *
274
- * The fake is not here, and that is deliberate: it lives in `test/fakes/<id>.ts`,
275
- * which only gkm reads, so nothing in a deployed bundle can reach it. Its
276
- * credentials are a second key holding JSON, issued per stage the way a
277
- * {@link CredentialDeclaration}'s are.
278
- */
279
- interface ExternalApiDeclaration extends Node {
280
- kind: 'external-api';
281
- /**
282
- * Where it answers deployed: one URL for every stage, or one per stage name,
283
- * with `default` for any stage not listed. A local or test stage never reads
284
- * this — it is handed the fake.
285
- */
286
- url: string | Readonly<Record<string, string>>;
287
- }
288
- /**
289
- * An identity provider somebody else runs.
290
- *
291
- * Provisions nothing — the issuer already exists — so a target's whole job is
292
- * to hand the process an issuer and an audience, and the verifier discovers the
293
- * rest at runtime.
294
- *
295
- * It is a node rather than a field on whatever authenticates through it because
296
- * two surfaces can name the same provider, and because *which* population a
297
- * surface admits is a fact worth reading off the graph: an admin console
298
- * authenticated by the customer auth server is a finding, and it is only
299
- * visible if both are declarations.
300
- */
301
- interface OidcDeclaration extends Node {
302
- kind: 'oidc';
303
- /**
304
- * The issuer, when it does not vary by deployment.
305
- *
306
- * Absent means it arrives in the environment instead — a staging tenant, a
307
- * per-customer directory. The audience is never here: it identifies one
308
- * deployment to the provider.
309
- */
310
- issuer?: string;
311
- }
312
- /**
313
- * A key/value cache.
314
- *
315
- * Provides one URL. What is *in* that URL is the backend's business — Upstash's
316
- * REST API, a Redis endpoint, or a table in a database — and the scheme is what
317
- * picks the client, exactly as it does for object storage.
318
- *
319
- * Two ways to declare one, and the difference is a real statement rather than a
320
- * spelling. `new Cache('Sessions')` says *this app caches*, leaving where to the
321
- * deployment; `orders.cache('Sessions')` says *this app caches in that
322
- * database*, which is a fact about the application and belongs in its code. The
323
- * second is the same strengthening `orders.schema('AuthDb')` is over declaring a
324
- * second database.
325
- */
326
- interface CacheDeclaration extends Node {
327
- kind: 'cache';
328
- /**
329
- * The database this cache lives in, when it lives in one.
330
- *
331
- * Present only for a cache derived from a database. It removes a guess the
332
- * backend selection otherwise has to make — "the declared database" is
333
- * unambiguous with one and arbitrary with two — and it means the table's
334
- * schema and the role that reaches it come from the parent rather than from
335
- * a second convention.
336
- */
337
- of?: ConstructId;
338
- /**
339
- * The table entries are kept in, resolved against the connection's
340
- * `search_path`. Defaults to `cache`.
341
- */
342
- table?: string;
343
- }
344
- /**
345
- * An HTTP surface and the handlers mounted on it.
346
- *
347
- * The first kind that is not a resource in the ordinary sense: it owns an
348
- * address, and the functions it triggers are *nested inside it* rather than
349
- * listed beside it, because position carries the trigger — a handler here is
350
- * reached by its method and path and by nothing else.
351
- *
352
- * `authorizers` are names. A bare string is resolved by the target (`iam`), while
353
- * a {@link ConstructId} names a construct that carries its own implementation,
354
- * its database dependency, and its session typing.
355
- */
356
- interface RestApiDeclaration extends Node {
357
- kind: 'rest-api';
358
- /**
359
- * The label it answers on under a stage's domain — `api` for
360
- * `api.myapp.com`, from `domains` — and under the project locally,
361
- * `api.myapp.localhost`. Absent, its id kebab-cased.
362
- */
363
- subdomain?: string;
364
- /**
365
- * The app that serves this surface, relative to the workspace root.
366
- *
367
- * Required, and the only thing about its process a surface says: its code
368
- * is found through the workspace's `constructs` glob, and ports are
369
- * assigned in a stable order. Nothing infers it from the id.
370
- */
371
- path: string;
372
- /** Whether this surface streams into a Telescope. */
373
- telescope?: true;
374
- /**
375
- * The construct that authenticates this surface.
376
- *
377
- * An edge like any other — so the auth server learns this surface's origin,
378
- * and the two share a cookie domain — but a *named* one, because "who
379
- * authenticates me" is a different fact from "who I happen to call", and
380
- * only one of them decides what a request is allowed to be.
381
- *
382
- * It is the id rather than the client: what every endpoint consumes is
383
- * `verify(request) → Session | null`, and that is the one thing every
384
- * provider shares. A surface names its authenticator; the target decides
385
- * what verifying means.
386
- */
387
- auth?: ConstructId;
388
- /**
389
- * CORS tunables for this surface.
390
- *
391
- * *Who* may call it is never here — that is derived from the constructs
392
- * declaring an edge to this surface, and arrives as `<ID>_TRUSTED_ORIGINS`.
393
- * A hand-written origin list is the thing this model removes; these are the
394
- * knobs that genuinely cannot be derived from a graph.
395
- *
396
- * Omitted entirely, a surface still gets CORS — with the derived origins and
397
- * sensible defaults. There is nothing to opt into.
398
- */
399
- cors?: {
400
- /** Preflight cache lifetime in seconds. Default 86400. */
401
- maxAge?: number;
402
- /** Whether the browser may send credentials. Default true. */
403
- credentials?: boolean;
404
- /** Extra request headers to allow, beyond content-type and authorization. */
405
- allowHeaders?: readonly string[];
406
- /** Response headers the browser may read. */
407
- exposeHeaders?: readonly string[];
408
- };
409
- authorizers?: readonly string[];
410
- /** The authorizer applied where an endpoint names none. */
411
- defaultAuthorizer?: string;
412
- /**
413
- * Every route on this surface.
414
- *
415
- * Complete, always — a manifest that says "the routes are over there, run
416
- * this glob to find them" is not a manifest, it is a pointer to one. An
417
- * earlier version carried a `routes` glob for an application's own API and
418
- * left this empty, which meant the document *claimed no routes* while five
419
- * existed. Being incomplete is a gap; being wrong is worse.
420
- *
421
- * So a surface takes its endpoints as a list and reads method, path and
422
- * handler off them. That also removes a duplicate: the glob was written once
423
- * in `gkm.config.ts` and again on the construct, two strings that could
424
- * disagree about the same thing.
425
- */
426
- endpoints: readonly RestApiEndpoint[];
427
- /**
428
- * Other surfaces this one calls.
429
- *
430
- * **Not a dependency, and deliberately not spelled like one.** A dependency
431
- * is an injection: `resolveEdges` gives a function exactly the constructs it
432
- * declared and nothing else, which is what makes least privilege fall out of
433
- * the graph instead of out of discipline. A surface-level `dependencies`
434
- * would hand *every route* on this API whatever the surface named — which is
435
- * precisely the over-granting that rule exists to prevent.
436
- *
437
- * What this records is weaker and only flows one way: it puts this API's
438
- * origin on the called surface's trusted-origin list. Nothing links from it,
439
- * nothing is granted by it, and per-route edges stay on the endpoints where
440
- * they belong.
441
- */
442
- calls?: readonly Dependency[];
443
- /**
444
- * The `Telemetry` node this surface's process emits through, by id — an
445
- * edge, read by `dependenciesOf` like any other.
446
- */
447
- telemetry?: ConstructId;
448
- }
449
- /** One glob, or several. Mirrors the CLI's `Routes` without depending on it. */
450
- type Glob = string | readonly string[];
451
- /**
452
- * How the process serving a declaration is built and run.
453
- *
454
- * The half of an application that a graph genuinely cannot derive. *What*
455
- * exists — a surface, a site, the edges between them — is declared and read
456
- * back from the manifest. *Where its source lives and which globs find its
457
- * code* is not derivable from anything: it is a fact about a directory.
458
- *
459
- * It lives on the declaration rather than in a config `apps` block because the
460
- * two were the same list written twice, and the copy in config was the one that
461
- * could disagree. A site declared `path: 'apps/web'` and an app entry declared
462
- * `path: 'apps/web'`, and nothing checked them against each other; a surface
463
- * that config had no entry for simply never deployed.
464
- *
465
- * Only the declaration that *is* an app carries one. Two surfaces in one
466
- * process means one of them has the spec and the other collapses onto it —
467
- * which is the same rule that decides deploy units, now stated once.
468
- */
469
- interface AppSpec {
470
- /** Where its source lives, relative to the workspace root. */
471
- path: string;
472
- /**
473
- * The port it answers on locally.
474
- *
475
- * Optional, and normally omitted: ports are assigned in a stable order so
476
- * that adding a site does not renumber the others.
477
- */
478
- port?: number;
479
- telescope?: string | boolean | Record<string, unknown>;
480
- studio?: string | boolean | Record<string, unknown>;
481
- openapi?: boolean | Record<string, unknown>;
482
- runtime?: 'node' | 'bun';
483
- /** Env files to load, in order. */
484
- env?: Glob;
485
- /** Entry module for an app the build does not generate. */
486
- entry?: string;
487
- /** Modules to import when sniffing which env vars a frontend reads. */
488
- config?: {
489
- client?: string;
490
- server?: string;
491
- };
492
- }
493
- /**
494
- * A frontend — a construct like any other, which is what removes the last
495
- * mechanism that ran in parallel to the graph.
496
- *
497
- * Its edges are what make it worth declaring. A site depending on an API is the
498
- * single fact behind four things that are hand-maintained otherwise: the site's
499
- * build-time `VITE_API_URL`, the API's CORS origins, the auth server's trusted
500
- * origins, and which generated client lands in which app. None of those are
501
- * declared anywhere here, because all four are the *same* edge read from one
502
- * end or the other.
503
- *
504
- * `variant` is the framework, because the framework changes the code you write:
505
- * it selects how the values are delivered (`VITE_`, `NEXT_PUBLIC_`, a
506
- * `config.json`), never which values there are.
507
- */
508
- interface SiteDeclaration extends Node {
509
- kind: 'site';
510
- variant: 'static' | 'next' | 'tanstack';
511
- /**
512
- * The label it answers on under a stage's domain — `api` for
513
- * `api.myapp.com`, from `domains` — and under the project locally,
514
- * `api.myapp.localhost`. Absent, its id kebab-cased.
515
- */
516
- subdomain?: string;
517
- /**
518
- * How it is built and run, `path` included.
519
- *
520
- * Required: a site is always its own app, and says where it lives.
521
- */
522
- app: AppSpec;
523
- /**
524
- * Whether this is the site the base domain points at.
525
- *
526
- * Structural rather than config: *which* site is primary does not vary by
527
- * stage, even though its hostname does — that is what `app.domain` is for.
528
- *
529
- * Only meaningful when a project has more than one site, and then only when
530
- * none of them is named `web`. The convention still holds first, because it
531
- * is a convention people already rely on.
532
- */
533
- root?: boolean;
534
- /**
535
- * What it calls. On a node rather than on a handler because a site has no
536
- * single entrypoint — the whole app is the consumer.
537
- */
538
- dependencies: readonly Dependency[];
539
- /**
540
- * The `Telemetry` node it emits through, by id. A site is handed none of
541
- * the node's keys: they are a server's, and `PUBLIC.telemetry` is empty, so
542
- * nothing of it can reach a bundle.
543
- */
544
- telemetry?: ConstructId;
545
- }
546
- /**
547
- * A mobile app — Expo — declared like a site, for the same reason: its edges
548
- * are what make it worth declaring.
549
- *
550
- * It differs from a site in one way that matters. A browser is reached at an
551
- * origin; an app is reached through its URL *scheme*. So what a surface trusts
552
- * for it is `shop://`, not an address, and what it provides is that scheme —
553
- * the same on every stage.
554
- *
555
- * It ships through its own toolchain (EAS, the stores), so nothing deploys it:
556
- * it is an app the targets derive for, never one they run.
557
- */
558
- interface MobileAppDeclaration extends Node {
559
- kind: 'mobile-app';
560
- /** Which toolchain builds it. Expo is the one that ships. */
561
- variant: 'expo';
562
- /** Where it lives — `path` included. */
563
- app: AppSpec;
564
- /**
565
- * Its URL scheme: `shop`.
566
- *
567
- * Normally omitted, and then it is the project's name. The same on every
568
- * stage.
569
- */
570
- scheme?: string;
571
- /** What it calls — the surfaces whose URLs it is built with. */
572
- dependencies: readonly Dependency[];
573
- }
574
- /**
575
- * A process with no port.
576
- *
577
- * The sibling of `RestApiDeclaration`, and the answer to a question the model
578
- * could not previously state: *what runs this cron?* A cron, a subscriber and a
579
- * queue consumer all have to run somewhere, and until now the only thing that
580
- * said where was the directory the file happened to sit in — so a background
581
- * job was owned by a glob, and a project that was nothing but background jobs
582
- * had to declare an HTTP surface with no routes on it to be deployable at all.
583
- *
584
- * It takes no authorizer. A `RestApi` needs one because an HTTP surface can
585
- * ship open by omission; nothing calls a worker from outside, so there is no
586
- * default to get wrong. That difference is the reason these are two constructs
587
- * rather than one with a flag.
588
- */
589
- interface WorkerDeclaration extends Node {
590
- kind: 'worker';
591
- /**
592
- * A worker declares no app, because it is not one.
593
- *
594
- * It names the process that runs a cron, a subscriber or a queue consumer.
595
- * On a server target that process is a container of its own, built from the
596
- * app whose directory holds the work — so the worker needs no path to say
597
- * where its code is.
598
- */
599
- /** Surfaces and resources it calls, which is what grants it access. */
600
- dependencies?: readonly Dependency[];
601
- /** The `Telemetry` node its process emits through, by id. */
602
- telemetry?: ConstructId;
603
- }
604
- /** One route on a surface. */
605
- interface RestApiEndpoint extends Fn {
606
- method: string;
607
- path: string;
608
- authorizer?: string;
609
- }
610
- /**
611
- * A point-to-point queue and the single consumer that drains it.
612
- *
613
- * Provides one key, the producer's connection string. The protocol in it picks
614
- * the transport — `pgboss://` locally, `sqs://` deployed — so a producer names
615
- * no broker, exactly as a database consumer names no cloud.
616
- *
617
- * The consumer side provides nothing: a worker is reached *through* its queue,
618
- * so there is no second key and nothing can depend on a handler.
619
- */
620
- interface QueueDeclaration extends Node {
621
- kind: 'queue';
622
- /** FIFO ordering, where the transport offers it. */
623
- fifo?: boolean;
624
- /**
625
- * The single consumer that drains it.
626
- *
627
- * Nested rather than listed beside the queue, because **position carries the
628
- * trigger**: a handler here is reached by messages arriving on this queue and
629
- * by nothing else, so there is no `trigger` field to keep in step with it.
630
- */
631
- worker: Fn;
632
- }
633
- /**
634
- * A topic — pub/sub fan-out, one publisher and any number of subscribers.
635
- *
636
- * Like a queue it provides only the producer's string; a subscriber is bound to
637
- * the topic rather than depending on it, so the binding is an edge the deploy
638
- * target reads, not an env key. Locally both sides meet on the same pg-boss
639
- * connection, which is why the subscriber needs no key of its own.
640
- */
641
- interface TopicDeclaration extends Node {
642
- kind: 'topic';
643
- /** The event type names this topic carries. */
644
- events: readonly string[];
645
- /**
646
- * The handlers bound to it, each with the events it wants.
647
- *
648
- * Nested for the same reason a queue's worker is: position is the trigger. A
649
- * subscriber is *bound* to a topic rather than depending on it, which is why
650
- * it holds no key of its own and cannot be reached except through the topic.
651
- */
652
- subscribers: readonly (Fn & {
653
- events: readonly string[];
654
- })[];
655
- }
656
- /**
657
- * A function invoked directly, with no surface in front of it.
658
- *
659
- * An `Fn` rather than a `Node`, because a function *is* a handler — there is no
660
- * resource beside it to declare. It provides its own address, so something else
661
- * can depend on it and be given a way to call it.
662
- */
663
- interface FunctionDeclaration extends Fn {
664
- kind: 'function';
665
- }
666
- /**
667
- * A function on a schedule.
668
- *
669
- * The schedule is the trigger and it is structural: *that* something runs
670
- * nightly is a fact about the application, while which timezone a stage
671
- * interprets it in is not.
672
- */
673
- interface CronDeclaration extends Fn {
674
- kind: 'cron';
675
- /** A rate or cron expression, e.g. `rate(1 day)`. */
676
- schedule: string;
677
- }
678
- /**
679
- * Every declaration. A discriminated union, so `kind` gives exhaustiveness *and*
680
- * per-kind fields — there is no separate enum to keep in step, and no shape
681
- * carrying fields that belong to a different kind.
682
- */
683
- /**
684
- * What an application emits — traces and logs over OTLP — and never where it
685
- * goes: the deploy picks the provider and the sample rate, per stage.
686
- *
687
- * A node like any other, so each process that uses it is an edge from its
688
- * surface, worker or site, and the keys it provides reach exactly those
689
- * processes. The keys are OpenTelemetry's own, not the node's id: an SDK reads
690
- * `OTEL_EXPORTER_OTLP_ENDPOINT` and nothing else.
691
- */
692
- interface TelemetryDeclaration extends Node {
693
- kind: 'telemetry';
694
- /** Request paths no span is recorded for — `/health`. A trailing `*` matches a prefix. */
695
- ignorePaths?: readonly string[];
696
- /** Resource attributes every span and log carries — `service.namespace`. */
697
- attributes?: Readonly<Record<string, string>>;
698
- }
699
- /**
700
- * What a `Telemetry` node provides to each process with an edge to it.
701
- *
702
- * `OTEL_SERVICE_NAME` is resolved per process — the app's name — so it is
703
- * listed here but composed by whichever target hands it over.
704
- */
705
- export declare const TELEMETRY_KEYS: readonly ['OTEL_EXPORTER_OTLP_ENDPOINT', 'OTEL_EXPORTER_OTLP_HEADERS', 'OTEL_SERVICE_NAME', 'OTEL_TRACES_SAMPLER', 'OTEL_TRACES_SAMPLER_ARG'];
706
- type TelemetryKey = (typeof TELEMETRY_KEYS)[number];
707
- type Declaration = ObjectsDeclaration | FileServerDeclaration | EmailDeclaration | DatabaseDeclaration | DatabaseReaderDeclaration | DatabaseSchemaDeclaration | CacheDeclaration | SecretDeclaration | CredentialDeclaration | EncryptionDeclaration | RestApiDeclaration | SiteDeclaration | MobileAppDeclaration | WorkerDeclaration | OidcDeclaration | ExternalApiDeclaration | QueueDeclaration | TopicDeclaration | FunctionDeclaration | CronDeclaration | TelemetryDeclaration;
708
- /** A declaration that provisions nothing of its own and names a parent. */
709
- /**
710
- * A declaration that names a parent.
711
- *
712
- * A union rather than an `Extract`, because one kind is *optionally* derived: a
713
- * cache lives in a database when it was declared from one and stands alone
714
- * otherwise, so `of` is optional on it and an `Extract<…, { of: ConstructId }>`
715
- * would not select it. {@link isDerived} tests the value rather than the kind
716
- * for exactly that reason.
717
- */
718
- type DerivedDeclaration = Extract<Declaration, {
719
- of: ConstructId;
720
- }> | CacheDeclaration;
721
- type DerivedKind = DerivedDeclaration['kind'];
722
- /**
723
- * What each kind may derive from.
724
- *
725
- * Small enough to state exhaustively, and stating it makes cycles impossible
726
- * without a graph walk: readers are terminal, so no chain can return to its
727
- * start. There is no `writer` — the database *is* the writer, which is what
728
- * keeps a replica from being reached by accident.
729
- */
730
- export declare const DERIVES_FROM: Readonly<Record<DerivedKind, readonly string[]>>;
731
- type DeclarationKind = Declaration['kind'];
1
+ //#region src/index.d.ts
732
2
  /**
733
- * The manifest: every construct keyed by its id.
734
- *
735
- * Flat rather than grouped, because `Dependency.target` resolves as
736
- * `m[target]` — a lookup that stays O(1) and identical whether the edge points
737
- * at a resource, a surface, or another function.
3
+ * Deployment manifest types — the build output of `gkm build` that enumerates a
4
+ * project's deployable units (routes, functions, crons, subscribers, queues)
5
+ * with the metadata an infrastructure layer needs to provision them.
738
6
  *
739
- * Use it as a **constraint, not an annotation**. `gkm build` emits
740
- * `as const satisfies ConstructManifest`, which checks the shape while keeping
741
- * every id, kind, and provided key a literal — annotating with this type
742
- * instead would widen them all to `string` and consumers could no longer select
743
- * anything:
7
+ * `gkm build` writes a single TypeScript module per provider
8
+ * (`<out>/manifest/aws.ts`) of the form:
744
9
  *
745
10
  * ```ts
746
- * export const constructs = {
747
- * Uploads: { kind: 'objects', id: 'Uploads', provides: ['UPLOADS_URL'] },
748
- * } as const satisfies ConstructManifest;
749
- *
750
- * type Ids = IdsOf<typeof constructs>; // 'Uploads'
751
- * type Env = ProvidedKeys<typeof constructs, 'Uploads'>; // 'UPLOADS_URL'
11
+ * export const manifest = { routes: [...], functions: [...], ... } as const;
12
+ * export type Route = (typeof manifest.routes)[number];
13
+ * // ...derived types
752
14
  * ```
753
- */
754
- type ConstructManifest = Readonly<Record<ConstructId, Declaration>>;
755
- /** Every id in a manifest. */
756
- type IdsOf<M extends ConstructManifest> = Extract<keyof M, string>;
757
- /** The declaration for one id. */
758
- type DeclarationOf<M extends ConstructManifest, K extends IdsOf<M>> = M[K];
759
- /** Every id of a given kind — what an adapter iterates when provisioning. */
760
- type IdsOfKind<M extends ConstructManifest, K extends DeclarationKind> = { [Id in IdsOf<M>]: M[Id]['kind'] extends K ? Id : never; }[IdsOf<M>];
761
- /** The env keys one construct provides. */
762
- type ProvidedKeys<M extends ConstructManifest, K extends IdsOf<M>> = M[K] extends {
763
- provides: readonly (infer P)[];
764
- } ? P : never;
765
- /** Every env key any construct in the manifest provides. */
766
- type AllProvidedKeys<M extends ConstructManifest> = { [Id in IdsOf<M>]: ProvidedKeys<M, Id>; }[IdsOf<M>];
767
- /**
768
- * What each kind provides, by role rather than by provider syntax.
769
- *
770
- * This is the contract between the construct that declares a key and the cloud
771
- * component that supplies its value — an interface rather than shared code,
772
- * because a shared codec would have to contain `bucket` and `region`, and
773
- * provider words in the neutral layer is the problem this design exists to fix.
774
- *
775
- * How a value is composed and parsed stays private to each provider pair, so
776
- * `s3://` and `gs://` never appear here.
777
- */
778
- interface ProvidesByKind {
779
- objects: {
780
- url: string;
781
- };
782
- /** Where the served objects answer. Public: a browser is the point of it. */
783
- 'file-server': {
784
- url: string;
785
- };
786
- /**
787
- * An `smtp://` URL, credentials included — never shippable — and the
788
- * identity mail is sent from.
789
- *
790
- * The sending address is the one thing about mail that genuinely differs per
791
- * stage (`myapp.test` locally, a verified domain deployed), so it travels
792
- * beside the URL rather than being written into the construct.
793
- */
794
- email: {
795
- url: string;
796
- from: string;
797
- /**
798
- * Where the mail it sent can be read — Mailpit's web inbox. Local
799
- * stages only: deployed mail has no inbox, so nothing deployed supplies
800
- * it, and an app that reads it falls back to the phone's own mail app.
801
- */
802
- inboxUrl?: string;
803
- };
804
- /**
805
- * One key, the runtime role's. The owner URL is not here by design — see
806
- * {@link DatabaseDeclaration}.
807
- */
808
- database: {
809
- url: string;
810
- };
811
- 'database-reader': {
812
- url: string;
813
- };
814
- 'database-schema': {
815
- url: string;
816
- };
817
- /** The endpoint and its token, in one string. */
818
- cache: {
819
- url: string;
820
- };
821
- /**
822
- * Where the surface answers, who may call it, and the domain its cookies
823
- * are scoped to.
824
- *
825
- * Only `url` is a fact about the surface itself. The other two are read off
826
- * its *inbound* edges — every construct that depends on it — which is why a
827
- * surface never lists its own callers: nothing enumerates the things that
828
- * point at it, the graph already does.
829
- *
830
- * `trustedOrigins` and `cookieDomain` are one key each rather than a list
831
- * and a structure, because both cross a process boundary as environment.
832
- */
833
- 'rest-api': {
834
- url: string;
835
- /** Comma-separated. Empty when nothing declares an edge to this surface. */
836
- trustedOrigins: string;
837
- /**
838
- * The parent domain shared by the surface and its callers, leading dot
839
- * included — `.example.com`. Absent where there is nothing to share:
840
- * one host locally, unrelated hosts deployed.
841
- */
842
- cookieDomain: string;
843
- };
844
- /** The value itself. A secret has no address to hand out instead. */
845
- secret: {
846
- value: string;
847
- };
848
- /**
849
- * The credential as one JSON object, parsed and validated by the construct
850
- * that declared the schema.
851
- */
852
- credential: {
853
- credentials: string;
854
- };
855
- /** Where the key is: a keyring, or a KMS key and its HMAC key. */
856
- encryption: {
857
- url: string;
858
- };
859
- /**
860
- * The producer's connection string. One key, not two: the consumer is
861
- * reached through the queue rather than by an address of its own.
862
- */
863
- queue: {
864
- publisherConnectionString: string;
865
- };
866
- topic: {
867
- publisherConnectionString: string;
868
- };
869
- /** Where to invoke it — what lets something else depend on a function. */
870
- function: {
871
- url: string;
872
- };
873
- /** A cron is reached by its schedule and by nothing else, so it provides none. */
874
- cron: Record<never, never>;
875
- /** Where the site is served. Public for the same reason an API's is. */
876
- site: {
877
- url: string;
878
- };
879
- /**
880
- * The URL scheme it answers on — `shop`, on every stage. What its
881
- * `app.config.ts` registers and what the surfaces it calls trust.
882
- */
883
- 'mobile-app': {
884
- scheme: string;
885
- };
886
- /**
887
- * Where tokens come from and which audience they must carry.
888
- *
889
- * Two keys because the halves have different lifetimes: the issuer may be a
890
- * fact about the product, the audience is always a fact about one
891
- * deployment.
892
- */
893
- oidc: {
894
- issuer: string;
895
- audience: string;
896
- };
897
- /**
898
- * Where it answers — the provider, or its fake — and the credentials it
899
- * was issued for this stage, as one JSON object.
900
- */
901
- 'external-api': {
902
- url: string;
903
- credentials: string;
904
- };
905
- /**
906
- * A worker is reached by nothing — it reaches out, to a queue, a schedule,
907
- * a topic. So it publishes no address, the way a cron does not.
908
- */
909
- worker: Record<never, never>;
910
- telemetry: Record<never, never>;
911
- }
912
- type Provides<K extends keyof ProvidesByKind> = ProvidesByKind[K];
913
- /**
914
- * Which provided values may be shipped to a browser.
915
- *
916
- * Drives client-side prefixing (`VITE_`, `NEXT_PUBLIC_`) and nothing else — it
917
- * is not a restriction on what may be depended on, since a server-side consumer
918
- * can legitimately use any of them. A bucket's `url` presigns and stays private.
919
- */
920
- export declare const PUBLIC: { readonly [K in keyof ProvidesByKind]: readonly (keyof ProvidesByKind[K])[]; };
921
- //#endregion
922
- //#region src/derive.d.ts
923
- /** Whether a declaration names a parent. */
924
- export declare function isDerived(declaration: Declaration): declaration is DerivedDeclaration & {
925
- of: ConstructId;
926
- };
927
- /**
928
- * Check every derived construct against its parent.
929
- *
930
- * Two rules: the parent exists, and its kind may vend this one. Together they
931
- * make cycles unreachable — a reader is terminal, so no chain of `of` can
932
- * return to where it started, and no walk is needed to prove it.
933
- */
934
- export declare function assertDerivations(manifest: ConstructManifest): void;
935
- /**
936
- * The order constructs must be provisioned in: every parent before its children.
937
- *
938
- * Resources are leaves and so come first in any order; only derived nodes
939
- * constrain the sequence, and they form a shallow forest rather than a general
940
- * graph. This walks each node's ancestors on demand instead of running a full
941
- * topological sort, which is the same result at this depth and reads as what it
942
- * is.
943
- *
944
- * Assumes {@link assertDerivations} has passed — a missing parent would
945
- * otherwise be a silent omission here rather than an error.
946
- */
947
- export declare function provisionOrder(manifest: ConstructManifest): string[];
948
- /**
949
- * Every edge a declaration carries, wherever the kind happens to keep them.
950
- *
951
- * Dependencies live in two places by design: on a node when the whole construct
952
- * is the consumer (a site), and on each nested handler when the construct is a
953
- * surface (a `rest-api`, whose routes each depend on their own things and
954
- * nothing more). Flattening that difference here is what lets every consumer of
955
- * the graph — reverse lookups, filtering, reference checks — ask one question.
956
- */
957
- export declare function dependenciesOf(declaration: Declaration): readonly Dependency[];
958
- /**
959
- * The ids that depend on one construct — the graph read backwards.
960
- *
961
- * This is the whole mechanism behind CORS origins and trusted origins. Both are
962
- * lists of *callers*, and a caller is exactly an inbound edge, so neither is
963
- * ever written down: a surface that listed its own callers would have to be
964
- * edited every time something new called it, which is the hand-maintained list
965
- * this replaces.
966
- *
967
- * Sorted, because it feeds a comma-separated env value that would otherwise
968
- * change whenever the manifest's key order did — and a value that churns is a
969
- * container that redeploys for no reason.
970
- */
971
- export declare function dependentsOf(manifest: ConstructManifest, id: ConstructId): string[];
972
- /**
973
- * How each site variant names a value it ships to the browser.
974
- *
975
- * The prefix *is* the framework's contract — `VITE_`, `NEXT_PUBLIC_` and
976
- * `EXPO_PUBLIC_` all mean "inline this into the bundle" — so it is the one thing
977
- * a variant changes, and it changes nothing else.
978
- */
979
- export declare const PUBLIC_PREFIX: Record<SiteDeclaration['variant'] | MobileAppDeclaration['variant'], string>;
980
- /**
981
- * The keys a site's bundle needs, mapped to the key each value comes from —
982
- * `{ VITE_API_URL: 'API_URL' }`.
983
- *
984
- * A rename, not a second derivation: `API_URL` is resolved once, by whatever
985
- * resolved it for the server, and the site reads the same value under the name
986
- * its bundler will inline. That is what keeps a site and its API from coming to
987
- * disagree about where the API is.
988
- *
989
- * Filtered by `PUBLIC` rather than by what the site asked for. A site may
990
- * legitimately depend on anything — its server half, where it has one, reads env
991
- * exactly as a function does — so this is not a restriction on edges. It decides
992
- * one thing: which values may be prefixed into a bundle, which is what keeps
993
- * `ORDERS_URL` and its password out of a JavaScript file served to the public.
994
- *
995
- * Shared by every target for the same reason `providedKeyFor` is: a site built
996
- * locally and the same site built by a deploy must inline the same names.
997
- */
998
- export declare function publicEnvFor(declaration: SiteDeclaration | MobileAppDeclaration, manifest: ConstructManifest): Record<string, string>;
999
- //#endregion
1000
- //#region src/encryption.d.ts
1001
- /**
1002
- * The `kms://` URL an `Encryption` construct reads on AWS.
1003
- *
1004
- * Here because two packages meet on it: `@geekmidas/cloud` composes it from
1005
- * the keys it provisioned, and `@geekmidas/constructs` parses it to reach
1006
- * them. One function, so the two cannot disagree about a parameter's name.
1007
- */
1008
- export declare const KMS_SCHEME = "kms:";
1009
- /**
1010
- * `kms://<region>?key=<key arn>&index=<hmac key arn>[&endpoint=<url>]`
1011
- *
1012
- * `endpoint` reaches an emulator instead of AWS — floci, locally and in tests —
1013
- * the same parameter an `sns://` connection string carries for the same reason.
1014
- */
1015
- export declare function kmsUrl(input: {
1016
- region: string;
1017
- key: string;
1018
- index: string;
1019
- endpoint?: string;
1020
- }): string;
1021
- //#endregion
1022
- //#region src/errors.d.ts
1023
- /**
1024
- * Manifest errors.
1025
- *
1026
- * Messages state the rule, which is constant; the offending value is a field.
1027
- * An interpolated message cannot be matched on, reads differently every time it
1028
- * is thrown, and carries user input into every log line that touches it.
1029
- */
1030
- /** A construct id that cannot survive the names derived from it. */
1031
- export declare class InvalidConstructId extends Error {
1032
- /** What was passed in. */
1033
- readonly input: string;
1034
- /** What canonicalising it produced, which is what failed the rule. */
1035
- readonly canonical: string;
1036
- constructor(input: string, canonical: string);
1037
- }
1038
- /** A derived construct naming a parent the manifest does not contain. */
1039
- export declare class UnknownParent extends Error {
1040
- /** The derived construct. */
1041
- readonly id: string;
1042
- /** The parent it named. */
1043
- readonly of: string;
1044
- /** Ids the manifest does contain, for the caller to match against. */
1045
- readonly available: readonly string[];
1046
- constructor(id: string, of: string, available: readonly string[]);
1047
- }
1048
- /**
1049
- * A derived construct naming a parent that may not vend it — a reader of a
1050
- * reader, a schema of a schema.
1051
- */
1052
- export declare class IllegalDerivation extends Error {
1053
- readonly id: string;
1054
- readonly kind: string;
1055
- /** The kind of the parent it named. */
1056
- readonly parentKind: string;
1057
- /** The parent kinds that may vend this one. */
1058
- readonly allowed: readonly string[];
1059
- constructor(id: string, kind: string, parentKind: string, allowed: readonly string[]);
1060
- }
1061
- //#endregion
1062
- //#region src/external.d.ts
1063
- /** The key in a by-stage `url` that answers for every stage not listed. */
1064
- export declare const DEFAULT_STAGE_URL = "default";
1065
- /**
1066
- * Where an external API answers on a deployed stage.
1067
- *
1068
- * One URL answers every stage; a record answers by stage name, falling back
1069
- * to `default`. Read at build and deploy, so a stage nobody gave a URL fails
1070
- * there — with the stage named — rather than at the first request.
1071
- *
1072
- * @throws {NoUrlForStage} when a record names neither the stage nor a default.
1073
- */
1074
- export declare function externalApiUrl(declaration: Pick<ExternalApiDeclaration, 'id' | 'url'>, stage: string): string;
1075
- /** An external API's `url` record has nothing for the stage being deployed. */
1076
- export declare class NoUrlForStage extends Error {
1077
- readonly id: string;
1078
- readonly stage: string;
1079
- readonly stages: readonly string[];
1080
- constructor(id: string, stage: string, stages: readonly string[]);
1081
- }
1082
- //#endregion
1083
- //#region src/headers.d.ts
1084
- /**
1085
- * Headers gkm reserves for its own services' calls to each other.
1086
- *
1087
- * Shared by `@geekmidas/constructs`, which sends and reads them, and
1088
- * `@geekmidas/cli`, whose edges strip them from every request that arrives
1089
- * from outside — so the two cannot name them differently.
1090
- */
1091
- /**
1092
- * The address of the client a service-to-service call is made on behalf of.
1093
- *
1094
- * An API asking its auth server for a session sends the client's address in
1095
- * this rather than `X-Forwarded-For`: a forwarding header marks a request as
1096
- * outside traffic, which would start the auth server's span in a new trace,
1097
- * and the address is still needed — Better Auth rate-limits per client.
1098
- *
1099
- * Reserved: the stack's edge (Caddy, Traefik, the `gkm dev` edge) removes it
1100
- * from incoming requests, and the auth server ignores it on any request that
1101
- * is not an internal caller's, so it cannot be spoofed from outside.
1102
- */
1103
- export declare const CLIENT_IP_HEADER = "x-gkm-client-ip";
1104
- //#endregion
1105
- //#region src/migrations.d.ts
1106
- /** Where every construct's folder lives, from the project root. */
1107
- export declare const MIGRATIONS_ROOT = "db";
1108
- /** The two folders a construct's folder holds, and nothing else. */
1109
- export declare const DATABASE_FOLDERS: readonly ['migrations', 'seeds'];
1110
- /** One construct whose schema is changed by migrations. */
1111
- interface MigrationTarget {
1112
- /** The construct's canonical id, e.g. `AuthDatabase`. */
1113
- id: ConstructId;
1114
- /** Its folder, relative to the project root: `db/auth-database`. */
1115
- folder: string;
1116
- /** Its schema's history: `db/auth-database/migrations`. */
1117
- migrations: string;
1118
- /** The reference data it needs to run: `db/auth-database/seeds`. */
1119
- seeds: string;
1120
- /** For a schema tenant, the database it lives in. */
1121
- of?: ConstructId;
1122
- }
1123
- /**
1124
- * The constructs that take migrations, parents before the tenants inside them.
1125
- *
1126
- * A database and each schema tenant: every one has an owner role and a schema
1127
- * of its own, and so a history of its own. A reader has neither — it reads its
1128
- * parent's schema — and a cache's table is created by provisioning, not by a
1129
- * migration anyone writes.
1130
- */
1131
- export declare function migrationTargets(manifest: ConstructManifest): MigrationTarget[];
1132
- /**
1133
- * A construct's folder, relative to the project root.
1134
- *
1135
- * Named by the construct — `AuthDatabase` is `db/auth-database` — in the case
1136
- * every other name outside TypeScript takes. Lowercase, because a folder that
1137
- * differs from another only in case is two folders on Linux and one on a Mac;
1138
- * and `canonicalId` reads it back to the construct exactly, which is how a
1139
- * folder naming nothing is caught rather than skipped.
1140
- */
1141
- export declare function databaseFolder(id: ConstructId): string;
1142
- /** Where a construct's migrations live: `db/auth-database/migrations`. */
1143
- export declare function migrationFolder(id: ConstructId): string;
1144
- /**
1145
- * Where a construct's seeds live: `db/auth-database/seeds`.
1146
- *
1147
- * Reference data the app needs to run — a permission catalogue, roles, lookup
1148
- * tables. Seeds are upserts run on every pass, after the migrations, so a
1149
- * changed seed is applied by running it again. Sample data is factories'.
1150
- */
1151
- export declare function seedFolder(id: ConstructId): string;
1152
- //#endregion
1153
- //#region src/mobile.d.ts
1154
- /**
1155
- * How a mobile app is reached: its URL scheme, and the origins a surface trusts
1156
- * for it.
1157
- *
1158
- * Stated once, here, because three targets ask — the local one (`gkm dev`,
1159
- * `gkm test`), Dokploy and AWS — and a scheme one of them derived differently
1160
- * would be an app whose sign-in callback the auth server refuses on exactly one
1161
- * of them.
1162
- */
1163
- /**
1164
- * An app's scheme: the one given, or the project's name made a valid scheme.
1165
- *
1166
- * One for every stage. A development build and the store build register the
1167
- * same scheme, so the auth server of whichever stage a build talks to trusts
1168
- * it, and nothing about the app differs between stages but its URLs.
1169
15
  *
1170
- * A scheme is a letter followed by letters, digits, `+`, `-` or `.` — so
1171
- * `Corner Shop` becomes `corner-shop`, and a name that starts with a digit gets a
1172
- * leading `app`.
16
+ * This is the dependency-free data contract shared between the producer
17
+ * (`@geekmidas/cli`) and consumers (e.g. `@geekmidas/cloud/sst`'s `fromManifest`
18
+ * integrators).
1173
19
  */
1174
- export declare function schemeBase(project: string, given?: string): string;
1175
- /** Where an Expo development server runs, for the `exp://` origins it sends. */
1176
- interface MetroHost {
1177
- /** The hosts a device reaches it on: the LAN address, `localhost`. */
1178
- hosts: readonly string[];
1179
- /** Metro's port, when the target placed it — otherwise any port. */
1180
- port?: number;
1181
- }
1182
- /**
1183
- * The origins a surface trusts for a mobile caller.
1184
- *
1185
- * Better Auth's Expo plugin sends the app's scheme as its origin
1186
- * (`shop://`), and a link can carry a path after it (`shop://*`).
1187
- * In development, Expo Go sends `exp://<host>:<port>` instead — so a local
1188
- * stage adds the hosts a device actually reaches Metro on, and Metro's port
1189
- * when the target placed it: a whole subnet is not trusted because one phone
1190
- * is on it.
1191
- */
1192
- export declare function mobileOrigins(scheme: string, metro?: MetroHost): string[];
1193
- /** Whether an origin is a web one — what a cookie domain is derived from. */
1194
- export declare function isWebOrigin(origin: string): boolean;
1195
- //#endregion
1196
- //#region src/naming.d.ts
1197
- /**
1198
- * `UPPER_SNAKE_CASE`, with numbers kept against the word they follow.
1199
- *
1200
- * Matches `environmentCase` in `@geekmidas/envkit`, which reads the values these
1201
- * names key. The two must agree exactly, so this is the implementation and that
1202
- * one should defer to it.
1203
- *
1204
- * @example environmentCase('sendEmail') // 'SEND_EMAIL'
1205
- * @example environmentCase('api2') // 'API2' (digit joins its word)
1206
- */
1207
- export declare function environmentCase(name: string): string;
1208
20
  /**
1209
- * The env key a construct provides for one of its roles.
1210
- *
1211
- * @example provideKey('Uploads', 'url') // 'UPLOADS_URL'
1212
- * @example provideKey('Uploads', 'cdnUrl') // 'UPLOADS_CDN_URL'
21
+ * A manifest field is either a flat list or, when the build is partitioned
22
+ * (e.g. by authorizer), an object keyed by partition name. Readonly-tolerant so
23
+ * the `as const` generated manifest assigns cleanly.
1213
24
  */
1214
- export declare function provideKey(id: string, role: string): string;
1215
- /**
1216
- * A construct's canonical id — PascalCase.
1217
- *
1218
- * `uploads`, `Uploads`, `user_uploads`, and `user-uploads` all canonicalise to
1219
- * the same id, so declaring two of them is a duplicate rather than a collision
1220
- * to detect.
1221
- *
1222
- * Runtime only. Writing the id in PascalCase is what keeps the *type* usable:
1223
- * the service key is `Uncapitalize<TName>`, a TypeScript intrinsic, so no
1224
- * type-level transform is needed and none has to be kept in step with this one.
1225
- *
1226
- * @example canonicalId('user-uploads') // 'UserUploads'
1227
- */
1228
- export declare function canonicalId(input: string): string;
1229
- /**
1230
- * The key a construct is reached under in the service record.
1231
- *
1232
- * The runtime twin of `Uncapitalize<TName>`, which types it — they must agree,
1233
- * so they live next to each other rather than being re-derived by each
1234
- * construct.
1235
- *
1236
- * @example serviceKey('UserUploads') // 'userUploads' → services.userUploads
1237
- */
1238
- export declare function serviceKey(id: string): string;
1239
- /**
1240
- * The table a cache keeps its entries in, when nobody named one.
1241
- *
1242
- * Derived from the cache's own id rather than fixed at `cache`, because a
1243
- * database may hold more than one and two caches sharing a table share a
1244
- * keyspace — `orders.cache('Sessions')` and `orders.cache('Rates')` would
1245
- * silently read each other's entries and evict each other's keys.
1246
- *
1247
- * Prefixed rather than suffixed so every cache sorts together in `\dt`, and
1248
- * prefixed at all so a cache named for a thing the application also stores —
1249
- * `orders.cache('Users')` — cannot collide with the table holding that thing.
1250
- *
1251
- * Read by whoever composes the URL and by whoever creates the table, so both
1252
- * default the same way.
1253
- *
1254
- * @example cacheTable('Sessions') // 'cache_sessions'
1255
- */
1256
- export declare function cacheTable(id: string): string;
1257
- /**
1258
- * Kebab-cases an identifier, acronym- and digit-aware.
1259
- *
1260
- * `userName` → `user-name`, `APIKey` → `api-key`, `S3Bucket` → `s3-bucket`.
1261
- *
1262
- * The last of those is why this is here rather than `snakecase(id)` with the
1263
- * underscores swapped: lodash splits a digit from the letter beside it, so
1264
- * `S3Bucket` became `s-3-bucket` on one provider and `s3-bucket` on the other.
1265
- * Two implementations of one rule, agreeing on every id anybody had tried.
1266
- *
1267
- * `environmentCase` already corrected for the same thing in the other
1268
- * direction — `api2` keeps its digit — so the two spellings of "kebab this id"
1269
- * in this file did not even agree with each other.
1270
- */
1271
- export declare function kebabCase(value: string): string;
1272
- /**
1273
- * The physical name a target provisions a construct under — lowercase kebab,
1274
- * scoped so two stages or apps sharing an account cannot collide.
1275
- *
1276
- * **One rule, every provider.** A construct is named the same thing on AWS and
1277
- * on Dokploy, which is what lets a name be read across them: `Database` in the
1278
- * `production` stage of `kitchen-sink` is `production-kitchen-sink-database`
1279
- * wherever it lands. The SST target's `prefixedName` is this function under
1280
- * another signature and defers to it.
1281
- *
1282
- * Idempotent in its prefix: an id that already carries the scope is not given a
1283
- * second one, so composing names cannot double up.
1284
- *
1285
- * @example cloudName({ stage: 'prod', app: 'myapp' }, 'UserUploads')
1286
- * // 'prod-myapp-user-uploads'
1287
- */
1288
- export declare function cloudName(scope: {
1289
- stage: string;
1290
- app: string;
1291
- }, id: string): string;
1292
- /**
1293
- * {@link cloudName} for a caller that holds its scope as a list.
1294
- *
1295
- * The SST target's stacks add a segment of their own, so the prefix is not
1296
- * always two parts — which is the only reason this signature exists.
1297
- */
1298
- export declare function scopedName(scope: readonly string[], id: string): string;
1299
- /**
1300
- * The domain a cookie must be scoped to so a surface and its callers share it.
1301
- *
1302
- * Derived from the addresses rather than configured, for the same reason the
1303
- * origins are: the set of things that talk to a surface is already in the graph,
1304
- * and the domain they have in common is a fact about that set. Returned with the
1305
- * leading dot a `Domain` attribute wants.
1306
- *
1307
- * Returns `undefined` when there is nothing to scope, which is the common case
1308
- * and not a failure:
1309
- *
1310
- * - **One host.** Locally everything is `localhost` on different ports, and
1311
- * cookies ignore the port — so a `Domain` would add nothing and `.localhost`
1312
- * is not a domain a browser will accept.
1313
- * - **Nothing in common.** Unrelated hosts cannot share a cookie at all, and
1314
- * emitting the longest common suffix anyway would be a value that silently
1315
- * fails to set.
1316
- *
1317
- * **The public-suffix limit, stated rather than discovered.** Two apps on
1318
- * `a.vercel.app` and `b.vercel.app` share `.vercel.app`, which every browser
1319
- * rejects because it is a registrable suffix rather than a registrable domain.
1320
- * Resolving that correctly needs the Public Suffix List, which is a downloaded,
1321
- * expiring dataset — so this requires at least two labels and otherwise trusts
1322
- * the addresses, and the value stays overridable for the case it gets wrong.
1323
- */
1324
- export declare function cookieDomain(urls: readonly string[]): string | undefined;
1325
- /**
1326
- * The env key a construct's provided role actually becomes.
1327
- *
1328
- * Almost always `provideKey(id, role)` — and `secret` is the exception, because
1329
- * a secret's *name* is its key: `Auth` signs with `AUTH_SECRET`, which is also
1330
- * what better-auth's own tooling looks for, and qualifying it by role would
1331
- * produce `AUTH_SECRET_VALUE`.
1332
- *
1333
- * It lives here rather than in each target because two targets deriving the
1334
- * same key separately is exactly the drift the app/infra contract check exists
1335
- * to catch — and a check deriving the key differently from the thing it checks
1336
- * cannot catch anything.
1337
- */
1338
- export declare function providedKeyFor(id: string, kind: DeclarationKind, role: string): string;
1339
- //#endregion
1340
- //#region src/index.d.ts
25
+ type ManifestField<T> = readonly T[] | Readonly<Record<string, readonly T[]>>;
26
+ /** Flatten a manifest field (array or partitioned) into a plain array. */
27
+ declare function flattenManifestField<T>(field: ManifestField<T> | undefined): T[];
1341
28
  /** A single HTTP route. */
1342
- export interface RouteInfo {
29
+ interface RouteInfo {
1343
30
  /** Route path, e.g. `/users/{id}`. */
1344
31
  path: string;
1345
32
  /** HTTP method, e.g. `GET`. */
@@ -1350,37 +37,19 @@ export interface RouteInfo {
1350
37
  memorySize?: number;
1351
38
  /** Required environment variables (a trailing `?` marks an optional var). */
1352
39
  environment?: readonly string[];
1353
- /**
1354
- * The constructs this handler declared an edge to, by id.
1355
- *
1356
- * What `.dependsOn()` was given, carried through the build so the manifest
1357
- * records the edge rather than only its shadow. `environment` is that shadow —
1358
- * the keys the handler reads — and it cannot be turned back into edges, which
1359
- * is why both exist and only this one grants anything.
1360
- */
1361
- dependencies?: readonly string[];
1362
40
  /** Authorizer name: `none`, `iam`, or a declared authorizer. */
1363
41
  authorizer: string;
1364
42
  }
1365
43
  /** A standalone Lambda function. */
1366
- export interface FunctionInfo {
44
+ interface FunctionInfo {
1367
45
  name: string;
1368
46
  handler: string;
1369
47
  timeout?: number;
1370
48
  memorySize?: number;
1371
49
  environment?: readonly string[];
1372
- /**
1373
- * The constructs this handler declared an edge to, by id.
1374
- *
1375
- * What `.dependsOn()` was given, carried through the build so the manifest
1376
- * records the edge rather than only its shadow. `environment` is that shadow —
1377
- * the keys the handler reads — and it cannot be turned back into edges, which
1378
- * is why both exist and only this one grants anything.
1379
- */
1380
- dependencies?: readonly string[];
1381
50
  }
1382
51
  /** A scheduled (cron) function. */
1383
- export interface CronInfo {
52
+ interface CronInfo {
1384
53
  name: string;
1385
54
  handler: string;
1386
55
  /** Schedule expression, e.g. `rate(1 day)` or `cron(0 12 * * ? *)`. */
@@ -1388,44 +57,26 @@ export interface CronInfo {
1388
57
  timeout?: number;
1389
58
  memorySize?: number;
1390
59
  environment?: readonly string[];
1391
- /**
1392
- * The constructs this handler declared an edge to, by id.
1393
- *
1394
- * What `.dependsOn()` was given, carried through the build so the manifest
1395
- * records the edge rather than only its shadow. `environment` is that shadow —
1396
- * the keys the handler reads — and it cannot be turned back into edges, which
1397
- * is why both exist and only this one grants anything.
1398
- */
1399
- dependencies?: readonly string[];
1400
60
  }
1401
61
  /** An event subscriber function (topic/queue resolved by `transport`). */
1402
- export interface SubscriberInfo {
62
+ interface SubscriberInfo {
1403
63
  name: string;
1404
64
  handler: string;
1405
65
  subscribedEvents: readonly string[];
1406
66
  /** Delivery transport — `topic` (SNS fan-out) or `queue` (SQS). */
1407
67
  transport?: 'topic' | 'queue';
1408
- /** The {@link TopicInfo.name} this subscriber binds to (via `worker.topic(topic)`). */
68
+ /** The {@link TopicInfo.name} this subscriber binds to (via `s.topic(topic)`). */
1409
69
  topic?: string;
1410
70
  timeout?: number;
1411
71
  memorySize?: number;
1412
72
  environment?: readonly string[];
1413
- /**
1414
- * The constructs this handler declared an edge to, by id.
1415
- *
1416
- * What `.dependsOn()` was given, carried through the build so the manifest
1417
- * records the edge rather than only its shadow. `environment` is that shadow —
1418
- * the keys the handler reads — and it cannot be turned back into edges, which
1419
- * is why both exist and only this one grants anything.
1420
- */
1421
- dependencies?: readonly string[];
1422
73
  }
1423
74
  /**
1424
75
  * A pub/sub topic — fan-out. A *resource* (no handler): it declares the event
1425
76
  * contract; producers publish via the derived publisher and {@link SubscriberInfo}s
1426
77
  * bind to it. Infra provisions an SNS topic.
1427
78
  */
1428
- export interface TopicInfo {
79
+ interface TopicInfo {
1429
80
  name: string;
1430
81
  /** The event type names this topic carries. */
1431
82
  events: readonly string[];
@@ -1433,7 +84,7 @@ export interface TopicInfo {
1433
84
  fifo?: boolean;
1434
85
  }
1435
86
  /** A queue worker — a queue and its single consumer. */
1436
- export interface QueueInfo {
87
+ interface QueueInfo {
1437
88
  name: string;
1438
89
  handler: string;
1439
90
  /** SQS event-source batch size. */
@@ -1443,13 +94,21 @@ export interface QueueInfo {
1443
94
  timeout?: number;
1444
95
  memorySize?: number;
1445
96
  environment?: readonly string[];
1446
- /**
1447
- * The constructs the worker declared an edge to, by id.
1448
- *
1449
- * See {@link RouteInfo.dependencies} — same field, same reason.
1450
- */
1451
- dependencies?: readonly string[];
1452
97
  }
98
+ /**
99
+ * The full deployment manifest — the shape of `export const manifest` in a
100
+ * generated `manifest/<provider>.ts`. Each field is a {@link ManifestField}
101
+ * (flat or partitioned).
102
+ */
103
+ interface Manifest {
104
+ routes: ManifestField<RouteInfo>;
105
+ functions?: ManifestField<FunctionInfo>;
106
+ crons?: ManifestField<CronInfo>;
107
+ subscribers?: ManifestField<SubscriberInfo>;
108
+ queues?: ManifestField<QueueInfo>;
109
+ topics?: ManifestField<TopicInfo>;
110
+ }
111
+ //# sourceMappingURL=index.d.ts.map
1453
112
  //#endregion
1454
- export type { AllProvidedKeys, AppSpec, CacheDeclaration, ConstructId, ConstructManifest, ConstructName, CredentialDeclaration, CronDeclaration, DatabaseDeclaration, DatabaseReaderDeclaration, DatabaseSchemaDeclaration, Declaration, DeclarationKind, DeclarationOf, Dependency, DerivedDeclaration, DerivedKind, EmailDeclaration, EncryptionDeclaration, ExternalApiDeclaration, FileServerDeclaration, Fn, FunctionDeclaration, Glob, IdsOf, IdsOfKind, MetroHost, MigrationTarget, MobileAppDeclaration, Node, ObjectsDeclaration, OidcDeclaration, PostgresVersion, ProvidedKeys, Provides, ProvidesByKind, QueueDeclaration, RestApiDeclaration, RestApiEndpoint, SecretDeclaration, SiteDeclaration, TelemetryDeclaration, TelemetryKey, TopicDeclaration, WorkerDeclaration };
113
+ export { CronInfo, FunctionInfo, Manifest, ManifestField, QueueInfo, RouteInfo, SubscriberInfo, TopicInfo, flattenManifestField };
1455
114
  //# sourceMappingURL=index.d.cts.map