@lunora/platform 1.0.0-alpha.3 → 1.0.0-alpha.31

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.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { type D as DirectShardDirectory, type S as ScheduleOptions, type a as ScheduledJob, type b as ScheduledJobStatus, type c as SchedulerHost, type d as ShardAlarms, type e as ShardAsyncSqlExec, type f as ShardDirectory, type g as ShardHost, type h as ShardJurisdiction, type i as ShardKvListOptions, type j as ShardKvStore, type k as ShardSqlCursor, type l as ShardSqlExec, type m as ShardStub, type n as SocketHandle, type o as SocketHost, type p as SqlRow, type T as TwoStepShardDirectory, r as resolveShard } from "./packem_shared/socket-host.d-GABARI_Q.js";
1
+ export { type D as DirectShardDirectory, type S as ScheduleOptions, type a as ScheduledJob, type b as ScheduledJobStatus, type c as SchedulerHost, type d as ShardAlarms, type e as ShardDirectory, type f as ShardHost, type g as ShardJurisdiction, type h as ShardKvListOptions, type i as ShardKvStore, type j as ShardRegionHint, type k as ShardSqlCursor, type l as ShardSqlExec, type m as ShardStub, type n as SocketHandle, type o as SocketHost, type p as SqlRow, type T as TwoStepShardDirectory, r as resolveShard } from "./packem_shared/socket-host.d-Dn14vebI.js";
2
2
  /**
3
3
  * The subset of the Cloudflare `ExecutionContext` the Lunora worker entry and
4
4
  * the framework mount seams rely on — `waitUntil` for fire-and-forget work that
@@ -22,6 +22,13 @@ export { type D as DirectShardDirectory, type S as ScheduleOptions, type a as Sc
22
22
  * fall back to {@link NOOP_EXECUTION_CONTEXT}.
23
23
  */
