@lunora/platform 1.0.0-alpha.6 → 1.0.0-alpha.8

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,11 +317,12 @@ 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;
324
324
  };
325
+ sha256?: ArrayBuffer | string;
325
326
  }) => Promise<R2ObjectLike>;
326
327
  /** Resume an in-progress multipart upload by id (R2 `resumeMultipartUpload`). Optional; see {@link Storage.resumeMultipartUpload}. */
327
328
  resumeMultipartUpload?: (key: string, uploadId: string) => R2MultipartUploadLike;
@@ -497,6 +498,16 @@ interface PlatformCapabilities {
497
498
  mail?: Capability;
498
499
  /** Object storage (R2 / S3 / MinIO). */
499
500
  objectStorage?: Capability;
501
+ /**
502
+ * Snapshot backups kept in object storage rather than on the machine
503
+ * that took them — `lunora backup create|list|restore --bucket`, and
504
+ * the platform's own `backupCron`. Distinct from
505
+ * `objectStorage` above because it needs three things a
506
+ * bucket alone does not imply: an admin-gated read of one object
507
+ * (`GET /_lunora/admin/storage/object`), a checksum-verified write, and
508
+ * a scheduler to run the unattended half.
509
+ */
510
+ objectStorageBackups?: Capability;
500
511
  /** Pipelines / streaming data. */
501
512
  pipelines?: Capability;
502
513
  /** Queue-backed workpools. */
@@ -535,33 +546,44 @@ declare const CLOUDFLARE_CAPABILITIES: PlatformCapabilities;
535
546
  * The Node capability matrix — `@lunora/platform-node`'s honest self-rating
536
547
  * (plan 234).
537
548
  *
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.
549
+ * `@lunora/platform-node` implements every contract in this package
550
+ * (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
551
+ * `SchedulerHost`) over `better-sqlite3` and an in-process registry, plus the
552
+ * `.global()` table backend via `@lunora/sql-store`. It began as a spike to run
553
+ * the conformance TCK against a second host; the durability gaps that spike
554
+ * surfaced alarms and scheduler jobs that were persisted but never re-armed,
555
+ * socket attachments that lived only in memory — are closed, and each is now
556
+ * pinned by a restart test rather than only by a simulated recycle.
551
557
  *
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`.
558
+ * `scheduler` and `shardAlarms` were rated `"unsupported"` under plan 267, on
559
+ * the grounds that the host stored and timed both while its timer body only
560
+ * cleared bookkeeping nothing dispatched the scheduled function or woke the
561
+ * alarm. That rating was correct for the code it described, and the code is
562
+ * what changed: both now dispatch (through `onDispatch` / `onAlarm`) and both
563
+ * re-arm from their durable rows on construction, so `"emulated"` built on
564
+ * lower-level primitives and *working* is now the honest reading.
565
+ *
566
+ * What remains genuinely absent is everything a single Node process cannot
567
+ * distribute: placement across nodes, failover, and most Cloudflare-specific
568
+ * product bindings (Vectorize, Workers AI, Containers, Browser Rendering,
569
+ * Analytics Engine, Secrets Store, Hyperdrive). Workflows, object storage and
570
+ * queues are the three that CAN be emulated locally — `defineWorkflow` handlers
571
+ * compile onto the `@visulima/workflow` engine, R2 becomes a filesystem bucket,
572
+ * and Queues becomes a durable table with the same batch/ack/retry/dead-letter
573
+ * semantics — so those three are rated `"emulated"`; the rest of the Cloudflare
574
+ * products most `ctx.*` surfaces are built on are rated `"unsupported"` here
575
+ * rather than left undeclared — see `gateAgainstMatrix` in `@lunora/codegen`,
576
+ * whose fail-closed gate (plan 229) treats an undeclared feature as unsupported
577
+ * anyway, but under a different diagnostic name than an honest, explicit rating.
578
+ *
579
+ * Almost nothing here is rated `"native"`, and that is the matrix's own
580
+ * definition doing its job rather than a hedge: `native` means the platform
581
+ * itself provides the feature, and a bare Node process provides essentially
582
+ * none of them — Lunora builds alarms out of `setTimeout` plus a durable row,
583
+ * a KV store out of a SQL table, and `.global()` tables out of a second SQLite
584
+ * file. `localSql` is the exception, because SQLite genuinely is the platform
585
+ * primitive there. The ratings say who does the work; the notes say how well.
586
+ * Both are argued in detail in `plans/234-node-host-findings.md`.
565
587
  */
566
588
  declare const NODE_CAPABILITIES: PlatformCapabilities;
567
589
  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,11 +317,12 @@ 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;
324
324
  };
325
+ sha256?: ArrayBuffer | string;
325
326
  }) => Promise<R2ObjectLike>;
326
327
  /** Resume an in-progress multipart upload by id (R2 `resumeMultipartUpload`). Optional; see {@link Storage.resumeMultipartUpload}. */
327
328
  resumeMultipartUpload?: (key: string, uploadId: string) => R2MultipartUploadLike;
@@ -497,6 +498,16 @@ interface PlatformCapabilities {
497
498
  mail?: Capability;
498
499
  /** Object storage (R2 / S3 / MinIO). */
499
500
  objectStorage?: Capability;
501
+ /**
502
+ * Snapshot backups kept in object storage rather than on the machine
503
+ * that took them — `lunora backup create|list|restore --bucket`, and
504
+ * the platform's own `backupCron`. Distinct from
505
+ * `objectStorage` above because it needs three things a
506
+ * bucket alone does not imply: an admin-gated read of one object
507
+ * (`GET /_lunora/admin/storage/object`), a checksum-verified write, and
508
+ * a scheduler to run the unattended half.
509
+ */
510
+ objectStorageBackups?: Capability;
500
511
  /** Pipelines / streaming data. */
501
512
  pipelines?: Capability;
502
513
  /** Queue-backed workpools. */
@@ -535,33 +546,44 @@ declare const CLOUDFLARE_CAPABILITIES: PlatformCapabilities;
535
546
  * The Node capability matrix — `@lunora/platform-node`'s honest self-rating
536
547
  * (plan 234).
537
548
  *
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.
549
+ * `@lunora/platform-node` implements every contract in this package
550
+ * (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
551
+ * `SchedulerHost`) over `better-sqlite3` and an in-process registry, plus the
552
+ * `.global()` table backend via `@lunora/sql-store`. It began as a spike to run
553
+ * the conformance TCK against a second host; the durability gaps that spike
554
+ * surfaced alarms and scheduler jobs that were persisted but never re-armed,
555
+ * socket attachments that lived only in memory — are closed, and each is now
556
+ * pinned by a restart test rather than only by a simulated recycle.
551
557
  *
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`.
558
+ * `scheduler` and `shardAlarms` were rated `"unsupported"` under plan 267, on
559
+ * the grounds that the host stored and timed both while its timer body only
560
+ * cleared bookkeeping nothing dispatched the scheduled function or woke the
561
+ * alarm. That rating was correct for the code it described, and the code is
562
+ * what changed: both now dispatch (through `onDispatch` / `onAlarm`) and both
563
+ * re-arm from their durable rows on construction, so `"emulated"` built on
564
+ * lower-level primitives and *working* is now the honest reading.
565
+ *
566
+ * What remains genuinely absent is everything a single Node process cannot
567
+ * distribute: placement across nodes, failover, and most Cloudflare-specific
568
+ * product bindings (Vectorize, Workers AI, Containers, Browser Rendering,
569
+ * Analytics Engine, Secrets Store, Hyperdrive). Workflows, object storage and
570
+ * queues are the three that CAN be emulated locally — `defineWorkflow` handlers
571
+ * compile onto the `@visulima/workflow` engine, R2 becomes a filesystem bucket,
572
+ * and Queues becomes a durable table with the same batch/ack/retry/dead-letter
573
+ * semantics — so those three are rated `"emulated"`; the rest of the Cloudflare
574
+ * products most `ctx.*` surfaces are built on are rated `"unsupported"` here
575
+ * rather than left undeclared — see `gateAgainstMatrix` in `@lunora/codegen`,
576
+ * whose fail-closed gate (plan 229) treats an undeclared feature as unsupported
577
+ * anyway, but under a different diagnostic name than an honest, explicit rating.
578
+ *
579
+ * Almost nothing here is rated `"native"`, and that is the matrix's own
580
+ * definition doing its job rather than a hedge: `native` means the platform
581
+ * itself provides the feature, and a bare Node process provides essentially
582
+ * none of them — Lunora builds alarms out of `setTimeout` plus a durable row,
583
+ * a KV store out of a SQL table, and `.global()` tables out of a second SQLite
584
+ * file. `localSql` is the exception, because SQLite genuinely is the platform
585
+ * primitive there. The ratings say who does the work; the notes say how well.
586
+ * Both are argued in detail in `plans/234-node-host-findings.md`.
565
587
  */
566
588
  declare const NODE_CAPABILITIES: PlatformCapabilities;
567
589
  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-Bv7ZBLQW.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"},objectStorageBackups:{level:"native",note:"`lunora backup create|list|restore --bucket` writes NDJSON snapshots + a manifest sidecar per snapshot through the admin storage routes (checksum-verified upload, admin-gated object read), and `backupCron`/`backupStore` runs the same layout unattended on a Cron Trigger. Both are bounded by what a single request body / a Worker isolate can hold, not by 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"},objectStorageBackups:{level:"emulated",note:"The commands work unchanged, but the bucket underneath is createNodeR2Bucket — a directory on the same machine the CLI runs on, so a bucket-backed backup here is not the separate failure domain it is on Cloudflare. The scheduled half additionally needs this host's scheduler, which exists but is not a shipping target"},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.6",
3
+ "version": "1.0.0-alpha.8",
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};