@lunora/platform 0.0.0 → 1.0.0-alpha.2

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.
@@ -0,0 +1,563 @@
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-Cq5uVbiH.js";
2
+ /**
3
+ * The subset of the Cloudflare `ExecutionContext` the Lunora worker entry and
4
+ * the framework mount seams rely on — `waitUntil` for fire-and-forget work that
5
+ * must outlive the response, and `passThroughOnException` for the top-level
6
+ * error posture.
7
+ *
8
+ * It is deliberately **not** a package. `@lunora/runtime` is the leaf server
9
+ * runtime and `@lunora/nuxt` is a framework integration that intentionally does
10
+ * not depend on `@lunora/runtime`'s worker types, yet both need this exact
11
+ * shape: the runtime to build/forward the worker `fetch`, Nuxt to forward an
12
+ * inbound request to the user's composed worker. Each imports this file by
13
+ * relative path and the bundler (packem/rollup) inlines it: no runtime
14
+ * dependency edge is created, the helper is duplicated only in emitted output,
15
+ * never in source. One source of truth, zero deps. See AGENTS.md → "Top-level
16
+ * `shared/` — bundler-inlined source".
17
+ *
18
+ * Both methods are **optional**: a real Cloudflare `ExecutionContext` always
19
+ * supplies them, but a host that mounts Lunora as a sub-handler (Nitro/H3, a
20
+ * non-Cloudflare preview, a unit test) may hand over a partial context or none
21
+ * at all. Callers therefore invoke them defensively (`ctx.waitUntil?.(…)`) or
22
+ * fall back to {@link NOOP_EXECUTION_CONTEXT}.
23
+ */
24
+ interface ExecutionContextLike {
25
+ cache?: {
26
+ purge: (options: {
27
+ purgeEverything?: boolean;
28
+ tags?: string[];
29
+ }) => Promise<unknown>;
30
+ };
31
+ passThroughOnException?: () => void;
32
+ waitUntil?: (promise: Promise<unknown>) => void;
33
+ }
34
+ /**
35
+ * No-op `ExecutionContext` used when the host runtime didn't supply one (a
36
+ * non-Cloudflare preview, or a unit test), so the worker's `fetch` always
37
+ * receives a valid third argument.
38
+ */
39
+ declare const NOOP_EXECUTION_CONTEXT: ExecutionContextLike;
40
+ /**
41
+ * Canonical provider-neutral binding projections. These mirror the existing
42
+ * per-package `*Like` types; in later phases each package's copy becomes a
43
+ * type-only re-export of these canonical definitions.
44
+ *
45
+ * For now, this module is additive: existing packages keep their local copies
46
+ * unchanged, so Phase 0 carries zero runtime or type-breaking risk.
47
+ */
48
+ /** Per-message send options. */
49
+ interface QueueSendOptionsLike {
50
+ delaySeconds?: number;
51
+ }
52
+ /** One message in a batch send. */
53
+ interface QueueSendRequestLike<Body = unknown> {
54
+ body: Body;
55
+ delaySeconds?: number;
56
+ }
57
+ /** One delivered queue message (consumer side). */
58
+ interface QueueMessageLike<Body = unknown> {
59
+ ack: () => void;
60
+ readonly attempts: number;
61
+ readonly body: Body;
62
+ readonly id: string;
63
+ retry: (options?: {
64
+ delaySeconds?: number;
65
+ }) => void;
66
+ readonly timestamp: Date;
67
+ }
68
+ /** A single vector match. */
69
+ interface VectorMatchLike {
70
+ id: string;
71
+ metadata?: Record<string, unknown>;
72
+ score: number;
73
+ }
74
+ /** Vector query/upsert record. */
75
+ interface VectorRecordLike {
76
+ id: string;
77
+ metadata?: Record<string, unknown>;
78
+ values: number[];
79
+ }
80
+ /** A single analytics data point. */
81
+ interface AnalyticsEngineDataPointLike {
82
+ blobs?: string[];
83
+ doubles?: number[];
84
+ indexes?: string[];
85
+ }
86
+ interface D1PreparedStatementLike {
87
+ all: <T = unknown>() => Promise<{
88
+ results: T[];
89
+ success: boolean;
90
+ }>;
91
+ bind: (...values: unknown[]) => D1PreparedStatementLike;
92
+ first: <T = unknown>(column?: string) => Promise<T | null>;
93
+ raw: <T = unknown>() => Promise<T[][]>;
94
+ run: <T = unknown>() => Promise<{
95
+ meta?: Record<string, unknown>;
96
+ results?: T[];
97
+ success: boolean;
98
+ }>;
99
+ }
100
+ interface D1SessionLike {
101
+ batch?: (statements: D1PreparedStatementLike[]) => Promise<unknown[]>;
102
+ getBookmark: () => string | null;
103
+ prepare: (sql: string) => D1PreparedStatementLike;
104
+ }
105
+ /**
106
+ * Minimal structural projection of `D1Database` to keep the adapter
107
+ * compatible with the real workers-types value as well as unit-test doubles.
108
+ */
109
+ interface D1DatabaseLike {
110
+ batch?: (statements: D1PreparedStatementLike[]) => Promise<unknown[]>;
111
+ prepare: (sql: string) => D1PreparedStatementLike;
112
+ withSession: (bookmark?: string) => D1SessionLike;
113
+ }
114
+ /**
115
+ * One Analytics Engine data point, mirroring the positional shape
116
+ * `writeDataPoint` accepts. AE stores up to 20 string `blobs`, up to 20 numeric
117
+ * `doubles`, and exactly **one** `index` (the high-cardinality sampling key) per
118
+ * data point — the SQL API later exposes them as `blob1..blob20`,
119
+ * `double1..double20`, and `index1`.
120
+ */
121
+ interface AnalyticsEngineDataPoint {
122
+ /** String columns, mapped positionally to `blob1..blob20`. */
123
+ blobs?: (ArrayBuffer | null | string)[];
124
+ /** Numeric columns, mapped positionally to `double1..double20`. */
125
+ doubles?: number[];
126
+ /** Sampling key, exposed as `index1`. AE accepts at most one. */
127
+ indexes?: (ArrayBuffer | string)[];
128
+ }
129
+ /**
130
+ * Minimal structural projection of workers-types' `AnalyticsEngineDataset`,
131
+ * kept loose enough for a plain-object fake in unit tests. `writeDataPoint` is
132
+ * fire-and-forget: it returns `void` and never throws on the hot path.
133
+ */
134
+ interface AnalyticsEngineDatasetLike {
135
+ writeDataPoint: (event: AnalyticsEngineDataPoint) => void;
136
+ }
137
+ /**
138
+ * The value types Workers KV can store / return. Mirrors Cloudflare's
139
+ * `KVNamespace` `get`/`put` body unions; declared here so the package stays
140
+ * runtime-agnostic and the `*Like` interfaces don't pull in
141
+ * `@cloudflare/workers-types` at runtime.
142
+ */
143
+ type KvValue = ReadableStream | ArrayBuffer | ArrayBufferView | string;
144
+ /** How a raw KV read should decode the stored value. Mirrors KV's `type` option. */
145
+ type KvValueType = "text" | "json" | "arrayBuffer" | "stream";
146
+ /**
147
+ * Per-read options forwarded to the binding. `cacheTtl` is KV's edge-cache TTL
148
+ * (seconds, min 60); `type` selects the decode mode for `Kv.getRaw` (in `@lunora/bindings/kv`).
149
+ */
150
+ interface KvGetOptions {
151
+ /** KV edge-cache TTL in seconds (minimum 60). Forwarded verbatim. */
152
+ cacheTtl?: number;
153
+ /** Decode mode for a raw read. `Kv.get` (in `@lunora/bindings/kv`) always uses `"json"`. */
154
+ type?: KvValueType;
155
+ }
156
+ /**
157
+ * Minimal projection of Cloudflare's `KVNamespace`. Declared structurally so
158
+ * unit tests can pass a plain `Map`-backed double; the real binding satisfies
159
+ * the same shape. Mirrors `R2BucketLike` in `@lunora/storage`.
160
+ */
161
+ interface KVNamespaceLike {
162
+ /** Delete a key. No-op if the key is absent. */
163
+ delete: (key: string) => Promise<void>;
164
+ /**
165
+ * Read a value. The real binding overloads on `options.type`; declared here
166
+ * as the broad union so a structural double need only return the value (or
167
+ * `null` when absent).
168
+ */
169
+ get: (key: string, options?: KvGetOptions | KvValueType) => Promise<unknown>;
170
+ /**
171
+ * Read a value together with its associated metadata. Returns
172
+ * `{ value: null, metadata: null }` when the key is absent.
173
+ */
174
+ getWithMetadata: (key: string, options?: KvGetOptions | KvValueType) => Promise<{
175
+ metadata: unknown;
176
+ value: unknown;
177
+ }>;
178
+ /** List keys, optionally filtered by `prefix` and paginated via `cursor`. */
179
+ list: (options?: {
180
+ cursor?: string;
181
+ limit?: number;
182
+ prefix?: string;
183
+ }) => Promise<KvNamespaceListResult>;
184
+ /** Write a value, optionally with TTL/expiration and metadata. */
185
+ put: (key: string, value: KvValue, options?: KvNamespacePutOptions) => Promise<void>;
186
+ }
187
+ /** The raw put options the KV binding accepts (mirrors `KVNamespacePutOptions`). */
188
+ interface KvNamespacePutOptions {
189
+ /** Absolute expiration as a Unix timestamp (seconds). Mutually exclusive with `expirationTtl`. */
190
+ expiration?: number;
191
+ /** Relative expiration in seconds from now (minimum 60). Mutually exclusive with `expiration`. */
192
+ expirationTtl?: number;
193
+ /** Arbitrary JSON metadata stored alongside the value, returned by `getWithMetadata`/`list`. */
194
+ metadata?: unknown;
195
+ }
196
+ /** One key entry as returned by the KV binding's `list`. */
197
+ interface KvListKey<Metadata = unknown> {
198
+ /** Absolute expiration (Unix seconds), when the key has one. */
199
+ expiration?: number;
200
+ /** The key's metadata, when set at write time. */
201
+ metadata?: Metadata;
202
+ /** The key name. */
203
+ name: string;
204
+ }
205
+ /** The raw `list` result shape returned by the KV binding. */
206
+ type KvNamespaceListResult<Metadata = unknown> = {
207
+ cacheStatus?: string | null;
208
+ cursor: string;
209
+ keys: KvListKey<Metadata>[];
210
+ list_complete: false;
211
+ } | {
212
+ cacheStatus?: string | null;
213
+ keys: KvListKey<Metadata>[];
214
+ list_complete: true;
215
+ };
216
+ /**
217
+ * Minimal structural projection of `VectorizeIndex` so unit tests can pass a
218
+ * plain-object double and the real Cloudflare binding satisfies the same shape.
219
+ * Mirrors the surface documented at
220
+ * https://developers.cloudflare.com/vectorize/reference/client-api/.
221
+ */
222
+ 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>;
229
+ }
230
+ type VectorMetric = "cosine" | "euclidean" | "dot-product";
231
+ interface VectorizeVector {
232
+ id: string;
233
+ metadata?: Record<string, unknown>;
234
+ namespace?: string;
235
+ values: ReadonlyArray<number>;
236
+ }
237
+ interface VectorizeQueryOptions {
238
+ filter?: Record<string, unknown>;
239
+ namespace?: string;
240
+ returnMetadata?: "none" | "indexed" | "all";
241
+ returnValues?: boolean;
242
+ topK?: number;
243
+ }
244
+ interface VectorizeMatch {
245
+ id: string;
246
+ metadata?: Record<string, unknown>;
247
+ namespace?: string;
248
+ score: number;
249
+ values?: ReadonlyArray<number>;
250
+ }
251
+ interface VectorizeMatches {
252
+ count: number;
253
+ matches: ReadonlyArray<VectorizeMatch>;
254
+ }
255
+ interface VectorizeUpsertMutation {
256
+ mutationId: string;
257
+ }
258
+ interface VectorizeDeleteMutation {
259
+ count?: number;
260
+ mutationId: string;
261
+ }
262
+ interface VectorizeIndexDetails {
263
+ dimensions: number;
264
+ processedUpToDatetime?: string;
265
+ processedUpToMutation?: string;
266
+ vectorsCount: number;
267
+ }
268
+ /**
269
+ * A single-range read against R2: an `{ offset, length }` window (at least one
270
+ * bound required, mirroring R2's own `R2Range`) or a `{ suffix }` tail. The
271
+ * subset of `R2Range` that {@link Storage.download} forwards so a caller can
272
+ * stream just the bytes it needs instead of the whole object.
273
+ */
274
+ type R2RangeLike = {
275
+ length: number;
276
+ offset?: number;
277
+ } | {
278
+ length?: number;
279
+ offset: number;
280
+ } | {
281
+ suffix: number;
282
+ };
283
+ /**
284
+ * Minimal projection of `R2Bucket`. Declared structurally so unit tests can
285
+ * pass a plain object double; the real binding satisfies the same shape.
286
+ */
287
+ interface R2BucketLike {
288
+ /**
289
+ * Begin a multipart upload (R2 `createMultipartUpload`). Optional so existing
290
+ * test doubles still satisfy the type; {@link Storage.createMultipartUpload}
291
+ * throws a clear error when the binding lacks it.
292
+ */
293
+ createMultipartUpload?: (key: string, options?: {
294
+ customMetadata?: Record<string, string>;
295
+ httpMetadata?: {
296
+ contentType?: string;
297
+ };
298
+ }) => Promise<R2MultipartUploadLike>;
299
+ delete: (key: string) => Promise<void>;
300
+ get: (key: string, options?: {
301
+ range?: R2RangeLike;
302
+ }) => Promise<R2ObjectBodyLike | null>;
303
+ /**
304
+ * Fetch an object's metadata without its body (R2 HEAD). Returns `null` when
305
+ * the object is absent. Declared optional so existing test doubles that only
306
+ * implement `get`/`put`/`list`/`delete` still satisfy the type; callers that
307
+ * need metadata fall back to a 0-length ranged `get()` when `head` is absent.
308
+ */
309
+ head?: (key: string) => Promise<R2ObjectLike | null>;
310
+ list: (options?: {
311
+ cursor?: string;
312
+ delimiter?: string;
313
+ limit?: number;
314
+ prefix?: string;
315
+ }) => Promise<{
316
+ cursor?: string;
317
+ objects: R2ObjectLike[];
318
+ truncated?: boolean;
319
+ }>;
320
+ put: (key: string, body: ReadableStream | ArrayBuffer | Blob | string | null, options?: {
321
+ customMetadata?: Record<string, string>;
322
+ httpMetadata?: {
323
+ contentType?: string;
324
+ };
325
+ }) => Promise<R2ObjectLike>;
326
+ /** Resume an in-progress multipart upload by id (R2 `resumeMultipartUpload`). Optional; see {@link Storage.resumeMultipartUpload}. */
327
+ resumeMultipartUpload?: (key: string, uploadId: string) => R2MultipartUploadLike;
328
+ }
329
+ /** One uploaded multipart part — returned by `uploadPart`, required to `complete`. Mirrors R2's `R2UploadedPart`. */
330
+ interface R2UploadedPartLike {
331
+ etag: string;
332
+ partNumber: number;
333
+ }
334
+ /**
335
+ * An in-progress multipart upload, mirroring R2's `R2MultipartUpload`. Each part
336
+ * (except the last) must be uniform in size. The object does not guarantee the
337
+ * underlying upload still exists — a parallel `complete`/`abort` can invalidate
338
+ * it — so wrap each call in error handling.
339
+ */
340
+ interface R2MultipartUploadLike {
341
+ /** Abort the upload, discarding any uploaded parts. */
342
+ abort: () => Promise<void>;
343
+ /** Finish the upload from the collected parts; resolves to the stored object. */
344
+ complete: (uploadedParts: R2UploadedPartLike[]) => Promise<R2ObjectLike>;
345
+ /** The object key being assembled. */
346
+ readonly key: string;
347
+ /** The R2 upload id (persist it to resume across requests). */
348
+ readonly uploadId: string;
349
+ /** Upload one part (1-indexed); returns the `{ partNumber, etag }` to pass to `complete`. */
350
+ uploadPart: (partNumber: number, value: ArrayBuffer | ArrayBufferView | Blob | ReadableStream | string) => Promise<R2UploadedPartLike>;
351
+ }
352
+ interface R2ObjectLike {
353
+ /**
354
+ * R2-computed checksums. The real binding exposes `sha256` as an
355
+ * `ArrayBuffer` (present only when R2 stored a SHA-256 for the object);
356
+ * declared optional so fakes and non-checksummed objects type-check.
357
+ */
358
+ checksums?: {
359
+ sha256?: ArrayBuffer;
360
+ };
361
+ customMetadata?: Record<string, string>;
362
+ etag: string;
363
+ /**
364
+ * The quoted form of {@link R2ObjectLike.etag} (e.g. `"abc123"`), suitable
365
+ * for emitting directly as an HTTP `ETag` header. The real binding always
366
+ * provides it; declared optional so existing doubles that only set `etag`
367
+ * still type-check (callers fall back to quoting `etag`).
368
+ */
369
+ httpEtag?: string;
370
+ httpMetadata?: {
371
+ contentType?: string;
372
+ };
373
+ key: string;
374
+ /**
375
+ * Hex-encoded SHA-256 of the object body, surfaced by `download()`/`list()`
376
+ * when R2 carries a checksum (derived from {@link R2ObjectLike.checksums}).
377
+ */
378
+ sha256?: string;
379
+ /**
380
+ * Base64-encoded SHA-256 of the object body, surfaced alongside
381
+ * {@link R2ObjectLike.sha256} from the same checksum. Base64 is the encoding
382
+ * RFC 9530 digest headers (`Repr-Digest`/`Content-Digest`) require, so HTTP
383
+ * layers can emit a spec-compliant digest without re-deriving it.
384
+ */
385
+ sha256Base64?: string;
386
+ size: number;
387
+ /**
388
+ * When the object was written. The real binding exposes this as a `Date`;
389
+ * declared optional so fakes that omit it still type-check.
390
+ * {@link Storage.getMetadata} normalises it to epoch ms.
391
+ */
392
+ uploaded?: Date;
393
+ }
394
+ interface R2ObjectBodyLike extends R2ObjectLike {
395
+ arrayBuffer: () => Promise<ArrayBuffer>;
396
+ body: ReadableStream | null;
397
+ text: () => Promise<string>;
398
+ }
399
+ /** How a queue message body is serialized on the wire (Cloudflare default `"json"`). */
400
+ type QueueContentType = "bytes" | "json" | "text" | "v8";
401
+ /** Options for a single `producer.send(body, options?)`. */
402
+ interface QueueSendOptions {
403
+ /** Wire serialization for this message (defaults to the queue's content type). */
404
+ contentType?: QueueContentType;
405
+ /** Per-message delivery delay in seconds (0–43200, i.e. up to 12 hours). */
406
+ delaySeconds?: number;
407
+ }
408
+ /** Options for a `producer.sendBatch(messages, options?)`. */
409
+ interface QueueSendBatchOptions {
410
+ /** Delivery delay applied to the whole batch, in seconds. */
411
+ delaySeconds?: number;
412
+ }
413
+ /** One entry in a `sendBatch` call — a body plus optional per-message overrides. */
414
+ interface MessageSendRequestLike<Body = unknown> {
415
+ body: Body;
416
+ contentType?: QueueContentType;
417
+ delaySeconds?: number;
418
+ }
419
+ /**
420
+ * Minimal structural projection of workers-types' `Queue&lt;Body>` (the producer
421
+ * 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.
423
+ */
424
+ interface QueueBindingLike<Body = unknown> {
425
+ send: (message: Body, options?: QueueSendOptions) => Promise<unknown>;
426
+ sendBatch: (messages: Iterable<MessageSendRequestLike<Body>>, options?: QueueSendBatchOptions) => Promise<unknown>;
427
+ }
428
+ /** Structural mirror of workers-types' `Message&lt;Body>` (one delivered message). */
429
+ interface MessageLike<Body = unknown> {
430
+ /** Acknowledge this message so it is not redelivered. */
431
+ ack: () => void;
432
+ readonly attempts: number;
433
+ readonly body: Body;
434
+ readonly id: string;
435
+ /** Explicitly retry this message (optionally after a delay). */
436
+ retry: (options?: QueueRetryOptions) => void;
437
+ readonly timestamp: Date;
438
+ }
439
+ /** Structural mirror of workers-types' `MessageBatch&lt;Body>` handed to a consumer. */
440
+ interface MessageBatchLike<Body = unknown> {
441
+ /** Acknowledge every message in the batch. */
442
+ ackAll: () => void;
443
+ readonly messages: ReadonlyArray<MessageLike<Body>>;
444
+ /** The queue name this batch was delivered from (`batch.queue`), used to route. */
445
+ readonly queue: string;
446
+ /** Retry every message in the batch (optionally after a delay). */
447
+ retryAll: (options?: QueueRetryOptions) => void;
448
+ }
449
+ /** Options for retrying a message / batch (`message.retry({ delaySeconds })`). */
450
+ interface QueueRetryOptions {
451
+ delaySeconds?: number;
452
+ }
453
+ /**
454
+ * `PlatformCapabilities` — the capability matrix type that describes which
455
+ * Lunora features a target platform supports natively, emulates, or cannot
456
+ * support at all.
457
+ *
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.
461
+ */
462
+ /** Support level for a single feature on a target platform. */
463
+ type CapabilityLevel = "native" | "emulated" | "unsupported";
464
+ /** Metadata about a capability's support level. */
465
+ interface Capability {
466
+ /** Whether the feature is native, emulated, or unsupported. */
467
+ level: CapabilityLevel;
468
+ /** Optional human-readable note (e.g. "requires AWS EventBridge", "limited to 1000 sockets"). */
469
+ note?: string;
470
+ }
471
+ /**
472
+ * The full capability matrix for a platform. Each key maps to a `ctx.*`
473
+ * feature or a subsystem; the value describes the target's support level.
474
+ */
475
+ interface PlatformCapabilities {
476
+ /** Feature-level capabilities. */
477
+ features: {
478
+ /** AI inference (Workers AI / Bedrock / OpenAI). */
479
+ ai?: Capability;
480
+ /** Analytics / observability sinks. */
481
+ analytics?: Capability;
482
+ /** Browser rendering / headless browser. */
483
+ browser?: Capability;
484
+ /** Container execution (Cloudflare Containers / Fargate). */
485
+ containers?: Capability;
486
+ /** Cross-shard fan-out queries. */
487
+ crossShardFanout?: Capability;
488
+ /** Global (replicated) tables backed by a SQL store. */
489
+ globalTables?: Capability;
490
+ /** BYO database via connection pooling (Hyperdrive / RDS Proxy). */
491
+ hyperdrive?: Capability;
492
+ /** Key-value storage (KV / Redis / DynamoDB). */
493
+ keyValueStore?: Capability;
494
+ /** Local SQL execution inside a shard. */
495
+ localSql?: Capability;
496
+ /** Email sending (Resend / SES / etc). */
497
+ mail?: Capability;
498
+ /** Object storage (R2 / S3 / MinIO). */
499
+ objectStorage?: Capability;
500
+ /** Pipelines / streaming data. */
501
+ pipelines?: Capability;
502
+ /** Queue-backed workpools. */
503
+ queues?: Capability;
504
+ /** Cron triggers / scheduled functions. */
505
+ scheduler?: Capability;
506
+ /** Secrets management. */
507
+ secrets?: Capability;
508
+ /** Alarms / scheduled wakeup inside a shard. */
509
+ shardAlarms?: Capability;
510
+ /** Durable Object-style sharded state. */
511
+ shardedState?: Capability;
512
+ /** Vector database (Vectorize / pgvector / Pinecone). */
513
+ vectorStore?: Capability;
514
+ /** Hibernated WebSocket subscriptions. */
515
+ websocketHibernation?: Capability;
516
+ /** Durable workflows (step-based). */
517
+ workflows?: Capability;
518
+ };
519
+ /** Platform identifier used in codegen and config (e.g. "cloudflare", "aws"). */
520
+ id: string;
521
+ /** Human-readable platform name (e.g. "Cloudflare", "AWS", "Rivet"). */
522
+ name: string;
523
+ }
524
+ /**
525
+ * The Cloudflare capability matrix — the reference implementation.
526
+ *
527
+ * `native` means the platform itself provides the feature; `emulated` means
528
+ * Lunora builds it on top of lower-level platform primitives (or a third-party
529
+ * service) rather than consuming a first-class product. Codegen and Studio read
530
+ * this distinction to report parity honestly, so a feature Lunora implements
531
+ * itself must not be reported as native even when it works flawlessly.
532
+ */
533
+ declare const CLOUDFLARE_CAPABILITIES: PlatformCapabilities;
534
+ /**
535
+ * The Node capability matrix — `@lunora/platform-node`'s honest self-rating
536
+ * (plan 234).
537
+ *
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. Both ratings, and the `"unsupported"` ones for `scheduler`
559
+ * durability and `globalTables`, are argued in detail in
560
+ * `plans/234-node-host-findings.md`.
561
+ */
562
+ declare const NODE_CAPABILITIES: PlatformCapabilities;
563
+ 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 };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{NOOP_EXECUTION_CONTEXT as E}from"./packem_shared/NOOP_EXECUTION_CONTEXT-YmXqH-jH.mjs";import{CLOUDFLARE_CAPABILITIES as O,NODE_CAPABILITIES as e}from"./packem_shared/CLOUDFLARE_CAPABILITIES-CfXSyHOn.mjs";import{resolveShard as C}from"./packem_shared/resolveShard-uGhAKuTB.mjs";export{O as CLOUDFLARE_CAPABILITIES,e as NODE_CAPABILITIES,E as NOOP_EXECUTION_CONTEXT,C as resolveShard};
@@ -0,0 +1 @@
1
+ const e={id:"cloudflare",name:"Cloudflare",features:{shardedState:{level:"native",note:"Durable Objects with SQLite"},globalTables:{level:"native",note:"D1 with Sessions API"},websocketHibernation:{level:"native",note:"DO WebSocket hibernation"},localSql:{level:"native",note:"state.storage.sql (SQLite)"},shardAlarms:{level:"native",note:"state.storage.setAlarm"},crossShardFanout:{level:"emulated",note:"Lunora query coordinator + relay tier over Durable Objects"},queues:{level:"native",note:"Cloudflare Queues"},workflows:{level:"native",note:"Cloudflare Workflows"},scheduler:{level:"emulated",note:"SchedulerDO (Lunora, on DO alarms) + declarative Cron Triggers; no runtime cron registration"},objectStorage:{level:"native",note:"R2"},keyValueStore:{level:"native",note:"Workers KV"},vectorStore:{level:"native",note:"Vectorize"},ai:{level:"native",note:"Workers AI"},browser:{level:"native",note:"Browser Rendering"},containers:{level:"native",note:"Cloudflare Containers"},analytics:{level:"native",note:"Analytics Engine"},pipelines:{level:"native",note:"Cloudflare Pipelines"},mail:{level:"emulated",note:"Resend (third-party) via Cloudflare Queues"},secrets:{level:"native",note:"Secrets Store"},hyperdrive:{level:"native",note:"Cloudflare Hyperdrive"}}},t={id:"node",name:"Node",features:{shardedState:{level:"emulated",note:"One better-sqlite3 database per shard key, one process — no distributed placement or failover"},globalTables:{level:"unsupported",note:"No replicated SQL store (D1-equivalent) implemented"},websocketHibernation:{level:"emulated",note:"In-process socket registry; attachments/tags survive a simulated recycle, not a process restart, and nothing is ever actually evicted from memory"},localSql:{level:"native",note:"better-sqlite3 (synchronous, embedded)"},shardAlarms:{level:"emulated",note:"In-process setTimeout; the timestamp can be persisted to SQLite but nothing re-arms it on process restart"},crossShardFanout:{level:"unsupported",note:"No query coordinator / relay tier implemented"},queues:{level:"unsupported",note:"No Cloudflare Queues equivalent implemented"},workflows:{level:"unsupported",note:"No Cloudflare Workflows equivalent implemented"},scheduler:{level:"emulated",note:"In-process setTimeout only; not durable across a process restart, and no dynamic cron registration is implemented"},objectStorage:{level:"unsupported",note:"No R2/S3-equivalent binding implemented"},keyValueStore:{level:"emulated",note:"better-sqlite3 table behind the ShardKvStore API — not a dedicated KV product"},vectorStore:{level:"unsupported",note:"No Vectorize-equivalent binding implemented"},ai:{level:"unsupported",note:"No Workers AI-equivalent binding implemented"},browser:{level:"unsupported",note:"No headless-browser binding implemented"},containers:{level:"unsupported",note:"No container orchestration implemented"},analytics:{level:"unsupported",note:"No Analytics Engine-equivalent binding implemented"},pipelines:{level:"unsupported",note:"No Pipelines-equivalent binding implemented"},mail:{level:"unsupported",note:"@lunora/mail's queue-backed sends need a queues binding, which this target does not provide"},secrets:{level:"unsupported",note:"No Secrets Store-equivalent binding implemented (a real host would likely map this to env vars)"},hyperdrive:{level:"unsupported",note:"No connection-pooling binding implemented"}}};export{e as CLOUDFLARE_CAPABILITIES,t as NODE_CAPABILITIES};
@@ -0,0 +1 @@
1
+ const t={passThroughOnException:()=>{},waitUntil:()=>{}};export{t as NOOP_EXECUTION_CONTEXT};
@@ -0,0 +1 @@
1
+ import{DatabaseSync as k}from"node:sqlite";let w=0,v=0;const B=()=>(w+=1,`socket-${w}`),D=()=>(v+=1,`job-${v}`),L=n=>n===void 0?null:n,C=n=>typeof n=="string"?new TextEncoder().encode(n).buffer:n instanceof ArrayBuffer?n:ArrayBuffer.isView(n)?n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength):new ArrayBuffer(0),R=()=>{const n=new k(":memory:"),s={alarmAt:null,alarmTimeout:null,pending:[],running:!1},i=new Map,f=new Map,u=new Map,T={exec:(e,...t)=>{const r=n.prepare(e),a=t.map(L),o=e.trim().toLowerCase().startsWith("select")?r.all(...a):(r.run(...a),[]);return{[Symbol.iterator]:()=>o[Symbol.iterator](),one:()=>{if(o.length!==1)throw new Error(`expected exactly one row, got ${String(o.length)}`);return o[0]},toArray:()=>[...o]}}},A={all:async(e,t)=>n.prepare(e).all(...t),run:async(e,t)=>{const r=n.prepare(e).run(...t);return{rowsAffected:Number(r.changes)}}},g=()=>{if(s.running||s.pending.length===0)return;const e=s.pending.shift();e!==void 0&&(s.running=!0,e.function_().then(e.resolve,e.reject).finally(()=>{s.running=!1,g()}))},b={alarms:{delete:()=>{s.alarmAt=null,s.alarmTimeout!==null&&(clearTimeout(s.alarmTimeout),s.alarmTimeout=null)},get:()=>s.alarmAt,set:e=>{const t=typeof e=="number"?e:e.getTime();s.alarmAt=t,s.alarmTimeout!==null&&clearTimeout(s.alarmTimeout);const r=Math.max(0,t-Date.now());s.alarmTimeout=setTimeout(()=>{s.alarmAt=null,s.alarmTimeout=null},r)}},asyncSql:A,runSerialized:e=>new Promise((t,r)=>{s.pending.push({function_:e,reject:a=>{r(a)},resolve:a=>{t(a)}}),g()}),sql:T,transaction:async e=>{n.exec("BEGIN");try{const t=await e();return n.exec("COMMIT"),t}catch(t){throw n.exec("ROLLBACK"),t}},waitUntil:()=>{}},c=new WeakMap,h=e=>{const t={bufferedAmount:e.bufferedAmount,close:(r,a)=>{e.closed=!0},deserializeAttachment:()=>e.attachment,send:r=>{e.received.push(typeof r=="string"?r:C(r))},serializeAttachment:r=>{e.attachment=r,f.set(e.id,r)}};return e.handle=t,c.set(t,e.id),t},M={accept:(e,t,r)=>{const a=B(),o={attachment:t,bufferedAmount:0,closed:!1,raw:e,handle:null,id:a,received:[],tags:new Set(r)};return i.set(a,o),u.set(a,new Set(r)),t!==void 0&&f.set(a,t),h(o)},getSockets:e=>{const t=[...i.values()];return(e===void 0?t:t.filter(r=>r.tags.has(e))).map(r=>r.handle)},handleFor:e=>[...i.values()].find(t=>t.raw===e)?.handle,idFor:e=>{const t=c.get(e);if(t===void 0)throw new Error("reference host: idFor called with a handle this host never issued");return t},removeTag:(e,t)=>{const r=i.get(c.get(e)??"");r!==void 0&&(t===void 0?r.tags.clear():r.tags.delete(t),u.set(c.get(e)??"",new Set(r.tags)))},setTag:(e,t)=>{const r=c.get(e)??"",a=i.get(r);a!==void 0&&(a.tags.add(t),u.set(r,new Set(a.tags)))}},p={get:e=>({fetch:async()=>new Response(String(e))}),getByName:e=>({fetch:async()=>new Response(e)}),idForName:e=>`shard:${e}`,jurisdiction:e=>p},d=new Map,S={delete:async e=>d.delete(e),get:async e=>d.get(e),list:async e=>{const t=e?.prefix??"",r=new Map;for(const[a,o]of d)a.startsWith(t)&&r.set(a,o);return r},put:async(e,t)=>{d.set(e,structuredClone(t))}},l=new Map,m=new Map,y=(e,t)=>({attempts:t.attempts,functionPath:t.functionPath,id:e,scheduledFor:t.scheduledFor});return{awaitAlarmFired:async e=>{await new Promise(t=>{setTimeout(t,Math.max(0,e-Date.now())+30)})},cleanup:()=>{n.close(),s.alarmTimeout!==null&&clearTimeout(s.alarmTimeout);for(const e of l.values())clearTimeout(e.timer)},directory:p,kv:S,readFrames:e=>(i.get(c.get(e)??"")?.received??[]).filter(t=>typeof t=="string"),restoreSocket:(e,t)=>{const r={attachment:t,bufferedAmount:0,closed:!1,handle:null,id:e,raw:void 0,received:[],tags:new Set(u.get(e))};return i.set(e,r),h(r)},scheduler:{cancel:async e=>{const t=l.get(e);return t===void 0?!1:(clearTimeout(t.timer),l.delete(e),!0)},cron:async()=>{},deadLetter:{list:async()=>[...m].map(([e,t])=>y(e,t)),requeue:async e=>{const t=m.get(e);return t===void 0?!1:(m.delete(e),l.set(e,{...t,attempts:0,timer:void 0}),!0)}},list:async()=>[...l].map(([e,t])=>y(e,t)),schedule:async(e,t,r)=>{const a=D();let o;r?.at===void 0?o=Date.now()+(r?.delayMs??0):o=typeof r.at=="number"?r.at:r.at.getTime();const x=Math.max(0,o-Date.now()),F=setTimeout(()=>{l.delete(a)},x);return l.set(a,{args:t,attempts:0,functionPath:e,options:r??{},scheduledFor:o,timer:F}),{id:a,scheduledFor:o}}},simulateDeadLetter:async e=>{const t=l.get(e);return t===void 0?!1:(clearTimeout(t.timer),l.delete(e),m.set(e,{...t,attempts:(t.options.retry?.maxAttempts??5)+1,timer:void 0}),!0)},shard:b,simulateRecycle:()=>{i.clear()},socket:M}};export{R as createReferenceHost};
@@ -0,0 +1 @@
1
+ const t=(e,o)=>e.getByName!==void 0?e.getByName(o):e.get(e.idForName(o));export{t as resolveShard};