24
24
  interface ExecutionContextLike {
25
+ /**
26
+ * Present only when Cloudflare Access authenticated the request against a
27
+ * policy attached to the **Worker** (rather than to a hostname). `undefined`
28
+ * on every unauthenticated request, so its presence is itself the "Access
29
+ * authorized this caller" signal — see {@link AccessContextLike}.
30
+ */
31
+ access?: AccessContextLike;
25
32
  cache?: {
26
33
  purge: (options: {
27
34
  purgeEverything?: boolean;
@@ -31,6 +38,48 @@ interface ExecutionContextLike {
31
38
  passThroughOnException?: () => void;
32
39
  waitUntil?: (promise: Promise<unknown>) => void;
33
40
  }
41
+ /**
42
+ * The identity Cloudflare Access attaches to a Worker-protected request.
43
+ *
44
+ * Shape follows the Access application-token payload: `sub` is the stable per-user
45
+ * id, `email` the verified address, `common_name` the service-token name (machine
46
+ * callers, whose `sub` is empty), and `exp` the credential expiry in epoch
47
+ * **seconds**. Group membership is whatever the Access policy emits — a list of
48
+ * names, or of `{ id, name }` objects — hence `unknown`; normalize before use.
49
+ *
50
+ * Cloudflare may add further fields, so the index signature keeps them rather
51
+ * than dropping them: this is a view of a payload we do not own.
52
+ */
53
+ interface AccessIdentityLike {
54
+ [claim: string]: unknown;
55
+ /** Service-token name. Present for non-interactive (machine) callers instead of `email`. */
56
+ common_name?: string;
57
+ /** Verified user email. Present for interactive (SSO) callers. */
58
+ email?: string;
59
+ /** Credential expiry, epoch **seconds**. */
60
+ exp?: number;
61
+ /** IdP group membership — names or `{ id, name }` objects, depending on the policy. */
62
+ groups?: unknown;
63
+ /** Display name from the identity provider, when it emits one. */
64
+ name?: string;
65
+ /** Stable per-user id, and what consumers key a user on. Empty for service tokens. */
66
+ sub?: string;
67
+ /** Cloudflare's per-user UUID. Carried through, but deliberately not used as an id — only this path emits it, so keying on it would not match the JWT path. */
68
+ user_uuid?: string;
69
+ }
70
+ /**
71
+ * The `ctx.access` facade Cloudflare exposes on a Worker protected by Access.
72
+ *
73
+ * Reading the identity from here is preferable to verifying the
74
+ * `Cf-Access-Jwt-Assertion` header: the platform has already authenticated the
75
+ * caller, so there is no JWKS fetch, no audience check to get wrong, and nothing
76
+ * a request can forge — the field simply does not exist unless Access authorized
77
+ * the call. The header path remains the fallback for hostname-scoped Access
78
+ * applications, which do not populate this.
79
+ */
80
+ interface AccessContextLike {
81
+ getIdentity: () => AccessIdentityLike | null | undefined | Promise<AccessIdentityLike | null | undefined>;
82
+ }
34
83
  /**
35
84
  * No-op `ExecutionContext` used when the host runtime didn't supply one (a
36
85
  * non-Cloudflare preview, or a unit test), so the worker's `fetch` always
@@ -65,6 +114,35 @@ interface QueueMessageLike<Body = unknown> {
65
114
  }) => void;
66
115
  readonly timestamp: Date;
67
116
  }
117
+ /** Options accepted by {@link HttpCacheLike.match} and {@link HttpCacheLike.delete}. */
118
+ interface HttpCacheQueryOptions {
119
+ /** Match a non-`GET` request against a stored `GET` entry. */
120
+ ignoreMethod?: boolean;
121
+ }
122
+ /**
123
+ * Minimal projection of one Web Cache API cache — the store a host puts in front
124
+ * of the app, reached on Cloudflare as `caches.default` (the colo cache).
125
+ *
126
+ * Only the three calls Lunora makes are declared, so a host that has a cache but
127
+ * not the whole `Cache` interface still satisfies it, and a unit test can pass a
128
+ * plain object double. This is a **host** primitive, not a binding: it is reached
129
+ * through a runtime global rather than `env`, and a target without one leaves it
130
+ * `undefined` rather than shipping a fake — see `httpCache` in
131
+ * `PlatformCapabilities`.
132
+ *
133
+ * The stored entry is keyed by the request, so a caller that needs `Vary`
134
+ * semantics must fold the varying header values into the key itself: Cloudflare's
135
+ * cache honours `Vary` for `Accept-Encoding` only, and a projection cannot make
136
+ * that portable.
137
+ */
138
+ interface HttpCacheLike {
139
+ /** Evict the entry stored under `request`. Resolves `true` when something was removed. */
140
+ delete: (request: Request | string, options?: HttpCacheQueryOptions) => Promise<boolean>;
141
+ /** The stored response for `request`, or `undefined` on a miss. */
142
+ match: (request: Request | string, options?: HttpCacheQueryOptions) => Promise<Response | undefined>;
143
+ /** Store `response` under `request`. Rejects for a `206`, a `Vary: *`, or a `Set-Cookie`-bearing response. */
144
+ put: (request: Request | string, response: Response) => Promise<void>;
145
+ }
68
146
  /** A single vector match. */
69
147
  interface VectorMatchLike {
70
148
  id: string;
@@ -85,6 +163,7 @@ interface AnalyticsEngineDataPointLike {
85
163
  }
86
164
  interface D1PreparedStatementLike {
87
165
  all: <T = unknown>() => Promise<{
166
+ meta?: Record<string, unknown>;
88
167
  results: T[];
89
168
  success: boolean;
90
169
  }>;
@@ -214,30 +293,53 @@ type KvNamespaceListResult<Metadata = unknown> = {
214
293
  list_complete: true;
215
294
  };
216
295
  /**
217
- * Minimal structural projection of `VectorizeIndex` so unit tests can pass a
296
+ * Minimal structural projection of `Vectorize` so unit tests can pass a
218
297
  * plain-object double and the real Cloudflare binding satisfies the same shape.
219
298
  * Mirrors the surface documented at
220
299
  * https://developers.cloudflare.com/vectorize/reference/client-api/.
300
+ *
301
+ * **Method** syntax, not arrow properties: `ReadonlyArray` is the correct (wider)
302
+ * parameter type, but an arrow property makes parameters strictly contravariant
303
+ * under `strictFunctionTypes`, and Cloudflare declares `deleteByIds(ids: string[])`
304
+ * — so the real binding did not satisfy this interface at all. Methods are checked
305
+ * bivariantly, which admits both it and a `readonly`-clean double.
306
+ * `binding-assignability.test-d.ts` in `@lunora/bindings` pins that against the
307
+ * published types. `this: void` keeps the members usable as bare references, which
308
+ * is what every `expect(double.query)` assertion in those tests does.
221
309
  */
222
310
  interface VectorizeIndexLike {
223
- deleteByIds: (ids: ReadonlyArray<string>) => Promise<VectorizeDeleteMutation>;
224
- describe?: () => Promise<VectorizeIndexDetails>;
225
- getByIds: (ids: ReadonlyArray<string>) => Promise<ReadonlyArray<VectorizeVector>>;
226
- insert: (vectors: ReadonlyArray<VectorizeVector>) => Promise<VectorizeUpsertMutation>;
227
- query: (vector: ReadonlyArray<number>, options?: VectorizeQueryOptions) => Promise<VectorizeMatches>;
228
- upsert: (vectors: ReadonlyArray<VectorizeVector>) => Promise<VectorizeUpsertMutation>;
311
+ deleteByIds(this: void, ids: ReadonlyArray<string>): Promise<VectorizeDeleteMutation>;
312
+ describe?(this: void): Promise<VectorizeIndexDetails>;
313
+ getByIds(this: void, ids: ReadonlyArray<string>): Promise<ReadonlyArray<VectorizeVector>>;
314
+ insert(this: void, vectors: ReadonlyArray<VectorizeVector>): Promise<VectorizeUpsertMutation>;
315
+ query(this: void, vector: VectorValues, options?: VectorizeQueryOptions): Promise<VectorizeMatches>;
316
+ upsert(this: void, vectors: ReadonlyArray<VectorizeVector>): Promise<VectorizeUpsertMutation>;
229
317
  }
230
318
  type VectorMetric = "cosine" | "euclidean" | "dot-product";
319
+ /**
320
+ * An embedding, as either a plain array or one of the typed arrays Vectorize
321
+ * accepts. The typed-array arms are not a convenience: Cloudflare's own
322
+ * `VectorizeVector.values` is `VectorFloatArray | number[]`, so a projection
323
+ * limited to `ReadonlyArray<number>` excluded every vector the real binding
324
+ * returns. Read one with `Array.from(...)` rather than an array method.
325
+ */
326
+ type VectorValues = Float32Array | Float64Array | ReadonlyArray<number>;
231
327
  interface VectorizeVector {
232
328
  id: string;
233
329
  metadata?: Record<string, unknown>;
234
330
  namespace?: string;
235
- values: ReadonlyArray<number>;
331
+ values: VectorValues;
236
332
  }
333
+ /**
334
+ * Query options, kept a superset of Cloudflare's own `VectorizeQueryOptions` so
335
+ * the real binding satisfies {@link VectorizeIndexLike}. `returnMetadata` carries
336
+ * the legacy `boolean` arm for that reason - pass one of the three levels; the
337
+ * boolean is what Cloudflare still accepts, not what callers should write.
338
+ */
237
339
  interface VectorizeQueryOptions {
238
340
  filter?: Record<string, unknown>;
239
341
  namespace?: string;
240
- returnMetadata?: "none" | "indexed" | "all";
342
+ returnMetadata?: "none" | "indexed" | "all" | boolean;
241
343
  returnValues?: boolean;
242
344
  topK?: number;
243
345
  }
@@ -246,7 +348,7 @@ interface VectorizeMatch {
246
348
  metadata?: Record<string, unknown>;
247
349
  namespace?: string;
248
350
  score: number;
249
- values?: ReadonlyArray<number>;
351
+ values?: VectorValues;
250
352
  }
251
353
  interface VectorizeMatches {
252
354
  count: number;
@@ -259,11 +361,24 @@ interface VectorizeDeleteMutation {
259
361
  count?: number;
260
362
  mutationId: string;
261
363
  }
364
+ /**
365
+ * What `describe()` reports. Both spellings of the row count are optional because
366
+ * Cloudflare renamed it between API generations — the beta `VectorizeIndex`
367
+ * returns `vectorsCount`, the current `Vectorize` returns `vectorCount` — and a
368
+ * projection that required either one excluded a real binding. Read them as
369
+ * `vectorCount ?? vectorsCount`.
370
+ *
371
+ * `processedUpTo*` are typed loosely for the same reason: documented as ISO 8601
372
+ * strings, typed as `number` in `@cloudflare/workers-types`.
373
+ */
262
374
  interface VectorizeIndexDetails {
263
375
  dimensions: number;
264
- processedUpToDatetime?: string;
265
- processedUpToMutation?: string;
266
- vectorsCount: number;
376
+ processedUpToDatetime?: number | string;
377
+ processedUpToMutation?: number | string;
378
+ /** The current `Vectorize.describe()` spelling. */
379
+ vectorCount?: number;
380
+ /** The beta `VectorizeIndex.describe()` spelling. */
381
+ vectorsCount?: number;
267
382
  }
268
383
  /**
269
384
  * A single-range read against R2: an `{ offset, length }` window (at least one
@@ -310,18 +425,22 @@ interface R2BucketLike {
310
425
  list: (options?: {
311
426
  cursor?: string;
312
427
  delimiter?: string;
428
+ include?: ("customMetadata" | "httpMetadata")[];
313
429
  limit?: number;
314
430
  prefix?: string;
431
+ startAfter?: string;
315
432
  }) => Promise<{
316
433
  cursor?: string;
434
+ delimitedPrefixes?: string[];
317
435
  objects: R2ObjectLike[];
318
436
  truncated?: boolean;
319
437
  }>;
320
- put: (key: string, body: ReadableStream | ArrayBuffer | Blob | string | null, options?: {
438
+ put: (key: string, body: ReadableStream | ArrayBuffer | ArrayBufferView | Blob | string | null, options?: {
321
439
  customMetadata?: Record<string, string>;
322
440
  httpMetadata?: {
323
441
  contentType?: string;
324
442
  };
443
+ sha256?: ArrayBuffer | string;
325
444
  }) => Promise<R2ObjectLike>;
326
445
  /** Resume an in-progress multipart upload by id (R2 `resumeMultipartUpload`). Optional; see {@link Storage.resumeMultipartUpload}. */
327
446
  resumeMultipartUpload?: (key: string, uploadId: string) => R2MultipartUploadLike;
@@ -417,15 +536,15 @@ interface MessageSendRequestLike<Body = unknown> {
417
536
  delaySeconds?: number;
418
537
  }
419
538
  /**
420
- * Minimal structural projection of workers-types' `Queue&lt;Body>` (the producer
539
+ * Minimal structural projection of workers-types' `Queue<Body>` (the producer
421
540
  * binding). The real binding's `send`/`sendBatch` resolve to a metadata object;
422
- * we widen the return to `Promise&lt;unknown>` so a plain-object fake satisfies it.
541
+ * we widen the return to `Promise<unknown>` so a plain-object fake satisfies it.
423
542
  */
424
543
  interface QueueBindingLike<Body = unknown> {
425
544
  send: (message: Body, options?: QueueSendOptions) => Promise<unknown>;
426
545
  sendBatch: (messages: Iterable<MessageSendRequestLike<Body>>, options?: QueueSendBatchOptions) => Promise<unknown>;
427
546
  }
428
- /** Structural mirror of workers-types' `Message&lt;Body>` (one delivered message). */
547
+ /** Structural mirror of workers-types' `Message<Body>` (one delivered message). */
429
548
  interface MessageLike<Body = unknown> {
430
549
  /** Acknowledge this message so it is not redelivered. */
431
550
  ack: () => void;
@@ -436,7 +555,7 @@ interface MessageLike<Body = unknown> {
436
555
  retry: (options?: QueueRetryOptions) => void;
437
556
  readonly timestamp: Date;
438
557
  }
439
- /** Structural mirror of workers-types' `MessageBatch&lt;Body>` handed to a consumer. */
558
+ /** Structural mirror of workers-types' `MessageBatch<Body>` handed to a consumer. */
440
559
  interface MessageBatchLike<Body = unknown> {
441
560
  /** Acknowledge every message in the batch. */
442
561
  ackAll: () => void;
@@ -455,9 +574,76 @@ interface QueueRetryOptions {
455
574
  * Lunora features a target platform supports natively, emulates, or cannot
456
575
  * support at all.
457
576
  *
458
- * Codegen consumes this matrix to omit unsupported `ctx.*` surfaces from
459
- * emitted types and to emit diagnostics for features that need emulation.
460
- * Docs and Studio also read it to show parity per target.
577
+ * # Who reads it
578
+ *
579
+ * **`@lunora/codegen` is the only consumer.** `gateAgainstMatrix`
580
+ * (`packages/codegen/src/platform-target.ts`) intersects an app's detected
581
+ * feature usage with the target's matrix and diagnoses exactly two states:
582
+ * `unsupported` (`platform_unsupported_feature`) and a key missing from the
583
+ * matrix altogether (`platform_undeclared_feature`, the fail-closed arm).
584
+ * `native` and `emulated` are emitted identically, with no diagnostic between
585
+ * them — that distinction exists for honest parity reporting, not for codegen.
586
+ *
587
+ * Nothing in `@lunora/studio` imports this package, and the per-feature table
588
+ * in `packages/platform-node/docs/index.mdx` is a hand-written copy held
589
+ * verbatim by `pnpm run lint:node-capabilities-docs`: change a rating or a note
590
+ * here first, then that table, or the check fails.
591
+ *
592
+ * # Gate-bearing keys
593
+ *
594
+ * A rating only gates something if `@lunora/codegen` reads it — either through a
595
+ * usage key mapped onto the feature (`CAPABILITY_ROWS` + `CAPABILITY_TO_FEATURE`,
596
+ * for an app-imported `ctx.*` module) or through a `PlatformSignals` entry (for
597
+ * something the app declares in its schema or a declaration file). The
598
+ * gate-bearing keys are:
599
+ *
600
+ * `agents`, `ai`, `analytics`, `browser`, `commitOrderedTables`, `containers`,
601
+ * `cronTriggers`, `crossShardFanout`, `durableStreams`, `globalTables`,
602
+ * `hyperdrive`, `images`, `keyValueStore`, `mail`, `objectStorage`,
603
+ * `pipelines`, `queues`, `relationGraph`, `scheduler`, `secrets`,
604
+ * `vectorStore`, `workflows`.
605
+ *
606
+ * Every other key here — `httpCache`, `identityProxy`,
607
+ * `localSql`, `memoryTables`, `objectStorageBackups`,
608
+ * `objectStorageCdcArchive`, `serverReactors`, `shardAlarms`, `shardedState`,
609
+ * `shardPlacement`, `shardReadReplicas`, `websocketHibernation` — is
610
+ * **advisory**: rating one `unsupported` omits no surface and warns nobody. It
611
+ * still records parity honestly, which is its job; it is not a gate.
612
+ *
613
+ * # Advisory is not one thing — there are two reasons, and only one is final
614
+ *
615
+ * Most advisory keys are advisory *by nature*: the feature is engine-internal
616
+ * (`shardAlarms`, `shardedState`, `shardPlacement`, `shardReadReplicas`,
617
+ * `websocketHibernation`, `localSql`, `serverReactors`) or degrades honestly on
618
+ * its own (`httpCache` falls back to headers-only, `identityProxy` to header
619
+ * verification). There is nothing an app declares for codegen to notice, so
620
+ * there is nothing to gate. These stay ratings, permanently.
621
+ *
622
+ * The rest are advisory only because nobody wired them, and they are the ones
623
+ * to watch: an app DOES declare the feature, codegen CAN see the declaration,
624
+ * and the rating is still consulted by nothing. Codegen already has the shape
625
+ * for exactly this — `PlatformSignals` in `platform-target.ts`, the second gate
626
+ * pass that diagnoses app-declared features with no `ctx.*` capability row
627
+ * (`agents`, `commitOrderedTables`, `cronTriggers`, `crossShardFanout`,
628
+ * `durableStreams`, `globalTables`, `queues`, `relationGraph`, `secrets`,
629
+ * `vectorStore`).
630
+ * Promoting one is three lines there: a `PlatformSignals` field, plus its entry
631
+ * in that module's signal-key list and its human-readable label — and then
632
+ * setting the signal from the IR.
633
+ *
634
+ * `commitOrderedTables` was promoted that way: `TableIR.commitOrdered` sits in
635
+ * the same IR that feeds `globalTables`, and until it was read a host rating it
636
+ * `unsupported` emitted the full `.commitOrdered()` surface with no diagnostic
637
+ * and silently lost commit ordering — the one guarantee the feature is.
638
+ * `memoryTables`, `objectStorageBackups` and `objectStorageCdcArchive` remain
639
+ * weaker instances of the same shape, still unpromoted.
640
+ *
641
+ * **Adding a feature key is therefore half a change.** The other half is a row
642
+ * in `CAPABILITY_ROWS` and an entry in `CAPABILITY_TO_FEATURE` (for an
643
+ * app-imported `ctx.*` module), or a `PlatformSignals` entry (for something the
644
+ * app declares in its schema), or a deliberate decision that the key is advisory
645
+ * by nature — recorded here. Silence means the rating ships as documentation
646
+ * while the surface it describes is emitted anyway.
461
647
  */
462
648
  /** Support level for a single feature on a target platform. */
463
649
  type CapabilityLevel = "native" | "emulated" | "unsupported";
@@ -475,40 +661,216 @@ interface Capability {
475
661
  interface PlatformCapabilities {
476
662
  /** Feature-level capabilities. */
477
663
  features: {
664
+ /**
665
+ * Durable agents — a `defineAgent` export in `lunora/agents.ts`.
666
+ *
667
+ * Its own key rather than a facet of `workflows` or `ai`, because an
668
+ * agent needs BOTH and neither implies the other: the generated class
669
+ * compiles onto the host's workflow engine under an `AGENT_*` binding
670
+ * the emitted context resolves off `env`, and the loop it runs there
671
+ * calls model inference. A host that emulates workflows but has no
672
+ * inference (or no way to mount a generated class into its engine) can
673
+ * rate `workflows` honestly and still not run an agent.
674
+ */
675
+ agents?: Capability;
478
676
  /** AI inference (Workers AI / Bedrock / OpenAI). */
479
677
  ai?: Capability;
480
678
  /** Analytics / observability sinks. */
481
679
  analytics?: Capability;
482
680
  /** Browser rendering / headless browser. */
483
681
  browser?: Capability;
484
- /** Container execution (Cloudflare Containers / Fargate). */
682
+ /**
683
+ * `.commitOrdered()` tables — the `_commitSeq` system field: a per-shard
684
+ * integer allocated once per mutation and strictly increasing in commit
685
+ * order.
686
+ *
687
+ * Listed as a capability rather than assumed, because the ordering
688
+ * guarantee is not the engine's to give. It rests on two things the HOST
689
+ * provides: an atomic write boundary the counter bump shares with the
690
+ * rows it stamps, and serialized execution so two mutations cannot
691
+ * interleave their allocations. A host that offers neither can still
692
+ * create the counter and hand out increasing numbers — they just would
693
+ * not order commits, which is the whole contract.
694
+ *
695
+ * Gate-bearing: `TableIR.commitOrdered` feeds the `PlatformSignals`
696
+ * pass off the same IR the `globalTables` signal reads, so a host
697
+ * rating this `unsupported` refuses the app rather than emitting the
698
+ * full `.commitOrdered()` surface and silently dropping the ordering
699
+ * guarantee — which is the only thing the feature is.
700
+ */
701
+ commitOrderedTables?: Capability;
702
+ /**
703
+ * Container execution (Cloudflare Containers / Fargate), including
704
+ * `ctx.containers.<name>.exec`. Deliberately one rating rather than two:
705
+ * `exec` is a method on the accessor this key already gates, not a
706
+ * separate app-imported surface, so there is no usage signal codegen
707
+ * could gate it on independently and nothing that could act on a second
708
+ * rating. A host that can reach a container but cannot carry a command
709
+ * result back should say so in this note.
710
+ */
485
711
  containers?: Capability;
712
+ /**
713
+ * DECLARED cron triggers — the `cronJobs()` registrations codegen lifts
714
+ * into `LUNORA_CRONS`, dispatched by whatever the host wakes on a
715
+ * schedule.
716
+ *
717
+ * Separate from {@link PlatformCapabilities.features.scheduler}, which
718
+ * rates the imperative surface (`ctx.scheduler.runAfter/runAt`, a job
719
+ * the app enqueues at runtime). The two are genuinely independent: a
720
+ * host can dispatch enqueued jobs perfectly and still walk nothing into
721
+ * its declared crons, in which case an app's `crons.daily(...)` never
722
+ * fires. One rating covering both is how that shipped as green.
723
+ */
724
+ cronTriggers?: Capability;
486
725
  /** Cross-shard fan-out queries. */
487
726
  crossShardFanout?: Capability;
727
+ /**
728
+ * Durable streams: a `.stream()` run whose chunks are persisted and
729
+ * whose producer outlives the socket that opened it, so a reconnecting
730
+ * or second client resumes the same transcript.
731
+ */
732
+ durableStreams?: Capability;
488
733
  /** Global (replicated) tables backed by a SQL store. */
489
734
  globalTables?: Capability;
735
+ /**
736
+ * A shared HTTP cache in front of the app that the runtime can READ AND
737
+ * WRITE — the Web Cache API (`caches.default` on Cloudflare), projected
738
+ * as `HttpCacheLike`.
739
+ *
740
+ * Rated separately from the app merely emitting `Cache-Control`, because
741
+ * only this half needs a host primitive. Emitting the header is portable
742
+ * by construction: any host that returns an HTTP response can do it, and
743
+ * browsers and downstream CDNs honour it wherever the app runs. What is
744
+ * not portable is a store the Worker itself can `match`/`put` against,
745
+ * which is why `@lunora/runtime`'s REST edge cache degrades to
746
+ * headers-only on a target rated `unsupported` rather than failing.
747
+ */
748
+ httpCache?: Capability;
490
749
  /** BYO database via connection pooling (Hyperdrive / RDS Proxy). */
491
750
  hyperdrive?: Capability;
751
+ /**
752
+ * An identity-aware proxy in front of the app that authenticates the
753
+ * caller before the request reaches it, and hands the runtime a verified
754
+ * identity **out-of-band** — on the execution context rather than on the
755
+ * request (Cloudflare Access attached to a Worker; IAP; an ALB OIDC
756
+ * action).
757
+ *
758
+ * Rated separately from the header-stamping form of the same product
759
+ * because only this one needs a host primitive. An identity-aware proxy
760
+ * that merely adds a signed header is portable by construction: any host
761
+ * that receives an HTTP request can verify it, which is why
762
+ * `@lunora/cloudflare-access` still works on a target rated
763
+ * `unsupported` here (it falls back to the `Cf-Access-Jwt-Assertion`
764
+ * JWT). What is not portable is the identity arriving beside the
765
+ * request, which is why `ExecutionContextLike.access` is a projection a
766
+ * host either populates or does not.
767
+ */
768
+ identityProxy?: Capability;
769
+ /** Image transforms (resize/format/optimize) via an Images binding. */
770
+ images?: Capability;
492
771
  /** Key-value storage (KV / Redis / DynamoDB). */
493
772
  keyValueStore?: Capability;
494
773
  /** Local SQL execution inside a shard. */
495
774
  localSql?: Capability;
496
775
  /** Email sending (Resend / SES / etc). */
497
776
  mail?: Capability;
498
- /** Object storage (R2 / S3 / MinIO). */
777
+ /**
778
+ * `.memory()` tables — the ephemeral tier: rows cleared on every shard
779
+ * cold start, never written to the CDC changelog, refilled by
780
+ * `onShardInit`.
781
+ *
782
+ * The rating answers "does a memory table avoid durable storage on this
783
+ * host", NOT "does it work". The lifetime semantics are the engine's and
784
+ * hold everywhere; whether the rows actually stay out of the durable
785
+ * store depends on the host offering a second, memory-backed SQL handle,
786
+ * which is a per-target fact.
787
+ */
788
+ memoryTables?: Capability;
789
+ /**
790
+ * Object storage (R2 / S3 / MinIO).
791
+ *
792
+ * `ctx.storage.deleteAfterCommit(key)` rides on this rating and gets no
793
+ * key of its own: it needs no host primitive beyond the bucket. The
794
+ * post-commit flush uses `ShardHost.waitUntil` where the host has one and
795
+ * is awaited inline where it does not, so a host that can serve
796
+ * `objectStorage` serves the deferral at the same level.
797
+ */
499
798
  objectStorage?: Capability;
799
+ /**
800
+ * Snapshot backups kept in object storage rather than on the machine
801
+ * that took them — `lunora backup create|list|restore --bucket`, and
802
+ * the platform's own `backupCron`. Distinct from
803
+ * `objectStorage` above because it needs three things a
804
+ * bucket alone does not imply: an admin-gated read of one object
805
+ * (`GET /_lunora/admin/storage/object`), a checksum-verified write, and
806
+ * a scheduler to run the unattended half.
807
+ */
808
+ objectStorageBackups?: Capability;
809
+ /**
810
+ * The CDC changelog's cold tier: rows a retention sweep is about to
811
+ * destroy are written to an object-storage bucket first
812
+ * (`LUNORA_CDC_ARCHIVE`), and a consumer whose cursor has fallen below
813
+ * the retained window is served from there instead of being told to
814
+ * re-seed.
815
+ *
816
+ * Distinct from `objectStorage` because it needs the bucket to do one
817
+ * thing a plain byte store need not: resume a key-ordered listing from a
818
+ * position (`list({ startAfter })`). Without it the read-back re-lists
819
+ * the prefix from the front every time and stops finding the range it
820
+ * needs once enough segments precede the cursor — which fails as a
821
+ * refusal rather than a gap, but fails permanently and silently, so a
822
+ * host that cannot seek should say `unsupported` here rather than
823
+ * inherit `objectStorage`'s rating.
824
+ */
825
+ objectStorageCdcArchive?: Capability;
500
826
  /** Pipelines / streaming data. */
501
827
  pipelines?: Capability;
502
828
  /** Queue-backed workpools. */
503
829
  queues?: Capability;
830
+ /**
831
+ * `ctx.db.related(...)` — breadth-first traversal of the foreign-key
832
+ * graph the schema's `v.id("target")` columns describe, returning each
833
+ * reached row with its depth, the edge names walked to reach it, and a
834
+ * depth-decaying score.
835
+ *
836
+ * Rated on its own key rather than folded into `localSql`, because the
837
+ * two answer different questions: `localSql` says a shard can run SQL,
838
+ * while this says a host can serve the traversal's read SHAPE — an
839
+ * id lookup per out-edge and a batched `WHERE fk IN (...)` per in-edge,
840
+ * repeated per hop within one request. A host whose reads are remote
841
+ * enough that a multi-hop expansion cannot finish inside a request
842
+ * should say `unsupported` here even though every individual read works.
843
+ *
844
+ * Gate-bearing: codegen sets the `relationGraph` `PlatformSignals` flag
845
+ * from the schema IR's `v.id` columns, so a host rating it
846
+ * `unsupported` refuses the app rather than emitting a `related` that
847
+ * throws (or worse, silently returns nothing) on the first hop.
848
+ */
849
+ relationGraph?: Capability;
504
850
  /** Cron triggers / scheduled functions. */
505
851
  scheduler?: Capability;
506
852
  /** Secrets management. */
507
853
  secrets?: Capability;
854
+ /**
855
+ * `onQueryChange` reactors — server-side reactivity: a subscriber that is
856
+ * not a socket, woken after a write flush when a watched read's result
857
+ * changed.
858
+ *
859
+ * Host-dependent because the whole mechanism rests on the host being able
860
+ * to run work AFTER a write commits, on the same shard, without a client
861
+ * connection to hang it off — and on that work being serialized against
862
+ * further writes so a reactor's own writes cascade deterministically
863
+ * rather than interleaving.
864
+ */
865
+ serverReactors?: Capability;
508
866
  /** Alarms / scheduled wakeup inside a shard. */
509
867
  shardAlarms?: Capability;
510
868
  /** Durable Object-style sharded state. */
511
869
  shardedState?: Capability;
870
+ /** Geographic placement of a shard (`ShardPlacement.locationHint`). */
871
+ shardPlacement?: Capability;
872
+ /** Region-local read replicas of a shard, for one-shot queries. */
873
+ shardReadReplicas?: Capability;
512
874
  /** Vector database (Vectorize / pgvector / Pinecone). */
513
875
  vectorStore?: Capability;
514
876
  /** Hibernated WebSocket subscriptions. */
@@ -535,33 +897,72 @@ declare const CLOUDFLARE_CAPABILITIES: PlatformCapabilities;
535
897
  * The Node capability matrix — `@lunora/platform-node`'s honest self-rating
536
898
  * (plan 234).
537
899
  *
538
- * `@lunora/platform-node` is a spike: a `ShardHost`/`SocketHost`/
539
- * `ShardDirectory`/`ShardKvStore`/`SchedulerHost` implementation over
540
- * `better-sqlite3` and an in-process registry, built to run the conformance
541
- * TCK against a second host and discover what the contracts under-specify.
542
- * It is a single Node process with no distributed placement, no host-level
543
- * scheduler to re-arm timers after a restart, and no bindings at all for the
544
- * Cloudflare-specific products (R2, Vectorize, Workers AI, Queues,
545
- * Workflows, Containers, Browser Rendering, Analytics Engine, Secrets Store,
546
- * Hyperdrive) most `ctx.*` surfaces are built on. Every one of those is
547
- * rated `"unsupported"` here rather than left undeclared — see
548
- * `gateAgainstMatrix` in `@lunora/codegen`, whose fail-closed gate (plan
549
- * 229) treats an undeclared feature as unsupported anyway, but under a
550
- * different diagnostic name than an honest, explicit rating.
551
- *
552
- * Two features are rated `"emulated"` rather than `"native"` even though
553
- * this package fully implements their contract, because "native" would
554
- * overstate what a bare Node process provides on its own: `keyValueStore` is
555
- * a SQL table wearing a KV-shaped API, not a dedicated KV product, and
556
- * `websocketHibernation` never actually evicts a socket to save memory — it
557
- * only proves the attachment/tag durability half of the contract, not real
558
- * hibernation. `scheduler` and `shardAlarms` are rated `"unsupported"`, not
559
- * `"emulated"`: the Node host stores and times both, but its timer body only
560
- * clears bookkeeping nothing dispatches the scheduled function or wakes the
561
- * alarm callback. `"emulated"` means built on lower-level primitives and
562
- * working; never-dispatched is not that (plan 267). `globalTables` is also
563
- * `"unsupported"` no replicated SQL store is implemented. All ratings are
564
- * argued in detail in `plans/234-node-host-findings.md`.
900
+ * `@lunora/platform-node` implements every contract in this package
901
+ * (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
902
+ * `SchedulerHost`) over `better-sqlite3` and an in-process registry, plus the
903
+ * `.global()` table backend via `@lunora/sql-store`. It began as a spike to run
904
+ * the conformance TCK against a second host; the durability gaps that spike
905
+ * surfaced alarms and scheduler jobs that were persisted but never re-armed,
906
+ * socket attachments that lived only in memory — are closed, and each is now
907
+ * pinned by a restart test rather than only by a simulated recycle.
908
+ *
909
+ * `scheduler` and `shardAlarms` were rated `"unsupported"` under plan 267, on
910
+ * the grounds that the host stored and timed both while its timer body only
911
+ * cleared bookkeeping nothing dispatched the scheduled function or woke the
912
+ * alarm. That rating was correct for the code it described, and the code is
913
+ * what changed: both now dispatch (through `onDispatch` / `onAlarm`) and both
914
+ * re-arm from their durable rows on construction, so `"emulated"` built on
915
+ * lower-level primitives and *working* is now the honest reading.
916
+ *
917
+ * What remains genuinely absent is everything a single Node process cannot
918
+ * distribute: placement across nodes, failover, and most Cloudflare-specific
919
+ * product bindings (Vectorize, Workers AI, Containers, Browser Rendering,
920
+ * Analytics Engine, Secrets Store, Hyperdrive). Workflows, object storage and
921
+ * queues are the three that CAN be emulated locally `defineWorkflow` handlers
922
+ * compile onto the `@visulima/workflow` engine, R2 becomes a filesystem bucket,
923
+ * and Queues becomes a durable table with the same batch/ack/retry/dead-letter
924
+ * semantics so those three are rated `"emulated"`; the rest of the Cloudflare
925
+ * products most `ctx.*` surfaces are built on are rated `"unsupported"` here
926
+ * rather than left undeclared — see `gateAgainstMatrix` in `@lunora/codegen`,
927
+ * whose fail-closed gate (plan 229) treats an undeclared feature as unsupported
928
+ * anyway, but under a different diagnostic name than an honest, explicit rating.
929
+ *
930
+ * Almost nothing here is rated `"native"`, and that is the matrix's own
931
+ * definition doing its job rather than a hedge: `native` means the platform
932
+ * itself provides the feature, and a bare Node process provides essentially
933
+ * none of them — Lunora builds alarms out of `setTimeout` plus a durable row,
934
+ * a KV store out of a SQL table, and `.global()` tables out of a second SQLite
935
+ * file. `localSql` is the exception, because SQLite genuinely is the platform
936
+ * primitive there. The ratings say who does the work; the notes say how well.
937
+ * Both are argued in detail in `plans/234-node-host-findings.md`.
565
938
  */
566
939
  declare const NODE_CAPABILITIES: PlatformCapabilities;
567
- export { type AnalyticsEngineDataPoint, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, CLOUDFLARE_CAPABILITIES, type Capability, type CapabilityLevel, type D1DatabaseLike, type D1PreparedStatementLike, type D1SessionLike, type ExecutionContextLike, type KVNamespaceLike, type KvGetOptions, type KvListKey, type KvNamespaceListResult, type KvNamespacePutOptions, type KvValue, type KvValueType, type MessageBatchLike, type MessageLike, type MessageSendRequestLike, NODE_CAPABILITIES, NOOP_EXECUTION_CONTEXT, type PlatformCapabilities, type QueueBindingLike, type QueueContentType, type QueueMessageLike, type QueueRetryOptions, type QueueSendBatchOptions, type QueueSendOptions, type QueueSendOptionsLike, type QueueSendRequestLike, type R2BucketLike, type R2MultipartUploadLike, type R2ObjectBodyLike, type R2ObjectLike, type R2RangeLike, type R2UploadedPartLike, type VectorMatchLike, type VectorMetric, type VectorRecordLike, type VectorizeDeleteMutation, type VectorizeIndexDetails, type VectorizeIndexLike, type VectorizeMatch, type VectorizeMatches, type VectorizeQueryOptions, type VectorizeUpsertMutation, type VectorizeVector };
940
+ export { type AnalyticsEngineDataPoint, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, CLOUDFLARE_CAPABILITIES, type Capability, type CapabilityLevel, type D1DatabaseLike, type D1PreparedStatementLike, type D1SessionLike,
941
+ /**
942
+ * `@lunora/platform` — provider-neutral host contracts for Lunora.
943
+ *
944
+ * This package defines the structural interfaces that separate the Lunora
945
+ * engine from any specific host (Cloudflare Workers, AWS, Rivet, Node, etc.).
946
+ * It contains **types and capability metadata only** — near-zero runtime code.
947
+ *
948
+ * The contracts fall into four groups:
949
+ *
950
+ * 1. **Shard host** (`ShardHost`) — single-writer execution, transactions,
951
+ * local SQL, alarms, and background continuation per shard key.
952
+ * 2. **Socket host** (`SocketHost`) — hibernated WebSocket subscriptions with
953
+ * durable attachments and tagged fan-out.
954
+ * 3. **Shard directory** (`ShardDirectory`) — deterministic placement and RPC
955
+ * dispatch from shard keys to stubs.
956
+ * 4. **Scheduler host** (`SchedulerHost`) — durable delayed jobs, cron, and
957
+ * at-least-once dispatch.
958
+ *
959
+ * Plus canonical binding projections (`KVNamespaceLike`, `R2BucketLike`,
960
+ * `QueueBindingLike`, `D1DatabaseLike`, `VectorizeIndexLike`, …) and the
961
+ * `PlatformCapabilities` matrix that codegen uses to tailor emitted types per
962
+ * target.
963
+ *
964
+ * This package is **zero-dependency** and safe on every runtime (browser,
965
+ * workerd, Node). It is intended to be the leaf dependency every other
966
+ * `@lunora/*` package can import without creating cycles.
967
+ */
968
+ type ExecutionContextLike, type HttpCacheLike, type HttpCacheQueryOptions, type KVNamespaceLike, type KvGetOptions, type KvListKey, type KvNamespaceListResult, type KvNamespacePutOptions, type KvValue, type KvValueType, type MessageBatchLike, type MessageLike, type MessageSendRequestLike, NODE_CAPABILITIES, NOOP_EXECUTION_CONTEXT, type PlatformCapabilities, type QueueBindingLike, type QueueContentType, type QueueMessageLike, type QueueRetryOptions, type QueueSendBatchOptions, type QueueSendOptions, type QueueSendOptionsLike, type QueueSendRequestLike, type R2BucketLike, type R2MultipartUploadLike, type R2ObjectBodyLike, type R2ObjectLike, type R2RangeLike, type R2UploadedPartLike, type VectorMatchLike, type VectorMetric, type VectorRecordLike, type VectorValues, type VectorizeDeleteMutation, type VectorizeIndexDetails, type VectorizeIndexLike, type VectorizeMatch, type VectorizeMatches, type VectorizeQueryOptions, type VectorizeUpsertMutation, type VectorizeVector };