@rubytech/create-maxy-code 0.1.459 → 0.1.461
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/__tests__/cron-heartbeat-registration.test.js +57 -28
- package/dist/__tests__/launchd-plist.test.js +26 -0
- package/dist/cron-registration.js +30 -8
- package/dist/index.js +50 -9
- package/dist/launchd-plist.js +1 -1
- package/dist/uninstall.js +9 -3
- package/package.json +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js +116 -6
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/object-limits.test.js +20 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/object-limits.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.js +41 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +1 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +116 -48
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/index.d.ts +1 -0
- package/payload/platform/lib/storage-broker/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/index.js +1 -0
- package/payload/platform/lib/storage-broker/dist/index.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/object-limits.d.ts +27 -0
- package/payload/platform/lib/storage-broker/dist/object-limits.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/object-limits.js +27 -7
- package/payload/platform/lib/storage-broker/dist/object-limits.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/resource-limits.d.ts +102 -0
- package/payload/platform/lib/storage-broker/dist/resource-limits.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/resource-limits.js +105 -0
- package/payload/platform/lib/storage-broker/dist/resource-limits.js.map +1 -0
- package/payload/platform/lib/storage-broker/src/__tests__/cf-exec.test.ts +130 -7
- package/payload/platform/lib/storage-broker/src/__tests__/object-limits.test.ts +30 -0
- package/payload/platform/lib/storage-broker/src/__tests__/resource-limits.test.ts +49 -0
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +129 -56
- package/payload/platform/lib/storage-broker/src/index.ts +1 -0
- package/payload/platform/lib/storage-broker/src/object-limits.ts +26 -6
- package/payload/platform/lib/storage-broker/src/resource-limits.ts +105 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +5 -4
- package/payload/platform/plugins/cloudflare/PLUGIN.md +3 -1
- package/payload/platform/plugins/cloudflare/bin/portal-enrol.mjs +96 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/auth-route.test.ts +220 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/authorize.test.ts +201 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/file-routes.test.ts +178 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/harness.test.ts +32 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/passcode.test.ts +204 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-enrol.test.ts +207 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/ratelimit.test.ts +77 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/skill-contract.test.ts +46 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +55 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/upload-route.test.ts +255 -0
- package/payload/platform/plugins/cloudflare/mcp/package.json +5 -2
- package/payload/platform/plugins/cloudflare/references/r2-object-storage.md +46 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +125 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/authorize.ts +32 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/log.ts +12 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/passcode.mjs +193 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/ratelimit.ts +55 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/session.ts +85 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +44 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/auth.ts +119 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/delete.ts +89 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +70 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +41 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/upload.ts +151 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/public/index.html +42 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/public/portal.css +113 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/public/portal.js +155 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/schema.sql +73 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/wrangler.toml +21 -0
- package/payload/platform/plugins/cloudflare/skills/site-deploy/SKILL.md +8 -0
- package/payload/platform/plugins/docs/references/outlook-guide.md +3 -3
- package/payload/platform/plugins/docs/references/voice-mirror-guide.md +1 -0
- package/payload/platform/plugins/filesystem/PLUGIN.md +0 -1
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.d.ts +14 -10
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.d.ts.map +1 -1
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.js +14 -10
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.js.map +1 -1
- package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/outlook/PLUGIN.md +3 -5
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.js +75 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.js +187 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.js +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/index.js +3 -2
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.d.ts +43 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.js +65 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.d.ts +65 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.js +99 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.d.ts +6 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.d.ts +42 -22
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.js +59 -23
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.d.ts +15 -16
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.js +8 -41
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.js.map +1 -1
- package/payload/platform/plugins/outlook/references/auth.md +1 -1
- package/payload/platform/plugins/outlook/references/graph-surfaces.md +7 -3
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +1 -1
- package/payload/platform/plugins/storage-broker/PLUGIN.md +1 -1
- package/payload/platform/plugins/voice-mirror/PLUGIN.md +19 -7
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.js +35 -16
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.d.ts +31 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.d.ts.map +1 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.js +87 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.js.map +1 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js +111 -12
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-ingest-session-text.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-ingest-session-text.js +2 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-ingest-session-text.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.js +2 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.js +54 -21
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.d.ts +2 -2
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.js +12 -3
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/scripts/smoke.mjs +444 -0
- package/payload/platform/plugins/voice-mirror/skills/voice-mirror/SKILL.md +18 -2
- package/payload/platform/scripts/__tests__/logs-rotate.test.sh +256 -0
- package/payload/platform/scripts/__tests__/resume-tunnel.test.sh +79 -0
- package/payload/platform/scripts/logs-rotate.sh +204 -0
- package/payload/platform/scripts/resume-tunnel.sh +27 -0
- package/payload/platform/scripts/voice-author-key-audit.sh +155 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +41 -5
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.d.ts +1 -0
- package/payload/platform/services/claude-session-manager/dist/index.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +22 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.d.ts +2 -0
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.js +12 -0
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/mcp-descriptor-resolve.d.ts +17 -0
- package/payload/platform/services/claude-session-manager/dist/mcp-descriptor-resolve.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/mcp-descriptor-resolve.js +88 -0
- package/payload/platform/services/claude-session-manager/dist/mcp-descriptor-resolve.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts +122 -9
- package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-census.js +214 -29
- package/payload/platform/services/claude-session-manager/dist/pty-census.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +0 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +36 -24
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +13 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-memory-cap.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-memory-cap.js +4 -2
- package/payload/platform/services/claude-session-manager/dist/session-memory-cap.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts +150 -0
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js +333 -0
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.d.ts +10 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.js +55 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.js.map +1 -0
- package/payload/server/{chunk-Q6W4U6HL.js → chunk-JXWFVE5X.js} +83 -39
- package/payload/server/server.js +268 -73
- package/payload/server/{src-3I2RYZFB.js → src-4F37OHLK.js} +11 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.d.ts +0 -2
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js +0 -215
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js.map +0 -1
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* for. The ui route reads it through the barrel.
|
|
13
13
|
*/
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.R2_OBJECT_PUT_MAX_BODY_BYTES = exports.R2_OBJECT_MAX_BYTES = void 0;
|
|
15
|
+
exports.R2_OBJECT_PUT_MAX_BODY_BYTES = exports.R2_OBJECT_ENVELOPE_MAX_BODY_BYTES = exports.R2_OBJECT_MAX_BYTES = void 0;
|
|
16
16
|
exports.base64Ceiling = base64Ceiling;
|
|
17
17
|
exports.tooLargeMessage = tooLargeMessage;
|
|
18
18
|
/**
|
|
@@ -39,19 +39,39 @@ function base64Ceiling(bytes) {
|
|
|
39
39
|
return 4 * Math.ceil(bytes / 3);
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* The largest JSON body an object route's metadata envelope can legitimately
|
|
43
|
+
* carry: a bucket name, one key or prefix, and JSON punctuation. No payload term.
|
|
44
|
+
*
|
|
45
|
+
* Used twice, because it answers one question — how large can an envelope
|
|
46
|
+
* legitimately get. `/r2/object/list`, `get` and `delete` carry an envelope and
|
|
47
|
+
* nothing else, so this is their whole body limit (Task 1698). `put` carries the
|
|
48
|
+
* same envelope plus base64 object bytes, so it adds this to the base64 ceiling
|
|
49
|
+
* below, which is the allowance that keeps a legal at-cap put from being rejected
|
|
50
|
+
* by its own key.
|
|
51
|
+
*
|
|
52
|
+
* Deliberately one constant rather than two that happen to match. This is the
|
|
53
|
+
* opposite ruling to the email cap above, for the opposite reason: that pair must
|
|
54
|
+
* stay independent because the numbers track different constraints and would drift
|
|
55
|
+
* for different reasons. Here the constraint is identical, so a change to either
|
|
56
|
+
* use must move both.
|
|
57
|
+
*
|
|
58
|
+
* Sized from confirmed R2 limits rather than assumed. An object key is at most
|
|
59
|
+
* 1,024 bytes (developers.cloudflare.com/r2/platform/limits/) and a bucket name is
|
|
60
|
+
* 3-63 characters (developers.cloudflare.com/r2/buckets/create-buckets/), so a
|
|
61
|
+
* real envelope is about 1.1 KB, or roughly 6 KiB if every key byte takes a
|
|
62
|
+
* six-character \uXXXX escape. This clears that by about ten times and sits about
|
|
63
|
+
* 2,100 times below put's body limit: far above anything a real caller sends, far
|
|
64
|
+
* below anything that threatens house memory. Nothing here depends on either
|
|
65
|
+
* figure being exact.
|
|
46
66
|
*/
|
|
47
|
-
|
|
67
|
+
exports.R2_OBJECT_ENVELOPE_MAX_BODY_BYTES = 64 * 1024;
|
|
48
68
|
/**
|
|
49
69
|
* The largest JSON body /r2/object/put can legitimately carry.
|
|
50
70
|
*
|
|
51
71
|
* This bounds house memory; it does not adjudicate the last kilobyte. The exact
|
|
52
72
|
* decoded-byte rule lives in the MCP, which knows the source's size exactly.
|
|
53
73
|
*/
|
|
54
|
-
exports.R2_OBJECT_PUT_MAX_BODY_BYTES = base64Ceiling(exports.R2_OBJECT_MAX_BYTES) +
|
|
74
|
+
exports.R2_OBJECT_PUT_MAX_BODY_BYTES = base64Ceiling(exports.R2_OBJECT_MAX_BYTES) + exports.R2_OBJECT_ENVELOPE_MAX_BODY_BYTES;
|
|
55
75
|
function formatMiB(bytes) {
|
|
56
76
|
return (bytes / 1024 / 1024).toFixed(1);
|
|
57
77
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"object-limits.js","sourceRoot":"","sources":["../src/object-limits.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AAuBH,sCAEC;
|
|
1
|
+
{"version":3,"file":"object-limits.js","sourceRoot":"","sources":["../src/object-limits.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AAuBH,sCAEC;AAsDD,0CAIC;AAjFD;;;;;;;;;;;;;;;;;GAiBG;AACU,QAAA,mBAAmB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAErD,kFAAkF;AAClF,SAAgB,aAAa,CAAC,KAAa;IACzC,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACU,QAAA,iCAAiC,GAAG,EAAE,GAAG,IAAI,CAAC;AAE3D;;;;;GAKG;AACU,QAAA,4BAA4B,GACvC,aAAa,CAAC,2BAAmB,CAAC,GAAG,yCAAiC,CAAC;AAEzE,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,CAAC,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,eAAe,CAAC,IAAY,EAAE,WAAmB;IAC/D,OAAO,GAAG,IAAI,OAAO,SAAS,CAAC,WAAW,CAAC,SAAS,WAAW,8BAA8B,SAAS,CACpG,2BAAmB,CACpB,SAAS,2BAAmB,0BAA0B,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bounds on the request bodies the resource-level routes will buffer:
|
|
3
|
+
* /d1/create, /d1/query and /r2/create (Task 1702).
|
|
4
|
+
*
|
|
5
|
+
* Parallel to object-limits.ts, which bounds the object routes. Kept separate
|
|
6
|
+
* because the facts differ: an object envelope is dominated by R2's 1,024-byte
|
|
7
|
+
* object key limit, and these routes carry no key at all. Coupling them would tie
|
|
8
|
+
* a D1 database name to R2's key limit, so a revision to either would move a bound
|
|
9
|
+
* that has no reason to move.
|
|
10
|
+
*
|
|
11
|
+
* These are bounds on house memory. They do not adjudicate the last byte of a name
|
|
12
|
+
* or a statement — the same stance object-limits.ts takes, for the same reason. The
|
|
13
|
+
* house process holds the account-wide credential, so an out-of-memory kill there
|
|
14
|
+
* takes the brand server down, and on the target hardware that is a hard freeze
|
|
15
|
+
* that emits nothing.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately dependency-free, like object-limits.ts: the ui route reads these
|
|
18
|
+
* through the barrel, and nothing here needs the rest of the lib.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Cloudflare's documented maximum SQL statement length.
|
|
22
|
+
*
|
|
23
|
+
* "Maximum SQL statement length | 100,000 bytes (100 KB)"
|
|
24
|
+
* https://developers.cloudflare.com/d1/platform/limits/
|
|
25
|
+
*
|
|
26
|
+
* Recorded because it is what governs a legitimate `sql`, and because
|
|
27
|
+
* D1_QUERY_MAX_BODY_BYTES below is only knowable as safe by way of it.
|
|
28
|
+
*
|
|
29
|
+
* A second, independent bound applies to the same string on the way out, and is
|
|
30
|
+
* recorded here so nobody rediscovers it as a bug: cf-exec's d1Query shells
|
|
31
|
+
* `execFile("npx", ["wrangler", "d1", "execute", ..., "--command", sql])`, so the
|
|
32
|
+
* decoded statement travels as a single argv element. Linux caps one such element
|
|
33
|
+
* at MAX_ARG_STRLEN = 131,072 bytes and fails execve with E2BIG above it
|
|
34
|
+
* (https://man7.org/linux/man-pages/man2/execve.2.html). That is a property of the
|
|
35
|
+
* spawn path, not of D1. It does not bind first: 100,000 < 131,072, so a statement
|
|
36
|
+
* D1 would accept always fits the spawn.
|
|
37
|
+
*/
|
|
38
|
+
export declare const D1_MAX_SQL_STATEMENT_BYTES = 100000;
|
|
39
|
+
/**
|
|
40
|
+
* The worst-case expansion of a byte when it is serialized into a JSON string.
|
|
41
|
+
*
|
|
42
|
+
* A single ASCII control byte becomes the six characters \uXXXX. Multi-byte
|
|
43
|
+
* characters are cheaper per byte — a 3-byte UTF-8 character also becomes six
|
|
44
|
+
* characters (2x), and a non-BMP character becomes a 12-character surrogate pair
|
|
45
|
+
* for 4 bytes (3x) — so 6 is a true ceiling over bytes, reached by ASCII.
|
|
46
|
+
*
|
|
47
|
+
* This is why a body limit is not simply the statement limit: the cap bounds the
|
|
48
|
+
* wire body, and escaping moves the two apart.
|
|
49
|
+
*/
|
|
50
|
+
export declare const JSON_STRING_WORST_CASE_EXPANSION = 6;
|
|
51
|
+
/**
|
|
52
|
+
* The largest JSON body /d1/create or /r2/create can legitimately carry: a single
|
|
53
|
+
* resource name and JSON punctuation. No payload term, and no key term.
|
|
54
|
+
*
|
|
55
|
+
* Sized on headroom rather than a tight derivation, because only one of the two
|
|
56
|
+
* names it bounds has a documented hard limit:
|
|
57
|
+
*
|
|
58
|
+
* - R2 bucket name: 3-63 characters, lowercase letters, digits and hyphens.
|
|
59
|
+
* https://developers.cloudflare.com/r2/buckets/create-buckets/
|
|
60
|
+
* - D1 database name: no documented limit. The API reference states only
|
|
61
|
+
* `name: string`, "D1 database name" — no maxLength, pattern or charset.
|
|
62
|
+
* https://developers.cloudflare.com/api/resources/d1/subresources/database/
|
|
63
|
+
* The get-started guide gives guidance, not a limit: names "should use a
|
|
64
|
+
* combination of ASCII characters, shorter than 32 characters".
|
|
65
|
+
*
|
|
66
|
+
* So a real body is under ~100 bytes and 64 KiB clears it by roughly 650x, while
|
|
67
|
+
* sitting ~2,100x below put's body limit: far above anything a real caller sends,
|
|
68
|
+
* far below anything that threatens house memory. Nothing here depends on either
|
|
69
|
+
* figure being exact. An undocumented D1 name limit cannot plausibly approach 64
|
|
70
|
+
* KiB, and a request body is the wrong place to discover that it had.
|
|
71
|
+
*
|
|
72
|
+
* Deliberately not R2_OBJECT_ENVELOPE_MAX_BODY_BYTES, which happens to hold the
|
|
73
|
+
* same value. That constant is governed by R2's object key limit; this one is not,
|
|
74
|
+
* and the two must be free to drift. Couple what is one fact; separate what merely
|
|
75
|
+
* matches today.
|
|
76
|
+
*/
|
|
77
|
+
export declare const STORAGE_RESOURCE_NAME_MAX_BODY_BYTES: number;
|
|
78
|
+
/**
|
|
79
|
+
* The largest JSON body /d1/query can legitimately carry: a database name and a
|
|
80
|
+
* SQL statement.
|
|
81
|
+
*
|
|
82
|
+
* The envelope reasoning behind the object routes' bound does not govern this. A
|
|
83
|
+
* large but entirely legal migration or batch statement is not a 1,024-byte object
|
|
84
|
+
* key, so applying that constant here would reject correct queries.
|
|
85
|
+
*
|
|
86
|
+
* The bound is on the wire body, not the decoded statement, and JSON escaping moves
|
|
87
|
+
* those apart. The largest legal statement (D1_MAX_SQL_STATEMENT_BYTES) can present
|
|
88
|
+
* a wire body of up to
|
|
89
|
+
*
|
|
90
|
+
* JSON_STRING_WORST_CASE_EXPANSION * 100,000 + 65,536 = 665,536 bytes
|
|
91
|
+
*
|
|
92
|
+
* so capping at 100,000 would 413 a statement D1 would have accepted. 1 MiB clears
|
|
93
|
+
* the worst case by about 1.6x and sits ~133x below put's body limit.
|
|
94
|
+
*
|
|
95
|
+
* It is a flat number rather than that expression, for legibility at the call site.
|
|
96
|
+
* The arithmetic is not decoration: it is the only thing that makes 1 MiB knowable
|
|
97
|
+
* as safe, so resource-limits.test.ts asserts the relationship. If D1 ever raises
|
|
98
|
+
* its statement limit above 163,840 bytes, that test fails rather than this route
|
|
99
|
+
* quietly beginning to refuse legal SQL.
|
|
100
|
+
*/
|
|
101
|
+
export declare const D1_QUERY_MAX_BODY_BYTES: number;
|
|
102
|
+
//# sourceMappingURL=resource-limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-limits.d.ts","sourceRoot":"","sources":["../src/resource-limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,0BAA0B,SAAU,CAAC;AAElD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC,IAAI,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,oCAAoC,QAAY,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,uBAAuB,QAAc,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The bounds on the request bodies the resource-level routes will buffer:
|
|
4
|
+
* /d1/create, /d1/query and /r2/create (Task 1702).
|
|
5
|
+
*
|
|
6
|
+
* Parallel to object-limits.ts, which bounds the object routes. Kept separate
|
|
7
|
+
* because the facts differ: an object envelope is dominated by R2's 1,024-byte
|
|
8
|
+
* object key limit, and these routes carry no key at all. Coupling them would tie
|
|
9
|
+
* a D1 database name to R2's key limit, so a revision to either would move a bound
|
|
10
|
+
* that has no reason to move.
|
|
11
|
+
*
|
|
12
|
+
* These are bounds on house memory. They do not adjudicate the last byte of a name
|
|
13
|
+
* or a statement — the same stance object-limits.ts takes, for the same reason. The
|
|
14
|
+
* house process holds the account-wide credential, so an out-of-memory kill there
|
|
15
|
+
* takes the brand server down, and on the target hardware that is a hard freeze
|
|
16
|
+
* that emits nothing.
|
|
17
|
+
*
|
|
18
|
+
* Deliberately dependency-free, like object-limits.ts: the ui route reads these
|
|
19
|
+
* through the barrel, and nothing here needs the rest of the lib.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.D1_QUERY_MAX_BODY_BYTES = exports.STORAGE_RESOURCE_NAME_MAX_BODY_BYTES = exports.JSON_STRING_WORST_CASE_EXPANSION = exports.D1_MAX_SQL_STATEMENT_BYTES = void 0;
|
|
23
|
+
/**
|
|
24
|
+
* Cloudflare's documented maximum SQL statement length.
|
|
25
|
+
*
|
|
26
|
+
* "Maximum SQL statement length | 100,000 bytes (100 KB)"
|
|
27
|
+
* https://developers.cloudflare.com/d1/platform/limits/
|
|
28
|
+
*
|
|
29
|
+
* Recorded because it is what governs a legitimate `sql`, and because
|
|
30
|
+
* D1_QUERY_MAX_BODY_BYTES below is only knowable as safe by way of it.
|
|
31
|
+
*
|
|
32
|
+
* A second, independent bound applies to the same string on the way out, and is
|
|
33
|
+
* recorded here so nobody rediscovers it as a bug: cf-exec's d1Query shells
|
|
34
|
+
* `execFile("npx", ["wrangler", "d1", "execute", ..., "--command", sql])`, so the
|
|
35
|
+
* decoded statement travels as a single argv element. Linux caps one such element
|
|
36
|
+
* at MAX_ARG_STRLEN = 131,072 bytes and fails execve with E2BIG above it
|
|
37
|
+
* (https://man7.org/linux/man-pages/man2/execve.2.html). That is a property of the
|
|
38
|
+
* spawn path, not of D1. It does not bind first: 100,000 < 131,072, so a statement
|
|
39
|
+
* D1 would accept always fits the spawn.
|
|
40
|
+
*/
|
|
41
|
+
exports.D1_MAX_SQL_STATEMENT_BYTES = 100_000;
|
|
42
|
+
/**
|
|
43
|
+
* The worst-case expansion of a byte when it is serialized into a JSON string.
|
|
44
|
+
*
|
|
45
|
+
* A single ASCII control byte becomes the six characters \uXXXX. Multi-byte
|
|
46
|
+
* characters are cheaper per byte — a 3-byte UTF-8 character also becomes six
|
|
47
|
+
* characters (2x), and a non-BMP character becomes a 12-character surrogate pair
|
|
48
|
+
* for 4 bytes (3x) — so 6 is a true ceiling over bytes, reached by ASCII.
|
|
49
|
+
*
|
|
50
|
+
* This is why a body limit is not simply the statement limit: the cap bounds the
|
|
51
|
+
* wire body, and escaping moves the two apart.
|
|
52
|
+
*/
|
|
53
|
+
exports.JSON_STRING_WORST_CASE_EXPANSION = 6;
|
|
54
|
+
/**
|
|
55
|
+
* The largest JSON body /d1/create or /r2/create can legitimately carry: a single
|
|
56
|
+
* resource name and JSON punctuation. No payload term, and no key term.
|
|
57
|
+
*
|
|
58
|
+
* Sized on headroom rather than a tight derivation, because only one of the two
|
|
59
|
+
* names it bounds has a documented hard limit:
|
|
60
|
+
*
|
|
61
|
+
* - R2 bucket name: 3-63 characters, lowercase letters, digits and hyphens.
|
|
62
|
+
* https://developers.cloudflare.com/r2/buckets/create-buckets/
|
|
63
|
+
* - D1 database name: no documented limit. The API reference states only
|
|
64
|
+
* `name: string`, "D1 database name" — no maxLength, pattern or charset.
|
|
65
|
+
* https://developers.cloudflare.com/api/resources/d1/subresources/database/
|
|
66
|
+
* The get-started guide gives guidance, not a limit: names "should use a
|
|
67
|
+
* combination of ASCII characters, shorter than 32 characters".
|
|
68
|
+
*
|
|
69
|
+
* So a real body is under ~100 bytes and 64 KiB clears it by roughly 650x, while
|
|
70
|
+
* sitting ~2,100x below put's body limit: far above anything a real caller sends,
|
|
71
|
+
* far below anything that threatens house memory. Nothing here depends on either
|
|
72
|
+
* figure being exact. An undocumented D1 name limit cannot plausibly approach 64
|
|
73
|
+
* KiB, and a request body is the wrong place to discover that it had.
|
|
74
|
+
*
|
|
75
|
+
* Deliberately not R2_OBJECT_ENVELOPE_MAX_BODY_BYTES, which happens to hold the
|
|
76
|
+
* same value. That constant is governed by R2's object key limit; this one is not,
|
|
77
|
+
* and the two must be free to drift. Couple what is one fact; separate what merely
|
|
78
|
+
* matches today.
|
|
79
|
+
*/
|
|
80
|
+
exports.STORAGE_RESOURCE_NAME_MAX_BODY_BYTES = 64 * 1024;
|
|
81
|
+
/**
|
|
82
|
+
* The largest JSON body /d1/query can legitimately carry: a database name and a
|
|
83
|
+
* SQL statement.
|
|
84
|
+
*
|
|
85
|
+
* The envelope reasoning behind the object routes' bound does not govern this. A
|
|
86
|
+
* large but entirely legal migration or batch statement is not a 1,024-byte object
|
|
87
|
+
* key, so applying that constant here would reject correct queries.
|
|
88
|
+
*
|
|
89
|
+
* The bound is on the wire body, not the decoded statement, and JSON escaping moves
|
|
90
|
+
* those apart. The largest legal statement (D1_MAX_SQL_STATEMENT_BYTES) can present
|
|
91
|
+
* a wire body of up to
|
|
92
|
+
*
|
|
93
|
+
* JSON_STRING_WORST_CASE_EXPANSION * 100,000 + 65,536 = 665,536 bytes
|
|
94
|
+
*
|
|
95
|
+
* so capping at 100,000 would 413 a statement D1 would have accepted. 1 MiB clears
|
|
96
|
+
* the worst case by about 1.6x and sits ~133x below put's body limit.
|
|
97
|
+
*
|
|
98
|
+
* It is a flat number rather than that expression, for legibility at the call site.
|
|
99
|
+
* The arithmetic is not decoration: it is the only thing that makes 1 MiB knowable
|
|
100
|
+
* as safe, so resource-limits.test.ts asserts the relationship. If D1 ever raises
|
|
101
|
+
* its statement limit above 163,840 bytes, that test fails rather than this route
|
|
102
|
+
* quietly beginning to refuse legal SQL.
|
|
103
|
+
*/
|
|
104
|
+
exports.D1_QUERY_MAX_BODY_BYTES = 1024 * 1024;
|
|
105
|
+
//# sourceMappingURL=resource-limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-limits.js","sourceRoot":"","sources":["../src/resource-limits.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;GAkBG;;;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACU,QAAA,0BAA0B,GAAG,OAAO,CAAC;AAElD;;;;;;;;;;GAUG;AACU,QAAA,gCAAgC,GAAG,CAAC,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACU,QAAA,oCAAoC,GAAG,EAAE,GAAG,IAAI,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACU,QAAA,uBAAuB,GAAG,IAAI,GAAG,IAAI,CAAC"}
|
|
@@ -359,17 +359,23 @@ test("makeHouseCfExec runs wrangler with the scoped token, not the minter", asyn
|
|
|
359
359
|
// proves it), and every failure keeps its body.
|
|
360
360
|
// ---------------------------------------------------------------------------
|
|
361
361
|
|
|
362
|
-
function objectPage(
|
|
362
|
+
function objectPage(
|
|
363
|
+
keys: Array<string | { key: string; size: number }>,
|
|
364
|
+
opts: { cursor?: string; truncated?: boolean } = {},
|
|
365
|
+
) {
|
|
363
366
|
return JSON.stringify({
|
|
364
367
|
success: true,
|
|
365
368
|
errors: [],
|
|
366
369
|
messages: [],
|
|
367
|
-
result: keys.map((
|
|
368
|
-
key,
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
370
|
+
result: keys.map((k) => {
|
|
371
|
+
const entry = typeof k === "string" ? { key: k, size: 10 } : k;
|
|
372
|
+
return {
|
|
373
|
+
key: entry.key,
|
|
374
|
+
size: entry.size,
|
|
375
|
+
etag: "e1",
|
|
376
|
+
last_modified: "2026-01-01T00:00:00Z",
|
|
377
|
+
};
|
|
378
|
+
}),
|
|
373
379
|
result_info: { cursor: opts.cursor ?? "", is_truncated: opts.truncated ?? false },
|
|
374
380
|
});
|
|
375
381
|
}
|
|
@@ -544,3 +550,120 @@ test("r2ObjectList follows a cursor when is_truncated is absent, rather than tru
|
|
|
544
550
|
assert.deepEqual((await cf.r2ObjectList("b")).map((o) => o.key), ["a.jpg", "b.jpg"]);
|
|
545
551
|
assert.equal(calls.length, 2);
|
|
546
552
|
});
|
|
553
|
+
|
|
554
|
+
// --- r2ObjectFind (Task 1697) ---------------------------------------------
|
|
555
|
+
//
|
|
556
|
+
// Sizing a get needs one record, not the prefix. These tests pin the two
|
|
557
|
+
// properties that make the early exit safe: it matches the exact key, and it
|
|
558
|
+
// stops at the page carrying it.
|
|
559
|
+
|
|
560
|
+
test("r2ObjectFind returns the exact key when a prefix neighbour is listed first", async () => {
|
|
561
|
+
// Not hypothetical. Measured 2026-07-16 against the live v4 API: prefix=
|
|
562
|
+
// a/photo.jpg really does return a/photo.jpg.bak BEFORE a/photo.jpg, because
|
|
563
|
+
// the listing is not in ascending UTF-8 binary order. Taking the first element
|
|
564
|
+
// would size the neighbour, which either rejects a legal get or admits an
|
|
565
|
+
// oversized one.
|
|
566
|
+
const { fetchFn } = recordingFetch({
|
|
567
|
+
ok: true,
|
|
568
|
+
status: 200,
|
|
569
|
+
body: objectPage([
|
|
570
|
+
{ key: "a/photo.jpg.bak", size: 999 },
|
|
571
|
+
{ key: "a/photo.jpg", size: 3 },
|
|
572
|
+
]),
|
|
573
|
+
});
|
|
574
|
+
const cf = makeCfExec(cred, throwingRun, fetchFn);
|
|
575
|
+
const found = await cf.r2ObjectFind("b", "a/photo.jpg");
|
|
576
|
+
assert.equal(found?.key, "a/photo.jpg");
|
|
577
|
+
assert.equal(found?.size, 3);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
test("r2ObjectFind sends the key as the prefix, encoded", async () => {
|
|
581
|
+
const { fetchFn, calls } = recordingFetch({ ok: true, status: 200, body: objectPage([]) });
|
|
582
|
+
const cf = makeCfExec(cred, throwingRun, fetchFn);
|
|
583
|
+
await cf.r2ObjectFind("b", "person a/x.jpg");
|
|
584
|
+
assert.ok(calls[0].url.endsWith("/objects?prefix=person+a%2Fx.jpg"), calls[0].url);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
test("r2ObjectFind stops at the page carrying the match and fetches no further page", async () => {
|
|
588
|
+
// The page says truncated with a cursor, so r2ObjectList would fetch page 2.
|
|
589
|
+
// sequenceFetch throws on an unexpected call, so a second fetch fails here.
|
|
590
|
+
const { fetchFn, calls } = sequenceFetch([
|
|
591
|
+
{
|
|
592
|
+
ok: true,
|
|
593
|
+
status: 200,
|
|
594
|
+
body: objectPage([{ key: "k", size: 7 }], { cursor: "c1", truncated: true }),
|
|
595
|
+
},
|
|
596
|
+
]);
|
|
597
|
+
const cf = makeCfExec(cred, throwingRun, fetchFn);
|
|
598
|
+
const found = await cf.r2ObjectFind("b", "k");
|
|
599
|
+
assert.equal(found?.size, 7);
|
|
600
|
+
assert.equal(calls.length, 1);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
test("r2ObjectFind finds a match on a later page, never assuming position", async () => {
|
|
604
|
+
const { fetchFn, calls } = sequenceFetch([
|
|
605
|
+
{ ok: true, status: 200, body: objectPage(["x.jpg"], { cursor: "c1", truncated: true }) },
|
|
606
|
+
{ ok: true, status: 200, body: objectPage([{ key: "k", size: 5 }], { truncated: false }) },
|
|
607
|
+
]);
|
|
608
|
+
const cf = makeCfExec(cred, throwingRun, fetchFn);
|
|
609
|
+
const found = await cf.r2ObjectFind("b", "k");
|
|
610
|
+
assert.equal(found?.size, 5);
|
|
611
|
+
assert.equal(calls.length, 2);
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
test("r2ObjectFind returns null when the key is absent after exhausting pages", async () => {
|
|
615
|
+
const { fetchFn, calls } = sequenceFetch([
|
|
616
|
+
{ ok: true, status: 200, body: objectPage(["k.bak"], { cursor: "c1", truncated: true }) },
|
|
617
|
+
{ ok: true, status: 200, body: objectPage(["k.zzz"], { truncated: false }) },
|
|
618
|
+
]);
|
|
619
|
+
const cf = makeCfExec(cred, throwingRun, fetchFn);
|
|
620
|
+
assert.equal(await cf.r2ObjectFind("b", "k"), null);
|
|
621
|
+
assert.equal(calls.length, 2);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
test("r2ObjectFind throws on a repeated cursor instead of looping forever", async () => {
|
|
625
|
+
const { fetchFn } = sequenceFetch([
|
|
626
|
+
{ ok: true, status: 200, body: objectPage(["x"], { cursor: "c1", truncated: true }) },
|
|
627
|
+
{ ok: true, status: 200, body: objectPage(["y"], { cursor: "c1", truncated: true }) },
|
|
628
|
+
]);
|
|
629
|
+
const cf = makeCfExec(cred, throwingRun, fetchFn);
|
|
630
|
+
await assert.rejects(() => cf.r2ObjectFind("b", "k"), /repeated pagination cursor/);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
test("r2ObjectFind throws when truncated but no cursor is given", async () => {
|
|
634
|
+
const { fetchFn } = recordingFetch({
|
|
635
|
+
ok: true,
|
|
636
|
+
status: 200,
|
|
637
|
+
body: objectPage(["x"], { cursor: "", truncated: true }),
|
|
638
|
+
});
|
|
639
|
+
const cf = makeCfExec(cred, throwingRun, fetchFn);
|
|
640
|
+
await assert.rejects(() => cf.r2ObjectFind("b", "k"), /truncated but gave no pagination cursor/);
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
test("r2ObjectFind retains the body on a non-2xx", async () => {
|
|
644
|
+
const { fetchFn } = recordingFetch({ ok: false, status: 403, body: '{"errors":[{"code":10000}]}' });
|
|
645
|
+
const cf = makeCfExec(cred, throwingRun, fetchFn);
|
|
646
|
+
await assert.rejects(() => cf.r2ObjectFind("b", "k"), /10000/);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// Code review — the early exit changes observable behaviour on an anomalous
|
|
650
|
+
// page, and an unpinned behaviour change is how a regression starts. Pinned as a
|
|
651
|
+
// decision rather than left implicit.
|
|
652
|
+
//
|
|
653
|
+
// A page that reports truncated with no cursor is an anomaly r2ObjectList throws
|
|
654
|
+
// on, because a partial listing would corrupt the audit. r2ObjectFind returns at
|
|
655
|
+
// the match and never reaches that check, so the same response yields the size
|
|
656
|
+
// instead of an error. That is correct: the size is Cloudflare's own record for
|
|
657
|
+
// the exact key, the cap is enforced on it, and the anomaly concerns pages the
|
|
658
|
+
// sizing does not need. The audit's behaviour on the same response is unchanged
|
|
659
|
+
// and is pinned by "r2ObjectList throws when truncated but no cursor is given".
|
|
660
|
+
test("r2ObjectFind returns a match found before an anomalous page's guard is reached", async () => {
|
|
661
|
+
const { fetchFn } = recordingFetch({
|
|
662
|
+
ok: true,
|
|
663
|
+
status: 200,
|
|
664
|
+
body: objectPage([{ key: "k", size: 4 }], { cursor: "", truncated: true }),
|
|
665
|
+
});
|
|
666
|
+
const cf = makeCfExec(cred, throwingRun, fetchFn);
|
|
667
|
+
const found = await cf.r2ObjectFind("b", "k");
|
|
668
|
+
assert.equal(found?.size, 4);
|
|
669
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import {
|
|
4
|
+
R2_OBJECT_ENVELOPE_MAX_BODY_BYTES,
|
|
4
5
|
R2_OBJECT_MAX_BYTES,
|
|
5
6
|
R2_OBJECT_PUT_MAX_BODY_BYTES,
|
|
6
7
|
base64Ceiling,
|
|
@@ -53,6 +54,35 @@ test("the envelope allowance is negligible against the cap", () => {
|
|
|
53
54
|
assert.ok(allowance < R2_OBJECT_MAX_BYTES / 1000);
|
|
54
55
|
});
|
|
55
56
|
|
|
57
|
+
test("the envelope limit is 64 KiB", () => {
|
|
58
|
+
assert.equal(R2_OBJECT_ENVELOPE_MAX_BODY_BYTES, 64 * 1024);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// The sizing rationale, pinned rather than left in prose. An object key is at
|
|
62
|
+
// most 1,024 bytes and a bucket name at most 63 characters, so even a key whose
|
|
63
|
+
// every byte takes a six-character \uXXXX escape leaves an order of magnitude of
|
|
64
|
+
// headroom. A future edit that shrinks this toward a real envelope fails here.
|
|
65
|
+
test("the envelope limit clears a worst-case escaped envelope with headroom", () => {
|
|
66
|
+
const worstCase = 63 + 1024 * 6 + 64; // bucket + fully escaped key + punctuation
|
|
67
|
+
assert.ok(
|
|
68
|
+
R2_OBJECT_ENVELOPE_MAX_BODY_BYTES > worstCase * 5,
|
|
69
|
+
`${R2_OBJECT_ENVELOPE_MAX_BODY_BYTES} leaves too little headroom over ${worstCase}`,
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// The counterpart to "the cap is not email's attachment cap" above, and the
|
|
74
|
+
// opposite ruling for the opposite reason. That pair must stay apart because the
|
|
75
|
+
// two numbers track different constraints and would drift for different reasons.
|
|
76
|
+
// These two are one fact — how large can an envelope legitimately get — asked once
|
|
77
|
+
// by put's allowance and once by the metadata routes' whole body, so they must
|
|
78
|
+
// move together. This pins the composition; the test above pins the value.
|
|
79
|
+
test("put's body ceiling is composed of the same envelope constant", () => {
|
|
80
|
+
assert.equal(
|
|
81
|
+
R2_OBJECT_PUT_MAX_BODY_BYTES,
|
|
82
|
+
base64Ceiling(R2_OBJECT_MAX_BYTES) + R2_OBJECT_ENVELOPE_MAX_BODY_BYTES,
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
56
86
|
test("the over-cap message names the object and the limit", () => {
|
|
57
87
|
const msg = tooLargeMessage("photo.jpg", 150 * 1024 * 1024);
|
|
58
88
|
assert.match(msg, /photo\.jpg/);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import {
|
|
4
|
+
D1_MAX_SQL_STATEMENT_BYTES,
|
|
5
|
+
D1_QUERY_MAX_BODY_BYTES,
|
|
6
|
+
JSON_STRING_WORST_CASE_EXPANSION,
|
|
7
|
+
STORAGE_RESOURCE_NAME_MAX_BODY_BYTES,
|
|
8
|
+
} from "../resource-limits.js";
|
|
9
|
+
|
|
10
|
+
test("the recorded D1 statement limit is Cloudflare's documented 100,000 bytes", () => {
|
|
11
|
+
assert.equal(D1_MAX_SQL_STATEMENT_BYTES, 100_000);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("a resource-name body is bounded at 64 KiB", () => {
|
|
15
|
+
assert.equal(STORAGE_RESOURCE_NAME_MAX_BODY_BYTES, 64 * 1024);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("a D1 query body is bounded at 1 MiB", () => {
|
|
19
|
+
assert.equal(D1_QUERY_MAX_BODY_BYTES, 1024 * 1024);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// The guard. D1_QUERY_MAX_BODY_BYTES is a flat number rather than a computed
|
|
23
|
+
// expression, so nothing but this test holds it to the arithmetic that makes it
|
|
24
|
+
// safe. If D1 ever raises its statement limit above 163,840 bytes, the worst-case
|
|
25
|
+
// wire form of a legal statement exceeds 1 MiB and the route would begin rejecting
|
|
26
|
+
// correct queries with nothing to signal why. This fails first instead.
|
|
27
|
+
test("the D1 query body limit clears the worst-case wire form of a legal statement", () => {
|
|
28
|
+
const worstCaseLegalBody =
|
|
29
|
+
JSON_STRING_WORST_CASE_EXPANSION * D1_MAX_SQL_STATEMENT_BYTES +
|
|
30
|
+
STORAGE_RESOURCE_NAME_MAX_BODY_BYTES;
|
|
31
|
+
assert.ok(
|
|
32
|
+
D1_QUERY_MAX_BODY_BYTES > worstCaseLegalBody,
|
|
33
|
+
`D1_QUERY_MAX_BODY_BYTES (${D1_QUERY_MAX_BODY_BYTES}) must exceed the largest legal wire body (${worstCaseLegalBody}), or legal queries are rejected`,
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// The escape ceiling is the reason the bound is not simply D1's 100,000. Pin the
|
|
38
|
+
// factor against the runtime rather than restating the literal: a single ASCII
|
|
39
|
+
// control byte serializes to the six characters \uXXXX, which is the worst ratio a
|
|
40
|
+
// JSON string can reach over bytes.
|
|
41
|
+
test("the JSON worst-case expansion is the six-character \\uXXXX escape", () => {
|
|
42
|
+
assert.equal(JSON_STRING_WORST_CASE_EXPANSION, 6);
|
|
43
|
+
// Written as an escape rather than a literal control byte: a raw 0x01 in source
|
|
44
|
+
// is invisible in most editors and diffs.
|
|
45
|
+
const oneControlByte = "\u0001";
|
|
46
|
+
assert.equal(Buffer.byteLength(oneControlByte), 1);
|
|
47
|
+
// minus the two quotes JSON.stringify wraps the string in
|
|
48
|
+
assert.equal(JSON.stringify(oneControlByte).length - 2, JSON_STRING_WORST_CASE_EXPANSION);
|
|
49
|
+
});
|