mppx 0.17.1 → 0.17.4
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/CHANGELOG.md +65 -0
- package/dist/Errors.d.ts +94 -0
- package/dist/Errors.js +83 -0
- package/dist/Store.d.ts +413 -0
- package/dist/Store.js +837 -0
- package/dist/cli/cli.js +141 -136
- package/dist/cli/internal.js +28 -6
- package/dist/cli/plugins/index.js +16 -11
- package/dist/cli/sessions/Manager.js +12 -1
- package/dist/cli/utils.d.ts +2 -0
- package/dist/cli/utils.js +6 -0
- package/dist/cli/validate/challenge.d.ts +4 -0
- package/dist/cli/validate/challenge.js +9 -6
- package/dist/client/Mppx.d.ts +1 -1
- package/dist/client/Mppx.js +7 -3
- package/dist/client/internal/Fetch.d.ts +1 -1
- package/dist/client/internal/Fetch.js +9 -0
- package/dist/client/internal/MethodResponse.d.ts +8 -0
- package/dist/client/internal/MethodResponse.js +9 -0
- package/dist/client/internal/ProofRealm.d.ts +3 -3
- package/dist/client/internal/ProofRealm.js +4 -4
- package/dist/client/internal/protocols/Mcp.d.ts +4 -0
- package/dist/client/internal/protocols/Mcp.js +27 -14
- package/dist/cloudflare/ChannelStore.d.ts +97 -0
- package/dist/cloudflare/ChannelStore.js +304 -0
- package/dist/cloudflare/Channels.d.ts +92 -0
- package/dist/cloudflare/Channels.js +325 -0
- package/dist/cloudflare/index.d.ts +5 -0
- package/dist/cloudflare/index.js +5 -0
- package/dist/identity/Client.js +7 -1
- package/dist/internal/sse.d.ts +21 -0
- package/dist/internal/sse.js +82 -0
- package/dist/internal/version.d.ts +2 -2
- package/dist/internal/version.js +1 -1
- package/dist/mcp/client/McpClient.d.ts +2 -2
- package/dist/mcp/client/McpClient.js +5 -2
- package/dist/proxy/Proxy.d.ts +2 -0
- package/dist/proxy/Proxy.js +31 -6
- package/dist/proxy/internal/Replay.js +1 -1
- package/dist/server/Mppx.js +5 -4
- package/dist/server/Transport.js +1 -1
- package/dist/server/internal/html/config.d.ts +7 -1
- package/dist/server/internal/html/config.js +4 -0
- package/dist/stripe/internal/types.d.ts +82 -6
- package/dist/stripe/server/Charge.js +1 -2
- package/dist/stripe/server/Methods.d.ts +2 -0
- package/dist/stripe/server/Methods.js +2 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/stripe/server/internal/record-payment.d.ts +49 -0
- package/dist/stripe/server/internal/record-payment.js +75 -18
- package/dist/tempo/Methods.d.ts +8 -0
- package/dist/tempo/Methods.js +18 -2
- package/dist/tempo/client/Charge.d.ts +32 -1
- package/dist/tempo/client/Charge.js +201 -3
- package/dist/tempo/client/Methods.d.ts +16 -0
- package/dist/tempo/internal/fee-payer.d.ts +64 -14
- package/dist/tempo/internal/fee-payer.js +231 -20
- package/dist/tempo/internal/zone.d.ts +79 -0
- package/dist/tempo/internal/zone.js +133 -0
- package/dist/tempo/legacy/session/Chain.js +2 -0
- package/dist/tempo/server/Charge.d.ts +7 -3
- package/dist/tempo/server/Charge.js +100 -6
- package/dist/tempo/server/Methods.d.ts +40 -0
- package/dist/tempo/server/Subscription.js +1 -0
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/transport.d.ts +6 -0
- package/dist/tempo/server/internal/transport.js +21 -21
- package/dist/tempo/session/client/ChannelStore.d.ts +2 -0
- package/dist/tempo/session/client/CredentialState.js +2 -0
- package/dist/tempo/session/client/Session.d.ts +11 -1
- package/dist/tempo/session/client/Session.js +39 -6
- package/dist/tempo/session/client/SessionManager.d.ts +1 -1
- package/dist/tempo/session/client/SessionManager.js +8 -4
- package/dist/tempo/session/client/Transports.d.ts +2 -0
- package/dist/tempo/session/client/Transports.js +4 -1
- package/dist/tempo/session/precompile/Chain.js +1 -0
- package/dist/tempo/session/server/ChannelStore.d.ts +14 -0
- package/dist/tempo/session/server/ChannelStore.js +10 -3
- package/dist/tempo/session/server/CredentialVerification.js +1 -1
- package/dist/tempo/session/server/MeteredStream.d.ts +4 -0
- package/dist/tempo/session/server/MeteredStream.js +6 -1
- package/dist/tempo/session/server/Sse.d.ts +59 -0
- package/dist/tempo/session/server/Sse.js +175 -10
- package/dist/validation/core.js +2 -0
- package/dist/zod.d.ts +2 -1
- package/dist/zod.js +1 -1
- package/package.json +5 -3
- package/dist/stripe/server/internal/html/types.d.ts +0 -2
- package/dist/stripe/server/internal/html/types.js +0 -2
package/dist/Store.d.ts
CHANGED
|
@@ -99,6 +99,419 @@ export declare namespace cloudflare {
|
|
|
99
99
|
update: NonNullable<Parameters['update']>;
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Atomic store backed by Cloudflare Durable Objects.
|
|
104
|
+
*
|
|
105
|
+
* Each key lives in its own Durable Object instance (`idFromName(key)`; a key named
|
|
106
|
+
* like a replay shard, `mppx:replay:v<version>:<shard>`, or starting with `mppx:key:`
|
|
107
|
+
* uses `mppx:key:<key>`), so
|
|
108
|
+
* reads and writes of one key are linearizable across every Worker isolate and
|
|
109
|
+
* location, while different keys (such as different payment channels) scale
|
|
110
|
+
* out across instances. {@link AtomicStore.update} runs `fn` in the Worker and
|
|
111
|
+
* commits with a compare-and-swap in the object. When another writer changed
|
|
112
|
+
* the key in between, the swap returns the stored value and the update retries
|
|
113
|
+
* from it after a jittered backoff; {@link AtomicStore.tryClaim} runs in a single
|
|
114
|
+
* object call.
|
|
115
|
+
*
|
|
116
|
+
* Bind {@link DurableObjectStore} as a SQLite-backed Durable Object and pass
|
|
117
|
+
* its namespace. With `transport: 'rpc'`, the store calls the object's methods
|
|
118
|
+
* over Workers RPC instead of `fetch`, and `encoding: 'native'` then stores
|
|
119
|
+
* values as structured-clone values instead of JSON text.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* import { Store } from 'mppx/server'
|
|
124
|
+
*
|
|
125
|
+
* export class MppxStore extends Store.DurableObjectStore {}
|
|
126
|
+
*
|
|
127
|
+
* export default {
|
|
128
|
+
* fetch(request: Request, env: Env) {
|
|
129
|
+
* const store = Store.durableObject(env.MPPX_STORE)
|
|
130
|
+
* // ...
|
|
131
|
+
* },
|
|
132
|
+
* }
|
|
133
|
+
* ```
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* import { DurableObject } from 'cloudflare:workers'
|
|
138
|
+
* import { Store } from 'mppx/server'
|
|
139
|
+
*
|
|
140
|
+
* // Workers RPC needs a class that extends `DurableObject`.
|
|
141
|
+
* export class MppxStore extends Store.DurableObjectStore.rpc(DurableObject) {}
|
|
142
|
+
*
|
|
143
|
+
* const store = Store.durableObject(env.MPPX_STORE, { transport: 'rpc', encoding: 'native' })
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export declare function durableObject(namespace: durableObject.Namespace, options?: durableObject.Options): AtomicStore;
|
|
147
|
+
export declare namespace durableObject {
|
|
148
|
+
type Options = {
|
|
149
|
+
/**
|
|
150
|
+
* How values are stored. `json` stores JSON text (bigints as ox encodes them);
|
|
151
|
+
* `native` stores structured-clone values, so bigints and nested objects stay
|
|
152
|
+
* intact and values written directly with `storage.put` read back. `native`
|
|
153
|
+
* requires `transport: 'rpc'`. Stored values don't record their encoding, so keep
|
|
154
|
+
* one encoding per namespace (or `keyPrefix`): a `native` store reads `json` values
|
|
155
|
+
* as their JSON text, and `json` clients cannot read what it writes. @default 'json'
|
|
156
|
+
*/
|
|
157
|
+
encoding?: Encoding | undefined;
|
|
158
|
+
/** Prefix prepended to every backing store key. */
|
|
159
|
+
keyPrefix?: string | undefined;
|
|
160
|
+
/** Compare-and-swap attempts before {@link AtomicStore.update} gives up. @default 32 */
|
|
161
|
+
maxAttempts?: number | undefined;
|
|
162
|
+
/**
|
|
163
|
+
* Spreads replay claims ({@link AtomicStore.tryClaim}) over this many shard
|
|
164
|
+
* objects, named `mppx:replay:v<version>:<shard>` by an FNV-1a hash of the
|
|
165
|
+
* key, instead of one object per key: each shard holds its claims in a SQLite
|
|
166
|
+
* table (`mppx_replay_claims`) and clears expired ones with an alarm, so short-lived claims leave no
|
|
167
|
+
* objects behind. The class must be SQLite-backed. Claims then live only in
|
|
168
|
+
* the shards: `get` and `update` of a claimed key don't see them, a value
|
|
169
|
+
* stored at the key doesn't block a claim (mppx stores only expiring claims
|
|
170
|
+
* at its replay keys), and `delete` releases them. Changing the count moves keys between shards, so
|
|
171
|
+
* bump `replayShardVersion` with it to start from empty shards. A claim lives
|
|
172
|
+
* only where it was made: enabling sharding, changing the count or version, or
|
|
173
|
+
* running two settings side by side lets a key claimed under the old setting
|
|
174
|
+
* be claimed again until that claim expires. Change it when no unexpired
|
|
175
|
+
* claim matters, such as together with the secret that signs Challenges.
|
|
176
|
+
* Shards are objects of the bound class, so a subclass's `alarm`, handlers and
|
|
177
|
+
* constructor run in them too: skip the subclass's own work there with
|
|
178
|
+
* {@link DurableObjectStore.isReplayShard}, and never clear a shard's storage.
|
|
179
|
+
*/
|
|
180
|
+
replayShards?: number | undefined;
|
|
181
|
+
/** Version in the shard objects' names, to start a fresh set of shards. @default 1 */
|
|
182
|
+
replayShardVersion?: number | undefined;
|
|
183
|
+
/**
|
|
184
|
+
* How the store calls the object: `fetch` requests, or Workers RPC method
|
|
185
|
+
* calls, which need the class from {@link DurableObjectStore.rpc}. @default 'fetch'
|
|
186
|
+
*/
|
|
187
|
+
transport?: 'fetch' | 'rpc' | undefined;
|
|
188
|
+
};
|
|
189
|
+
/** How {@link durableObject} stores values: JSON text or structured-clone values. */
|
|
190
|
+
type Encoding = 'json' | 'native';
|
|
191
|
+
/** The Durable Object namespace binding of a {@link DurableObjectStore} class. */
|
|
192
|
+
type Namespace = {
|
|
193
|
+
idFromName: (name: string) => unknown;
|
|
194
|
+
get: (id: any) => {
|
|
195
|
+
fetch: (input: string, init: RequestInit) => Promise<Response>;
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Durable Object class that holds the values of {@link durableObject}.
|
|
201
|
+
*
|
|
202
|
+
* Export a subclass of it from your Worker and bind it in
|
|
203
|
+
* `wrangler.jsonc` with a `new_sqlite_classes` migration. It needs no
|
|
204
|
+
* `cloudflare:workers` import, so it bundles anywhere; for Workers RPC, extend
|
|
205
|
+
* {@link DurableObjectStore.rpc} instead.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```jsonc
|
|
209
|
+
* {
|
|
210
|
+
* "durable_objects": { "bindings": [{ "name": "MPPX_STORE", "class_name": "MppxStore" }] },
|
|
211
|
+
* "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MppxStore"] }]
|
|
212
|
+
* }
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
export declare class DurableObjectStore {
|
|
216
|
+
/** The object's state (`ctx`), for subclasses that use its storage or alarms. */
|
|
217
|
+
protected state: DurableObjectStore.State;
|
|
218
|
+
/** The Worker's bindings, for subclasses. */
|
|
219
|
+
protected env: unknown;
|
|
220
|
+
/**
|
|
221
|
+
* The object's storage: a subclass schedules alarms with it from {@link DurableObjectStore.onWrite}.
|
|
222
|
+
* A plain field, like `state` and `env`, so subclasses that assign `this.storage = state.storage` keep working.
|
|
223
|
+
*/
|
|
224
|
+
protected storage: DurableObjectStore.Storage;
|
|
225
|
+
constructor(state: DurableObjectStore.State, env?: unknown);
|
|
226
|
+
/**
|
|
227
|
+
* Returns this class rebuilt on `base`, the `DurableObject` class from
|
|
228
|
+
* `cloudflare:workers`: Workers RPC calls only reach classes that extend it,
|
|
229
|
+
* and taking it as an argument keeps this module free of that import. Called
|
|
230
|
+
* on a subclass, it keeps the subclass's methods (such as `onWrite` and
|
|
231
|
+
* `alarm`) and static members, but not its constructor, field initializers or `#private` members,
|
|
232
|
+
* which the copied methods cannot reach: subclass the returned class, or set
|
|
233
|
+
* state up lazily in ordinary members.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```ts
|
|
237
|
+
* import { DurableObject } from 'cloudflare:workers'
|
|
238
|
+
*
|
|
239
|
+
* export class MppxStore extends Store.DurableObjectStore.rpc(DurableObject) {}
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
static rpc<store extends typeof DurableObjectStore>(this: store, base: abstract new (state: any, env: any) => object): store;
|
|
243
|
+
/**
|
|
244
|
+
* Called for every applied put, delete, swap and claim (except claims in replay shards,
|
|
245
|
+
* which live in the shard's own table), after the storage write and before the
|
|
246
|
+
* response, with the decoded values (`null` when the key is absent). When the
|
|
247
|
+
* storage has `transaction()`, the write and this hook run in one transaction,
|
|
248
|
+
* passed as `storage`: make the hook's writes and alarms
|
|
249
|
+
* through it so they commit with the store's write, and throw to roll the
|
|
250
|
+
* write back. Make only storage calls and alarms here: awaiting other I/O
|
|
251
|
+
* (`fetch`, a queue send) opens the object's input gate before the write
|
|
252
|
+
* commits, so a concurrent swap or claim could act on the old value. Do that
|
|
253
|
+
* work from {@link DurableObjectStore.alarm} instead. Does nothing by default.
|
|
254
|
+
*/
|
|
255
|
+
protected onWrite(_key: string, _previous: unknown | null, _next: unknown | null, _storage: DurableObjectStore.Storage): void | Promise<void>;
|
|
256
|
+
/**
|
|
257
|
+
* Handles the object's alarm, which a subclass sets through
|
|
258
|
+
* {@link DurableObjectStore.storage}. In a replay shard it clears expired claims, so
|
|
259
|
+
* a subclass that overrides it must call `super.alarm()`, or expired claims stay stored.
|
|
260
|
+
* Elsewhere, including on key-value-backed objects, it does nothing. The subclass's own
|
|
261
|
+
* alarm work runs in shards too: skip it there with {@link DurableObjectStore.isReplayShard}.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* ```ts
|
|
265
|
+
* override async alarm() {
|
|
266
|
+
* await super.alarm()
|
|
267
|
+
* if (this.isReplayShard()) return
|
|
268
|
+
* // The subclass's own work.
|
|
269
|
+
* }
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
alarm(): Promise<void>;
|
|
273
|
+
/**
|
|
274
|
+
* Whether this object is a replay shard of {@link durableObject}'s `replayShards`: an
|
|
275
|
+
* object of the same class that holds only replay claims, in its own SQL table. A
|
|
276
|
+
* subclass skips its own work there, and must not clear its storage. It tells by the
|
|
277
|
+
* object's whole name (`mppx:replay:v<version>:<shard>`) where the runtime gives it (`ctx.id.name`, so it
|
|
278
|
+
* works in the constructor of a fresh shard), and otherwise by the claims table, which a
|
|
279
|
+
* shard creates on its first claim.
|
|
280
|
+
*/
|
|
281
|
+
protected isReplayShard(): boolean;
|
|
282
|
+
/** Whether this object holds the replay claims table, which a shard creates on its first claim. */
|
|
283
|
+
private hasReplayClaims;
|
|
284
|
+
/**
|
|
285
|
+
* Claims `key` until `expires` in this replay shard, unless it holds an
|
|
286
|
+
* unexpired claim: one `INSERT … ON CONFLICT DO UPDATE … WHERE` statement, so
|
|
287
|
+
* concurrent claims have exactly one winner. Schedules the alarm that clears it.
|
|
288
|
+
*/
|
|
289
|
+
claimReplay(key: string, expires: number): Promise<boolean>;
|
|
290
|
+
/** Releases `key`'s claim in this replay shard, if it has one. */
|
|
291
|
+
releaseReplay(key: string): Promise<void>;
|
|
292
|
+
/** Reads a key: its JSON text, or its stored value with `native` encoding. */
|
|
293
|
+
storeGet(key: string, _encoding?: durableObject.Encoding): Promise<unknown>;
|
|
294
|
+
/** Writes a key: JSON text, or any structured-clone value with `native` encoding. */
|
|
295
|
+
storePut(key: string, value: unknown, encoding?: durableObject.Encoding): Promise<void>;
|
|
296
|
+
/** Deletes a key. */
|
|
297
|
+
storeDelete(key: string, encoding?: durableObject.Encoding): Promise<void>;
|
|
298
|
+
/**
|
|
299
|
+
* Writes `value` (or deletes, for `null`) only if the key holds `expected`:
|
|
300
|
+
* the same JSON text, or with `native` encoding a structurally equal value.
|
|
301
|
+
* It reads and writes without awaiting other I/O in between, so the object's
|
|
302
|
+
* input gate makes it atomic.
|
|
303
|
+
*/
|
|
304
|
+
storeSwap(key: string, expected: unknown, value: unknown, encoding?: durableObject.Encoding): Promise<DurableObjectStore.SwapResult<unknown>>;
|
|
305
|
+
/** Records a replay marker until `expires` unless the key holds a value or an unexpired marker. */
|
|
306
|
+
storeClaim(key: string, expires: number, encoding?: durableObject.Encoding): Promise<boolean>;
|
|
307
|
+
/** Handles one store operation sent by `fetch`, as JSON text. */
|
|
308
|
+
fetch(request: Request): Promise<Response>;
|
|
309
|
+
/** The replay shard's claims table, created on first use. */
|
|
310
|
+
private replayClaims;
|
|
311
|
+
/** Writes or deletes (`null`) a key and runs {@link DurableObjectStore.onWrite}. `previous` is the value the caller already read, if any. */
|
|
312
|
+
private commit;
|
|
313
|
+
}
|
|
314
|
+
export declare namespace DurableObjectStore {
|
|
315
|
+
/** The subset of `DurableObjectState` the store uses. */
|
|
316
|
+
type State = {
|
|
317
|
+
/** The object's ID; `name` is set for objects addressed with `idFromName`. */
|
|
318
|
+
id?: {
|
|
319
|
+
readonly name?: string | undefined;
|
|
320
|
+
} | undefined;
|
|
321
|
+
storage: Storage;
|
|
322
|
+
};
|
|
323
|
+
/** The subset of `DurableObjectStorage` the store uses. */
|
|
324
|
+
type Storage = {
|
|
325
|
+
get: (key: string) => Promise<unknown>;
|
|
326
|
+
put(key: string, value: unknown): Promise<void>;
|
|
327
|
+
delete: (key: string) => Promise<unknown>;
|
|
328
|
+
/** The scheduled alarm time in milliseconds, or `null`. */
|
|
329
|
+
getAlarm?: (() => Promise<number | null>) | undefined;
|
|
330
|
+
/** Schedules the object's alarm, replacing any earlier one. */
|
|
331
|
+
setAlarm?: ((scheduledTime: number | Date) => Promise<void>) | undefined;
|
|
332
|
+
/** Cancels the object's alarm. */
|
|
333
|
+
deleteAlarm?: (() => Promise<void>) | undefined;
|
|
334
|
+
/** Runs `closure` in a transaction that rolls back when it throws. */
|
|
335
|
+
transaction?<result>(closure: (txn: Storage) => Promise<result>): Promise<result>;
|
|
336
|
+
/** The object's SQLite database, on SQLite-backed objects: replay shards need it. */
|
|
337
|
+
sql?: Sql | undefined;
|
|
338
|
+
};
|
|
339
|
+
/** The subset of `SqlStorage` replay shards use. */
|
|
340
|
+
type Sql = {
|
|
341
|
+
exec: (query: string, ...bindings: unknown[]) => {
|
|
342
|
+
toArray: () => Record<string, unknown>[];
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
/** A replay shard's methods that {@link durableObject} calls over Workers RPC. */
|
|
346
|
+
type ReplayShard = {
|
|
347
|
+
claimReplay: (key: string, expires: number) => Promise<boolean>;
|
|
348
|
+
releaseReplay: (key: string) => Promise<unknown>;
|
|
349
|
+
};
|
|
350
|
+
/** Result of a compare-and-swap: on a conflict, the stored value, so a retry needs no read. */
|
|
351
|
+
type SwapResult<value = string | null> = {
|
|
352
|
+
applied: true;
|
|
353
|
+
} | {
|
|
354
|
+
applied: false;
|
|
355
|
+
current: value;
|
|
356
|
+
};
|
|
357
|
+
/** The object's methods that {@link durableObject} calls, positionally, over Workers RPC. */
|
|
358
|
+
type Stub = {
|
|
359
|
+
storeGet: (key: string, encoding?: durableObject.Encoding) => Promise<unknown>;
|
|
360
|
+
storePut: (key: string, value: unknown, encoding?: durableObject.Encoding) => Promise<unknown>;
|
|
361
|
+
storeDelete: (key: string, encoding?: durableObject.Encoding) => Promise<unknown>;
|
|
362
|
+
storeSwap: (key: string, expected: unknown, value: unknown, encoding?: durableObject.Encoding) => Promise<SwapResult<unknown>>;
|
|
363
|
+
storeClaim: (key: string, expires: number, encoding?: durableObject.Encoding) => Promise<boolean>;
|
|
364
|
+
};
|
|
365
|
+
/** An operation sent from {@link durableObject} to the object. */
|
|
366
|
+
type Request = {
|
|
367
|
+
op: 'get';
|
|
368
|
+
key: string;
|
|
369
|
+
} | {
|
|
370
|
+
op: 'put';
|
|
371
|
+
key: string;
|
|
372
|
+
value: string;
|
|
373
|
+
} | {
|
|
374
|
+
op: 'delete';
|
|
375
|
+
key: string;
|
|
376
|
+
} | {
|
|
377
|
+
op: 'swap';
|
|
378
|
+
key: string;
|
|
379
|
+
expected: string | null;
|
|
380
|
+
value: string | null;
|
|
381
|
+
} | {
|
|
382
|
+
op: 'claim';
|
|
383
|
+
key: string;
|
|
384
|
+
expires: number;
|
|
385
|
+
} | {
|
|
386
|
+
op: 'claimReplay';
|
|
387
|
+
key: string;
|
|
388
|
+
expires: number;
|
|
389
|
+
} | {
|
|
390
|
+
op: 'releaseReplay';
|
|
391
|
+
key: string;
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Atomic store backed by a SQL database: Postgres, SQLite or Cloudflare D1.
|
|
396
|
+
*
|
|
397
|
+
* Values live as JSON text in one table (`mppx_store` by default, created on
|
|
398
|
+
* first use). Every write is a single statement, and {@link AtomicStore.update}
|
|
399
|
+
* commits with a compare-and-swap on the stored value
|
|
400
|
+
* (`UPDATE … WHERE key = ? AND value = ? RETURNING`, or
|
|
401
|
+
* `INSERT … ON CONFLICT DO NOTHING RETURNING` for a new key), retrying from the
|
|
402
|
+
* current value after a jittered backoff when another writer won. It needs no
|
|
403
|
+
* interactive transactions, so it works over HTTP drivers and D1, and only
|
|
404
|
+
* `ON CONFLICT` and `RETURNING` (Postgres, SQLite 3.35+, D1, libSQL).
|
|
405
|
+
*
|
|
406
|
+
* Use {@link postgres}, {@link sqlite} or {@link d1} for common drivers, or pass
|
|
407
|
+
* any driver as a `query` function.
|
|
408
|
+
*
|
|
409
|
+
* @example
|
|
410
|
+
* ```ts
|
|
411
|
+
* import postgres from 'postgres'
|
|
412
|
+
* import { Store } from 'mppx/server'
|
|
413
|
+
*
|
|
414
|
+
* const db = postgres(process.env.DATABASE_URL!)
|
|
415
|
+
* const store = Store.sql({ dialect: 'postgres', query: (text, params) => db.unsafe(text, params) })
|
|
416
|
+
* ```
|
|
417
|
+
*/
|
|
418
|
+
export declare function sql(driver: sql.Driver, options?: sql.Options): AtomicStore;
|
|
419
|
+
export declare namespace sql {
|
|
420
|
+
type Options = {
|
|
421
|
+
/** Prefix prepended to every backing store key. */
|
|
422
|
+
keyPrefix?: string | undefined;
|
|
423
|
+
/** Compare-and-swap attempts before {@link AtomicStore.update} gives up. @default 32 */
|
|
424
|
+
maxAttempts?: number | undefined;
|
|
425
|
+
/** Table holding the values, optionally schema-qualified. @default 'mppx_store' */
|
|
426
|
+
table?: string | undefined;
|
|
427
|
+
/** Create the table on first use (`CREATE TABLE IF NOT EXISTS`). Set `false` when migrations create it. @default true */
|
|
428
|
+
createTable?: boolean | undefined;
|
|
429
|
+
};
|
|
430
|
+
/** A result row. */
|
|
431
|
+
type Row = Record<string, unknown>;
|
|
432
|
+
/** Runs one statement with positional parameters and returns its rows. */
|
|
433
|
+
type Driver = {
|
|
434
|
+
/** Placeholder style: Postgres numbers them (`$1`), SQLite and D1 use `?`. */
|
|
435
|
+
dialect: 'postgres' | 'sqlite';
|
|
436
|
+
query: (sql: string, params: unknown[]) => Promise<Row[]>;
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Atomic SQL store on a Postgres client: `pg` (`Pool` or `Client`),
|
|
441
|
+
* `@neondatabase/serverless` (`Pool` or `neon()`), `@vercel/postgres`,
|
|
442
|
+
* Supabase's Postgres connection or PGlite. See {@link sql}.
|
|
443
|
+
*
|
|
444
|
+
* @example
|
|
445
|
+
* ```ts
|
|
446
|
+
* import { Pool } from 'pg'
|
|
447
|
+
* import { Store } from 'mppx/server'
|
|
448
|
+
*
|
|
449
|
+
* const store = Store.postgres(new Pool({ connectionString: process.env.DATABASE_URL }))
|
|
450
|
+
* ```
|
|
451
|
+
*/
|
|
452
|
+
export declare function postgres(client: postgres.Client, options?: sql.Options): AtomicStore;
|
|
453
|
+
export declare namespace postgres {
|
|
454
|
+
/** Any client with `query(text, values)` that resolves to `{ rows }` or to the rows. */
|
|
455
|
+
type Client = {
|
|
456
|
+
query: (text: string, values: unknown[]) => Promise<{
|
|
457
|
+
rows: sql.Row[];
|
|
458
|
+
} | sql.Row[]>;
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Atomic SQL store on a SQLite database: `node:sqlite` (`DatabaseSync`),
|
|
463
|
+
* `better-sqlite3` or `bun:sqlite`. See {@link sql}. For libSQL or Turso, use
|
|
464
|
+
* {@link sql} with dialect `sqlite` and return the result set's rows:
|
|
465
|
+
* `Store.sql({ dialect: 'sqlite', query: async (sql, args) => (await client.execute({ sql, args: args as InArgs })).rows })`.
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* ```ts
|
|
469
|
+
* import { DatabaseSync } from 'node:sqlite'
|
|
470
|
+
* import { Store } from 'mppx/server'
|
|
471
|
+
*
|
|
472
|
+
* const store = Store.sqlite(new DatabaseSync('mppx.db'))
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
475
|
+
export declare function sqlite(database: sqlite.Database, options?: sql.Options): AtomicStore;
|
|
476
|
+
export declare namespace sqlite {
|
|
477
|
+
/** A database whose prepared statements run with `all(...params)`, or `run(...params)` when better-sqlite3 marks them as returning no rows. */
|
|
478
|
+
type Database = {
|
|
479
|
+
prepare: (sql: string) => {
|
|
480
|
+
all: (...params: any[]) => unknown;
|
|
481
|
+
run?: ((...params: any[]) => unknown) | undefined;
|
|
482
|
+
/** better-sqlite3: whether the statement returns rows. */
|
|
483
|
+
reader?: boolean | undefined;
|
|
484
|
+
};
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Atomic SQL store on a Cloudflare D1 database binding. See {@link sql}.
|
|
489
|
+
*
|
|
490
|
+
* @example
|
|
491
|
+
* ```ts
|
|
492
|
+
* import { Store } from 'mppx/server'
|
|
493
|
+
*
|
|
494
|
+
* export default {
|
|
495
|
+
* fetch(request: Request, env: { DB: D1Database }) {
|
|
496
|
+
* const store = Store.d1(env.DB)
|
|
497
|
+
* // ...
|
|
498
|
+
* },
|
|
499
|
+
* }
|
|
500
|
+
* ```
|
|
501
|
+
*/
|
|
502
|
+
export declare function d1(database: d1.Database, options?: sql.Options): AtomicStore;
|
|
503
|
+
export declare namespace d1 {
|
|
504
|
+
/** The subset of `D1Database` the store uses. */
|
|
505
|
+
type Database = {
|
|
506
|
+
prepare: (sql: string) => {
|
|
507
|
+
bind: (...values: any[]) => {
|
|
508
|
+
all: () => Promise<{
|
|
509
|
+
results: unknown[];
|
|
510
|
+
}>;
|
|
511
|
+
};
|
|
512
|
+
};
|
|
513
|
+
};
|
|
514
|
+
}
|
|
102
515
|
/** In-memory store backed by a `Map`. JSON-roundtrips values to match production behavior. */
|
|
103
516
|
export declare function memory(options?: memory.Options): AtomicStore;
|
|
104
517
|
export declare namespace memory {
|