@lunora/platform 1.0.0-alpha.5 → 1.0.0-alpha.7

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.
@@ -1 +1 @@
1
- import{createReferenceHost as o}from"../packem_shared/createReferenceHost-fs0Q8_VA.mjs";import{defineHostContractSuite as f}from"./suite.mjs";export{o as createReferenceHost,f as defineHostContractSuite};
1
+ import{createReferenceHost as o}from"../packem_shared/createReferenceHost-NGFPX8d2.mjs";import{defineHostContractSuite as f}from"./suite.mjs";export{o as createReferenceHost,f as defineHostContractSuite};
package/dist/index.d.mts CHANGED
@@ -317,7 +317,7 @@ interface R2BucketLike {
317
317
  objects: R2ObjectLike[];
318
318
  truncated?: boolean;
319
319
  }>;
320
- put: (key: string, body: ReadableStream | ArrayBuffer | Blob | string | null, options?: {
320
+ put: (key: string, body: ReadableStream | ArrayBuffer | ArrayBufferView | Blob | string | null, options?: {
321
321
  customMetadata?: Record<string, string>;
322
322
  httpMetadata?: {
323
323
  contentType?: string;
@@ -417,15 +417,15 @@ interface MessageSendRequestLike<Body = unknown> {
417
417
  delaySeconds?: number;
418
418
  }
419
419
  /**
420
- * Minimal structural projection of workers-types' `Queue&lt;Body>` (the producer
420
+ * Minimal structural projection of workers-types' `Queue<Body>` (the producer
421
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.
422
+ * we widen the return to `Promise<unknown>` so a plain-object fake satisfies it.
423
423
  */
424
424
  interface QueueBindingLike<Body = unknown> {
425
425
  send: (message: Body, options?: QueueSendOptions) => Promise<unknown>;
426
426
  sendBatch: (messages: Iterable<MessageSendRequestLike<Body>>, options?: QueueSendBatchOptions) => Promise<unknown>;
427
427
  }
428
- /** Structural mirror of workers-types' `Message&lt;Body>` (one delivered message). */
428
+ /** Structural mirror of workers-types' `Message<Body>` (one delivered message). */
429
429
  interface MessageLike<Body = unknown> {
430
430
  /** Acknowledge this message so it is not redelivered. */
431
431
  ack: () => void;
@@ -436,7 +436,7 @@ interface MessageLike<Body = unknown> {
436
436
  retry: (options?: QueueRetryOptions) => void;
437
437
  readonly timestamp: Date;
438
438
  }
439
- /** Structural mirror of workers-types' `MessageBatch&lt;Body>` handed to a consumer. */
439
+ /** Structural mirror of workers-types' `MessageBatch<Body>` handed to a consumer. */
440
440
  interface MessageBatchLike<Body = unknown> {
441
441
  /** Acknowledge every message in the batch. */
442
442
  ackAll: () => void;
@@ -535,33 +535,44 @@ declare const CLOUDFLARE_CAPABILITIES: PlatformCapabilities;
535
535
  * The Node capability matrix — `@lunora/platform-node`'s honest self-rating
536
536
  * (plan 234).
537
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.
538
+ * `@lunora/platform-node` implements every contract in this package
539
+ * (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
540
+ * `SchedulerHost`) over `better-sqlite3` and an in-process registry, plus the
541
+ * `.global()` table backend via `@lunora/sql-store`. It began as a spike to run
542
+ * the conformance TCK against a second host; the durability gaps that spike
543
+ * surfaced alarms and scheduler jobs that were persisted but never re-armed,
544
+ * socket attachments that lived only in memory — are closed, and each is now
545
+ * pinned by a restart test rather than only by a simulated recycle.
551
546
  *
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`.
547
+ * `scheduler` and `shardAlarms` were rated `"unsupported"` under plan 267, on
548
+ * the grounds that the host stored and timed both while its timer body only
549
+ * cleared bookkeeping nothing dispatched the scheduled function or woke the
550
+ * alarm. That rating was correct for the code it described, and the code is
551
+ * what changed: both now dispatch (through `onDispatch` / `onAlarm`) and both
552
+ * re-arm from their durable rows on construction, so `"emulated"` built on
553
+ * lower-level primitives and *working* is now the honest reading.
554
+ *
555
+ * What remains genuinely absent is everything a single Node process cannot
556
+ * distribute: placement across nodes, failover, and most Cloudflare-specific
557
+ * product bindings (Vectorize, Workers AI, Containers, Browser Rendering,
558
+ * Analytics Engine, Secrets Store, Hyperdrive). Workflows, object storage and
559
+ * queues are the three that CAN be emulated locally — `defineWorkflow` handlers
560
+ * compile onto the `@visulima/workflow` engine, R2 becomes a filesystem bucket,
561
+ * and Queues becomes a durable table with the same batch/ack/retry/dead-letter
562
+ * semantics — so those three are rated `"emulated"`; the rest of the Cloudflare
563
+ * products most `ctx.*` surfaces are built on are rated `"unsupported"` here
564
+ * rather than left undeclared — see `gateAgainstMatrix` in `@lunora/codegen`,
565
+ * whose fail-closed gate (plan 229) treats an undeclared feature as unsupported
566
+ * anyway, but under a different diagnostic name than an honest, explicit rating.
567
+ *
568
+ * Almost nothing here is rated `"native"`, and that is the matrix's own
569
+ * definition doing its job rather than a hedge: `native` means the platform
570
+ * itself provides the feature, and a bare Node process provides essentially
571
+ * none of them — Lunora builds alarms out of `setTimeout` plus a durable row,
572
+ * a KV store out of a SQL table, and `.global()` tables out of a second SQLite
573
+ * file. `localSql` is the exception, because SQLite genuinely is the platform
574
+ * primitive there. The ratings say who does the work; the notes say how well.
575
+ * Both are argued in detail in `plans/234-node-host-findings.md`.
565
576
  */
566
577
  declare const NODE_CAPABILITIES: PlatformCapabilities;
567
578
  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.d.ts CHANGED
@@ -317,7 +317,7 @@ interface R2BucketLike {
317
317
  objects: R2ObjectLike[];
318
318
  truncated?: boolean;
319
319
  }>;
320
- put: (key: string, body: ReadableStream | ArrayBuffer | Blob | string | null, options?: {
320
+ put: (key: string, body: ReadableStream | ArrayBuffer | ArrayBufferView | Blob | string | null, options?: {
321
321
  customMetadata?: Record<string, string>;
322
322
  httpMetadata?: {
323
323
  contentType?: string;
@@ -417,15 +417,15 @@ interface MessageSendRequestLike<Body = unknown> {
417
417
  delaySeconds?: number;
418
418
  }
419
419
  /**
420
- * Minimal structural projection of workers-types' `Queue&lt;Body>` (the producer
420
+ * Minimal structural projection of workers-types' `Queue<Body>` (the producer
421
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.
422
+ * we widen the return to `Promise<unknown>` so a plain-object fake satisfies it.
423
423
  */
424
424
  interface QueueBindingLike<Body = unknown> {
425
425
  send: (message: Body, options?: QueueSendOptions) => Promise<unknown>;
426
426
  sendBatch: (messages: Iterable<MessageSendRequestLike<Body>>, options?: QueueSendBatchOptions) => Promise<unknown>;
427
427
  }
428
- /** Structural mirror of workers-types' `Message&lt;Body>` (one delivered message). */
428
+ /** Structural mirror of workers-types' `Message<Body>` (one delivered message). */
429
429
  interface MessageLike<Body = unknown> {
430
430
  /** Acknowledge this message so it is not redelivered. */
431
431
  ack: () => void;
@@ -436,7 +436,7 @@ interface MessageLike<Body = unknown> {
436
436
  retry: (options?: QueueRetryOptions) => void;
437
437
  readonly timestamp: Date;
438
438
  }
439
- /** Structural mirror of workers-types' `MessageBatch&lt;Body>` handed to a consumer. */
439
+ /** Structural mirror of workers-types' `MessageBatch<Body>` handed to a consumer. */
440
440
  interface MessageBatchLike<Body = unknown> {
441
441
  /** Acknowledge every message in the batch. */
442
442
  ackAll: () => void;
@@ -535,33 +535,44 @@ declare const CLOUDFLARE_CAPABILITIES: PlatformCapabilities;
535
535
  * The Node capability matrix — `@lunora/platform-node`'s honest self-rating
536
536
  * (plan 234).
537
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.
538
+ * `@lunora/platform-node` implements every contract in this package
539
+ * (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
540
+ * `SchedulerHost`) over `better-sqlite3` and an in-process registry, plus the
541
+ * `.global()` table backend via `@lunora/sql-store`. It began as a spike to run
542
+ * the conformance TCK against a second host; the durability gaps that spike
543
+ * surfaced alarms and scheduler jobs that were persisted but never re-armed,
544
+ * socket attachments that lived only in memory — are closed, and each is now
545
+ * pinned by a restart test rather than only by a simulated recycle.
551
546
  *
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`.
547
+ * `scheduler` and `shardAlarms` were rated `"unsupported"` under plan 267, on
548
+ * the grounds that the host stored and timed both while its timer body only
549
+ * cleared bookkeeping nothing dispatched the scheduled function or woke the
550
+ * alarm. That rating was correct for the code it described, and the code is
551
+ * what changed: both now dispatch (through `onDispatch` / `onAlarm`) and both
552
+ * re-arm from their durable rows on construction, so `"emulated"` built on
553
+ * lower-level primitives and *working* is now the honest reading.
554
+ *
555
+ * What remains genuinely absent is everything a single Node process cannot
556
+ * distribute: placement across nodes, failover, and most Cloudflare-specific
557
+ * product bindings (Vectorize, Workers AI, Containers, Browser Rendering,
558
+ * Analytics Engine, Secrets Store, Hyperdrive). Workflows, object storage and
559
+ * queues are the three that CAN be emulated locally — `defineWorkflow` handlers
560
+ * compile onto the `@visulima/workflow` engine, R2 becomes a filesystem bucket,
561
+ * and Queues becomes a durable table with the same batch/ack/retry/dead-letter
562
+ * semantics — so those three are rated `"emulated"`; the rest of the Cloudflare
563
+ * products most `ctx.*` surfaces are built on are rated `"unsupported"` here
564
+ * rather than left undeclared — see `gateAgainstMatrix` in `@lunora/codegen`,
565
+ * whose fail-closed gate (plan 229) treats an undeclared feature as unsupported
566
+ * anyway, but under a different diagnostic name than an honest, explicit rating.
567
+ *
568
+ * Almost nothing here is rated `"native"`, and that is the matrix's own
569
+ * definition doing its job rather than a hedge: `native` means the platform
570
+ * itself provides the feature, and a bare Node process provides essentially
571
+ * none of them — Lunora builds alarms out of `setTimeout` plus a durable row,
572
+ * a KV store out of a SQL table, and `.global()` tables out of a second SQLite
573
+ * file. `localSql` is the exception, because SQLite genuinely is the platform
574
+ * primitive there. The ratings say who does the work; the notes say how well.
575
+ * Both are argued in detail in `plans/234-node-host-findings.md`.
565
576
  */
566
577
  declare const NODE_CAPABILITIES: PlatformCapabilities;
567
578
  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 CHANGED
@@ -1 +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-DMLgo_TI.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};
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-C09dwm8a.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; query/upsert namespace scoping is native (remote filter), but getByIds/deleteByIds id-path tenant isolation is facade-enforced (client-side verification) since Vectorize's id operations take no namespace option"},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:"emulated",note:"The @lunora/sql-store core on its own SQLite file via the reference sqliteDialect — full store semantics, but one node with no replication"},websocketHibernation:{level:"emulated",note:"Socket registry with attachments/tags persisted to SQLite, so subscription state survives a process restart; nothing is ever actually evicted from memory, so this is durability without hibernation's memory saving"},localSql:{level:"native",note:"better-sqlite3 (synchronous, embedded)"},shardAlarms:{level:"emulated",note:"setTimeout over a durable row, dispatched to onAlarm and re-armed on construction, so an alarm survives a restart and one whose time elapsed while the process was down fires late rather than never"},crossShardFanout:{level:"emulated",note:"@lunora/runtime's query coordinator over the in-process shard registry; listShardKeys is seeded from the shard files on disk, and answers every shard rather than only those holding the table (a correct superset, at the cost of visiting shards with nothing to say)"},queues:{level:"emulated",note:`createNodeQueueHost (@lunora/platform-node) — a QueueBindingLike producer per declared queue over a durable _lunora_queue_messages table, and a batched consumer feeding the same dispatchQueueBatch the Cloudflare host uses. delaySeconds (capped at 12h), all four content types, maxBatchSize/maxBatchTimeout assembly, per-message ack/retry with workerd's implicit-ack-on-return and retry-on-throw, maxRetries into a declared deadLetterQueue (or parked in place, never dropped), and a visibility window so a crash mid-handler redelivers. Delivery is driven by poll(); there is no timer, because this host has no dev server to own one. mode: "pull" queues are written but not consumed — nothing here serves the HTTP pull endpoint`},workflows:{level:"emulated",note:"createNodeWorkflowHost (@lunora/platform-node) compiles defineWorkflow handlers onto the @visulima/workflow engine (createRuntime): step/sleep/waitForEvent are durable + replay-safe, status maps to complete/errored/waiting/terminated, create({ id }) is honoured through a durable alias row (so ctx.spawn resolves and a retried create is one run), and runs survive a restart when backed by createNodeWorkflowStore (a SQLite WorkflowStore; the store is required, so no caller silently gets in-process-only state). Gaps: no pause/restart; terminate is not a barrier, so an activation already in flight overwrites the tombstone; ctx.run dispatches to an endpoint no Node HTTP server serves; ctx.parallel's synchronous join cannot interleave within one trigger activation"},scheduler:{level:"emulated",note:"SQLite job table dispatched to onDispatch and re-armed on construction, with retry backoff and a dead-letter queue; the only host implementing runtime cron registration (SchedulerHost.cron), which Cloudflare cannot offer"},objectStorage:{level:"emulated",note:"createNodeR2Bucket (@lunora/platform-node) — an R2BucketLike over the local filesystem (fs/promises, head/list/range). One file per object with the metadata in a trailer, so the single rename that publishes the bytes publishes their checksum and content-type with them, and a get reads body and metadata through one handle rather than reopening the path. put streams into the staged file and .body streams the requested range; .arrayBuffer()/.text() still allocate the range they return. The body is single-use, as R2's is. Keys fold the way the host filesystem folds them, so `A` and `a` are one object on a case-insensitive volume where real R2 keeps two. No multipart uploads, no presigned URLs"},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};
@@ -1 +1 @@
1
- import{DatabaseSync as R}from"node:sqlite";let b=0,S=0;const j=()=>(b+=1,`socket-${b}`),C=()=>(S+=1,`job-${S}`),q=n=>n===void 0?null:n,E=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),O=()=>{const n=new R(":memory:"),s={alarmAt:null,alarmTimeout:null,pending:[],running:!1},l=new Map,f=new Map,u=new Map,x={exec:(e,...t)=>{const a=n.prepare(e),r=t.map(q),o=e.trim().toLowerCase().startsWith("select")?a.all(...r):(a.run(...r),[]);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]}}},M={all:async(e,t)=>n.prepare(e).all(...t),run:async(e,t)=>{const a=n.prepare(e).run(...t);return{rowsAffected:Number(a.changes)}}},h=()=>{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,h()}))},F=e=>new Promise((t,a)=>{s.pending.push({function_:e,reject:r=>{a(r)},resolve:r=>{t(r)}}),h()});let g=Promise.resolve();const p=async e=>{n.exec("BEGIN");try{const t=await e();return n.exec("COMMIT"),t}catch(t){throw n.exec("ROLLBACK"),t}},D={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 a=Math.max(0,t-Date.now());s.alarmTimeout=setTimeout(()=>{s.alarmAt=null,s.alarmTimeout=null},a)}},asyncSql:M,runSerialized:F,sql:x,transaction:e=>{const t=g.then(()=>p(e),()=>p(e));return g=t.then(()=>{},()=>{}),t},waitUntil:()=>{}},c=new WeakMap,w=e=>{const t={bufferedAmount:e.bufferedAmount,close:(a,r)=>{e.closed=!0},deserializeAttachment:()=>e.attachment,send:a=>{e.received.push(typeof a=="string"?a:E(a))},serializeAttachment:a=>{e.attachment=a,f.set(e.id,a)}};return e.handle=t,c.set(t,e.id),t},P={accept:(e,t,a)=>{const r=j(),o={attachment:t,bufferedAmount:0,closed:!1,raw:e,handle:null,id:r,received:[],tags:new Set(a)};return l.set(r,o),u.set(r,new Set(a)),t!==void 0&&f.set(r,t),w(o)},getSockets:e=>{const t=[...l.values()];return(e===void 0?t:t.filter(a=>a.tags.has(e))).map(a=>a.handle)},handleFor:e=>[...l.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 a=l.get(c.get(e)??"");a!==void 0&&(t===void 0?a.tags.clear():a.tags.delete(t),u.set(c.get(e)??"",new Set(a.tags)))},setTag:(e,t)=>{const a=c.get(e)??"",r=l.get(a);r!==void 0&&(r.tags.add(t),u.set(a,new Set(r.tags)))}},y={get:e=>({fetch:async()=>new Response(String(e))}),getByName:e=>({fetch:async()=>new Response(e)}),idForName:e=>`shard:${e}`,jurisdiction:e=>y},d=new Map,k={delete:async e=>d.delete(e),get:async e=>d.get(e),list:async e=>{const t=e?.prefix??"",a=new Map;for(const[r,o]of d)r.startsWith(t)&&a.set(r,o);return a},put:async(e,t)=>{d.set(e,structuredClone(t))}},i=new Map,m=new Map,v=new Set,T=(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)})},awaitJobDispatched:async e=>{const t=i.get(e);return t!==void 0&&await new Promise(a=>{setTimeout(a,Math.max(0,t.scheduledFor-Date.now())+30)}),v.has(e)},cleanup:()=>{n.close(),s.alarmTimeout!==null&&clearTimeout(s.alarmTimeout);for(const e of i.values())clearTimeout(e.timer)},directory:y,kv:k,readFrames:e=>(l.get(c.get(e)??"")?.received??[]).filter(t=>typeof t=="string"),restoreSocket:(e,t)=>{const a={attachment:t,bufferedAmount:0,closed:!1,handle:null,id:e,raw:void 0,received:[],tags:new Set(u.get(e))};return l.set(e,a),w(a)},scheduler:{cancel:async e=>{const t=i.get(e);return t===void 0?!1:(clearTimeout(t.timer),i.delete(e),!0)},cron:async()=>{},deadLetter:{list:async()=>[...m].map(([e,t])=>T(e,t)),requeue:async e=>{const t=m.get(e);return t===void 0?!1:(m.delete(e),i.set(e,{...t,attempts:0,timer:void 0}),!0)}},list:async()=>[...i].map(([e,t])=>T(e,t)),schedule:async(e,t,a)=>{const r=C();let o;a?.at===void 0?o=Date.now()+(a?.delayMs??0):o=typeof a.at=="number"?a.at:a.at.getTime();const B=Math.max(0,o-Date.now()),L=setTimeout(()=>{const A=i.get(r);A!==void 0&&(A.attempts+=1),v.add(r),i.delete(r)},B);return i.set(r,{args:t,attempts:0,functionPath:e,options:a??{},scheduledFor:o,timer:L}),{id:r,scheduledFor:o}}},simulateDeadLetter:async e=>{const t=i.get(e);return t===void 0?!1:(clearTimeout(t.timer),i.delete(e),m.set(e,{...t,attempts:(t.options.retry?.maxAttempts??5)+1,timer:void 0}),!0)},shard:D,simulateRecycle:()=>{l.clear()},socket:P}};export{O as createReferenceHost};
1
+ import{DatabaseSync as R}from"node:sqlite";let b=0,S=0;const j=()=>(b+=1,`socket-${b}`),C=()=>(S+=1,`job-${S}`),q=n=>n===void 0?null:n,E=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),O=()=>{const n=new R(":memory:"),s={alarmAt:null,alarmTimeout:null,pending:[],running:!1},l=new Map,f=new Map,u=new Map,x={exec:(e,...t)=>{const a=n.prepare(e),r=t.map(q),o=e.trim().toLowerCase().startsWith("select")?a.all(...r):(a.run(...r),[]);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]}}},M={all:async(e,t)=>n.prepare(e).all(...t),run:async(e,t)=>{const a=n.prepare(e).run(...t);return{rowsAffected:Number(a.changes)}}},h=()=>{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,h()}))},F=e=>new Promise((t,a)=>{s.pending.push({function_:e,reject:r=>{a(r)},resolve:r=>{t(r)}}),h()});let g=Promise.resolve();const p=async e=>{n.exec("BEGIN");try{const t=await e();return n.exec("COMMIT"),t}catch(t){throw n.exec("ROLLBACK"),t}},D={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 a=Math.max(0,t-Date.now());s.alarmTimeout=setTimeout(()=>{s.alarmAt=null,s.alarmTimeout=null},a)}},asyncSql:M,runSerialized:F,sql:x,transaction:e=>{const t=g.then(()=>p(e),()=>p(e));return g=t.then(()=>{},()=>{}),t},waitUntil:()=>{}},c=new WeakMap,w=e=>{const t={bufferedAmount:e.bufferedAmount,close:(a,r)=>{e.closed=!0},deserializeAttachment:()=>e.attachment,send:a=>{e.received.push(typeof a=="string"?a:E(a))},serializeAttachment:a=>{e.attachment=a,f.set(e.id,a)}};return e.handle=t,c.set(t,e.id),t},P={accept:(e,t,a)=>{const r=j(),o={attachment:t,bufferedAmount:0,closed:!1,raw:e,handle:null,id:r,received:[],tags:new Set(a)};return l.set(r,o),u.set(r,new Set(a)),t!==void 0&&f.set(r,t),w(o)},getSockets:e=>{const t=[...l.values()];return(e===void 0?t:t.filter(a=>a.tags.has(e))).map(a=>a.handle)},handleFor:e=>[...l.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 a=c.get(e)??"",r=l.get(a);r!==void 0&&(t===void 0?r.tags.clear():r.tags.delete(t),u.set(a,new Set(r.tags)))},setTag:(e,t)=>{const a=c.get(e)??"",r=l.get(a);r!==void 0&&(r.tags.add(t),u.set(a,new Set(r.tags)))}},y={get:e=>({fetch:async()=>new Response(String(e))}),getByName:e=>({fetch:async()=>new Response(e)}),idForName:e=>`shard:${e}`,jurisdiction:e=>y},d=new Map,k={delete:async e=>d.delete(e),get:async e=>d.get(e),list:async e=>{const t=e?.prefix??"",a=new Map;for(const[r,o]of d)r.startsWith(t)&&a.set(r,o);return a},put:async(e,t)=>{d.set(e,structuredClone(t))}},i=new Map,m=new Map,v=new Set,T=(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)})},awaitJobDispatched:async e=>{const t=i.get(e);return t!==void 0&&await new Promise(a=>{setTimeout(a,Math.max(0,t.scheduledFor-Date.now())+30)}),v.has(e)},cleanup:()=>{n.close(),s.alarmTimeout!==null&&clearTimeout(s.alarmTimeout);for(const e of i.values())clearTimeout(e.timer)},directory:y,kv:k,readFrames:e=>(l.get(c.get(e)??"")?.received??[]).filter(t=>typeof t=="string"),restoreSocket:(e,t)=>{const a={attachment:t,bufferedAmount:0,closed:!1,handle:null,id:e,raw:void 0,received:[],tags:new Set(u.get(e))};return l.set(e,a),w(a)},scheduler:{cancel:async e=>{const t=i.get(e);return t===void 0?!1:(clearTimeout(t.timer),i.delete(e),!0)},cron:async()=>{},deadLetter:{list:async()=>[...m].map(([e,t])=>T(e,t)),requeue:async e=>{const t=m.get(e);return t===void 0?!1:(m.delete(e),i.set(e,{...t,attempts:0,timer:void 0}),!0)}},list:async()=>[...i].map(([e,t])=>T(e,t)),schedule:async(e,t,a)=>{const r=C();let o;a?.at===void 0?o=Date.now()+(a?.delayMs??0):o=typeof a.at=="number"?a.at:a.at.getTime();const B=Math.max(0,o-Date.now()),L=setTimeout(()=>{const A=i.get(r);A!==void 0&&(A.attempts+=1),v.add(r),i.delete(r)},B);return i.set(r,{args:t,attempts:0,functionPath:e,options:a??{},scheduledFor:o,timer:L}),{id:r,scheduledFor:o}}},simulateDeadLetter:async e=>{const t=i.get(e);return t===void 0?!1:(clearTimeout(t.timer),i.delete(e),m.set(e,{...t,attempts:(t.options.retry?.maxAttempts??5)+1,timer:void 0}),!0)},shard:D,simulateRecycle:()=>{l.clear()},socket:P}};export{O as createReferenceHost};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/platform",
3
- "version": "1.0.0-alpha.5",
3
+ "version": "1.0.0-alpha.7",
4
4
  "description": "Provider-neutral host contracts for Lunora: shard/socket/directory/scheduler interfaces, binding projections, and the platform capability matrix",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -1 +0,0 @@
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; query/upsert namespace scoping is native (remote filter), but getByIds/deleteByIds id-path tenant isolation is facade-enforced (client-side verification) since Vectorize's id operations take no namespace option"},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:"unsupported",note:"In-process bookkeeping only — the armed timer clears state and never wakes anything; no dispatch, and nothing re-arms across a 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:"unsupported",note:"Jobs are stored and timed but never dispatched — no delivery, no retries; also not durable across a process restart"},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};