@pramen/server 0.0.48 → 0.0.50
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/auth.d.ts +4 -3
- package/dist/cli.js +17 -30
- package/dist/dev.d.ts +1 -0
- package/dist/dev.js +9 -0
- package/dist/durable-object.d.ts +1 -0
- package/dist/durable-object.js +13 -6
- package/dist/index.d.ts +7 -5
- package/dist/index.js +6 -2
- package/dist/pramen.d.ts +4 -2
- package/dist/runtime/acl.d.ts +14 -5
- package/dist/runtime/acl.js +1 -5
- package/dist/runtime/db.d.ts +3 -2
- package/dist/runtime/dev-token.d.ts +4 -0
- package/dist/runtime/dev-token.js +34 -0
- package/dist/runtime/dispatch.d.ts +3 -1
- package/dist/runtime/dispatch.js +2 -0
- package/dist/runtime/driver.d.ts +8 -5
- package/dist/runtime/errors.d.ts +6 -0
- package/dist/runtime/errors.js +8 -0
- package/dist/runtime/mail.d.ts +2 -1
- package/dist/runtime/protocol.d.ts +4 -3
- package/dist/runtime/queue-consumer.d.ts +4 -2
- package/dist/runtime/queue.d.ts +3 -2
- package/dist/runtime/read-engine.d.ts +11 -9
- package/dist/runtime/read-engine.js +4 -1
- package/dist/runtime/registry.d.ts +4 -1
- package/dist/runtime/registry.js +0 -3
- package/dist/runtime/schema-diff.d.ts +6 -6
- package/dist/runtime/schema-diff.js +4 -4
- package/dist/runtime/storage.d.ts +4 -4
- package/dist/runtime/storage.js +8 -63
- package/dist/runtime/token.d.ts +22 -0
- package/dist/runtime/token.js +88 -0
- package/dist/sdk/acl.d.ts +18 -11
- package/dist/sdk/handlers.d.ts +16 -3
- package/dist/sdk/infer.d.ts +17 -0
- package/dist/worker.d.ts +2 -1
- package/dist/worker.js +20 -10
- package/package.json +13 -3
- package/src/auth.ts +7 -6
- package/src/cli.ts +22 -36
- package/src/dev.ts +10 -0
- package/src/durable-object.ts +18 -9
- package/src/index.ts +14 -4
- package/src/pramen.ts +4 -2
- package/src/runtime/acl.ts +45 -31
- package/src/runtime/db.ts +14 -12
- package/src/runtime/dev-token.ts +36 -0
- package/src/runtime/dispatch.ts +7 -3
- package/src/runtime/driver.ts +11 -7
- package/src/runtime/errors.ts +9 -0
- package/src/runtime/mail.ts +2 -1
- package/src/runtime/migrate.ts +2 -1
- package/src/runtime/outbox.ts +2 -1
- package/src/runtime/protocol.ts +5 -3
- package/src/runtime/queue-consumer.ts +4 -2
- package/src/runtime/queue.ts +4 -2
- package/src/runtime/read-engine.ts +27 -21
- package/src/runtime/registry.ts +5 -1
- package/src/runtime/schema-diff.ts +14 -14
- package/src/runtime/storage.ts +14 -62
- package/src/runtime/token.ts +94 -0
- package/src/sdk/acl.ts +29 -14
- package/src/sdk/handlers.ts +17 -3
- package/src/sdk/infer.ts +21 -0
- package/src/worker.ts +24 -11
package/dist/auth.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Identity } from "./sdk/acl";
|
|
2
|
+
import type { JsonObject } from "./sdk/infer";
|
|
2
3
|
/** Verifies a JWT and returns its claims, or null if invalid. Implementations
|
|
3
4
|
* differ only in how they verify the signature. */
|
|
4
5
|
export interface VerifyStrategy {
|
|
5
|
-
verify(token: string): Promise<
|
|
6
|
+
verify(token: string): Promise<JsonObject | null>;
|
|
6
7
|
}
|
|
7
8
|
/** Optional, opt-in claim validation layered on top of signature + exp/nbf. All
|
|
8
9
|
* default OFF (unset) so existing tokens keep verifying; a deployment turns these on
|
|
@@ -22,7 +23,7 @@ export declare class HmacStrategy implements VerifyStrategy {
|
|
|
22
23
|
private readonly secret;
|
|
23
24
|
private readonly opts;
|
|
24
25
|
constructor(secret: string, opts?: VerifyOptions);
|
|
25
|
-
verify(token: string): Promise<
|
|
26
|
+
verify(token: string): Promise<JsonObject | null>;
|
|
26
27
|
}
|
|
27
28
|
/** RS256 verified against a remote JWKS. Public keys are fetched once and cached
|
|
28
29
|
* (TTL); a token with an unknown `kid` triggers one forced refetch to pick up key
|
|
@@ -35,7 +36,7 @@ export declare class JwksStrategy implements VerifyStrategy {
|
|
|
35
36
|
private fetchedAt;
|
|
36
37
|
private inflight;
|
|
37
38
|
constructor(url: string, ttlMs?: number, opts?: VerifyOptions);
|
|
38
|
-
verify(token: string): Promise<
|
|
39
|
+
verify(token: string): Promise<JsonObject | null>;
|
|
39
40
|
private lookup;
|
|
40
41
|
private keyFor;
|
|
41
42
|
private refresh;
|
package/dist/cli.js
CHANGED
|
@@ -20,29 +20,10 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
20
20
|
import { dirname, resolve } from "node:path";
|
|
21
21
|
import { createTableSql } from "./runtime/ddl";
|
|
22
22
|
import { schemaHash } from "./runtime/migrate";
|
|
23
|
-
import {
|
|
23
|
+
import { diffSchemaFingerprint, schemaFingerprint } from "./runtime/schema-diff";
|
|
24
24
|
import { entitiesInPartition, partitionsOf } from "./sdk/schema";
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* with AUTH_SECRET when set, else the dev secret from the scaffolded oblaka.ts. */
|
|
28
|
-
const DEV_SECRET = "dev-secret-change-me";
|
|
29
|
-
function bytesToB64url(bytes) {
|
|
30
|
-
let bin = "";
|
|
31
|
-
for (const b of bytes)
|
|
32
|
-
bin += String.fromCharCode(b);
|
|
33
|
-
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
34
|
-
}
|
|
35
|
-
const strToB64url = (s) => bytesToB64url(new TextEncoder().encode(s));
|
|
36
|
-
async function sign(payload) {
|
|
37
|
-
const secret = process.env.AUTH_SECRET || DEV_SECRET;
|
|
38
|
-
const now = Math.floor(Date.now() / 1000);
|
|
39
|
-
const header = strToB64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
|
|
40
|
-
const body = strToB64url(JSON.stringify({ iat: now, exp: now + 3600, ...payload }));
|
|
41
|
-
const data = `${header}.${body}`;
|
|
42
|
-
const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
43
|
-
const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(data));
|
|
44
|
-
return `${data}.${bytesToB64url(new Uint8Array(sig))}`;
|
|
45
|
-
}
|
|
25
|
+
import { signDevToken } from "./runtime/dev-token";
|
|
26
|
+
const sign = (payload) => signDevToken(payload);
|
|
46
27
|
/** The sub-schema of a single partition (used to mirror the DO's per-partition hash,
|
|
47
28
|
* which migrate() computes over exactly this subset). For a single-partition app the
|
|
48
29
|
* subset equals the whole schema, so the hash is identical to the unpartitioned case. */
|
|
@@ -90,7 +71,7 @@ Usage: pramen <command>
|
|
|
90
71
|
init [dir] scaffold a new project (app.ts + worker.ts + oblaka.ts)
|
|
91
72
|
schema sql print CREATE TABLE statements for the schema
|
|
92
73
|
schema hash print the schema hash
|
|
93
|
-
schema snapshot save the schema
|
|
74
|
+
schema snapshot save the schema fingerprint to .pramen/schema.json
|
|
94
75
|
schema diff compare the schema to the snapshot (safe vs unsafe)
|
|
95
76
|
schema status compare a deployed tenant's schema to the local schema
|
|
96
77
|
[--tenant t] [--url u] [--token jwt]
|
|
@@ -113,20 +94,23 @@ async function schemaCmd(sub) {
|
|
|
113
94
|
if (sub === "snapshot") {
|
|
114
95
|
const { schema } = await loadApp();
|
|
115
96
|
mkdirSync(dirname(snapshotPath), { recursive: true });
|
|
116
|
-
const snap = { hash: schemaHash(schema),
|
|
97
|
+
const snap = { hash: schemaHash(schema), fingerprint: schemaFingerprint(schema) };
|
|
117
98
|
writeFileSync(snapshotPath, JSON.stringify(snap, null, 2) + "\n");
|
|
118
|
-
console.log(`wrote ${snapshotPath} (${Object.keys(snap.
|
|
99
|
+
console.log(`wrote ${snapshotPath} (${Object.keys(snap.fingerprint).length} tables)`);
|
|
119
100
|
return;
|
|
120
101
|
}
|
|
121
102
|
if (sub === "diff") {
|
|
122
103
|
const { schema } = await loadApp();
|
|
123
|
-
const next =
|
|
104
|
+
const next = schemaFingerprint(schema);
|
|
124
105
|
if (!existsSync(snapshotPath)) {
|
|
125
106
|
console.log("no snapshot — run `pramen schema snapshot` to set a baseline.");
|
|
126
107
|
return;
|
|
127
108
|
}
|
|
128
|
-
|
|
129
|
-
|
|
109
|
+
// `fingerprint` was called `shape` before; keep reading an existing snapshot so an
|
|
110
|
+
// upgrade doesn't force a re-baseline.
|
|
111
|
+
const snap = JSON.parse(readFileSync(snapshotPath, "utf8"));
|
|
112
|
+
const prev = snap.fingerprint ?? snap["shape"] ?? {};
|
|
113
|
+
const changes = diffSchemaFingerprint(prev, next);
|
|
130
114
|
if (changes.length === 0) {
|
|
131
115
|
console.log("no changes since snapshot.");
|
|
132
116
|
return;
|
|
@@ -181,7 +165,7 @@ async function schemaCmd(sub) {
|
|
|
181
165
|
console.log(`live: ${live.hash ?? "(none)"}`);
|
|
182
166
|
console.log(`current: ${current}`);
|
|
183
167
|
console.log(upToDate ? "✓ up to date" : "⚠ BEHIND — migrates on the tenant's next boot");
|
|
184
|
-
const want =
|
|
168
|
+
const want = schemaFingerprint(subset);
|
|
185
169
|
for (const table of Object.keys(want)) {
|
|
186
170
|
const liveCols = new Set(live.tables[table] ?? []);
|
|
187
171
|
const missing = Object.keys(want[table].columns).filter((col) => !liveCols.has(col));
|
|
@@ -202,7 +186,10 @@ async function tokenCmd(args) {
|
|
|
202
186
|
fail("token: <sub> required");
|
|
203
187
|
const roles = pos.slice(1);
|
|
204
188
|
const tenants = flag("tenant")?.split(",");
|
|
205
|
-
|
|
189
|
+
const claims = { sub, roles: roles.length ? roles : ["admin"] };
|
|
190
|
+
if (tenants)
|
|
191
|
+
claims.tenants = tenants;
|
|
192
|
+
console.log(await sign(claims));
|
|
206
193
|
}
|
|
207
194
|
function initCmd(args) {
|
|
208
195
|
const dir = resolve(process.cwd(), positionals(args)[0] ?? ".");
|
package/dist/dev.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { signDevToken, DEV_SECRET } from "./runtime/dev-token";
|
package/dist/dev.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// `@pramen/server/dev` — tooling helpers, deliberately NOT on the authoring entry point.
|
|
2
|
+
//
|
|
3
|
+
// `signDevToken` mints an HS256 JWT for local development: the `pramen` and `pramen-cms`
|
|
4
|
+
// bins and the repo's test helper all use it. It is kept off `@pramen/server` because its
|
|
5
|
+
// secret lookup reads `process.env.AUTH_SECRET`, which does not exist in a deployed
|
|
6
|
+
// Worker (bindings and secrets arrive on `env`) — so an app handler importing it from the
|
|
7
|
+
// main entry would silently sign with the PUBLISHED dev constant while appearing to honour
|
|
8
|
+
// AUTH_SECRET. A separate subpath makes "this is dev tooling" part of the import.
|
|
9
|
+
export { signDevToken, DEV_SECRET } from "./runtime/dev-token";
|
package/dist/durable-object.d.ts
CHANGED
package/dist/durable-object.js
CHANGED
|
@@ -164,9 +164,10 @@ export class PramenDOBase extends DurableObject {
|
|
|
164
164
|
return new Response(null, { status: 101, webSocket: client });
|
|
165
165
|
}
|
|
166
166
|
const name = new URL(request.url).pathname.replace(/^\/rpc\//, "");
|
|
167
|
-
|
|
167
|
+
// The RPC body is JSON — parse it into the domain type once, here at the boundary.
|
|
168
|
+
let input = null;
|
|
168
169
|
if (request.method === "POST") {
|
|
169
|
-
input = await request.json().catch(() =>
|
|
170
|
+
input = ((await request.json().catch(() => null)) ?? null);
|
|
170
171
|
}
|
|
171
172
|
try {
|
|
172
173
|
const { result, kind, touched, enqueued } = await dispatch(this.app.handlers, this.app.schema, this.driver, this.kv, this.filesFor(this.tenant), this.envBag, this.ctxFor(identity), name, input);
|
|
@@ -202,6 +203,8 @@ export class PramenDOBase extends DurableObject {
|
|
|
202
203
|
files: this.filesFor(this.tenant),
|
|
203
204
|
env: this.envBag,
|
|
204
205
|
identity,
|
|
206
|
+
tenant: this.tenant,
|
|
207
|
+
store: "do",
|
|
205
208
|
tasks: tasksFacade(this.driver),
|
|
206
209
|
mail: createMail(this.envBag, this.kv),
|
|
207
210
|
queue: createQueue(this.envBag),
|
|
@@ -291,12 +294,12 @@ export class PramenDOBase extends DurableObject {
|
|
|
291
294
|
await this.ensureMigrated();
|
|
292
295
|
switch (msg.type) {
|
|
293
296
|
case "subscribe":
|
|
294
|
-
return this.onSubscribe(ws, msg.id, msg.name, msg.input);
|
|
297
|
+
return this.onSubscribe(ws, msg.id, msg.name, msg.input ?? null);
|
|
295
298
|
case "unsubscribe":
|
|
296
299
|
this.setSubs(ws, this.getSubs(ws).filter((s) => s.id !== msg.id));
|
|
297
300
|
return;
|
|
298
301
|
case "call":
|
|
299
|
-
return this.onCall(ws, msg.id, msg.name, msg.input);
|
|
302
|
+
return this.onCall(ws, msg.id, msg.name, msg.input ?? null);
|
|
300
303
|
default:
|
|
301
304
|
return this.send(ws, { type: "error", id: "", error: "unknown message type" });
|
|
302
305
|
}
|
|
@@ -532,12 +535,16 @@ export class PramenDOBase extends DurableObject {
|
|
|
532
535
|
// Carry the schema so any consumer of this context (not just Db) can compile
|
|
533
536
|
// relation-aware `where` rules into subqueries, and the active partition so Db's
|
|
534
537
|
// table-access guard rejects any table outside this DO's partition.
|
|
535
|
-
return { acl: this.acl, identity, schema: this.app.schema, partition };
|
|
538
|
+
return { acl: this.acl, identity, schema: this.app.schema, partition, tenant: this.tenant };
|
|
536
539
|
}
|
|
537
540
|
// The DO env (bindings + vars + secrets) handed to handlers as ctx.env. Loosely
|
|
538
541
|
// typed at the boundary so handlers can read any var/secret without a DoEnv cast.
|
|
542
|
+
widenedEnv = null;
|
|
539
543
|
get envBag() {
|
|
540
|
-
|
|
544
|
+
// `this.env` is fixed for the DO's lifetime, so widen it once. This getter is read
|
|
545
|
+
// inside the per-subscription live-query loop, where a copy per read would allocate
|
|
546
|
+
// one whole binding bag per subscription on every write.
|
|
547
|
+
return (this.widenedEnv ??= { ...this.env });
|
|
541
548
|
}
|
|
542
549
|
// One Files facade per DO (a DO serves one tenant). Backed by the R2 binding;
|
|
543
550
|
// signing uses FILES_SECRET. Handlers mint signed urls; the bytes never enter here.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, hidden, defaultTo, primaryKey, generated, expr, ExprDefault, trigger } from "./sdk/schema";
|
|
1
|
+
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, hidden, defaultTo, primaryKey, generated, expr, ExprDefault, trigger, partitionOf, DEFAULT_PARTITION } from "./sdk/schema";
|
|
2
2
|
export type { TriggerDef, TriggerOp } from "./sdk/schema";
|
|
3
3
|
export { isValidUuid } from "./sdk/uuid";
|
|
4
4
|
export type { DefaultValue, FieldType, FieldDef, EntityFields, EntityDef, SchemaDef, RelationDef, RelationDefs, BelongsToDef, HasManyDef, ManyToManyDef, OneHasOneDef, OneHasOneInverseDef, OnDelete, } from "./sdk/schema";
|
|
5
5
|
export { createApp } from "./sdk/app";
|
|
6
6
|
export { query, mutation, authorizeHandler } from "./sdk/handlers";
|
|
7
|
-
export type { Handler, HandlerContext, HandlerKind, HandlerMap, HandlerOpts, HandlerAuth, Tasks, TaskHandler, AppTaskMap, BootstrapContext, BootstrapFn } from "./sdk/handlers";
|
|
7
|
+
export type { EnvBag, Handler, HandlerContext, HandlerKind, HandlerMap, HandlerOpts, HandlerAuth, Tasks, TaskHandler, AppTaskMap, BootstrapContext, BootstrapFn } from "./sdk/handlers";
|
|
8
8
|
export { $identity, $input, $now, allow, deny, policy, resolve, role, isAllow, isDeny, isResolver, isIdentityMarker, isInputMarker, isNowMarker } from "./sdk/acl";
|
|
9
9
|
export type { Action, Identity, IdentityMarker, InputMarker, NowMarker, Policy, PolicyRule, PolicyRules, Role, Validator, WhereRule, ConditionalFields, FieldsFn, RelationAclRule, SetValue, ResolverFn, ResolverContext, ResolverDb, } from "./sdk/acl";
|
|
10
|
-
export type { Cell, FieldsOf, InferInsert, InferRow, InferUpdate, JsonValue, ProjectedRow, RelationsOf, RelationsResult, WhereClause, WhereInput, WhereOps, } from "./sdk/infer";
|
|
10
|
+
export type { Cell, FieldsOf, InferInsert, InferRow, InferUpdate, JsonValue, JsonObject, SqlValue, CellValue, Row, ProjectedRow, RelationsOf, RelationsResult, WhereClause, WhereInput, WhereOps, } from "./sdk/infer";
|
|
11
11
|
export { Kv, denySession, allowSession, isSessionDenied } from "./runtime/kv";
|
|
12
12
|
export type { FileRef, Files, SignUploadOpts, SignDownloadOpts, HeadResult } from "./sdk/files";
|
|
13
13
|
export { R2Adapter, MemoryAdapter, createFiles, handleFileRequest } from "./runtime/storage";
|
|
14
|
+
export { signToken, verifyToken, isUsableSecret, resolveSecret, MIN_TOKEN_SECRET_LEN } from "./runtime/token";
|
|
15
|
+
export type { ExpiringToken } from "./runtime/token";
|
|
14
16
|
export type { StorageAdapter, PutResult, GetResult } from "./runtime/storage";
|
|
15
17
|
export { Mail, CloudflareEmailAdapter, KvMailAdapter, MemoryMailAdapter, UnconfiguredMailAdapter, createMail } from "./runtime/mail";
|
|
16
18
|
export type { MailMessage, MailAddress, MailAdapter, SendEmailBinding } from "./runtime/mail";
|
|
@@ -18,6 +20,6 @@ export { Queue, CloudflareQueueAdapter, MemoryQueueAdapter, createQueue, discove
|
|
|
18
20
|
export type { QueueAdapter, QueueProducerBinding, QueueSendOptions, QueueSendRequest, QueueBatchOptions, QueueContentType } from "./runtime/queue";
|
|
19
21
|
export { routeQueue, dispatchQueueBatch } from "./runtime/queue-consumer";
|
|
20
22
|
export type { QueueContext, QueueHandler, QueueMessage, QueueBatch, AppQueueMap } from "./runtime/queue-consumer";
|
|
21
|
-
export { PramenError, BadRequest, Unauthorized, Forbidden } from "./runtime/errors";
|
|
23
|
+
export { PramenError, BadRequest, Unauthorized, Forbidden, Conflict } from "./runtime/errors";
|
|
22
24
|
export { sqliteDialect, postgresDialect, DoSqliteDriver, D1Driver } from "./runtime/driver";
|
|
23
|
-
export type { Driver, Dialect,
|
|
25
|
+
export type { Driver, Dialect, DriverRow } from "./runtime/driver";
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// which only exists in the Workers runtime; keeping it separate lets the CLI, tests,
|
|
8
8
|
// and codegen load an app.ts for its schema without dragging in the DO runtime.
|
|
9
9
|
// --- schema authoring ---
|
|
10
|
-
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, hidden, defaultTo, primaryKey, generated, expr, ExprDefault, trigger } from "./sdk/schema";
|
|
10
|
+
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, hidden, defaultTo, primaryKey, generated, expr, ExprDefault, trigger, partitionOf, DEFAULT_PARTITION } from "./sdk/schema";
|
|
11
11
|
export { isValidUuid } from "./sdk/uuid";
|
|
12
12
|
// --- app + handlers ---
|
|
13
13
|
export { createApp } from "./sdk/app";
|
|
@@ -17,12 +17,16 @@ export { $identity, $input, $now, allow, deny, policy, resolve, role, isAllow, i
|
|
|
17
17
|
// --- kv (ctx.kv) + session denylist (hard token revocation) ---
|
|
18
18
|
export { Kv, denySession, allowSession, isSessionDenied } from "./runtime/kv";
|
|
19
19
|
export { R2Adapter, MemoryAdapter, createFiles, handleFileRequest } from "./runtime/storage";
|
|
20
|
+
// Signed capability tokens (HMAC-SHA256) — the machinery behind signed file urls and
|
|
21
|
+
// page-preview links. Exported so an app (or @pramen/cms) can mint its own capability url
|
|
22
|
+
// without a second signing implementation.
|
|
23
|
+
export { signToken, verifyToken, isUsableSecret, resolveSecret, MIN_TOKEN_SECRET_LEN } from "./runtime/token";
|
|
20
24
|
// --- mail (ctx.mail) ---
|
|
21
25
|
export { Mail, CloudflareEmailAdapter, KvMailAdapter, MemoryMailAdapter, UnconfiguredMailAdapter, createMail } from "./runtime/mail";
|
|
22
26
|
// --- queue (ctx.queue — Cloudflare Queues) ---
|
|
23
27
|
export { Queue, CloudflareQueueAdapter, MemoryQueueAdapter, createQueue, discoverQueueBindings } from "./runtime/queue";
|
|
24
28
|
export { routeQueue, dispatchQueueBatch } from "./runtime/queue-consumer";
|
|
25
29
|
// --- errors ---
|
|
26
|
-
export { PramenError, BadRequest, Unauthorized, Forbidden } from "./runtime/errors";
|
|
30
|
+
export { PramenError, BadRequest, Unauthorized, Forbidden, Conflict } from "./runtime/errors";
|
|
27
31
|
// --- substrate seam (advanced: bring your own SQL backend) ---
|
|
28
32
|
export { sqliteDialect, postgresDialect, DoSqliteDriver, D1Driver } from "./runtime/driver";
|
package/dist/pramen.d.ts
CHANGED
|
@@ -4,13 +4,15 @@ import { type SchemaDef } from "./sdk/schema";
|
|
|
4
4
|
import type { AppTaskMap, HandlerMap, BootstrapFn } from "./sdk/handlers";
|
|
5
5
|
import type { AppQueueMap, QueueBatch } from "./runtime/queue-consumer";
|
|
6
6
|
import type { Role } from "./sdk/acl";
|
|
7
|
+
import type { EnvBag } from "./sdk/handlers";
|
|
8
|
+
import type { JsonValue } from "./sdk/infer";
|
|
7
9
|
/** Injected into a public route's handler — forward a privileged mutation into the
|
|
8
10
|
* tenant's DO without the handler importing any deploy-side code (so app.ts stays
|
|
9
11
|
* authoring-only). The synthetic identity defaults to the admin role. */
|
|
10
12
|
export interface RouteContext {
|
|
11
13
|
callPrivileged(opts: {
|
|
12
14
|
name: string;
|
|
13
|
-
input?:
|
|
15
|
+
input?: JsonValue;
|
|
14
16
|
tenant?: string;
|
|
15
17
|
roles?: string[];
|
|
16
18
|
}): Promise<Response>;
|
|
@@ -25,7 +27,7 @@ export interface PublicRoute {
|
|
|
25
27
|
method: string;
|
|
26
28
|
/** Exact pathname to match (e.g. "/stripe/webhook"). */
|
|
27
29
|
path: string;
|
|
28
|
-
handler: (request: Request, env:
|
|
30
|
+
handler: (request: Request, env: EnvBag, ctx: RouteContext) => Response | Promise<Response>;
|
|
29
31
|
}
|
|
30
32
|
/** The user-facing app: a schema, the handler map, ACL roles, and optional public
|
|
31
33
|
* (pre-auth) routes. `example/app.ts` exports this shape. */
|
package/dist/runtime/acl.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { type Action, type FieldsFn, type Identity, type PolicyRule, type ResolverDb, type Role, type Validator } from "../sdk/acl";
|
|
1
|
+
import { type Action, type FieldsFn, type Identity, type PolicyRule, type ResolverDb, type Role, type Validator, type WhereRule } from "../sdk/acl";
|
|
2
2
|
import { type SqlExpr } from "./read-engine";
|
|
3
3
|
import { PramenError } from "./errors";
|
|
4
|
+
import type { Row } from "../sdk/infer";
|
|
4
5
|
import type { SchemaDef } from "../sdk/schema";
|
|
5
6
|
export declare class AclDenied extends PramenError {
|
|
6
7
|
readonly entity: string;
|
|
@@ -26,6 +27,14 @@ export interface AclContext {
|
|
|
26
27
|
/** The app schema — lets `where` rules traverse relations (`{ rel: { col } }`),
|
|
27
28
|
* compiled to a subquery with the related entity's read scope AND-merged in. */
|
|
28
29
|
readonly schema?: SchemaDef;
|
|
30
|
+
/** The tenant this request is for — the `x-pramen-tenant` value the DO was addressed
|
|
31
|
+
* with. Carried so a handler can mint a tenant-scoped capability (e.g. a signed page
|
|
32
|
+
* preview link) without the caller supplying, and thus being able to forge, a tenant. */
|
|
33
|
+
readonly tenant?: string;
|
|
34
|
+
/** Which substrate is serving this request — `"do"` (a Durable Object, the default) or
|
|
35
|
+
* `"d1"` (the shared D1 database, selected per-request with `x-pramen-store: d1`). A
|
|
36
|
+
* handler needs this when a capability it mints can only be redeemed on one of them. */
|
|
37
|
+
readonly store?: "do" | "d1";
|
|
29
38
|
/** The partition this DO serves. When set, Db rejects any access to a table that
|
|
30
39
|
* lives in a different partition (a partition-DO only owns its own tables). Unset
|
|
31
40
|
* (e.g. the D1/Worker shared-store path) disables the guard — a no-op. */
|
|
@@ -78,15 +87,15 @@ export declare const MAX_REL_DEPTH = 5;
|
|
|
78
87
|
* `allowRelations` is false for single-table contexts (cell-level `when`, which is
|
|
79
88
|
* evaluated in memory and cannot do a SQL round-trip): a relation key then raises a
|
|
80
89
|
* clear authoring error instead of emitting a `sub` node that throws at read time. */
|
|
81
|
-
export declare function compileScopedWhere(rule:
|
|
90
|
+
export declare function compileScopedWhere(rule: WhereRule, entity: string, ctx: AclContext, depth?: number, allowRelations?: boolean): SqlExpr;
|
|
82
91
|
export declare function resolveScope(ctx: AclContext, entity: string, action: Action, depth?: number): Scope;
|
|
83
92
|
/** Effective visible fields for one row = base ∪ matching-conditional ∪ fn-output.
|
|
84
93
|
* Returns null (all fields) when the base is null or a resolver grants everything. */
|
|
85
|
-
export declare function effectiveFields(scope: Scope, row:
|
|
94
|
+
export declare function effectiveFields(scope: Scope, row: Row, identity: Identity | null): string[] | null;
|
|
86
95
|
/** Forced values + validators for a write, gathered from matched write policies.
|
|
87
96
|
* `set` values are resolved against the identity; later policies override earlier. */
|
|
88
97
|
export interface WriteRules {
|
|
89
|
-
set:
|
|
98
|
+
set: Row;
|
|
90
99
|
validators: Validator[];
|
|
91
100
|
}
|
|
92
101
|
export declare function resolveWriteRules(ctx: AclContext, entity: string, action: Action): WriteRules;
|
|
@@ -94,4 +103,4 @@ export declare function resolveWriteRules(ctx: AclContext, entity: string, actio
|
|
|
94
103
|
* target's own read scope OR a parent read policy's relation rule with directAccess. */
|
|
95
104
|
export declare function resolveRelationScope(ctx: AclContext, parentEntity: string, relName: string, target: string): Scope;
|
|
96
105
|
/** Project a row to the permitted fields. null = all. */
|
|
97
|
-
export declare function projectRow(row:
|
|
106
|
+
export declare function projectRow(row: Row, fields: string[] | null): Row;
|
package/dist/runtime/acl.js
CHANGED
|
@@ -85,10 +85,6 @@ function getPath(obj, path) {
|
|
|
85
85
|
return path.split(".").reduce((acc, seg) => (acc == null ? undefined : acc[seg]), obj ?? undefined);
|
|
86
86
|
}
|
|
87
87
|
const UNRESOLVED = Symbol("unresolved");
|
|
88
|
-
// Resolve a value that may be an $identity marker (against the caller), an
|
|
89
|
-
// $input marker (against the request input — a capability/by-key grant), or a
|
|
90
|
-
// $now marker (the evaluation instant). An unresolvable marker yields UNRESOLVED,
|
|
91
|
-
// which makes its rule match nothing. $now always resolves.
|
|
92
88
|
function resolveValue(v, identity, input) {
|
|
93
89
|
if (isNowMarker(v))
|
|
94
90
|
return new Date().toISOString();
|
|
@@ -180,7 +176,7 @@ function pkOf(schema, entity) {
|
|
|
180
176
|
* are skipped: they're re-scoped against THEIR own target's read scope downstream.
|
|
181
177
|
* Mirrors Db.assertReadableWhere's recursion for the top-level user `where`. */
|
|
182
178
|
function assertReadableRelationWhere(where, target, fields, ctx) {
|
|
183
|
-
const targetRels =
|
|
179
|
+
const targetRels = ctx.schema?.[target]?.relations ?? {};
|
|
184
180
|
for (const [k, v] of Object.entries(where)) {
|
|
185
181
|
if (k === "AND" || k === "OR") {
|
|
186
182
|
for (const g of v)
|
package/dist/runtime/db.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type AclContext } from "./acl";
|
|
2
|
+
import type { CellValue, Row as SharedRow } from "../sdk/infer";
|
|
2
3
|
import { type AggFn } from "./read-engine";
|
|
3
4
|
import type { Driver } from "./driver";
|
|
4
5
|
import { type EntityFields, type SchemaDef } from "../sdk/schema";
|
|
5
6
|
import type { Cell, FieldsOf, InferInsert, InferRow, InferUpdate, RelationsOf, RelationsResult, WhereClause } from "../sdk/infer";
|
|
6
|
-
type Row =
|
|
7
|
+
type Row = SharedRow;
|
|
7
8
|
type Id = string | number | bigint;
|
|
8
9
|
type OrderSpec<S extends SchemaDef, T extends keyof S> = {
|
|
9
10
|
column: keyof FieldsOf<S[T]> & string;
|
|
@@ -215,7 +216,7 @@ export declare class Db<S extends SchemaDef = SchemaDef> {
|
|
|
215
216
|
/** Delete a row by id within scope. Returns whether a row was deleted. */
|
|
216
217
|
delete<T extends keyof S & string>(table: T, id: Id): Promise<boolean>;
|
|
217
218
|
/** Escape hatch — raw SQL, NOT ACL-checked. For system/internal use only. */
|
|
218
|
-
exec(sql: string, ...params:
|
|
219
|
+
exec(sql: string, ...params: CellValue[]): Promise<Row[]>;
|
|
219
220
|
private returningClause;
|
|
220
221
|
private scopeClause;
|
|
221
222
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** The scaffolded oblaka.ts dev secret. Used only when AUTH_SECRET is unset. */
|
|
2
|
+
export declare const DEV_SECRET = "dev-secret-change-me";
|
|
3
|
+
/** Sign a dev JWT (1h). Secret: the `secret` argument, else `AUTH_SECRET`, else DEV_SECRET. */
|
|
4
|
+
export declare function signDevToken(payload: Record<string, unknown>, secret?: string): Promise<string>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Mint an HS256 JWT for local development and tooling — what a real auth service would
|
|
2
|
+
// issue, without running one.
|
|
3
|
+
//
|
|
4
|
+
// Extracted because it was already written twice (the `pramen` CLI and the repo's test
|
|
5
|
+
// helper) and `@pramen/cms`'s own bin would have been a third. One implementation, in the
|
|
6
|
+
// package that owns tokens.
|
|
7
|
+
//
|
|
8
|
+
// NOT an auth system: the dev fallback secret is public, so a token signed with it is
|
|
9
|
+
// worthless anywhere `AUTH_SECRET` is set to something real. That is the point — it makes
|
|
10
|
+
// a forgotten secret fail loudly rather than quietly accept dev tokens in production.
|
|
11
|
+
/** The scaffolded oblaka.ts dev secret. Used only when AUTH_SECRET is unset. */
|
|
12
|
+
export const DEV_SECRET = "dev-secret-change-me";
|
|
13
|
+
function bytesToB64url(bytes) {
|
|
14
|
+
let bin = "";
|
|
15
|
+
for (const b of bytes)
|
|
16
|
+
bin += String.fromCharCode(b);
|
|
17
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
18
|
+
}
|
|
19
|
+
const strToB64url = (s) => bytesToB64url(new TextEncoder().encode(s));
|
|
20
|
+
/** Sign a dev JWT (1h). Secret: the `secret` argument, else `AUTH_SECRET`, else DEV_SECRET. */
|
|
21
|
+
export async function signDevToken(payload, secret) {
|
|
22
|
+
// `||`, not `??`: an AUTH_SECRET set to the empty string previously fell through to
|
|
23
|
+
// DEV_SECRET, and signing with an empty HMAC key instead would produce tokens the server
|
|
24
|
+
// rejects with no useful message.
|
|
25
|
+
const env = globalThis.process?.env;
|
|
26
|
+
const key = secret || env?.AUTH_SECRET || DEV_SECRET;
|
|
27
|
+
const now = Math.floor(Date.now() / 1000);
|
|
28
|
+
const header = strToB64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
|
|
29
|
+
const body = strToB64url(JSON.stringify({ iat: now, exp: now + 3600, ...payload }));
|
|
30
|
+
const data = `${header}.${body}`;
|
|
31
|
+
const cryptoKey = await crypto.subtle.importKey("raw", new TextEncoder().encode(key), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
32
|
+
const sig = await crypto.subtle.sign("HMAC", cryptoKey, new TextEncoder().encode(data));
|
|
33
|
+
return `${data}.${bytesToB64url(new Uint8Array(sig))}`;
|
|
34
|
+
}
|
|
@@ -5,6 +5,8 @@ import type { Kv } from "./kv";
|
|
|
5
5
|
import type { Files } from "../sdk/files";
|
|
6
6
|
import type { SchemaDef } from "../sdk/schema";
|
|
7
7
|
import { type AppTaskMap, type HandlerContext, type HandlerKind, type HandlerMap, type Tasks } from "../sdk/handlers";
|
|
8
|
+
import type { EnvBag } from "../sdk/handlers";
|
|
9
|
+
import type { JsonValue } from "../sdk/infer";
|
|
8
10
|
export interface DispatchResult {
|
|
9
11
|
readonly result: unknown;
|
|
10
12
|
readonly kind: HandlerKind;
|
|
@@ -17,4 +19,4 @@ export interface DispatchResult {
|
|
|
17
19
|
export declare function tasksFacade(driver: Driver, onEnqueue?: () => void): Tasks;
|
|
18
20
|
/** Bind `app.tasks` (which take a ctx) into the ctx-free `TaskMap` the drainer calls. */
|
|
19
21
|
export declare function bindTasks(appTasks: AppTaskMap | undefined, ctx: HandlerContext): TaskMap;
|
|
20
|
-
export declare function dispatch(handlers: HandlerMap, schema: SchemaDef, driver: Driver, kv: Kv, files: Files, env:
|
|
22
|
+
export declare function dispatch(handlers: HandlerMap, schema: SchemaDef, driver: Driver, kv: Kv, files: Files, env: EnvBag, acl: AclContext, name: string, input: JsonValue): Promise<DispatchResult>;
|
package/dist/runtime/dispatch.js
CHANGED
|
@@ -62,6 +62,8 @@ export async function dispatch(handlers, schema, driver, kv, files, env, acl, na
|
|
|
62
62
|
files,
|
|
63
63
|
env,
|
|
64
64
|
identity: acl.identity,
|
|
65
|
+
tenant: acl.tenant ?? "main",
|
|
66
|
+
store: acl.store ?? "do",
|
|
65
67
|
tasks: tasksFacade(driver, () => enqueued++),
|
|
66
68
|
mail: createMail(env, kv),
|
|
67
69
|
queue: createQueue(env),
|
package/dist/runtime/driver.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
/** A raw row exactly as the substrate returns it, before pramen's object↔JSON codec.
|
|
2
|
+
* Distinct from the decoded `Row` handlers see at the `Db` chokepoint. */
|
|
3
|
+
export type DriverRow = Record<string, SqlValue>;
|
|
4
|
+
import type { CellValue, SqlValue } from "../sdk/infer";
|
|
2
5
|
export interface Dialect {
|
|
3
6
|
/** Render an identifier (table/column), quoting as the backend requires. */
|
|
4
7
|
id(name: string): string;
|
|
@@ -7,7 +10,7 @@ export interface Dialect {
|
|
|
7
10
|
/** Whether INSERT/UPDATE/DELETE ... RETURNING is supported (SQLite/Postgres yes; MySQL no). */
|
|
8
11
|
readonly returning: boolean;
|
|
9
12
|
/** Coerce a JS value for binding (e.g. boolean → 0/1 on SQLite). */
|
|
10
|
-
encode(v:
|
|
13
|
+
encode(v: CellValue): CellValue;
|
|
11
14
|
}
|
|
12
15
|
/** Render an identifier as a standard double-quoted name (`"order"`), guarding its
|
|
13
16
|
* shape first. SQLite (DO SQLite + D1) and Postgres all accept double-quoted
|
|
@@ -27,7 +30,7 @@ export interface Driver {
|
|
|
27
30
|
readonly dialect: Dialect;
|
|
28
31
|
/** Run a parameterized statement and return the result rows (empty for writes
|
|
29
32
|
* without RETURNING). Params are already dialect-encoded by the caller. */
|
|
30
|
-
exec(sql: string, params:
|
|
33
|
+
exec(sql: string, params: CellValue[]): Promise<DriverRow[]>;
|
|
31
34
|
/** Run `fn` inside a transaction: commit on resolve, roll back on throw. */
|
|
32
35
|
transaction<T>(fn: () => Promise<T>): Promise<T>;
|
|
33
36
|
/** Run a fixed sequence of write statements ATOMICALLY with FK checks deferred to the
|
|
@@ -46,7 +49,7 @@ export declare class DoSqliteDriver implements Driver {
|
|
|
46
49
|
private readonly storage;
|
|
47
50
|
readonly dialect: Dialect;
|
|
48
51
|
constructor(storage: DurableObjectStorage);
|
|
49
|
-
exec(sql: string, params:
|
|
52
|
+
exec(sql: string, params: CellValue[]): Promise<DriverRow[]>;
|
|
50
53
|
transaction<T>(fn: () => Promise<T>): Promise<T>;
|
|
51
54
|
}
|
|
52
55
|
/** How a D1Driver's session is anchored (passed to `db.withSession`):
|
|
@@ -79,7 +82,7 @@ export declare class D1Driver implements Driver {
|
|
|
79
82
|
constructor(db: D1Database, opts?: {
|
|
80
83
|
start?: D1SessionStart;
|
|
81
84
|
});
|
|
82
|
-
exec(sql: string, params:
|
|
85
|
+
exec(sql: string, params: CellValue[]): Promise<DriverRow[]>;
|
|
83
86
|
/** The session's latest bookmark (null before any query). Threaded back to the client
|
|
84
87
|
* via the `x-pramen-d1-bookmark` response header so a subsequent request can anchor a
|
|
85
88
|
* fresh session at it and read its own writes. */
|
package/dist/runtime/errors.d.ts
CHANGED
|
@@ -15,6 +15,12 @@ export declare class Unauthorized extends PramenError {
|
|
|
15
15
|
export declare class Forbidden extends PramenError {
|
|
16
16
|
constructor(message?: string);
|
|
17
17
|
}
|
|
18
|
+
/** 409 — the request conflicts with the current state of the resource. For
|
|
19
|
+
* optimistic concurrency (a stale `expectedVersion`) and for uniqueness clashes the
|
|
20
|
+
* caller could resolve by retrying with different input. */
|
|
21
|
+
export declare class Conflict extends PramenError {
|
|
22
|
+
constructor(message?: string);
|
|
23
|
+
}
|
|
18
24
|
export interface ErrorBody {
|
|
19
25
|
ok: false;
|
|
20
26
|
error: string;
|
package/dist/runtime/errors.js
CHANGED
|
@@ -29,6 +29,14 @@ export class Forbidden extends PramenError {
|
|
|
29
29
|
super(message, 403, "forbidden");
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
/** 409 — the request conflicts with the current state of the resource. For
|
|
33
|
+
* optimistic concurrency (a stale `expectedVersion`) and for uniqueness clashes the
|
|
34
|
+
* caller could resolve by retrying with different input. */
|
|
35
|
+
export class Conflict extends PramenError {
|
|
36
|
+
constructor(message = "conflict") {
|
|
37
|
+
super(message, 409, "conflict");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
32
40
|
function classify(err) {
|
|
33
41
|
if (err instanceof PramenError) {
|
|
34
42
|
return { status: err.status, body: { ok: false, error: err.message, code: err.code } };
|
package/dist/runtime/mail.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Kv } from "./kv";
|
|
2
|
+
import type { EnvBag } from "../sdk/handlers";
|
|
2
3
|
export interface MailAddress {
|
|
3
4
|
email: string;
|
|
4
5
|
name?: string;
|
|
@@ -77,4 +78,4 @@ export declare class UnconfiguredMailAdapter implements MailAdapter {
|
|
|
77
78
|
* a synthetic dev sender — an EXPLICIT dev opt-in, never the production default.
|
|
78
79
|
* - else → fail closed: a `send` throws (so a missing-MAIL_FROM prod doesn't silently
|
|
79
80
|
* stash security emails in KV). */
|
|
80
|
-
export declare function createMail(env:
|
|
81
|
+
export declare function createMail(env: EnvBag, kv?: Kv): Mail;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { JsonValue } from "../sdk/infer";
|
|
1
2
|
export interface SubscribeMsg {
|
|
2
3
|
type: "subscribe";
|
|
3
4
|
id: string;
|
|
4
5
|
name: string;
|
|
5
|
-
input?:
|
|
6
|
+
input?: JsonValue;
|
|
6
7
|
}
|
|
7
8
|
export interface UnsubscribeMsg {
|
|
8
9
|
type: "unsubscribe";
|
|
@@ -12,7 +13,7 @@ export interface CallMsg {
|
|
|
12
13
|
type: "call";
|
|
13
14
|
id: string;
|
|
14
15
|
name: string;
|
|
15
|
-
input?:
|
|
16
|
+
input?: JsonValue;
|
|
16
17
|
}
|
|
17
18
|
export type ClientMsg = SubscribeMsg | UnsubscribeMsg | CallMsg;
|
|
18
19
|
export type ServerMsg = {
|
|
@@ -32,7 +33,7 @@ export type ServerMsg = {
|
|
|
32
33
|
export interface Subscription {
|
|
33
34
|
id: string;
|
|
34
35
|
name: string;
|
|
35
|
-
input:
|
|
36
|
+
input: JsonValue;
|
|
36
37
|
/** Tables the query read — the coarse prefilter for which writes might matter. */
|
|
37
38
|
tables: string[];
|
|
38
39
|
/** Digest of the last result pushed — used to suppress no-op (row-level) pushes. */
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Mail } from "./mail";
|
|
2
2
|
import type { Queue } from "./queue";
|
|
3
3
|
import type { Kv } from "./kv";
|
|
4
|
+
import type { EnvBag } from "../sdk/handlers";
|
|
5
|
+
import type { JsonValue } from "../sdk/infer";
|
|
4
6
|
/** One received message (the Cloudflare Queues `Message` shape). */
|
|
5
7
|
export interface QueueMessage<Body = unknown> {
|
|
6
8
|
readonly id: string;
|
|
@@ -30,7 +32,7 @@ export interface QueueBatch<Body = unknown> {
|
|
|
30
32
|
* tenant data via `ctx.callPrivileged`. */
|
|
31
33
|
export interface QueueContext {
|
|
32
34
|
/** The Worker environment (bindings + vars + secrets). */
|
|
33
|
-
readonly env:
|
|
35
|
+
readonly env: EnvBag;
|
|
34
36
|
/** Project KV (cross-tenant). */
|
|
35
37
|
readonly kv: Kv;
|
|
36
38
|
/** Send email (the notification path). */
|
|
@@ -41,7 +43,7 @@ export interface QueueContext {
|
|
|
41
43
|
* The message body should carry the `tenant`. */
|
|
42
44
|
callPrivileged(opts: {
|
|
43
45
|
name: string;
|
|
44
|
-
input?:
|
|
46
|
+
input?: JsonValue;
|
|
45
47
|
tenant?: string;
|
|
46
48
|
roles?: string[];
|
|
47
49
|
partition?: string;
|
package/dist/runtime/queue.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EnvBag } from "../sdk/handlers";
|
|
1
2
|
/** Cloudflare Queues content type for a sent message. Omitted ⇒ the platform default
|
|
2
3
|
* (v8 structured clone). Use "json" for cross-runtime / external consumers. */
|
|
3
4
|
export type QueueContentType = "text" | "bytes" | "json" | "v8";
|
|
@@ -64,9 +65,9 @@ export declare class MemoryQueueAdapter implements QueueAdapter {
|
|
|
64
65
|
/** Discover the Cloudflare Queues producer bindings in an environment: any value that
|
|
65
66
|
* exposes BOTH `send` and `sendBatch` functions (which excludes the email `send`-only
|
|
66
67
|
* binding, KV, R2, D1, the DO namespace, …). Returns name → binding. */
|
|
67
|
-
export declare function discoverQueueBindings(env:
|
|
68
|
+
export declare function discoverQueueBindings(env: EnvBag): Record<string, QueueProducerBinding>;
|
|
68
69
|
/** Build `ctx.queue` from the environment: a Cloudflare adapter over the discovered
|
|
69
70
|
* producer bindings. Sending to an undeclared queue fails closed (the adapter throws).
|
|
70
71
|
* There is no silent capture fallback — declare the `Queue` binding and it exists in
|
|
71
72
|
* dev (lopata) and miniflare too. */
|
|
72
|
-
export declare function createQueue(env:
|
|
73
|
+
export declare function createQueue(env: EnvBag): Queue;
|