@lunora/platform-cloudflare 1.0.0-alpha.100 → 1.0.0-alpha.102
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/README.md +1 -1
- package/dist/index.d.mts +95 -5
- package/dist/index.d.ts +95 -5
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/createSecretValueHost-BaYQQbQ9.mjs +1 -0
- package/dist/packem_shared/createShardPlatform-ZR4yt0P8.mjs +1 -0
- package/package.json +2 -2
- package/dist/packem_shared/createShardAlarms-Cm9SOaSc.mjs +0 -1
- package/dist/packem_shared/createShardPlatform-lSOGSs8s.mjs +0 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ The Cloudflare implementation of the [`@lunora/platform`](../platform) host cont
|
|
|
8
8
|
import { createShardPlatform } from "@lunora/platform-cloudflare";
|
|
9
9
|
|
|
10
10
|
// Inside a Durable Object:
|
|
11
|
-
const platform = createShardPlatform(state);
|
|
11
|
+
const platform = createShardPlatform(state, env);
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
## Why it exists
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DurableObjectNamespace, DurableObjectState } from '@cloudflare/workers-types';
|
|
2
|
-
import { ShardAlarms, ShardDirectory, ShardHost, ShardKvStore, SocketHost, PlatformCapabilities, SchedulerHost } from '@lunora/platform';
|
|
2
|
+
import { SecretValueHost, SecretValueStore, ShardAlarms, ShardDirectory, ShardHost, ShardKvStore, SocketHost, PlatformCapabilities, SchedulerHost } from '@lunora/platform';
|
|
3
3
|
/** The alarm subset of `state.storage`, structurally (shared by shard + session hosts). */
|
|
4
4
|
interface AlarmStorageLike {
|
|
5
5
|
deleteAlarm?: () => Promise<void>;
|
|
@@ -24,18 +24,58 @@ interface KvStorageLike {
|
|
|
24
24
|
delete: (key: string) => Promise<boolean | number>;
|
|
25
25
|
get: <T = unknown>(key: string) => Promise<T | undefined>;
|
|
26
26
|
list?: <T = unknown>(options?: {
|
|
27
|
+
limit?: number;
|
|
27
28
|
prefix?: string;
|
|
28
29
|
}) => Promise<Map<string, T>>;
|
|
29
30
|
put: (key: string, value: unknown) => Promise<void>;
|
|
30
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* {@link KvStorageLike} plus `transaction`, which {@link createSecretValueHost}
|
|
34
|
+
* needs for its read-check-bind in `take`. Method syntax on purpose, so the real
|
|
35
|
+
* `DurableObjectStorage` (whose transaction closure takes its own transaction
|
|
36
|
+
* type) is accepted without a cast.
|
|
37
|
+
*/
|
|
38
|
+
interface SecretStorageLike extends KvStorageLike {
|
|
39
|
+
list: <T = unknown>(options?: {
|
|
40
|
+
limit?: number;
|
|
41
|
+
prefix?: string;
|
|
42
|
+
}) => Promise<Map<string, T>>;
|
|
43
|
+
transaction: <T>(closure: (txn: KvStorageLike) => Promise<T>) => Promise<T>;
|
|
44
|
+
}
|
|
31
45
|
/**
|
|
32
46
|
* Turn a Durable Object's `state.storage` into the provider-neutral
|
|
33
47
|
* {@link ShardKvStore}. The DO's KV surface already matches the contract
|
|
34
|
-
* method-for-method; this adapter
|
|
35
|
-
*
|
|
36
|
-
*
|
|
48
|
+
* method-for-method; this adapter normalizes the return of `delete` (workerd
|
|
49
|
+
* resolves a boolean, some doubles a number), fails closed on a `list` the
|
|
50
|
+
* underlying storage does not provide, and keeps secret records invisible: `get`
|
|
51
|
+
* and `list` skip a reserved key whose stored value is secret-shaped, and
|
|
52
|
+
* `put`/`delete` refuse to touch one.
|
|
37
53
|
*/
|
|
38
54
|
declare const createShardKvStore: (storage: KvStorageLike) => ShardKvStore;
|
|
55
|
+
/**
|
|
56
|
+
* Turn a Durable Object's `state.storage` into a {@link SecretValueHost}.
|
|
57
|
+
*
|
|
58
|
+
* Records live under {@link SECRET_VALUE_KEY_PREFIX} in the shard's own
|
|
59
|
+
* key-value storage. Keys sort by expiry (a zero-padded `expiresAt` leads the
|
|
60
|
+
* ref), so the sweep in `put` reads the earliest-expiring records first and stops
|
|
61
|
+
* at the first live one in a page.
|
|
62
|
+
*
|
|
63
|
+
* `take` runs its read, claimant bind and write inside `storage.transaction`, so
|
|
64
|
+
* two takes cannot both observe the ref unbound. `options.now` exists for the
|
|
65
|
+
* conformance suite's clock and defaults to `Date.now`.
|
|
66
|
+
*/
|
|
67
|
+
declare const createSecretValueStore: (storage: SecretStorageLike, options?: {
|
|
68
|
+
now?: () => number;
|
|
69
|
+
}) => SecretValueStore;
|
|
70
|
+
/**
|
|
71
|
+
* Turn a Durable Object's `state.storage` and Worker `env` into a
|
|
72
|
+
* {@link SecretValueHost}: the reserved-namespace store wrapped in AES-256-GCM.
|
|
73
|
+
* With no key bound, every `put` and `take` refuses with `SecretValueKeyError`;
|
|
74
|
+
* nothing falls back to storing plaintext.
|
|
75
|
+
*/
|
|
76
|
+
declare const createSecretValueHost: (storage: SecretStorageLike, env: unknown, options?: {
|
|
77
|
+
now?: () => number;
|
|
78
|
+
}) => SecretValueHost;
|
|
39
79
|
declare const createSocketHost: (state: DurableObjectState) => SocketHost;
|
|
40
80
|
/**
|
|
41
81
|
* Turn a Durable Object namespace binding into a {@link ShardDirectory}.
|
|
@@ -57,6 +97,8 @@ declare const createShardDirectory: (namespace: DurableObjectNamespace) => Shard
|
|
|
57
97
|
interface ShardPlatform {
|
|
58
98
|
/** Durable key-value storage for this shard. */
|
|
59
99
|
kv: ShardKvStore;
|
|
100
|
+
/** Human-supplied secret values held for one durable step, kept out of `kv`'s reach. */
|
|
101
|
+
secretValues: SecretValueHost;
|
|
60
102
|
/** Single-writer execution, local SQL, transactions, alarms, identity. */
|
|
61
103
|
shard: ShardHost;
|
|
62
104
|
/** Hibernated WebSocket subscriptions for this shard. */
|
|
@@ -106,8 +148,12 @@ interface WorkerPlatformOptions {
|
|
|
106
148
|
* `state` is typed `unknown` so this package does not force
|
|
107
149
|
* `@cloudflare/workers-types` onto callers that are already holding the runtime
|
|
108
150
|
* object; the adapters narrow it internally.
|
|
151
|
+
*
|
|
152
|
+
* `env` carries the Worker secrets the secret-value host seals under. Leave it out
|
|
153
|
+
* for a shard that holds no key: the host then refuses every put and take rather
|
|
154
|
+
* than store plaintext.
|
|
109
155
|
*/
|
|
110
|
-
declare const createShardPlatform: (state: unknown) => ShardPlatform;
|
|
156
|
+
declare const createShardPlatform: (state: unknown, env?: unknown) => ShardPlatform;
|
|
111
157
|
/**
|
|
112
158
|
* Compose every Worker-scoped contract from the Worker's `env`.
|
|
113
159
|
*
|
|
@@ -117,6 +163,50 @@ declare const createShardPlatform: (state: unknown) => ShardPlatform;
|
|
|
117
163
|
*/
|
|
118
164
|
declare const createWorkerPlatform: (env: unknown, options?: WorkerPlatformOptions) => WorkerPlatform;
|
|
119
165
|
export { type ShardPlatform, type WorkerPlatform, type WorkerPlatformOptions,
|
|
166
|
+
/**
|
|
167
|
+
* `@lunora/platform-cloudflare` — the Cloudflare implementation of the
|
|
168
|
+
* `@lunora/platform` contracts.
|
|
169
|
+
*
|
|
170
|
+
* `@lunora/platform` says what a host must provide; this says how Cloudflare
|
|
171
|
+
* provides it, over `DurableObjectState`, hibernatable WebSockets, and a
|
|
172
|
+
* `DurableObjectNamespace`. A second target ships the same surface from its own
|
|
173
|
+
* `@lunora/platform-<target>` package, so adding one is a sibling rather than a
|
|
174
|
+
* refactor of `@lunora/do`.
|
|
175
|
+
*
|
|
176
|
+
* # Why this depends on nothing but the contracts
|
|
177
|
+
*
|
|
178
|
+
* An earlier version of this package sat *above* `@lunora/do` — it composed
|
|
179
|
+
* adapters that lived there, and so depended on the very package it was meant
|
|
180
|
+
* to make replaceable. That inverts the end-state, and with one host it also
|
|
181
|
+
* had zero consumers, so it was dissolved. This version is the other way up:
|
|
182
|
+
* the adapters live here, `@lunora/do` depends on this, and the only imports
|
|
183
|
+
* are `@lunora/platform` plus Cloudflare's own types. That is what makes the
|
|
184
|
+
* seam real rather than aspirational — `@lunora/do` can no longer reach a
|
|
185
|
+
* provider API except through a contract.
|
|
186
|
+
*/
|
|
187
|
+
createSecretValueHost,
|
|
188
|
+
/**
|
|
189
|
+
* `@lunora/platform-cloudflare` — the Cloudflare implementation of the
|
|
190
|
+
* `@lunora/platform` contracts.
|
|
191
|
+
*
|
|
192
|
+
* `@lunora/platform` says what a host must provide; this says how Cloudflare
|
|
193
|
+
* provides it, over `DurableObjectState`, hibernatable WebSockets, and a
|
|
194
|
+
* `DurableObjectNamespace`. A second target ships the same surface from its own
|
|
195
|
+
* `@lunora/platform-<target>` package, so adding one is a sibling rather than a
|
|
196
|
+
* refactor of `@lunora/do`.
|
|
197
|
+
*
|
|
198
|
+
* # Why this depends on nothing but the contracts
|
|
199
|
+
*
|
|
200
|
+
* An earlier version of this package sat *above* `@lunora/do` — it composed
|
|
201
|
+
* adapters that lived there, and so depended on the very package it was meant
|
|
202
|
+
* to make replaceable. That inverts the end-state, and with one host it also
|
|
203
|
+
* had zero consumers, so it was dissolved. This version is the other way up:
|
|
204
|
+
* the adapters live here, `@lunora/do` depends on this, and the only imports
|
|
205
|
+
* are `@lunora/platform` plus Cloudflare's own types. That is what makes the
|
|
206
|
+
* seam real rather than aspirational — `@lunora/do` can no longer reach a
|
|
207
|
+
* provider API except through a contract.
|
|
208
|
+
*/
|
|
209
|
+
createSecretValueStore,
|
|
120
210
|
/**
|
|
121
211
|
* `@lunora/platform-cloudflare` — the Cloudflare implementation of the
|
|
122
212
|
* `@lunora/platform` contracts.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DurableObjectNamespace, DurableObjectState } from '@cloudflare/workers-types';
|
|
2
|
-
import { ShardAlarms, ShardDirectory, ShardHost, ShardKvStore, SocketHost, PlatformCapabilities, SchedulerHost } from '@lunora/platform';
|
|
2
|
+
import { SecretValueHost, SecretValueStore, ShardAlarms, ShardDirectory, ShardHost, ShardKvStore, SocketHost, PlatformCapabilities, SchedulerHost } from '@lunora/platform';
|
|
3
3
|
/** The alarm subset of `state.storage`, structurally (shared by shard + session hosts). */
|
|
4
4
|
interface AlarmStorageLike {
|
|
5
5
|
deleteAlarm?: () => Promise<void>;
|
|
@@ -24,18 +24,58 @@ interface KvStorageLike {
|
|
|
24
24
|
delete: (key: string) => Promise<boolean | number>;
|
|
25
25
|
get: <T = unknown>(key: string) => Promise<T | undefined>;
|
|
26
26
|
list?: <T = unknown>(options?: {
|
|
27
|
+
limit?: number;
|
|
27
28
|
prefix?: string;
|
|
28
29
|
}) => Promise<Map<string, T>>;
|
|
29
30
|
put: (key: string, value: unknown) => Promise<void>;
|
|
30
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* {@link KvStorageLike} plus `transaction`, which {@link createSecretValueHost}
|
|
34
|
+
* needs for its read-check-bind in `take`. Method syntax on purpose, so the real
|
|
35
|
+
* `DurableObjectStorage` (whose transaction closure takes its own transaction
|
|
36
|
+
* type) is accepted without a cast.
|
|
37
|
+
*/
|
|
38
|
+
interface SecretStorageLike extends KvStorageLike {
|
|
39
|
+
list: <T = unknown>(options?: {
|
|
40
|
+
limit?: number;
|
|
41
|
+
prefix?: string;
|
|
42
|
+
}) => Promise<Map<string, T>>;
|
|
43
|
+
transaction: <T>(closure: (txn: KvStorageLike) => Promise<T>) => Promise<T>;
|
|
44
|
+
}
|
|
31
45
|
/**
|
|
32
46
|
* Turn a Durable Object's `state.storage` into the provider-neutral
|
|
33
47
|
* {@link ShardKvStore}. The DO's KV surface already matches the contract
|
|
34
|
-
* method-for-method; this adapter
|
|
35
|
-
*
|
|
36
|
-
*
|
|
48
|
+
* method-for-method; this adapter normalizes the return of `delete` (workerd
|
|
49
|
+
* resolves a boolean, some doubles a number), fails closed on a `list` the
|
|
50
|
+
* underlying storage does not provide, and keeps secret records invisible: `get`
|
|
51
|
+
* and `list` skip a reserved key whose stored value is secret-shaped, and
|
|
52
|
+
* `put`/`delete` refuse to touch one.
|
|
37
53
|
*/
|
|
38
54
|
declare const createShardKvStore: (storage: KvStorageLike) => ShardKvStore;
|
|
55
|
+
/**
|
|
56
|
+
* Turn a Durable Object's `state.storage` into a {@link SecretValueHost}.
|
|
57
|
+
*
|
|
58
|
+
* Records live under {@link SECRET_VALUE_KEY_PREFIX} in the shard's own
|
|
59
|
+
* key-value storage. Keys sort by expiry (a zero-padded `expiresAt` leads the
|
|
60
|
+
* ref), so the sweep in `put` reads the earliest-expiring records first and stops
|
|
61
|
+
* at the first live one in a page.
|
|
62
|
+
*
|
|
63
|
+
* `take` runs its read, claimant bind and write inside `storage.transaction`, so
|
|
64
|
+
* two takes cannot both observe the ref unbound. `options.now` exists for the
|
|
65
|
+
* conformance suite's clock and defaults to `Date.now`.
|
|
66
|
+
*/
|
|
67
|
+
declare const createSecretValueStore: (storage: SecretStorageLike, options?: {
|
|
68
|
+
now?: () => number;
|
|
69
|
+
}) => SecretValueStore;
|
|
70
|
+
/**
|
|
71
|
+
* Turn a Durable Object's `state.storage` and Worker `env` into a
|
|
72
|
+
* {@link SecretValueHost}: the reserved-namespace store wrapped in AES-256-GCM.
|
|
73
|
+
* With no key bound, every `put` and `take` refuses with `SecretValueKeyError`;
|
|
74
|
+
* nothing falls back to storing plaintext.
|
|
75
|
+
*/
|
|
76
|
+
declare const createSecretValueHost: (storage: SecretStorageLike, env: unknown, options?: {
|
|
77
|
+
now?: () => number;
|
|
78
|
+
}) => SecretValueHost;
|
|
39
79
|
declare const createSocketHost: (state: DurableObjectState) => SocketHost;
|
|
40
80
|
/**
|
|
41
81
|
* Turn a Durable Object namespace binding into a {@link ShardDirectory}.
|
|
@@ -57,6 +97,8 @@ declare const createShardDirectory: (namespace: DurableObjectNamespace) => Shard
|
|
|
57
97
|
interface ShardPlatform {
|
|
58
98
|
/** Durable key-value storage for this shard. */
|
|
59
99
|
kv: ShardKvStore;
|
|
100
|
+
/** Human-supplied secret values held for one durable step, kept out of `kv`'s reach. */
|
|
101
|
+
secretValues: SecretValueHost;
|
|
60
102
|
/** Single-writer execution, local SQL, transactions, alarms, identity. */
|
|
61
103
|
shard: ShardHost;
|
|
62
104
|
/** Hibernated WebSocket subscriptions for this shard. */
|
|
@@ -106,8 +148,12 @@ interface WorkerPlatformOptions {
|
|
|
106
148
|
* `state` is typed `unknown` so this package does not force
|
|
107
149
|
* `@cloudflare/workers-types` onto callers that are already holding the runtime
|
|
108
150
|
* object; the adapters narrow it internally.
|
|
151
|
+
*
|
|
152
|
+
* `env` carries the Worker secrets the secret-value host seals under. Leave it out
|
|
153
|
+
* for a shard that holds no key: the host then refuses every put and take rather
|
|
154
|
+
* than store plaintext.
|
|
109
155
|
*/
|
|
110
|
-
declare const createShardPlatform: (state: unknown) => ShardPlatform;
|
|
156
|
+
declare const createShardPlatform: (state: unknown, env?: unknown) => ShardPlatform;
|
|
111
157
|
/**
|
|
112
158
|
* Compose every Worker-scoped contract from the Worker's `env`.
|
|
113
159
|
*
|
|
@@ -117,6 +163,50 @@ declare const createShardPlatform: (state: unknown) => ShardPlatform;
|
|
|
117
163
|
*/
|
|
118
164
|
declare const createWorkerPlatform: (env: unknown, options?: WorkerPlatformOptions) => WorkerPlatform;
|
|
119
165
|
export { type ShardPlatform, type WorkerPlatform, type WorkerPlatformOptions,
|
|
166
|
+
/**
|
|
167
|
+
* `@lunora/platform-cloudflare` — the Cloudflare implementation of the
|
|
168
|
+
* `@lunora/platform` contracts.
|
|
169
|
+
*
|
|
170
|
+
* `@lunora/platform` says what a host must provide; this says how Cloudflare
|
|
171
|
+
* provides it, over `DurableObjectState`, hibernatable WebSockets, and a
|
|
172
|
+
* `DurableObjectNamespace`. A second target ships the same surface from its own
|
|
173
|
+
* `@lunora/platform-<target>` package, so adding one is a sibling rather than a
|
|
174
|
+
* refactor of `@lunora/do`.
|
|
175
|
+
*
|
|
176
|
+
* # Why this depends on nothing but the contracts
|
|
177
|
+
*
|
|
178
|
+
* An earlier version of this package sat *above* `@lunora/do` — it composed
|
|
179
|
+
* adapters that lived there, and so depended on the very package it was meant
|
|
180
|
+
* to make replaceable. That inverts the end-state, and with one host it also
|
|
181
|
+
* had zero consumers, so it was dissolved. This version is the other way up:
|
|
182
|
+
* the adapters live here, `@lunora/do` depends on this, and the only imports
|
|
183
|
+
* are `@lunora/platform` plus Cloudflare's own types. That is what makes the
|
|
184
|
+
* seam real rather than aspirational — `@lunora/do` can no longer reach a
|
|
185
|
+
* provider API except through a contract.
|
|
186
|
+
*/
|
|
187
|
+
createSecretValueHost,
|
|
188
|
+
/**
|
|
189
|
+
* `@lunora/platform-cloudflare` — the Cloudflare implementation of the
|
|
190
|
+
* `@lunora/platform` contracts.
|
|
191
|
+
*
|
|
192
|
+
* `@lunora/platform` says what a host must provide; this says how Cloudflare
|
|
193
|
+
* provides it, over `DurableObjectState`, hibernatable WebSockets, and a
|
|
194
|
+
* `DurableObjectNamespace`. A second target ships the same surface from its own
|
|
195
|
+
* `@lunora/platform-<target>` package, so adding one is a sibling rather than a
|
|
196
|
+
* refactor of `@lunora/do`.
|
|
197
|
+
*
|
|
198
|
+
* # Why this depends on nothing but the contracts
|
|
199
|
+
*
|
|
200
|
+
* An earlier version of this package sat *above* `@lunora/do` — it composed
|
|
201
|
+
* adapters that lived there, and so depended on the very package it was meant
|
|
202
|
+
* to make replaceable. That inverts the end-state, and with one host it also
|
|
203
|
+
* had zero consumers, so it was dissolved. This version is the other way up:
|
|
204
|
+
* the adapters live here, `@lunora/do` depends on this, and the only imports
|
|
205
|
+
* are `@lunora/platform` plus Cloudflare's own types. That is what makes the
|
|
206
|
+
* seam real rather than aspirational — `@lunora/do` can no longer reach a
|
|
207
|
+
* provider API except through a contract.
|
|
208
|
+
*/
|
|
209
|
+
createSecretValueStore,
|
|
120
210
|
/**
|
|
121
211
|
* `@lunora/platform-cloudflare` — the Cloudflare implementation of the
|
|
122
212
|
* `@lunora/platform` contracts.
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{createSecretValueHost as t,createSecretValueStore as a,createShardAlarms as o,createShardDirectory as c,createShardHost as S,createShardKvStore as d,createSocketHost as h}from"./packem_shared/createSecretValueHost-BaYQQbQ9.mjs";import{createShardPlatform as m,createWorkerPlatform as f}from"./packem_shared/createShardPlatform-ZR4yt0P8.mjs";export{t as createSecretValueHost,a as createSecretValueStore,o as createShardAlarms,c as createShardDirectory,S as createShardHost,d as createShardKvStore,m as createShardPlatform,h as createSocketHost,f as createWorkerPlatform};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as b}from"@lunora/errors";import{clampSecretValueTtl as k,encryptingSecretValueHost as A,findSocketTagBudgetViolation as _}from"@lunora/platform";const T=(e,t,o)=>{const c=e.storage.sql;if(typeof c?.exec!="function")throw new TypeError("storage.sql.exec is not available on this DurableObjectState");return c.exec(t,...o)},U=e=>({delete:()=>typeof e.deleteAlarm=="function"?e.deleteAlarm():Promise.resolve(),get:()=>typeof e.getAlarm=="function"?e.getAlarm():null,set:t=>typeof e.setAlarm=="function"?e.setAlarm(t):Promise.resolve()}),R=(e,t)=>{if(!(!(e instanceof Error)||e===t||e.cause!==void 0))try{Object.defineProperty(e,"cause",{configurable:!0,value:t,writable:!0})}catch{}},C=e=>{const{storage:t}=e,o={get databaseSize(){return e.storage.sql?.databaseSize},exec:(n,...a)=>T(e,n,a)},c=U(t),r=async n=>{if(typeof e.blockConcurrencyWhile=="function"){const a=await e.blockConcurrencyWhile(async()=>{try{return{ok:!0,value:await n()}}catch(u){return{error:u,ok:!1}}});if(!a.ok)throw a.error;return a.value}return n()},i=async n=>{const a=t;if(typeof a?.transaction=="function"){let u;try{return await a.transaction(async()=>{try{return await n()}catch(s){throw u={value:s},s}})}catch(s){throw u?(R(u.value,s),u.value):s}}return n()};return{alarms:c,runSerialized:r,shardKey:e.id?.name,sql:o,transaction:i,waitUntil:typeof e.waitUntil=="function"?n=>{e.waitUntil(n)}:void 0}},w="__lunora_secret_value:",x=32,p=e=>e.startsWith(w),d=e=>{if(typeof e!="object"||e===null)return!1;const t=e;return typeof t.expiresAt=="number"&&Number.isFinite(t.expiresAt)&&typeof t.scope=="string"&&typeof t.value=="string"&&(t.claimant===void 0||typeof t.claimant=="string")},D=e=>({delete:async t=>{if(p(t)&&d(await e.get(t)))throw new TypeError(`key "${t}" holds a secret value and cannot be deleted through the KV surface`);return!!await e.delete(t)},get:async t=>{const o=await e.get(t);return p(t)&&d(o)?void 0:o},list:async t=>{if(typeof e.list!="function")throw new TypeError("storage.list is not available on this DurableObjectStorage");const o=t?.prefix,c=await e.list(o===void 0?{}:{prefix:o});for(const[r,i]of c)p(r)&&d(i)&&c.delete(r);return c},put:async(t,o)=>{if(p(t)&&(d(o)||d(await e.get(t))))throw new TypeError(`key "${t}" is reserved for secret values and cannot be written through the KV surface`);await e.put(t,o)}}),O=(e,t={})=>{const o=t.now??Date.now,c=r=>`${w}${r}`;return{discard:async r=>{const i=c(r);return d(await e.get(i))?!!await e.delete(i):!1},put:async(r,i)=>{const n=o(),a=n+k(i.ttlMs),u=`${String(a).padStart(15,"0")}.${crypto.randomUUID()}`,s=c(u),l=await r(u);if(await e.get(s)!==void 0)throw new Error(`secret value ref "${u}" is already in use`);await e.put(s,{expiresAt:a,scope:i.scope,value:l});const E=[...await e.list({limit:x,prefix:w})].filter(([,f])=>d(f)&&f.expiresAt<=n).map(([f])=>f);return await Promise.all(E.map(async f=>e.delete(f))),u},take:(r,i,n)=>e.transaction(async a=>{const u=c(r),s=await a.get(u);if(!d(s))return;if(s.expiresAt<=o()){await a.delete(u);return}if(s.scope!==i.scope||s.claimant!==void 0&&s.claimant!==i.claimant)return;const l=await n(s.value);if(l!==void 0){if(s.expiresAt<=o()){await a.delete(u);return}return s.claimant===void 0&&await a.put(u,{...s,claimant:i.claimant}),l}})}},K=(e,t,o={})=>{const c=t??{},r=i=>typeof i=="string"?i:void 0;return A(O(e,o),{key:r(c.LUNORA_SECRET_VALUE_KEY),previousKey:r(c.LUNORA_SECRET_VALUE_KEY_PREVIOUS)})},h="lunora-socket:",y=new WeakMap,S=new WeakMap;let g=0;const m=(e,t)=>{const{getTags:o}=e;if(typeof o=="function")try{return o.call(e,t).find(c=>c.startsWith(h))}catch{return}},W=(e,t)=>{const o=m(e,t);if(o!==void 0)return o.slice(h.length);const c=y.get(t);if(c!==void 0)return c;const r=S.get(t);if(r!==void 0)return r;g+=1;const i=`cf-socket-${String(g)}`;return S.set(t,i),i},$=e=>{const t=e,o=new WeakSet;let c=0,r;const i=()=>{const n=e.getWebSockets();return(r?.generation!==c||r.length!==n.length)&&(r={generation:c,length:n.length,set:new WeakSet(n)}),r.set};return{newPair:()=>{if(t.openSocketPair!==void 0)return t.openSocketPair();const n=new WebSocketPair;return{client:n[0],server:n[1]}},upgradeResponse:n=>t.upgradeResponse!==void 0?t.upgradeResponse(n):new Response(null,{status:101,webSocket:n}),accept:(n,a,u)=>{const s=_(u);if(s!==void 0)throw new b("SOCKET_TAG_BUDGET_EXCEEDED",s);const l=n,v=crypto.randomUUID();return e.acceptWebSocket(l,[`${h}${v}`,...u??[]]),y.set(l,v),c+=1,a!==void 0&&l.serializeAttachment?.(a),l},getSockets:n=>e.getWebSockets(n),handleFor:n=>{const a=n;if(y.has(a)||m(e,a)!==void 0)return a;if(!o.has(a)){if(i().has(a))return a;o.add(a)}},idFor:n=>W(e,n)}},P=e=>{const t=r=>({fetch:i=>r.fetch(i)}),o=r=>r===void 0?void 0:{locationHint:r};return{get:(r,i)=>t(e.get(r,o(i))),getByName:(r,i)=>t(e.get(e.idFromName(r),o(i))),idForName:r=>e.idFromName(r),jurisdiction:r=>P(e.jurisdiction(r))}};export{K as createSecretValueHost,O as createSecretValueStore,U as createShardAlarms,P as createShardDirectory,C as createShardHost,D as createShardKvStore,$ as createSocketHost};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createSocketHost as n,createSecretValueHost as l,createShardHost as d,createShardKvStore as i,createShardDirectory as u}from"./createSecretValueHost-BaYQQbQ9.mjs";import{CLOUDFLARE_CAPABILITIES as s}from"@lunora/platform";const m=(t,r={})=>{const e=t;return{kv:i(e.storage),shard:d(e),secretValues:l(e.storage,r),sockets:n(e)}},S=(t,r={})=>{const e=t??{},o=c=>{const a=e[c];if(a==null)throw new Error(`@lunora/platform-cloudflare: no Durable Object namespace bound as "${c}" — add it to wrangler.jsonc's durable_objects.bindings`);return u(a)};return r.scheduler===void 0?{capabilities:s,directory:o}:{capabilities:s,directory:o,scheduler:r.scheduler}};export{m as createShardPlatform,S as createWorkerPlatform};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/platform-cloudflare",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.102",
|
|
4
4
|
"description": "Cloudflare implementation of the Lunora platform contracts: ShardHost, SocketHost, ShardDirectory, ShardKvStore over Durable Objects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
|
|
47
|
-
"@lunora/platform": "1.0.0-alpha.
|
|
47
|
+
"@lunora/platform": "1.0.0-alpha.72"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@cloudflare/workers-types": ">=4.0.0"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as h}from"@lunora/errors";const w=(e,t,o)=>{const c=e.storage.sql;if(typeof c?.exec!="function")throw new TypeError("storage.sql.exec is not available on this DurableObjectState");return c.exec(t,...o)},k=e=>({delete:()=>typeof e.deleteAlarm=="function"?e.deleteAlarm():Promise.resolve(),get:()=>typeof e.getAlarm=="function"?e.getAlarm():null,set:t=>typeof e.setAlarm=="function"?e.setAlarm(t):Promise.resolve()}),b=(e,t)=>{if(!(!(e instanceof Error)||e===t||e.cause!==void 0))try{Object.defineProperty(e,"cause",{configurable:!0,value:t,writable:!0})}catch{}},D=e=>{const{storage:t}=e,o={get databaseSize(){return e.storage.sql?.databaseSize},exec:(r,...i)=>w(e,r,i)},c=k(t),n=async r=>{if(typeof e.blockConcurrencyWhile=="function"){const i=await e.blockConcurrencyWhile(async()=>{try{return{ok:!0,value:await r()}}catch(l){return{error:l,ok:!1}}});if(!i.ok)throw i.error;return i.value}return r()},a=async r=>{const i=t;if(typeof i?.transaction=="function"){let l;try{return await i.transaction(async()=>{try{return await r()}catch(s){throw l={value:s},s}})}catch(s){throw l?(b(l.value,s),l.value):s}}return r()};return{alarms:c,runSerialized:n,shardKey:e.id?.name,sql:o,transaction:a,waitUntil:typeof e.waitUntil=="function"?r=>{e.waitUntil(r)}:void 0}},_=e=>({delete:async t=>!!await e.delete(t),get:t=>e.get(t),list:t=>{if(typeof e.list!="function")throw new TypeError("storage.list is not available on this DurableObjectStorage");return e.list(t)},put:(t,o)=>e.put(t,o)}),d="lunora-socket:",u=new WeakMap,g=new WeakMap;let S=0;const v=(e,t)=>{const{getTags:o}=e;if(typeof o=="function")try{return o.call(e,t).find(c=>c.startsWith(d))}catch{return}},E=(e,t)=>{const o=v(e,t);if(o!==void 0)return o.slice(d.length);const c=u.get(t);if(c!==void 0)return c;const n=g.get(t);if(n!==void 0)return n;S+=1;const a=`cf-socket-${String(S)}`;return g.set(t,a),a},p=10,y=256,m=1,T=e=>{if(!e||e.length===0)return;const t=p-m;if(e.length>t)throw new h("SOCKET_TAG_BUDGET_EXCEEDED",`${String(e.length)} tags supplied; Cloudflare allows ${String(p)} per socket and 1 is reserved for the host's identity tag — pass at most ${String(t)}.`);const o=e.find(c=>c.length>y);if(o!==void 0)throw new h("SOCKET_TAG_BUDGET_EXCEEDED",`Tag ${JSON.stringify(o)} is ${String(o.length)} characters; Cloudflare allows at most ${String(y)} characters per tag.`)},W=e=>{const t=e,o=new WeakSet;let c=0,n;const a=()=>{const r=e.getWebSockets();return(n?.generation!==c||n.length!==r.length)&&(n={generation:c,length:r.length,set:new WeakSet(r)}),n.set};return{newPair:()=>{if(t.openSocketPair!==void 0)return t.openSocketPair();const r=new WebSocketPair;return{client:r[0],server:r[1]}},upgradeResponse:r=>t.upgradeResponse!==void 0?t.upgradeResponse(r):new Response(null,{status:101,webSocket:r}),accept:(r,i,l)=>{T(l);const s=r,f=crypto.randomUUID();return e.acceptWebSocket(s,[`${d}${f}`,...l??[]]),u.set(s,f),c+=1,i!==void 0&&s.serializeAttachment?.(i),s},getSockets:r=>e.getWebSockets(r),handleFor:r=>{const i=r;if(u.has(i)||v(e,i)!==void 0)return i;if(!o.has(i)){if(a().has(i))return i;o.add(i)}},idFor:r=>E(e,r)}},A=e=>{const t=n=>({fetch:a=>n.fetch(a)}),o=n=>n===void 0?void 0:{locationHint:n};return{get:(n,a)=>t(e.get(n,o(a))),getByName:(n,a)=>t(e.get(e.idFromName(n),o(a))),idForName:n=>e.idFromName(n),jurisdiction:n=>A(e.jurisdiction(n))}};export{k as createShardAlarms,A as createShardDirectory,D as createShardHost,_ as createShardKvStore,W as createSocketHost};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{createSocketHost as n,createShardHost as d,createShardKvStore as l,createShardDirectory as i}from"./createShardAlarms-Cm9SOaSc.mjs";import{CLOUDFLARE_CAPABILITIES as c}from"@lunora/platform";const h=e=>{const r=e;return{kv:l(r.storage),shard:d(r),sockets:n(r)}},m=(e,r={})=>{const s=e??{},a=o=>{const t=s[o];if(t==null)throw new Error(`@lunora/platform-cloudflare: no Durable Object namespace bound as "${o}" — add it to wrangler.jsonc's durable_objects.bindings`);return i(t)};return r.scheduler===void 0?{capabilities:c,directory:a}:{capabilities:c,directory:a,scheduler:r.scheduler}};export{h as createShardPlatform,m as createWorkerPlatform};
|