@lunora/platform 1.0.0-alpha.56 → 1.0.0-alpha.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts
CHANGED
|
@@ -1190,9 +1190,9 @@ interface PlatformCapabilities {
|
|
|
1190
1190
|
* differs is which primitives exist, and that difference is exactly this
|
|
1191
1191
|
* matrix.
|
|
1192
1192
|
*
|
|
1193
|
-
* Ratings track celld **v0.6.
|
|
1193
|
+
* Ratings track celld **v0.6.1** and derive from its documented compatibility
|
|
1194
1194
|
* surface (`docs/cloudflare-compat.md`, `docs/services/*.md`,
|
|
1195
|
-
* `docs/limitations.md` in the celld repo; v0.6.0
|
|
1195
|
+
* `docs/limitations.md` in the celld repo; v0.6.0 was celld's first beta). The host contracts
|
|
1196
1196
|
* behind `shardedState`, `localSql`, `shardAlarms`, `commitOrderedTables` and
|
|
1197
1197
|
* `websocketHibernation` are also exercised by the conformance TCK against a
|
|
1198
1198
|
* live single-node celld (`@lunora/platform-celld`'s `celld` vitest project),
|
package/dist/index.d.ts
CHANGED
|
@@ -1190,9 +1190,9 @@ interface PlatformCapabilities {
|
|
|
1190
1190
|
* differs is which primitives exist, and that difference is exactly this
|
|
1191
1191
|
* matrix.
|
|
1192
1192
|
*
|
|
1193
|
-
* Ratings track celld **v0.6.
|
|
1193
|
+
* Ratings track celld **v0.6.1** and derive from its documented compatibility
|
|
1194
1194
|
* surface (`docs/cloudflare-compat.md`, `docs/services/*.md`,
|
|
1195
|
-
* `docs/limitations.md` in the celld repo; v0.6.0
|
|
1195
|
+
* `docs/limitations.md` in the celld repo; v0.6.0 was celld's first beta). The host contracts
|
|
1196
1196
|
* behind `shardedState`, `localSql`, `shardAlarms`, `commitOrderedTables` and
|
|
1197
1197
|
* `websocketHibernation` are also exercised by the conformance TCK against a
|
|
1198
1198
|
* live single-node celld (`@lunora/platform-celld`'s `celld` vitest project),
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{NOOP_EXECUTION_CONTEXT as E}from"./packem_shared/NOOP_EXECUTION_CONTEXT-YmXqH-jH.mjs";import{LUNORA_ROLE_PREFIX as e,resolveShard as L}from"./packem_shared/LUNORA_ROLE_PREFIX-Da37XHgu.mjs";import{default as t}from"./packem_shared/CELLD_CAPABILITIES-BaeuWhr9.mjs";import{default as O}from"./packem_shared/CLOUDFLARE_CAPABILITIES-B0YUrj1a.mjs";import{default as C}from"./packem_shared/NODE_CAPABILITIES-
|
|
1
|
+
import{NOOP_EXECUTION_CONTEXT as E}from"./packem_shared/NOOP_EXECUTION_CONTEXT-YmXqH-jH.mjs";import{LUNORA_ROLE_PREFIX as e,resolveShard as L}from"./packem_shared/LUNORA_ROLE_PREFIX-Da37XHgu.mjs";import{default as t}from"./packem_shared/CELLD_CAPABILITIES-BaeuWhr9.mjs";import{default as O}from"./packem_shared/CLOUDFLARE_CAPABILITIES-B0YUrj1a.mjs";import{default as C}from"./packem_shared/NODE_CAPABILITIES-pqAqGhrM.mjs";export{t as CELLD_CAPABILITIES,O as CLOUDFLARE_CAPABILITIES,e as LUNORA_ROLE_PREFIX,C as NODE_CAPABILITIES,E as NOOP_EXECUTION_CONTEXT,L as resolveShard};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={id:"node",name:"Node",features:{shardedState:{level:"emulated",note:"One better-sqlite3 database per shard key, one process — no distributed placement or failover. Shard keys are percent-encoded into basenames with A-Z escaped, so `Tenant` and `tenant` stay two databases on a case-insensitive volume (APFS, NTFS) rather than folding into one. There is also no input gate: Cloudflare defers every other dispatch for the span of a mutation, whereas this host can only refuse — SQL issued from another task while a transaction is open throws a retryable `SHARD_UNAVAILABLE` (503) rather than reading rows that are about to roll back, so a read that merely arrived mid-mutation is retried instead of failing the request"},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, and createNodeSocketHost can rehydrate a row into a handle — but restoreSocket is not a SocketHost member and neither createNodePlatform nor createNodeShardRegistry surfaces it, so nothing composed here reassociates a reconnecting client; the conformance host is its only caller. Nothing is ever actually evicted from memory either, so this is durability without hibernation's memory saving and without its rehydration"},durableStreams:{level:"unsupported",note:"The transcript store is host-neutral (@lunora/shard-engine), but the attach/produce state machine lives in @lunora/do and nothing in this host mounts it. Gate-bearing: codegen refuses an app that declares a durable stream on this target, rather than emitting one that silently behaves as an ephemeral stream"},commitOrderedTables:{level:"emulated",note:"The sequence orders commits correctly, but the serialization it depends on is Lunora's per-shard write gate rather than a platform property — one process, one better-sqlite3 handle per shard key. Correct here; not something the host guarantees the way a Durable Object does"},localSql:{level:"native",note:"better-sqlite3 (synchronous, embedded)"},serverReactors:{level:"emulated",note:"Same engine-level implementation as Cloudflare; the per-shard serialization it depends on is the host's own write gate rather than a platform guarantee"},memoryTables:{level:"emulated",note:"Same shape as Cloudflare and for a different reason: better-sqlite3 CAN open `:memory:`, but a shard's memory tables share the one handle its durable tables use, so they are cleared rather than never written. A host process also outlives far more than a Durable Object does, so cold starts — and therefore `onShardInit` — are much rarer here than in production on Cloudflare; do not use this target to judge how often a memory table is actually empty"},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. Delivery is at-least-once as it is on workerd: a handler that throws is re-delivered with exponential backoff (6 attempts, from 100ms) and then abandoned, and an alarm set or deleted inside a transaction is armed only if that transaction commits"},shardPlacement:{level:"unsupported",note:"One process — every shard lives where the process does, so a location hint has nowhere to place it"},shardReadReplicas:{level:"unsupported",note:"One process and one region: a replica here would be a second copy of a database already on the same disk"},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). That registry is the host's own, so the write-time ShardRegistryDO registration a Cloudflare shard does has nothing to report to here and does not run"},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, retryDelay, 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. maxConcurrency is ignored: poll() delivers at most one batch per queue and settles it before moving on. Cloudflare's byte ceilings are enforced on send — 128 KiB per message and 256 KiB per sendBatch, measured over the encoded body — because @lunora/queue leaves both to the platform and this host is the only point where those bytes already exist. mode: "pull" queues are written but not consumed — nothing here serves the HTTP pull endpoint. Gap: message.run and ctx.run dispatch to LUNORA_ORIGIN_URL's /_lunora/scheduler/dispatch, which no Node HTTP server serves, so a handler that calls a Lunora function fails at runtime; its batch retries like any other failure until maxRetries sends it to the deadLetterQueue or parks it`},workerLoaders:{level:"unsupported",note:"No isolate loader: Node has no V8-isolate sandbox for untrusted code, and a `node:vm` context is not a security boundary"},workflowRollback:{level:"emulated",note:"createNodeWorkflowHost unwinds each settled step's rollback in reverse declaration order when a later step fails its last attempt. The compensations are plain calls, not durable steps — a crash during the unwind leaves it half-done — and rollbackConfig is ignored"},workflows:{level:"emulated",note:"createNodeWorkflowHost (@lunora/platform-node) binds each workflow on env under its class name — the key `ctx.exports` carries on Cloudflare — and 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). step.do's per-step retries and rollbacks are emulated by the adapter, not the engine: the attempt loop runs inside the one memoized step, so a crash mid-backoff restarts that step at attempt 1 rather than resuming the countdown, and compensations are plain calls unwound in reverse declaration order, so the adapter ignores rollbackConfig's retries and a crash mid-unwind leaves it half-done (@lunora/workflow itself still reads rollbackConfig.timeout to end a rollback's decline wait). A `retries.delay` function is awaited in that same loop and its result replaces the backoff, as on Cloudflare. defineWorkflow's limits and defaultRetention are wrangler settings this host never reads: no per-instance step cap is enforced, and a finished run's state stays until it is deleted. delete and deleteBatch (capped at 100 ids) drop a run's stored state behind the same in-process barrier as terminate, but leave no tombstone, so a deleted run's status reads `unknown`. terminate is a barrier within the process: a terminated run's writes are dropped, so an activation already in flight cannot overwrite the tombstone — it is not a barrier across processes, which would need the lease rather than a set. Gaps: step.do's config.timeout is not emulated — the callback receives no AbortSignal, so a timeout here could only reject while the work it was meant to cancel kept running. @lunora/workflow still ends a ctx.runStep decline wait 2s before the step's timeout (Cloudflare's 10-minute default when unset), so here that wait charges an attempt the host would not have ended, and the next attempt resumes it; no pause/restart; subscribe rejects NOT_IMPLEMENTED, since the engine keeps no per-instance event log to replay; ctx.run dispatches to an endpoint no Node HTTP server serves; ctx.parallel's synchronous join cannot interleave within one trigger activation"},workflowSchedules:{level:"unsupported",note:"createNodeWorkflowHost only runs instances something creates: nothing reads a workflow's schedules list, so a scheduled workflow would never start. Gate-bearing: codegen refuses an app that declares one here. Start it from a mutation with ctx.workflows.get(name).create() instead, re-armed through ctx.scheduler.runAt"},scheduler:{level:"emulated",note:"SQLite job table dispatched to onDispatch and re-armed on construction, with retry backoff and a dead-letter queue. It is also the only host implementing runtime cron registration (SchedulerHost.cron), which Cloudflare cannot offer — but nothing walks an app's DECLARED crons into that method, which is why cronTriggers is rated separately and unsupported here. This rating covers the imperative surface only: ctx.scheduler.runAfter/runAt do dispatch on this host"},relationGraph:{level:"emulated",note:"Identical to Cloudflare: the same engine-level traversal over the same ctx.db reads, served here by better-sqlite3 through this host's per-shard handle. One process and one disk, so a deep expansion is if anything cheaper than on workerd; what it is not is a platform feature"},cronTriggers:{level:"unsupported",note:"No runtime walks the generated LUNORA_CRONS map into SchedulerHost.cron, so the conformance suite is that method's only caller and a declared cron does not fire on this host. Gate-bearing: codegen refuses an app that declares one here rather than letting it deploy green and never run. Schedule the work explicitly with ctx.scheduler.runAfter/runAt instead"},authJurisdictionMove:{level:"unsupported",note:"Not applicable rather than missing: this host has no jurisdictions. A single process cannot restrict where state lives, so the shard registry offers no jurisdiction() and a worker built for a schema that declares one fails closed at construction. There is never a pinned auth object to copy into, so the admin ops are never wired"},agents:{level:"unsupported",note:"Nothing here mounts the generated agent classes: createNodeWorkflowHost compiles defineWorkflow handlers onto the @visulima/workflow engine, and an agent is a generated WorkflowEntrypoint resolved by its class name off `ctx.exports` or `env`, and this host binds neither. That missing mount is the whole gap: inference is not, since a model factory or AI SDK model object needs no binding and `ai` is emulated here through LUNORA_AI_PROXY_URL"},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"},objectStorageCdcArchive:{level:"emulated",note:"createNodeR2Bucket implements the `startAfter` seek the segment index needs, so the read-back behaves as it does on R2. Same caveat as the backups above: the bucket is a directory on the machine running the host, so archiving the changelog here moves it off SQLite but not off the disk that would take the shard with it"},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 are percent-escaped per path segment (`%`, `A-Z`, `:`, and a trailing `.` or space), so `A` and `a` stay two objects on a case-insensitive volume exactly as they are on R2, and a lowercase key containing no `%` or `:` and no segment ending in `.` or a space still maps to a byte-identical filename. The limits R2 would apply are applied here rather than left to the deploy to discover: put verifies a declared sha256 against the bytes received and stores nothing on a mismatch, and refuses customMetadata over R2's summed 2048-byte ceiling. Because R2 has no directories, neither does this bucket's behaviour — a deleted key's empty directory is pruned, so a later object may take that prefix, and delete of a key that is only a prefix is the no-op it is on R2 rather than a raw fs error. put honors R2's `onlyIf` etag preconditions (`etagMatches`, `etagDoesNotMatch`, `*`), checked and published under a per-key lock in this process, and delete takes one key or a batch. No multipart uploads and no presigned URLs, so the binding-backed resumable upload provider (`@lunora/storage/upload`) finishes only files under 5 MiB here: a larger one needs a multipart upload"},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, so codegen emits neither ctx.vectors nor the `backfillVectors` override; the admin op answers NOT_IMPLEMENTED, and an index's declared `model` is accepted but read by nothing"},ai:{level:"emulated",note:"No Workers AI-equivalent binding implemented, so `@cf/…` ids, `ctx.ai.run`, `ctx.ai.websearch` and the `rejectIfBusy` option are unavailable. `<provider>/<model>` slugs route to the OpenAI-compatible proxy named by the LUNORA_AI_PROXY_URL env var (LiteLLM, OpenRouter, a self-hosted one; bearer token in LUNORA_AI_PROXY_TOKEN) over plain fetch instead of AI Gateway, and a bring-your-own AI SDK model passes straight through"},aiSearch:{level:"unsupported",note:"No AI Search-equivalent binding implemented, so codegen omits `ctx.aiSearch`. A REST-backed emulation (account id + API token over fetch) was considered and not built: the REST and binding APIs differ, and nobody on this target has asked for it"},browser:{level:"unsupported",note:"No headless-browser binding implemented"},images:{level:"unsupported",note:"No Images-equivalent binding implemented"},containerEgressPolicy:{level:"unsupported",note:"No container orchestration implemented, so there is no container egress to police"},containerRuntimeScheduling:{level:"unsupported",note:"No container orchestration implemented, so there is no image to pick at start and no filesystem to snapshot"},containerSandboxTools:{level:"unsupported",note:"No container orchestration implemented, so there is no container filesystem to read, back up or mount a bucket into"},containers:{level:"unsupported",note:"No container orchestration implemented, so there is nothing for ctx.containers.<name>.exec, spawn or terminal to run a command in either"},analytics:{level:"unsupported",note:"No Analytics Engine-equivalent binding implemented"},analyticsSql:{level:"unsupported",note:"No Analytics SQL-equivalent binding implemented, so codegen omits `ctx.analyticsSql`. Not rated emulated on the strength of `createAnalyticsSqlRest`: that transport queries Cloudflare's hosted analytics with an API token, so it reads data a Node host never wrote, and an app can import it directly without the ctx surface"},artifacts:{level:"unsupported",note:"No Artifacts-equivalent binding implemented: a host would need a Git server with repo-scoped tokens, which no host contract carries"},edgeRequestMetadata:{level:"unsupported",note:'Nothing injects per-request platform metadata here — a Node request carries only what the caller wrote. Two telemetry surfaces degrade silently as a result: trustInboundTraceContext: "mtls" can never be satisfied, so it collapses to never-trust and every inbound traceparent is dropped (the runtime warns once when that actually happens, since no codegen gate can see a createWorker option), and the OTLP resource detector finds no placement attributes, so spans ship without them'},hostTraceFusion:{level:"unsupported",note:"No host-native trace tree to merge into, and cloudflare:workers is not importable here. Already capability-probed at runtime, so fuseCloudflareTraces is a no-op rather than a throw; onSpan remains the source of truth for the waterfall either way"},logArchive:{level:"unsupported",note:"createNodeR2Bucket is a directory on the local filesystem with no Iceberg catalog over it and no SQL engine to query it back, so records could be written but never read. The admin route is still registered here: it answers LOG_ARCHIVE_NOT_CONFIGURED (which the studio renders as a not-configured empty state) only while the logArchive table or the R2 SQL credentials are absent. Configure both and it stops failing closed — it builds an R2 SQL client and queries Cloudflare's API over the network, which is not this host serving the archive"},pipelines:{level:"unsupported",note:"No Pipelines-equivalent binding implemented"},pointInTimeRecovery:{level:"unsupported",note:"better-sqlite3 keeps no change log to rewind, and createNodeShardState deliberately exposes no bookmark methods rather than a stub that would report a restore that never happened, so getPitrBookmark / pitrRestore answer PITR_UNAVAILABLE. Recover from `lunora backup` snapshots instead (objectStorageBackups)"},mail:{level:"unsupported",note:"The queue tier this host lacked when the rating was written now exists (createNodeQueueHost), but nothing here composes a @lunora/mail transport or the queued-send consumer, so a send would be accepted and never delivered"},secrets:{level:"unsupported",note:"No Secrets Store-equivalent binding implemented (a real host would likely map this to env vars). Gate-bearing, and it has to be: ctx.secrets is a core built-in spliced into every context, so codegen refuses an app that reads it on this target instead of emitting a surface that throws on first use"},services:{level:"unsupported",note:"No sibling-Worker host: a Node process runs one app, with nothing a service binding could point at. Gate-bearing, so codegen omits ctx.services instead of emitting bindings that resolve to nothing"},topics:{level:"emulated",note:"Each subscription is a createNodeQueueHost queue, so a publish is one durable insert per subscription into _lunora_queue_messages. Inherits the queues row's gap: a subscription handler's message.run dispatches to an endpoint no Node HTTP server serves"},hyperdrive:{level:"unsupported",note:"No connection-pooling binding implemented"},httpCache:{level:"unsupported",note:"Nothing sits in front of this host to cache its responses, and Node exposes no Web Cache API global — the runtime's REST edge cache finds no HttpCacheLike here and degrades to emitting Cache-Control alone, which browsers and any CDN in front still honour"},requestOrigin:{level:"emulated",note:"The same runtime code reads it off request.url, but nothing here serves HTTP: the embedding server builds that Request, usually from the Host header the caller sent. Pin the host in the proxy in front, or configure a base (publicBaseUrl / STORAGE_PUBLIC_BASE_URL), which always wins over the origin. Because the caller can choose it here, never build links delivered to OTHER people from ctx.origin (password-reset or invite emails, webhooks, notifications): that is host-header poisoning. Use a configured canonical base URL for those; a signed URL returned to the same caller is fine"},identityProxy:{level:"unsupported",note:"Nothing sits in front of this host to authenticate callers, so it never populates the execution context's access identity. @lunora/cloudflare-access still works here through its Cf-Access-Jwt-Assertion fallback, which is a plain header check and needs no host support"}}};export{e as default};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/platform",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.58",
|
|
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:"node",name:"Node",features:{shardedState:{level:"emulated",note:"One better-sqlite3 database per shard key, one process — no distributed placement or failover. Shard keys are percent-encoded into basenames with A-Z escaped, so `Tenant` and `tenant` stay two databases on a case-insensitive volume (APFS, NTFS) rather than folding into one. There is also no input gate: Cloudflare defers every other dispatch for the span of a mutation, whereas this host can only refuse — SQL issued from another task while a transaction is open throws a retryable `SHARD_UNAVAILABLE` (503) rather than reading rows that are about to roll back, so a read that merely arrived mid-mutation is retried instead of failing the request"},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, and createNodeSocketHost can rehydrate a row into a handle — but restoreSocket is not a SocketHost member and neither createNodePlatform nor createNodeShardRegistry surfaces it, so nothing composed here reassociates a reconnecting client; the conformance host is its only caller. Nothing is ever actually evicted from memory either, so this is durability without hibernation's memory saving and without its rehydration"},durableStreams:{level:"unsupported",note:"The transcript store is host-neutral (@lunora/shard-engine), but the attach/produce state machine lives in @lunora/do and nothing in this host mounts it. Gate-bearing: codegen refuses an app that declares a durable stream on this target, rather than emitting one that silently behaves as an ephemeral stream"},commitOrderedTables:{level:"emulated",note:"The sequence orders commits correctly, but the serialization it depends on is Lunora's per-shard write gate rather than a platform property — one process, one better-sqlite3 handle per shard key. Correct here; not something the host guarantees the way a Durable Object does"},localSql:{level:"native",note:"better-sqlite3 (synchronous, embedded)"},serverReactors:{level:"emulated",note:"Same engine-level implementation as Cloudflare; the per-shard serialization it depends on is the host's own write gate rather than a platform guarantee"},memoryTables:{level:"emulated",note:"Same shape as Cloudflare and for a different reason: better-sqlite3 CAN open `:memory:`, but a shard's memory tables share the one handle its durable tables use, so they are cleared rather than never written. A host process also outlives far more than a Durable Object does, so cold starts — and therefore `onShardInit` — are much rarer here than in production on Cloudflare; do not use this target to judge how often a memory table is actually empty"},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. Delivery is at-least-once as it is on workerd: a handler that throws is re-delivered with exponential backoff (6 attempts, from 100ms) and then abandoned, and an alarm set or deleted inside a transaction is armed only if that transaction commits"},shardPlacement:{level:"unsupported",note:"One process — every shard lives where the process does, so a location hint has nowhere to place it"},shardReadReplicas:{level:"unsupported",note:"One process and one region: a replica here would be a second copy of a database already on the same disk"},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). That registry is the host's own, so the write-time ShardRegistryDO registration a Cloudflare shard does has nothing to report to here and does not run"},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. Cloudflare's byte ceilings are enforced on send — 128 KiB per message and 256 KiB per sendBatch, measured over the encoded body — because @lunora/queue leaves both to the platform and this host is the only point where those bytes already exist. mode: "pull" queues are written but not consumed — nothing here serves the HTTP pull endpoint. Gap: message.run and ctx.run dispatch to LUNORA_ORIGIN_URL's /_lunora/scheduler/dispatch, which no Node HTTP server serves, so a handler that calls a Lunora function fails at runtime; its batch retries like any other failure until maxRetries sends it to the deadLetterQueue or parks it`},workerLoaders:{level:"unsupported",note:"No isolate loader: Node has no V8-isolate sandbox for untrusted code, and a `node:vm` context is not a security boundary"},workflowRollback:{level:"emulated",note:"createNodeWorkflowHost unwinds each settled step's rollback in reverse declaration order when a later step fails its last attempt. The compensations are plain calls, not durable steps — a crash during the unwind leaves it half-done — and rollbackConfig is ignored"},workflows:{level:"emulated",note:"createNodeWorkflowHost (@lunora/platform-node) binds each workflow on env under its class name — the key `ctx.exports` carries on Cloudflare — and 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). step.do's per-step retries and rollbacks are emulated by the adapter, not the engine: the attempt loop runs inside the one memoized step, so a crash mid-backoff restarts that step at attempt 1 rather than resuming the countdown, and compensations are plain calls unwound in reverse declaration order, so the adapter ignores rollbackConfig's retries and a crash mid-unwind leaves it half-done (@lunora/workflow itself still reads rollbackConfig.timeout to end a rollback's decline wait). A `retries.delay` function is awaited in that same loop and its result replaces the backoff, as on Cloudflare. defineWorkflow's limits and defaultRetention are wrangler settings this host never reads: no per-instance step cap is enforced, and a finished run's state stays until it is deleted. delete and deleteBatch (capped at 100 ids) drop a run's stored state behind the same in-process barrier as terminate, but leave no tombstone, so a deleted run's status reads `unknown`. terminate is a barrier within the process: a terminated run's writes are dropped, so an activation already in flight cannot overwrite the tombstone — it is not a barrier across processes, which would need the lease rather than a set. Gaps: step.do's config.timeout is not emulated — the callback receives no AbortSignal, so a timeout here could only reject while the work it was meant to cancel kept running. @lunora/workflow still ends a ctx.runStep decline wait 2s before the step's timeout (Cloudflare's 10-minute default when unset), so here that wait charges an attempt the host would not have ended, and the next attempt resumes it; no pause/restart; subscribe rejects NOT_IMPLEMENTED, since the engine keeps no per-instance event log to replay; ctx.run dispatches to an endpoint no Node HTTP server serves; ctx.parallel's synchronous join cannot interleave within one trigger activation"},workflowSchedules:{level:"unsupported",note:"createNodeWorkflowHost only runs instances something creates: nothing reads a workflow's schedules list, so a scheduled workflow would never start. Gate-bearing: codegen refuses an app that declares one here. Start it from a mutation with ctx.workflows.get(name).create() instead, re-armed through ctx.scheduler.runAt"},scheduler:{level:"emulated",note:"SQLite job table dispatched to onDispatch and re-armed on construction, with retry backoff and a dead-letter queue. It is also the only host implementing runtime cron registration (SchedulerHost.cron), which Cloudflare cannot offer — but nothing walks an app's DECLARED crons into that method, which is why cronTriggers is rated separately and unsupported here. This rating covers the imperative surface only: ctx.scheduler.runAfter/runAt do dispatch on this host"},relationGraph:{level:"emulated",note:"Identical to Cloudflare: the same engine-level traversal over the same ctx.db reads, served here by better-sqlite3 through this host's per-shard handle. One process and one disk, so a deep expansion is if anything cheaper than on workerd; what it is not is a platform feature"},cronTriggers:{level:"unsupported",note:"No runtime walks the generated LUNORA_CRONS map into SchedulerHost.cron, so the conformance suite is that method's only caller and a declared cron does not fire on this host. Gate-bearing: codegen refuses an app that declares one here rather than letting it deploy green and never run. Schedule the work explicitly with ctx.scheduler.runAfter/runAt instead"},authJurisdictionMove:{level:"unsupported",note:"Not applicable rather than missing: this host has no jurisdictions. A single process cannot restrict where state lives, so the shard registry offers no jurisdiction() and a worker built for a schema that declares one fails closed at construction. There is never a pinned auth object to copy into, so the admin ops are never wired"},agents:{level:"unsupported",note:"Nothing here mounts the generated agent classes: createNodeWorkflowHost compiles defineWorkflow handlers onto the @visulima/workflow engine, and an agent is a generated WorkflowEntrypoint resolved by its class name off `ctx.exports` or `env`, and this host binds neither. That missing mount is the whole gap: inference is not, since a model factory or AI SDK model object needs no binding and `ai` is emulated here through LUNORA_AI_PROXY_URL"},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"},objectStorageCdcArchive:{level:"emulated",note:"createNodeR2Bucket implements the `startAfter` seek the segment index needs, so the read-back behaves as it does on R2. Same caveat as the backups above: the bucket is a directory on the machine running the host, so archiving the changelog here moves it off SQLite but not off the disk that would take the shard with it"},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 are percent-escaped per path segment (`%`, `A-Z`, `:`, and a trailing `.` or space), so `A` and `a` stay two objects on a case-insensitive volume exactly as they are on R2, and a lowercase key containing no `%` or `:` and no segment ending in `.` or a space still maps to a byte-identical filename. The limits R2 would apply are applied here rather than left to the deploy to discover: put verifies a declared sha256 against the bytes received and stores nothing on a mismatch, and refuses customMetadata over R2's summed 2048-byte ceiling. Because R2 has no directories, neither does this bucket's behaviour — a deleted key's empty directory is pruned, so a later object may take that prefix, and delete of a key that is only a prefix is the no-op it is on R2 rather than a raw fs error. put honors R2's `onlyIf` etag preconditions (`etagMatches`, `etagDoesNotMatch`, `*`), checked and published under a per-key lock in this process, and delete takes one key or a batch. No multipart uploads and no presigned URLs, so the binding-backed resumable upload provider (`@lunora/storage/upload`) finishes only files under 5 MiB here: a larger one needs a multipart upload"},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, so codegen emits neither ctx.vectors nor the `backfillVectors` override; the admin op answers NOT_IMPLEMENTED, and an index's declared `model` is accepted but read by nothing"},ai:{level:"emulated",note:"No Workers AI-equivalent binding implemented, so `@cf/…` ids, `ctx.ai.run`, `ctx.ai.websearch` and the `rejectIfBusy` option are unavailable. `<provider>/<model>` slugs route to the OpenAI-compatible proxy named by the LUNORA_AI_PROXY_URL env var (LiteLLM, OpenRouter, a self-hosted one; bearer token in LUNORA_AI_PROXY_TOKEN) over plain fetch instead of AI Gateway, and a bring-your-own AI SDK model passes straight through"},aiSearch:{level:"unsupported",note:"No AI Search-equivalent binding implemented, so codegen omits `ctx.aiSearch`. A REST-backed emulation (account id + API token over fetch) was considered and not built: the REST and binding APIs differ, and nobody on this target has asked for it"},browser:{level:"unsupported",note:"No headless-browser binding implemented"},images:{level:"unsupported",note:"No Images-equivalent binding implemented"},containerEgressPolicy:{level:"unsupported",note:"No container orchestration implemented, so there is no container egress to police"},containerRuntimeScheduling:{level:"unsupported",note:"No container orchestration implemented, so there is no image to pick at start and no filesystem to snapshot"},containerSandboxTools:{level:"unsupported",note:"No container orchestration implemented, so there is no container filesystem to read, back up or mount a bucket into"},containers:{level:"unsupported",note:"No container orchestration implemented, so there is nothing for ctx.containers.<name>.exec, spawn or terminal to run a command in either"},analytics:{level:"unsupported",note:"No Analytics Engine-equivalent binding implemented"},analyticsSql:{level:"unsupported",note:"No Analytics SQL-equivalent binding implemented, so codegen omits `ctx.analyticsSql`. Not rated emulated on the strength of `createAnalyticsSqlRest`: that transport queries Cloudflare's hosted analytics with an API token, so it reads data a Node host never wrote, and an app can import it directly without the ctx surface"},artifacts:{level:"unsupported",note:"No Artifacts-equivalent binding implemented: a host would need a Git server with repo-scoped tokens, which no host contract carries"},edgeRequestMetadata:{level:"unsupported",note:'Nothing injects per-request platform metadata here — a Node request carries only what the caller wrote. Two telemetry surfaces degrade silently as a result: trustInboundTraceContext: "mtls" can never be satisfied, so it collapses to never-trust and every inbound traceparent is dropped (the runtime warns once when that actually happens, since no codegen gate can see a createWorker option), and the OTLP resource detector finds no placement attributes, so spans ship without them'},hostTraceFusion:{level:"unsupported",note:"No host-native trace tree to merge into, and cloudflare:workers is not importable here. Already capability-probed at runtime, so fuseCloudflareTraces is a no-op rather than a throw; onSpan remains the source of truth for the waterfall either way"},logArchive:{level:"unsupported",note:"createNodeR2Bucket is a directory on the local filesystem with no Iceberg catalog over it and no SQL engine to query it back, so records could be written but never read. The admin route is still registered here: it answers LOG_ARCHIVE_NOT_CONFIGURED (which the studio renders as a not-configured empty state) only while the logArchive table or the R2 SQL credentials are absent. Configure both and it stops failing closed — it builds an R2 SQL client and queries Cloudflare's API over the network, which is not this host serving the archive"},pipelines:{level:"unsupported",note:"No Pipelines-equivalent binding implemented"},pointInTimeRecovery:{level:"unsupported",note:"better-sqlite3 keeps no change log to rewind, and createNodeShardState deliberately exposes no bookmark methods rather than a stub that would report a restore that never happened, so getPitrBookmark / pitrRestore answer PITR_UNAVAILABLE. Recover from `lunora backup` snapshots instead (objectStorageBackups)"},mail:{level:"unsupported",note:"The queue tier this host lacked when the rating was written now exists (createNodeQueueHost), but nothing here composes a @lunora/mail transport or the queued-send consumer, so a send would be accepted and never delivered"},secrets:{level:"unsupported",note:"No Secrets Store-equivalent binding implemented (a real host would likely map this to env vars). Gate-bearing, and it has to be: ctx.secrets is a core built-in spliced into every context, so codegen refuses an app that reads it on this target instead of emitting a surface that throws on first use"},services:{level:"unsupported",note:"No sibling-Worker host: a Node process runs one app, with nothing a service binding could point at. Gate-bearing, so codegen omits ctx.services instead of emitting bindings that resolve to nothing"},topics:{level:"emulated",note:"Each subscription is a createNodeQueueHost queue, so a publish is one durable insert per subscription into _lunora_queue_messages. Inherits the queues row's gap: a subscription handler's message.run dispatches to an endpoint no Node HTTP server serves"},hyperdrive:{level:"unsupported",note:"No connection-pooling binding implemented"},httpCache:{level:"unsupported",note:"Nothing sits in front of this host to cache its responses, and Node exposes no Web Cache API global — the runtime's REST edge cache finds no HttpCacheLike here and degrades to emitting Cache-Control alone, which browsers and any CDN in front still honour"},requestOrigin:{level:"emulated",note:"The same runtime code reads it off request.url, but nothing here serves HTTP: the embedding server builds that Request, usually from the Host header the caller sent. Pin the host in the proxy in front, or configure a base (publicBaseUrl / STORAGE_PUBLIC_BASE_URL), which always wins over the origin. Because the caller can choose it here, never build links delivered to OTHER people from ctx.origin (password-reset or invite emails, webhooks, notifications): that is host-header poisoning. Use a configured canonical base URL for those; a signed URL returned to the same caller is fine"},identityProxy:{level:"unsupported",note:"Nothing sits in front of this host to authenticate callers, so it never populates the execution context's access identity. @lunora/cloudflare-access still works here through its Cf-Access-Jwt-Assertion fallback, which is a plain header check and needs no host support"}}};export{e as default};
|