orez 0.4.24 → 0.4.25
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.
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* namespace routing primitives for the Cloudflare Durable Object deploy.
|
|
3
|
+
*
|
|
4
|
+
* a multi-tenant CF/orez deploy shards data into one DO instance per tenant
|
|
5
|
+
* namespace (`ns:<scope>-<id>`) plus a control-plane `singleton`. the worker
|
|
6
|
+
* tiers carry the chosen namespace in a header (and re-stamp it so an inbound
|
|
7
|
+
* value is never trusted) and must validate its shape before routing — an
|
|
8
|
+
* unvalidated namespace would let a client mint unbounded DO instances.
|
|
9
|
+
*
|
|
10
|
+
* the deployed worker entry classes are bundled strings in the consumer's
|
|
11
|
+
* deploy integration (awkward to unit-test), so the routing decision and the
|
|
12
|
+
* security-relevant shape validation live here as pure functions, tested in
|
|
13
|
+
* cf-do-shim.test.ts. consumers import these into their worker shims instead of
|
|
14
|
+
* copy-pasting the validation regex at every routing site.
|
|
15
|
+
*/
|
|
16
|
+
export interface NamespaceRoutingOptions {
|
|
17
|
+
/** allowed namespace scope prefixes. default `['proj', 'test']`. */
|
|
18
|
+
scopes?: readonly string[];
|
|
19
|
+
/**
|
|
20
|
+
* namespace values that resolve to the control-plane `singleton` instance,
|
|
21
|
+
* in addition to the empty string (which is always the singleton).
|
|
22
|
+
*/
|
|
23
|
+
controlPlaneNamespaces?: readonly string[];
|
|
24
|
+
/** request header the worker tiers carry the namespace in. default `x-orez-ns`. */
|
|
25
|
+
nsHeader?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* true when `ns` is a structurally valid tenant namespace: a configured scope
|
|
29
|
+
* prefix followed by a 1-64 char `[A-Za-z0-9_-]` id. this is the gate that
|
|
30
|
+
* keeps a stray header from minting an unbounded DO instance, so every routing
|
|
31
|
+
* site that forwards a namespace should run it.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isValidNamespace(ns: string, opts?: NamespaceRoutingOptions): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* resolve a raw namespace string to a DO instance name:
|
|
36
|
+
* - `''` or a control-plane alias -> `'singleton'`
|
|
37
|
+
* - a valid tenant namespace -> `'ns:<ns>'`
|
|
38
|
+
* - anything else -> `null` (caller should reject the request)
|
|
39
|
+
*/
|
|
40
|
+
export declare function doInstanceName(ns: string, opts?: NamespaceRoutingOptions): string | null;
|
|
41
|
+
interface HeaderReader {
|
|
42
|
+
get(name: string): string | null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* read the namespace from a request (the configured header, falling back to the
|
|
46
|
+
* `?ns=` query param) and resolve it to a DO instance name. returns `null` for a
|
|
47
|
+
* structurally invalid namespace so the worker can reply 400 instead of routing.
|
|
48
|
+
*/
|
|
49
|
+
export declare function doInstanceNameForRequest(request: {
|
|
50
|
+
headers: HeaderReader;
|
|
51
|
+
}, url: {
|
|
52
|
+
searchParams: HeaderReader;
|
|
53
|
+
}, opts?: NamespaceRoutingOptions): string | null;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=cf-do-shim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cf-do-shim.d.ts","sourceRoot":"","sources":["../../src/worker/cf-do-shim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1C,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAWD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,EACV,IAAI,GAAE,uBAA4B,GACjC,OAAO,CAET;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,MAAM,EACV,IAAI,GAAE,uBAA4B,GACjC,MAAM,GAAG,IAAI,CAKf;AAED,UAAU,YAAY;IACpB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CACjC;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,EAClC,GAAG,EAAE;IAAE,YAAY,EAAE,YAAY,CAAA;CAAE,EACnC,IAAI,GAAE,uBAA4B,GACjC,MAAM,GAAG,IAAI,CAIf"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* namespace routing primitives for the Cloudflare Durable Object deploy.
|
|
3
|
+
*
|
|
4
|
+
* a multi-tenant CF/orez deploy shards data into one DO instance per tenant
|
|
5
|
+
* namespace (`ns:<scope>-<id>`) plus a control-plane `singleton`. the worker
|
|
6
|
+
* tiers carry the chosen namespace in a header (and re-stamp it so an inbound
|
|
7
|
+
* value is never trusted) and must validate its shape before routing — an
|
|
8
|
+
* unvalidated namespace would let a client mint unbounded DO instances.
|
|
9
|
+
*
|
|
10
|
+
* the deployed worker entry classes are bundled strings in the consumer's
|
|
11
|
+
* deploy integration (awkward to unit-test), so the routing decision and the
|
|
12
|
+
* security-relevant shape validation live here as pure functions, tested in
|
|
13
|
+
* cf-do-shim.test.ts. consumers import these into their worker shims instead of
|
|
14
|
+
* copy-pasting the validation regex at every routing site.
|
|
15
|
+
*/
|
|
16
|
+
/** default namespace scope prefixes (`proj-<id>`, `test-<id>`). */
|
|
17
|
+
const DEFAULT_SCOPES = ['proj', 'test'];
|
|
18
|
+
function escapeForRegExp(value) {
|
|
19
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
20
|
+
}
|
|
21
|
+
function namespacePattern(scopes) {
|
|
22
|
+
const group = scopes.map(escapeForRegExp).join('|');
|
|
23
|
+
return new RegExp(`^(?:${group})-[A-Za-z0-9_-]{1,64}$`);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* true when `ns` is a structurally valid tenant namespace: a configured scope
|
|
27
|
+
* prefix followed by a 1-64 char `[A-Za-z0-9_-]` id. this is the gate that
|
|
28
|
+
* keeps a stray header from minting an unbounded DO instance, so every routing
|
|
29
|
+
* site that forwards a namespace should run it.
|
|
30
|
+
*/
|
|
31
|
+
export function isValidNamespace(ns, opts = {}) {
|
|
32
|
+
return namespacePattern(opts.scopes ?? DEFAULT_SCOPES).test(ns);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* resolve a raw namespace string to a DO instance name:
|
|
36
|
+
* - `''` or a control-plane alias -> `'singleton'`
|
|
37
|
+
* - a valid tenant namespace -> `'ns:<ns>'`
|
|
38
|
+
* - anything else -> `null` (caller should reject the request)
|
|
39
|
+
*/
|
|
40
|
+
export function doInstanceName(ns, opts = {}) {
|
|
41
|
+
if (!ns)
|
|
42
|
+
return 'singleton';
|
|
43
|
+
if ((opts.controlPlaneNamespaces ?? []).includes(ns))
|
|
44
|
+
return 'singleton';
|
|
45
|
+
if (!isValidNamespace(ns, opts))
|
|
46
|
+
return null;
|
|
47
|
+
return 'ns:' + ns;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* read the namespace from a request (the configured header, falling back to the
|
|
51
|
+
* `?ns=` query param) and resolve it to a DO instance name. returns `null` for a
|
|
52
|
+
* structurally invalid namespace so the worker can reply 400 instead of routing.
|
|
53
|
+
*/
|
|
54
|
+
export function doInstanceNameForRequest(request, url, opts = {}) {
|
|
55
|
+
const ns = request.headers.get(opts.nsHeader ?? 'x-orez-ns') || url.searchParams.get('ns') || '';
|
|
56
|
+
return doInstanceName(ns, opts);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=cf-do-shim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cf-do-shim.js","sourceRoot":"","sources":["../../src/worker/cf-do-shim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,mEAAmE;AACnE,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,CAAU,CAAA;AAchD,SAAS,eAAe,CAAC,KAAa;IACpC,OAAO,KAAK,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAA;AACrD,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAyB;IACjD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACnD,OAAO,IAAI,MAAM,CAAC,OAAO,KAAK,wBAAwB,CAAC,CAAA;AACzD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,EAAU,EACV,OAAgC,EAAE;IAElC,OAAO,gBAAgB,CAAC,IAAI,CAAC,MAAM,IAAI,cAAc,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACjE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,EAAU,EACV,OAAgC,EAAE;IAElC,IAAI,CAAC,EAAE;QAAE,OAAO,WAAW,CAAA;IAC3B,IAAI,CAAC,IAAI,CAAC,sBAAsB,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;QAAE,OAAO,WAAW,CAAA;IACxE,IAAI,CAAC,gBAAgB,CAAC,EAAE,EAAE,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAC5C,OAAO,KAAK,GAAG,EAAE,CAAA;AACnB,CAAC;AAMD;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAAkC,EAClC,GAAmC,EACnC,OAAgC,EAAE;IAElC,MAAM,EAAE,GACN,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,WAAW,CAAC,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;IACvF,OAAO,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;AACjC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orez",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.25",
|
|
4
4
|
"description": "PGlite-powered zero-sync development backend. No Docker required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"@electric-sql/pglite": "0.4.1",
|
|
88
88
|
"@electric-sql/pglite-tools": "^0.3.1",
|
|
89
89
|
"@pgsql/traverse": "17.2.6",
|
|
90
|
-
"bedrock-sqlite": "0.4.
|
|
90
|
+
"bedrock-sqlite": "0.4.25",
|
|
91
91
|
"citty": "^0.2.0",
|
|
92
92
|
"pg-gateway": "0.3.0-beta.4",
|
|
93
93
|
"pgsql-parser": "^17.9.11",
|