@rebasepro/server 0.24.0 → 0.24.1-canary.gcb918fc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -2
- package/dist/GCSStorageController-fv6JAZ87.js +402 -0
- package/dist/GCSStorageController-fv6JAZ87.js.map +1 -0
- package/dist/S3StorageController-BNg-pXiY.js +480 -0
- package/dist/S3StorageController-BNg-pXiY.js.map +1 -0
- package/dist/api/mount-path.d.ts +30 -0
- package/dist/api/mount.d.ts +6 -13
- package/dist/api/rest/api-generator.d.ts +53 -1
- package/dist/api/rest/auth-collection-writes.d.ts +28 -21
- package/dist/api/rest/field-access-query.d.ts +28 -8
- package/dist/api/rest/field-ops.d.ts +19 -7
- package/dist/api/rest/query-body.d.ts +10 -0
- package/dist/api/rest/write-validation.d.ts +7 -5
- package/dist/api/schema-editor-routes.d.ts +10 -1
- package/dist/api/sql-export-routes.d.ts +8 -0
- package/dist/api/types.d.ts +35 -0
- package/dist/{ast-schema-editor-CWqS_sLJ.js → ast-schema-editor-mCeSC03x.js} +3 -3
- package/dist/{ast-schema-editor-CWqS_sLJ.js.map → ast-schema-editor-mCeSC03x.js.map} +1 -1
- package/dist/auth/access.d.ts +57 -8
- package/dist/auth/admin-user-ops.d.ts +24 -2
- package/dist/auth/admin-users-route.d.ts +1 -0
- package/dist/auth/api-keys/api-key-middleware.d.ts +10 -3
- package/dist/auth/api-keys/api-key-routes.d.ts +4 -1
- package/dist/auth/builtin-auth-adapter.d.ts +13 -0
- package/dist/auth/email-change-routes.d.ts +3 -0
- package/dist/auth/expired-token-sweep.d.ts +5 -1
- package/dist/auth/interfaces.d.ts +10 -0
- package/dist/auth/jwks-routes.d.ts +5 -1
- package/dist/auth/jwt-crypto.d.ts +7 -0
- package/dist/auth/jwt-keys.d.ts +4 -0
- package/dist/auth/jwt.d.ts +282 -166
- package/dist/auth/magic-link-routes.d.ts +3 -0
- package/dist/auth/mfa-crypto.d.ts +9 -3
- package/dist/auth/mfa-gate.d.ts +4 -2
- package/dist/auth/mfa-routes.d.ts +6 -0
- package/dist/auth/middleware.d.ts +9 -2
- package/dist/auth/otp-routes.d.ts +3 -0
- package/dist/auth/rate-limiter.d.ts +61 -34
- package/dist/auth/registration-policy.d.ts +17 -0
- package/dist/auth/require-auth.d.ts +1 -1
- package/dist/auth/routes.d.ts +16 -0
- package/dist/auth/session-routes.d.ts +6 -0
- package/dist/auth/user-picking.d.ts +37 -0
- package/dist/auth/verify-credential.d.ts +25 -4
- package/dist/{auth-DMLngxn_.js → auth-GnONzteu.js} +2869 -1871
- package/dist/auth-GnONzteu.js.map +1 -0
- package/dist/auth_adapter-CIItCTc8.js +21 -0
- package/dist/auth_adapter-CIItCTc8.js.map +1 -0
- package/dist/backend/create-backend.d.ts +281 -0
- package/dist/backend/default-backend.d.ts +35 -0
- package/dist/backend/options.d.ts +912 -0
- package/dist/backend/scope.d.ts +23 -0
- package/dist/backend/services.d.ts +54 -0
- package/dist/backend-DTAOsLQc.js.map +1 -1
- package/dist/backup/backup-common.d.ts +14 -3
- package/dist/backup/backup-routes.d.ts +23 -2
- package/dist/backup/backup-storage.d.ts +3 -1
- package/dist/backup/index.d.ts +3 -1
- package/dist/{backup-CN0s50D2.js → backup-BnvkSLHi.js} +176 -35
- package/dist/backup-BnvkSLHi.js.map +1 -0
- package/dist/boot/boot.d.ts +2 -2
- package/dist/boot/driver.d.ts +3 -0
- package/dist/boot/env.d.ts +9 -2
- package/dist/boot/http-server.d.ts +22 -0
- package/dist/boot/options.d.ts +16 -1
- package/dist/boot/provision.d.ts +4 -2
- package/dist/boot/runtime-cors.d.ts +17 -0
- package/dist/boot/sources.d.ts +2 -0
- package/dist/boot/static-routing.d.ts +9 -0
- package/dist/{contract-routes-fz8i4pxs.js → contract-routes-Ey6ftnKH.js} +4 -20
- package/dist/contract-routes-Ey6ftnKH.js.map +1 -0
- package/dist/cron/cron-loader.d.ts +77 -18
- package/dist/cron/cron-routes.d.ts +1 -1
- package/dist/cron/cron-scheduler.d.ts +69 -3
- package/dist/cron/cron-store.d.ts +33 -5
- package/dist/cron/define-cron.d.ts +6 -2
- package/dist/cron/index.d.ts +3 -3
- package/dist/cron-loader-B-u0snqq.js +202 -0
- package/dist/cron-loader-B-u0snqq.js.map +1 -0
- package/dist/{cron-routes-Bc-SB0Se.js → cron-routes-OD6BstUj.js} +27 -4
- package/dist/cron-routes-OD6BstUj.js.map +1 -0
- package/dist/{cron-scheduler-CYQgco86.js → cron-scheduler-idtLPoN-.js} +299 -33
- package/dist/cron-scheduler-idtLPoN-.js.map +1 -0
- package/dist/cron-store-CZ30ZBi-.js +577 -0
- package/dist/cron-store-CZ30ZBi-.js.map +1 -0
- package/dist/email/smtp-email-service.d.ts +23 -0
- package/dist/email/types.d.ts +18 -0
- package/dist/env.d.ts +0 -37
- package/dist/filter-dialect-O1HK6Nvr.js +1628 -0
- package/dist/filter-dialect-O1HK6Nvr.js.map +1 -0
- package/dist/{function-loader-D7o5Epjj.js → function-loader-iKhOZatS.js} +68 -38
- package/dist/function-loader-iKhOZatS.js.map +1 -0
- package/dist/{function-routes-CaNG4waN.js → function-routes-j9TwpvQe.js} +5 -4
- package/dist/function-routes-j9TwpvQe.js.map +1 -0
- package/dist/functions/context.d.ts +9 -0
- package/dist/functions/define-function.d.ts +10 -6
- package/dist/functions/function-loader.d.ts +75 -18
- package/dist/functions/function-routes.d.ts +2 -1
- package/dist/functions/index.d.ts +5 -4
- package/dist/functions/index.js +42 -13
- package/dist/functions/index.js.map +1 -1
- package/dist/functions/internal.d.ts +2 -2
- package/dist/functions/proxy.d.ts +6 -2
- package/dist/functions/request-timeout.d.ts +7 -5
- package/dist/history/history-routes.d.ts +9 -0
- package/dist/history/retention-sweep.d.ts +81 -0
- package/dist/{history-recorder-Nr8zLvoU.js → history-recorder-BfWRwmPy.js} +3 -3
- package/dist/{history-recorder-Nr8zLvoU.js.map → history-recorder-BfWRwmPy.js.map} +1 -1
- package/dist/{history-store-rcAm_xFR.js → history-store-DPofGVDp.js} +2 -2
- package/dist/{history-store-rcAm_xFR.js.map → history-store-DPofGVDp.js.map} +1 -1
- package/dist/index.d.ts +23 -10
- package/dist/index.es.js +24423 -20038
- package/dist/index.es.js.map +1 -1
- package/dist/init/docs.d.ts +27 -10
- package/dist/init/process-safety.d.ts +7 -2
- package/dist/init/shutdown.d.ts +18 -3
- package/dist/init.d.ts +49 -865
- package/dist/internal-tables-L2t39RdT.js +136 -0
- package/dist/internal-tables-L2t39RdT.js.map +1 -0
- package/dist/jobs/job-queue.d.ts +8 -0
- package/dist/jobs/job-store.d.ts +24 -0
- package/dist/jobs/types.d.ts +25 -4
- package/dist/{jobs-DqYNfquG.js → jobs-BCbflZj2.js} +234 -24
- package/dist/jobs-BCbflZj2.js.map +1 -0
- package/dist/{jwt-R6bSPMjk.js → jwt-BlvuKoR6.js} +529 -454
- package/dist/jwt-BlvuKoR6.js.map +1 -0
- package/dist/{keys-GAVZqbqx.js → keys-COzQ2aE8.js} +2 -2
- package/dist/{keys-GAVZqbqx.js.map → keys-COzQ2aE8.js.map} +1 -1
- package/dist/load-failure-L5hvfDoG.js +80 -0
- package/dist/load-failure-L5hvfDoG.js.map +1 -0
- package/dist/mcp/mcp-routes.d.ts +6 -0
- package/dist/mcp/mcp-tools.d.ts +8 -0
- package/dist/mcp/oauth-routes.d.ts +7 -0
- package/dist/metrics/index.d.ts +18 -0
- package/dist/metrics/pool-wait.d.ts +50 -0
- package/dist/offline-QNQgsPOq-B2yGeXkn.js +3305 -0
- package/dist/offline-QNQgsPOq-B2yGeXkn.js.map +1 -0
- package/dist/offline-store-Cm5sAX81-CALiO4Gn.js +1915 -0
- package/dist/offline-store-Cm5sAX81-CALiO4Gn.js.map +1 -0
- package/dist/{openapi-generator-DAq_XVDu.js → openapi-generator-D9zUQmEp.js} +366 -82
- package/dist/openapi-generator-D9zUQmEp.js.map +1 -0
- package/dist/policy-PdzX-Opd.js +121 -0
- package/dist/policy-PdzX-Opd.js.map +1 -0
- package/dist/project_manifest-R6y_KHms.js +59 -0
- package/dist/project_manifest-R6y_KHms.js.map +1 -0
- package/dist/{proxy-qRlqeUmO.js → proxy-CkTlMGmh.js} +4 -3
- package/dist/proxy-CkTlMGmh.js.map +1 -0
- package/dist/{query-parser-BgiKJKvc.js → query-parser-4WkMoA_K.js} +510 -15
- package/dist/query-parser-4WkMoA_K.js.map +1 -0
- package/dist/{request-timeout-DgH7j8qO.js → request-timeout-Dnl2_OBc.js} +15 -8
- package/dist/request-timeout-Dnl2_OBc.js.map +1 -0
- package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
- package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
- package/dist/schema-edit/apply-schema-change.d.ts +10 -0
- package/dist/schema-edit/local-source.d.ts +29 -0
- package/dist/schema-edit/remote-source.d.ts +13 -3
- package/dist/schema-edit/source-editor.d.ts +28 -0
- package/dist/{schema-editor-routes-oIyuWl3L.js → schema-editor-routes-C11Tumyd.js} +54 -10
- package/dist/schema-editor-routes-C11Tumyd.js.map +1 -0
- package/dist/services/outbound-post.d.ts +50 -0
- package/dist/services/outbound-url-guard.d.ts +34 -5
- package/dist/services/routed-realtime-service.d.ts +5 -0
- package/dist/services/webhook-service.d.ts +66 -14
- package/dist/singleton.d.ts +9 -5
- package/dist/soft-delete-params-DfV3vVN5.js +668 -0
- package/dist/soft-delete-params-DfV3vVN5.js.map +1 -0
- package/dist/sql-export-routes-ClRwzIsC.js +240 -0
- package/dist/sql-export-routes-ClRwzIsC.js.map +1 -0
- package/dist/{src-CatHFUym.js → src-D5Y3N-S4.js} +2 -1
- package/dist/{src-CatHFUym.js.map → src-D5Y3N-S4.js.map} +1 -1
- package/dist/storage/GCSStorageController.d.ts +46 -2
- package/dist/storage/LocalStorageController.d.ts +67 -2
- package/dist/storage/S3StorageController.d.ts +37 -3
- package/dist/storage/image-transform.d.ts +5 -0
- package/dist/storage/maintenance-routes.d.ts +23 -0
- package/dist/storage/maintenance.d.ts +126 -0
- package/dist/storage/rendition-cache.d.ts +9 -11
- package/dist/storage/request-principals.d.ts +30 -0
- package/dist/storage/routes.d.ts +20 -0
- package/dist/storage/source-variables.d.ts +10 -0
- package/dist/storage/streams.d.ts +42 -0
- package/dist/storage/tus-handler.d.ts +46 -2
- package/dist/storage/types.d.ts +82 -7
- package/dist/storage/upload-validation.d.ts +13 -0
- package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
- package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
- package/dist/strings.es-BeZWAodn.js +256 -0
- package/dist/strings.es-BeZWAodn.js.map +1 -0
- package/dist/upload-validation-Dd-fnsbA.js +172 -0
- package/dist/upload-validation-Dd-fnsbA.js.map +1 -0
- package/dist/utils/byte-size.d.ts +10 -0
- package/dist/utils/compression.d.ts +6 -0
- package/dist/utils/load-failure.d.ts +14 -0
- package/package.json +5 -5
- package/dist/GCSStorageController-BSiP1c-f.js +0 -256
- package/dist/GCSStorageController-BSiP1c-f.js.map +0 -1
- package/dist/S3StorageController-CAwFRgjV.js +0 -276
- package/dist/S3StorageController-CAwFRgjV.js.map +0 -1
- package/dist/auth-DMLngxn_.js.map +0 -1
- package/dist/backup-CN0s50D2.js.map +0 -1
- package/dist/contract-routes-fz8i4pxs.js.map +0 -1
- package/dist/cron-loader-CwaANlOG.js +0 -118
- package/dist/cron-loader-CwaANlOG.js.map +0 -1
- package/dist/cron-routes-Bc-SB0Se.js.map +0 -1
- package/dist/cron-scheduler-CYQgco86.js.map +0 -1
- package/dist/cron-store-D2Q9-Aco.js +0 -300
- package/dist/cron-store-D2Q9-Aco.js.map +0 -1
- package/dist/dynamic-import-X40pTZUQ.js +0 -23
- package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
- package/dist/function-loader-D7o5Epjj.js.map +0 -1
- package/dist/function-routes-CaNG4waN.js.map +0 -1
- package/dist/jobs-DqYNfquG.js.map +0 -1
- package/dist/jwt-R6bSPMjk.js.map +0 -1
- package/dist/openapi-generator-DAq_XVDu.js.map +0 -1
- package/dist/proxy-qRlqeUmO.js.map +0 -1
- package/dist/query-parser-BgiKJKvc.js.map +0 -1
- package/dist/request-timeout-DgH7j8qO.js.map +0 -1
- package/dist/schema-editor-routes-oIyuWl3L.js.map +0 -1
- package/dist/soft-delete-params-BWPilMPF.js +0 -59
- package/dist/soft-delete-params-BWPilMPF.js.map +0 -1
- package/dist/src-I3aG1PcY.js +0 -6870
- package/dist/src-I3aG1PcY.js.map +0 -1
- package/dist/types-BfKcm9do.js +0 -39
- package/dist/types-BfKcm9do.js.map +0 -1
|
@@ -2,7 +2,7 @@ import { createRequire as __rebaseCreateRequire } from "module";
|
|
|
2
2
|
import __rebaseProcess from "process";
|
|
3
3
|
globalThis.process ??= __rebaseProcess;
|
|
4
4
|
__rebaseCreateRequire(import.meta.url);
|
|
5
|
-
import { O as DEFAULT_STORAGE_SOURCE_KEY } from "./src-
|
|
5
|
+
import { O as DEFAULT_STORAGE_SOURCE_KEY } from "./src-D5Y3N-S4.js";
|
|
6
6
|
//#region src/storage/keys.ts
|
|
7
7
|
/**
|
|
8
8
|
* Canonical storage keys and bucket names.
|
|
@@ -228,4 +228,4 @@ var InvalidListOptionsError = class extends Error {
|
|
|
228
228
|
//#endregion
|
|
229
229
|
export { canonicalStorageId as a, listingPrefix as c, canonicalStorageBucket as i, InvalidStorageBucketError as n, canonicalStorageKey as o, InvalidStorageKeyError as r, folderKey as s, InvalidListOptionsError as t };
|
|
230
230
|
|
|
231
|
-
//# sourceMappingURL=keys-
|
|
231
|
+
//# sourceMappingURL=keys-COzQ2aE8.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keys-GAVZqbqx.js","names":[],"sources":["../src/storage/keys.ts"],"sourcesContent":["/**\n * Canonical storage keys and bucket names.\n *\n * Storage is not under RLS, so a `storageAuthorize` hook is the whole access\n * control model — and a hook can only be correct if the key it is shown is the\n * key that is written. That is the invariant this module exists to hold: one\n * canonical string, computed once per request, handed to the hook, to the\n * controller, and to the download token alike.\n *\n * ## Why rejecting beats stripping\n *\n * The previous `sanitizeStorageKey` *stripped* `../` in a single pass. Two\n * things were wrong with that, and only one of them was the obvious one.\n *\n * The obvious one: a single pass is not a fixed point. `....//` contains `../`\n * at offset 2, so removing it leaves `../` behind — the sanitizer manufactured\n * the traversal it was there to remove. `users/alice/....//bob/x` came out as\n * `users/alice/../bob/x`, which a prefix hook reads as alice's (it starts with\n * `users/alice/`) and the filesystem reads as bob's. The hook approved one\n * object and the controller wrote another.\n *\n * The subtler one, and the reason this is a rewrite rather than a loop: even a\n * correct strip is a silent rewrite. A caller who asks to store at `a/../b` and\n * gets an object at `a/b` was not protected, they were misled — and every later\n * read, ownership row and audit line now refers to a path nobody chose. So a key\n * that means something other than what it says is refused (400), not repaired.\n *\n * Note what is NOT traversal under this rule: `....` is an ordinary directory\n * name, and `users/alice/....//bob/x` canonicalizes to\n * `users/alice/..../bob/x` — still comfortably inside alice's prefix, which is\n * exactly right. Only a real `..` segment is refused.\n */\n\nimport { DEFAULT_STORAGE_SOURCE_KEY } from \"@rebasepro/types\";\n\n/**\n * `path.posix.normalize`, without `node:path`.\n *\n * A storage key is a POSIX-shaped string that never touches a filesystem, so\n * reaching for `node:path` to fold `.` and `//` out of it was always a little\n * wrong on its own terms — on Windows the platform `path` would have applied\n * different rules to the same key. It also put the module that every storage\n * read, ownership row and audit line goes through on the list of things that\n * only run in a Node process.\n *\n * This is the algorithm Node implements, transcribed: segments are folded left\n * to right, `..` pops unless it would climb past the root of a relative path,\n * and both the leading and the trailing separator survive the round trip. The\n * `..` branch is unreachable from {@link canonicalStorageKey}, which refuses\n * those keys outright before it gets here — it exists so that this function is\n * *the* normalizer rather than a subset of one, and\n * `storage-keys.property.test.ts` holds it to that with a property test against\n * `path.posix.normalize` itself. Exported for exactly that — nothing outside\n * this module should be normalizing a key.\n */\nexport function normalizePosix(input: string): string {\n if (input.length === 0) return \".\";\n const isAbsolute = input.startsWith(\"/\");\n const trailingSeparator = input.endsWith(\"/\");\n\n const segments: string[] = [];\n for (const segment of input.split(\"/\")) {\n if (segment === \"\" || segment === \".\") continue;\n if (segment === \"..\") {\n if (segments.length > 0 && segments[segments.length - 1] !== \"..\") segments.pop();\n else if (!isAbsolute) segments.push(\"..\");\n continue;\n }\n segments.push(segment);\n }\n\n const joined = segments.join(\"/\");\n if (joined === \"\") {\n if (isAbsolute) return \"/\";\n return trailingSeparator ? \"./\" : \".\";\n }\n const withTrailing = trailingSeparator ? `${joined}/` : joined;\n return isAbsolute ? `/${withTrailing}` : withTrailing;\n}\n\n/** Longest key accepted, in UTF-16 code units. Matches the previous cap. */\nexport const MAX_STORAGE_KEY_LENGTH = 1024;\n\n/**\n * A key that cannot be canonicalized. Carries no path back to the caller\n * beyond what they sent, so it is safe to surface as a 400 message.\n */\nexport class InvalidStorageKeyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStorageKeyError\";\n }\n}\n\n/**\n * Canonicalize a caller-supplied storage key, or throw\n * {@link InvalidStorageKeyError}.\n *\n * Normalizations applied (safe, idempotent, and meaning-preserving):\n * - leading slashes removed — `/a/b` and `a/b` name the same object\n * - `.` segments and repeated slashes collapsed\n *\n * Refusals (the key means something other than what it says):\n * - any `..` segment, on either separator, at any depth\n * - null bytes\n * - keys longer than {@link MAX_STORAGE_KEY_LENGTH}\n *\n * A trailing slash is preserved: it is how the folder route marks a prefix.\n */\nexport function canonicalStorageKey(rawKey: string): string {\n if (rawKey.includes(\"\\0\")) {\n throw new InvalidStorageKeyError(\"Storage key contains a null byte.\");\n }\n if (rawKey.length > MAX_STORAGE_KEY_LENGTH) {\n throw new InvalidStorageKeyError(\n `Storage key exceeds the maximum length of ${MAX_STORAGE_KEY_LENGTH} characters.`\n );\n }\n\n // Split on both separators: `path.join` treats `\\` as a separator on\n // Windows, so `..\\` is traversal there even though POSIX reads it as a\n // filename. Checked against the RAW key, before any normalization — the\n // whole point is that `a/../b` is refused rather than quietly turned into\n // `b`.\n if (rawKey.split(/[\\\\/]/).some((segment) => segment === \"..\")) {\n throw new InvalidStorageKeyError(\n \"Storage key contains a '..' path segment. Keys must name an object directly.\"\n );\n }\n\n const withoutLeadingSlashes = rawKey.replace(/^\\/+/, \"\");\n if (withoutLeadingSlashes === \"\") return \"\";\n\n // Whether the key names a *directory* rather than an object: it ends with a\n // separator, or its last segment is the `.` that means \"this directory\".\n //\n // Recorded before normalizing, because normalization drops the\n // distinction in one of those two spellings and not the other: `public/.`\n // becomes `public` while `public/./` stays `public/`. That asymmetry breaks\n // the rule this module states — a trailing slash is preserved, because it is\n // how the folder route marks a prefix — and it is not cosmetic on `list`,\n // where a prefix of `public` also matches `publicity/` and hands back keys\n // the caller never asked for.\n const denotesDirectory = /(?:^|\\/)\\.?$/.test(withoutLeadingSlashes);\n\n // Safe now: with no `..` segment in the input, `normalize` can only collapse\n // `.` and duplicate slashes — it cannot climb.\n const normalized = normalizePosix(withoutLeadingSlashes);\n\n // `normalize(\".\")`, and `normalize(\"./\")` → `./`; neither names an object.\n if (normalized === \".\" || normalized === \"./\") return \"\";\n\n // Defensive: nothing reaching here still carries a leading `./`, since the\n // leading slashes are already gone and `normalize` only emits `./` for a\n // key that normalizes to nothing — which returned above.\n const key = normalized.replace(/^\\.\\//, \"\").replace(/^\\/+/, \"\");\n if (key === \"\") return \"\";\n return denotesDirectory && !key.endsWith(\"/\") ? `${key}/` : key;\n}\n\n/**\n * Canonicalize, or return `null` when the key is not canonicalizable.\n *\n * For callers that must fail closed without an exception — the download-token\n * middleware compares a request path against a granted path, and a key it\n * cannot canonicalize simply matches nothing.\n */\nexport function tryCanonicalStorageKey(rawKey: string): string | null {\n try {\n return canonicalStorageKey(rawKey);\n } catch {\n return null;\n }\n}\n\n/**\n * Canonicalize the storage *source* a request names (`?storageId=`), so that\n * \"the default source\" has exactly one spelling.\n *\n * The default source can be asked for three ways — the parameter omitted, sent\n * empty, or sent as the literal `(default)` — and all three resolve to the same\n * controller. Without a single spelling, the value derived when a download\n * token is minted and the value derived when it is presented can differ for the\n * same object, which is either a spurious 403 or, if the comparison is dropped\n * to stop those, no scoping at all.\n *\n * Deliberately *not* validated against the registry: this is a naming rule, not\n * an existence check, and it must give the same answer in `auth/` (which has no\n * registry) as in the storage routes. An id that names no source still\n * canonicalizes to itself and simply matches only itself.\n */\nexport function canonicalStorageId(rawStorageId: string | undefined | null): string {\n if (rawStorageId === undefined || rawStorageId === null) return DEFAULT_STORAGE_SOURCE_KEY;\n const trimmed = rawStorageId.trim();\n return trimmed === \"\" ? DEFAULT_STORAGE_SOURCE_KEY : trimmed;\n}\n\n/**\n * Longest bucket name accepted. 63 is the S3/GCS limit, and a local bucket is\n * one directory name, so the tighter of the two bounds everything.\n */\nexport const MAX_STORAGE_BUCKET_LENGTH = 63;\n\n/** A bucket name that does not name a bucket. See {@link canonicalStorageBucket}. */\nexport class InvalidStorageBucketError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStorageBucketError\";\n }\n}\n\n/**\n * One path segment: letters, digits, `.`, `_`, `-`, first character\n * alphanumeric. Deliberately narrow — it is the intersection of what S3, GCS\n * and a filesystem directory all accept, and it makes `..`, `.tus-uploads`,\n * absolute paths and anything containing a separator unrepresentable.\n */\nconst STORAGE_BUCKET_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\n/**\n * Canonicalize a caller-supplied bucket name, or throw\n * {@link InvalidStorageBucketError}.\n *\n * The bucket is the *other* caller-controlled routing value in an upload\n * request, and it was the one nobody validated. `LocalStorageController`\n * builds `join(basePath, bucket)` and then checks containment against that\n * result, so a bucket of `../../etc` moved the boundary rather than crossing\n * it — the guard passed because the guard's reference point was the attacker's.\n * The containment check now resolves against the storage root, and this is the\n * check at the route boundary that stops the value before it gets there.\n *\n * A bucket is configuration, not user data: there is no legitimate caller that\n * needs a separator, a leading dot, or a `..` in one. So this refuses rather\n * than repairs, exactly as {@link canonicalStorageKey} does — a rewritten\n * bucket would silently store the object somewhere the caller did not ask for.\n *\n * Returns `undefined` when the caller named no bucket (absent, or an empty\n * form field), which is how every controller spells \"use my default\". An empty\n * string used to reach `getFullPath` and resolve to the storage *root* rather\n * than the `default` bucket, which is a third place a bare key did not\n * round-trip.\n */\nexport function canonicalStorageBucket(rawBucket: string | undefined | null): string | undefined {\n if (rawBucket === undefined || rawBucket === null || rawBucket === \"\") return undefined;\n if (rawBucket.length > MAX_STORAGE_BUCKET_LENGTH) {\n throw new InvalidStorageBucketError(\n `Storage bucket exceeds the maximum length of ${MAX_STORAGE_BUCKET_LENGTH} characters.`\n );\n }\n if (!STORAGE_BUCKET_PATTERN.test(rawBucket)) {\n throw new InvalidStorageBucketError(\n \"Storage bucket must be a single name of letters, digits, '.', '_' or '-', \" +\n \"starting with a letter or digit.\"\n );\n }\n return rawBucket;\n}\n\n/**\n * The prefix an object store's listing should be given for a caller's key, and\n * the key a listing should hand back for a folder.\n *\n * The three controllers answer the same question in two different ways, and\n * they used to disagree about the answer. `LocalStorageController` resolves a\n * *directory* on disk, so `listObjects(\"products/images\")` and\n * `listObjects(\"products/images/\")` both list what is inside it. An object\n * store under `Delimiter: \"/\"` does not: `Prefix: \"products/images\"` matches\n * `products/images.txt` and `productsomething/`, and returns none of the files\n * inside `products/images/`. So the same call — the SDK's own documented one —\n * answered one thing against local dev and another against the S3 bucket in\n * production, which is the worst place for a divergence to live.\n *\n * `listingPrefix` is what to *send*: a trailing slash, always, because that is\n * the only spelling that means \"inside this folder\" to a delimiter listing.\n * `folderKey` is what to *return* for a common prefix: no trailing slash, the\n * form the local controller has always used, so a `fullPath` from a listing is\n * a key you can pass straight back to any of them.\n */\nexport function listingPrefix(rawPrefix: string): string | undefined {\n const key = rawPrefix.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n return key === \"\" ? undefined : `${key}/`;\n}\n\n/** @see listingPrefix */\nexport function folderKey(rawPrefix: string): string {\n return rawPrefix.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n}\n\n/**\n * A listing asked for paging a controller cannot honour: a page size below one,\n * or a page token it never issued.\n *\n * Refused rather than coerced. A page size of 0 answered an empty page whose\n * next token was the one it had been handed, so `while (pageToken)` never\n * ended; a token of `-1` read before the first entry and crashed. The route\n * turns this into a 400.\n */\nexport class InvalidListOptionsError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidListOptionsError\";\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"keys-COzQ2aE8.js","names":[],"sources":["../src/storage/keys.ts"],"sourcesContent":["/**\n * Canonical storage keys and bucket names.\n *\n * Storage is not under RLS, so a `storageAuthorize` hook is the whole access\n * control model — and a hook can only be correct if the key it is shown is the\n * key that is written. That is the invariant this module exists to hold: one\n * canonical string, computed once per request, handed to the hook, to the\n * controller, and to the download token alike.\n *\n * ## Why rejecting beats stripping\n *\n * The previous `sanitizeStorageKey` *stripped* `../` in a single pass. Two\n * things were wrong with that, and only one of them was the obvious one.\n *\n * The obvious one: a single pass is not a fixed point. `....//` contains `../`\n * at offset 2, so removing it leaves `../` behind — the sanitizer manufactured\n * the traversal it was there to remove. `users/alice/....//bob/x` came out as\n * `users/alice/../bob/x`, which a prefix hook reads as alice's (it starts with\n * `users/alice/`) and the filesystem reads as bob's. The hook approved one\n * object and the controller wrote another.\n *\n * The subtler one, and the reason this is a rewrite rather than a loop: even a\n * correct strip is a silent rewrite. A caller who asks to store at `a/../b` and\n * gets an object at `a/b` was not protected, they were misled — and every later\n * read, ownership row and audit line now refers to a path nobody chose. So a key\n * that means something other than what it says is refused (400), not repaired.\n *\n * Note what is NOT traversal under this rule: `....` is an ordinary directory\n * name, and `users/alice/....//bob/x` canonicalizes to\n * `users/alice/..../bob/x` — still comfortably inside alice's prefix, which is\n * exactly right. Only a real `..` segment is refused.\n */\n\nimport { DEFAULT_STORAGE_SOURCE_KEY } from \"@rebasepro/types\";\n\n/**\n * `path.posix.normalize`, without `node:path`.\n *\n * A storage key is a POSIX-shaped string that never touches a filesystem, so\n * reaching for `node:path` to fold `.` and `//` out of it was always a little\n * wrong on its own terms — on Windows the platform `path` would have applied\n * different rules to the same key. It also put the module that every storage\n * read, ownership row and audit line goes through on the list of things that\n * only run in a Node process.\n *\n * This is the algorithm Node implements, transcribed: segments are folded left\n * to right, `..` pops unless it would climb past the root of a relative path,\n * and both the leading and the trailing separator survive the round trip. The\n * `..` branch is unreachable from {@link canonicalStorageKey}, which refuses\n * those keys outright before it gets here — it exists so that this function is\n * *the* normalizer rather than a subset of one, and\n * `storage-keys.property.test.ts` holds it to that with a property test against\n * `path.posix.normalize` itself. Exported for exactly that — nothing outside\n * this module should be normalizing a key.\n */\nexport function normalizePosix(input: string): string {\n if (input.length === 0) return \".\";\n const isAbsolute = input.startsWith(\"/\");\n const trailingSeparator = input.endsWith(\"/\");\n\n const segments: string[] = [];\n for (const segment of input.split(\"/\")) {\n if (segment === \"\" || segment === \".\") continue;\n if (segment === \"..\") {\n if (segments.length > 0 && segments[segments.length - 1] !== \"..\") segments.pop();\n else if (!isAbsolute) segments.push(\"..\");\n continue;\n }\n segments.push(segment);\n }\n\n const joined = segments.join(\"/\");\n if (joined === \"\") {\n if (isAbsolute) return \"/\";\n return trailingSeparator ? \"./\" : \".\";\n }\n const withTrailing = trailingSeparator ? `${joined}/` : joined;\n return isAbsolute ? `/${withTrailing}` : withTrailing;\n}\n\n/** Longest key accepted, in UTF-16 code units. Matches the previous cap. */\nexport const MAX_STORAGE_KEY_LENGTH = 1024;\n\n/**\n * A key that cannot be canonicalized. Carries no path back to the caller\n * beyond what they sent, so it is safe to surface as a 400 message.\n */\nexport class InvalidStorageKeyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStorageKeyError\";\n }\n}\n\n/**\n * Canonicalize a caller-supplied storage key, or throw\n * {@link InvalidStorageKeyError}.\n *\n * Normalizations applied (safe, idempotent, and meaning-preserving):\n * - leading slashes removed — `/a/b` and `a/b` name the same object\n * - `.` segments and repeated slashes collapsed\n *\n * Refusals (the key means something other than what it says):\n * - any `..` segment, on either separator, at any depth\n * - null bytes\n * - keys longer than {@link MAX_STORAGE_KEY_LENGTH}\n *\n * A trailing slash is preserved: it is how the folder route marks a prefix.\n */\nexport function canonicalStorageKey(rawKey: string): string {\n if (rawKey.includes(\"\\0\")) {\n throw new InvalidStorageKeyError(\"Storage key contains a null byte.\");\n }\n if (rawKey.length > MAX_STORAGE_KEY_LENGTH) {\n throw new InvalidStorageKeyError(\n `Storage key exceeds the maximum length of ${MAX_STORAGE_KEY_LENGTH} characters.`\n );\n }\n\n // Split on both separators: `path.join` treats `\\` as a separator on\n // Windows, so `..\\` is traversal there even though POSIX reads it as a\n // filename. Checked against the RAW key, before any normalization — the\n // whole point is that `a/../b` is refused rather than quietly turned into\n // `b`.\n if (rawKey.split(/[\\\\/]/).some((segment) => segment === \"..\")) {\n throw new InvalidStorageKeyError(\n \"Storage key contains a '..' path segment. Keys must name an object directly.\"\n );\n }\n\n const withoutLeadingSlashes = rawKey.replace(/^\\/+/, \"\");\n if (withoutLeadingSlashes === \"\") return \"\";\n\n // Whether the key names a *directory* rather than an object: it ends with a\n // separator, or its last segment is the `.` that means \"this directory\".\n //\n // Recorded before normalizing, because normalization drops the\n // distinction in one of those two spellings and not the other: `public/.`\n // becomes `public` while `public/./` stays `public/`. That asymmetry breaks\n // the rule this module states — a trailing slash is preserved, because it is\n // how the folder route marks a prefix — and it is not cosmetic on `list`,\n // where a prefix of `public` also matches `publicity/` and hands back keys\n // the caller never asked for.\n const denotesDirectory = /(?:^|\\/)\\.?$/.test(withoutLeadingSlashes);\n\n // Safe now: with no `..` segment in the input, `normalize` can only collapse\n // `.` and duplicate slashes — it cannot climb.\n const normalized = normalizePosix(withoutLeadingSlashes);\n\n // `normalize(\".\")`, and `normalize(\"./\")` → `./`; neither names an object.\n if (normalized === \".\" || normalized === \"./\") return \"\";\n\n // Defensive: nothing reaching here still carries a leading `./`, since the\n // leading slashes are already gone and `normalize` only emits `./` for a\n // key that normalizes to nothing — which returned above.\n const key = normalized.replace(/^\\.\\//, \"\").replace(/^\\/+/, \"\");\n if (key === \"\") return \"\";\n return denotesDirectory && !key.endsWith(\"/\") ? `${key}/` : key;\n}\n\n/**\n * Canonicalize, or return `null` when the key is not canonicalizable.\n *\n * For callers that must fail closed without an exception — the download-token\n * middleware compares a request path against a granted path, and a key it\n * cannot canonicalize simply matches nothing.\n */\nexport function tryCanonicalStorageKey(rawKey: string): string | null {\n try {\n return canonicalStorageKey(rawKey);\n } catch {\n return null;\n }\n}\n\n/**\n * Canonicalize the storage *source* a request names (`?storageId=`), so that\n * \"the default source\" has exactly one spelling.\n *\n * The default source can be asked for three ways — the parameter omitted, sent\n * empty, or sent as the literal `(default)` — and all three resolve to the same\n * controller. Without a single spelling, the value derived when a download\n * token is minted and the value derived when it is presented can differ for the\n * same object, which is either a spurious 403 or, if the comparison is dropped\n * to stop those, no scoping at all.\n *\n * Deliberately *not* validated against the registry: this is a naming rule, not\n * an existence check, and it must give the same answer in `auth/` (which has no\n * registry) as in the storage routes. An id that names no source still\n * canonicalizes to itself and simply matches only itself.\n */\nexport function canonicalStorageId(rawStorageId: string | undefined | null): string {\n if (rawStorageId === undefined || rawStorageId === null) return DEFAULT_STORAGE_SOURCE_KEY;\n const trimmed = rawStorageId.trim();\n return trimmed === \"\" ? DEFAULT_STORAGE_SOURCE_KEY : trimmed;\n}\n\n/**\n * Longest bucket name accepted. 63 is the S3/GCS limit, and a local bucket is\n * one directory name, so the tighter of the two bounds everything.\n */\nexport const MAX_STORAGE_BUCKET_LENGTH = 63;\n\n/** A bucket name that does not name a bucket. See {@link canonicalStorageBucket}. */\nexport class InvalidStorageBucketError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStorageBucketError\";\n }\n}\n\n/**\n * One path segment: letters, digits, `.`, `_`, `-`, first character\n * alphanumeric. Deliberately narrow — it is the intersection of what S3, GCS\n * and a filesystem directory all accept, and it makes `..`, `.tus-uploads`,\n * absolute paths and anything containing a separator unrepresentable.\n */\nconst STORAGE_BUCKET_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\n/**\n * Canonicalize a caller-supplied bucket name, or throw\n * {@link InvalidStorageBucketError}.\n *\n * The bucket is the *other* caller-controlled routing value in an upload\n * request, and it was the one nobody validated. `LocalStorageController`\n * builds `join(basePath, bucket)` and then checks containment against that\n * result, so a bucket of `../../etc` moved the boundary rather than crossing\n * it — the guard passed because the guard's reference point was the attacker's.\n * The containment check now resolves against the storage root, and this is the\n * check at the route boundary that stops the value before it gets there.\n *\n * A bucket is configuration, not user data: there is no legitimate caller that\n * needs a separator, a leading dot, or a `..` in one. So this refuses rather\n * than repairs, exactly as {@link canonicalStorageKey} does — a rewritten\n * bucket would silently store the object somewhere the caller did not ask for.\n *\n * Returns `undefined` when the caller named no bucket (absent, or an empty\n * form field), which is how every controller spells \"use my default\". An empty\n * string used to reach `getFullPath` and resolve to the storage *root* rather\n * than the `default` bucket, which is a third place a bare key did not\n * round-trip.\n */\nexport function canonicalStorageBucket(rawBucket: string | undefined | null): string | undefined {\n if (rawBucket === undefined || rawBucket === null || rawBucket === \"\") return undefined;\n if (rawBucket.length > MAX_STORAGE_BUCKET_LENGTH) {\n throw new InvalidStorageBucketError(\n `Storage bucket exceeds the maximum length of ${MAX_STORAGE_BUCKET_LENGTH} characters.`\n );\n }\n if (!STORAGE_BUCKET_PATTERN.test(rawBucket)) {\n throw new InvalidStorageBucketError(\n \"Storage bucket must be a single name of letters, digits, '.', '_' or '-', \" +\n \"starting with a letter or digit.\"\n );\n }\n return rawBucket;\n}\n\n/**\n * The prefix an object store's listing should be given for a caller's key, and\n * the key a listing should hand back for a folder.\n *\n * The three controllers answer the same question in two different ways, and\n * they used to disagree about the answer. `LocalStorageController` resolves a\n * *directory* on disk, so `listObjects(\"products/images\")` and\n * `listObjects(\"products/images/\")` both list what is inside it. An object\n * store under `Delimiter: \"/\"` does not: `Prefix: \"products/images\"` matches\n * `products/images.txt` and `productsomething/`, and returns none of the files\n * inside `products/images/`. So the same call — the SDK's own documented one —\n * answered one thing against local dev and another against the S3 bucket in\n * production, which is the worst place for a divergence to live.\n *\n * `listingPrefix` is what to *send*: a trailing slash, always, because that is\n * the only spelling that means \"inside this folder\" to a delimiter listing.\n * `folderKey` is what to *return* for a common prefix: no trailing slash, the\n * form the local controller has always used, so a `fullPath` from a listing is\n * a key you can pass straight back to any of them.\n */\nexport function listingPrefix(rawPrefix: string): string | undefined {\n const key = rawPrefix.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n return key === \"\" ? undefined : `${key}/`;\n}\n\n/** @see listingPrefix */\nexport function folderKey(rawPrefix: string): string {\n return rawPrefix.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n}\n\n/**\n * A listing asked for paging a controller cannot honour: a page size below one,\n * or a page token it never issued.\n *\n * Refused rather than coerced. A page size of 0 answered an empty page whose\n * next token was the one it had been handed, so `while (pageToken)` never\n * ended; a token of `-1` read before the first entry and crashed. The route\n * turns this into a 400.\n */\nexport class InvalidListOptionsError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidListOptionsError\";\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createRequire as __rebaseCreateRequire } from "module";
|
|
2
|
+
import __rebaseProcess from "process";
|
|
3
|
+
globalThis.process ??= __rebaseProcess;
|
|
4
|
+
__rebaseCreateRequire(import.meta.url);
|
|
5
|
+
import * as path$1 from "path";
|
|
6
|
+
//#region ../common/src/util/deprecation.ts
|
|
7
|
+
/**
|
|
8
|
+
* Keys already warned about in this process. A deprecated name is usually
|
|
9
|
+
* reached on a hot path — every request, every render — and one line naming
|
|
10
|
+
* the replacement is enough; repeating it buries everything else in the log.
|
|
11
|
+
*/
|
|
12
|
+
var warnedKeys = /* @__PURE__ */ new Set();
|
|
13
|
+
/**
|
|
14
|
+
* Say once, per process, that a deprecated public name was used.
|
|
15
|
+
*
|
|
16
|
+
* `key` identifies the deprecated name (`"client.ws.setAuthTokenGetter"`):
|
|
17
|
+
* the first call with a key prints `message` with `console.warn`, every later
|
|
18
|
+
* call with the same key prints nothing. `message` should name the
|
|
19
|
+
* replacement, so the warning is the whole migration instruction.
|
|
20
|
+
*
|
|
21
|
+
* Node-free and browser-safe: it touches nothing but `console`.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* // Declared with `@deprecated Use \`newName\` instead.` in its JSDoc.
|
|
26
|
+
* export function oldName(...args: Parameters<typeof newName>) {
|
|
27
|
+
* warnDeprecated("oldName", "`oldName` is deprecated; use `newName` instead.");
|
|
28
|
+
* return newName(...args);
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
function warnDeprecated(key, message) {
|
|
33
|
+
if (warnedKeys.has(key)) return;
|
|
34
|
+
warnedKeys.add(key);
|
|
35
|
+
console.warn(`[Rebase] Deprecated: ${message}`);
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/utils/dynamic-import.ts
|
|
39
|
+
/**
|
|
40
|
+
* Native ESM dynamic import.
|
|
41
|
+
*
|
|
42
|
+
* Wrapped in `new Function` so TypeScript / bundlers don't down-compile the
|
|
43
|
+
* `import()` to `require()` — the loaders must import user-authored `.ts`/`.js`
|
|
44
|
+
* files at runtime using the host's real ESM loader. This is the production
|
|
45
|
+
* default for {@link loadFunctionsFromDirectory} / {@link loadCronJobsFromDirectory}.
|
|
46
|
+
*
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
var nativeDynamicImport = (() => {
|
|
50
|
+
const doImport = new Function("url", "return import(url)");
|
|
51
|
+
return (url) => doImport(url);
|
|
52
|
+
})();
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/utils/load-failure.ts
|
|
55
|
+
/**
|
|
56
|
+
* How a function or cron file that failed to import is listed in the boot
|
|
57
|
+
* log: `"<file> (syntax error at <file>:<line>: <message>)"` for a syntax
|
|
58
|
+
* error, `"<file> (threw: <message>)"` for anything else.
|
|
59
|
+
*
|
|
60
|
+
* The two need different remedies. A file that *threw* usually read an
|
|
61
|
+
* undefined env var at module scope; a file with a *syntax error* needs its
|
|
62
|
+
* syntax fixed, and the loaders' advice is keyed on which one this is. Node
|
|
63
|
+
* puts a syntax error's location only in the first line of its stack, so that
|
|
64
|
+
* is where the line number comes from.
|
|
65
|
+
*/
|
|
66
|
+
function describeLoadFailure(file, err) {
|
|
67
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
68
|
+
if (err instanceof Error && err.name === "SyntaxError") {
|
|
69
|
+
const firstLine = err.stack?.split("\n", 1)[0] ?? "";
|
|
70
|
+
const at = /:(\d+)(?::\d+)?$/.exec(firstLine);
|
|
71
|
+
return `${file} (syntax error${at ? ` at ${path$1.basename(file)}:${at[1]}` : ""}: ${message})`;
|
|
72
|
+
}
|
|
73
|
+
return `${file} (threw: ${message})`;
|
|
74
|
+
}
|
|
75
|
+
/** The advice for {@link describeLoadFailure}'s syntax-error entries. */
|
|
76
|
+
var SYNTAX_ERROR_ADVICE = " A file with a syntax error never ran: fix the syntax at the line shown (`tsc --noEmit` or your editor will point at it too).";
|
|
77
|
+
//#endregion
|
|
78
|
+
export { warnDeprecated as i, describeLoadFailure as n, nativeDynamicImport as r, SYNTAX_ERROR_ADVICE as t };
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=load-failure-L5hvfDoG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-failure-L5hvfDoG.js","names":[],"sources":["../../common/src/util/deprecation.ts","../src/utils/dynamic-import.ts","../src/utils/load-failure.ts"],"sourcesContent":["/**\n * Keys already warned about in this process. A deprecated name is usually\n * reached on a hot path — every request, every render — and one line naming\n * the replacement is enough; repeating it buries everything else in the log.\n */\nconst warnedKeys = new Set<string>();\n\n/**\n * Say once, per process, that a deprecated public name was used.\n *\n * `key` identifies the deprecated name (`\"client.ws.setAuthTokenGetter\"`):\n * the first call with a key prints `message` with `console.warn`, every later\n * call with the same key prints nothing. `message` should name the\n * replacement, so the warning is the whole migration instruction.\n *\n * Node-free and browser-safe: it touches nothing but `console`.\n *\n * @example\n * ```ts\n * // Declared with `@deprecated Use \\`newName\\` instead.` in its JSDoc.\n * export function oldName(...args: Parameters<typeof newName>) {\n * warnDeprecated(\"oldName\", \"`oldName` is deprecated; use `newName` instead.\");\n * return newName(...args);\n * }\n * ```\n */\nexport function warnDeprecated(key: string, message: string): void {\n if (warnedKeys.has(key)) return;\n warnedKeys.add(key);\n console.warn(`[Rebase] Deprecated: ${message}`);\n}\n\n/**\n * Forget which keys have been warned about, so a test can assert that a\n * deprecated name warns — once — regardless of what ran before it.\n *\n * @internal For tests only.\n */\nexport function resetDeprecationWarningsForTests(): void {\n warnedKeys.clear();\n}\n","/**\n * Shape returned by a dynamic module import: an object whose `default`\n * property holds the module's default export (CJS `module.exports` is exposed\n * as `default` by Node's ESM interop).\n */\nexport interface ImportedModule {\n default?: unknown;\n [key: string]: unknown;\n}\n\n/**\n * Imports a module by file URL. Injected into the directory loaders so tests\n * can supply a deterministic importer.\n */\nexport type ModuleImporter = (url: string) => Promise<ImportedModule>;\n\n/**\n * Native ESM dynamic import.\n *\n * Wrapped in `new Function` so TypeScript / bundlers don't down-compile the\n * `import()` to `require()` — the loaders must import user-authored `.ts`/`.js`\n * files at runtime using the host's real ESM loader. This is the production\n * default for {@link loadFunctionsFromDirectory} / {@link loadCronJobsFromDirectory}.\n *\n * @internal\n */\nexport const nativeDynamicImport: ModuleImporter = (() => {\n const doImport = new Function(\"url\", \"return import(url)\") as (url: string) => Promise<ImportedModule>;\n return (url: string) => doImport(url);\n})();\n","import * as path from \"path\";\n\n/**\n * How a function or cron file that failed to import is listed in the boot\n * log: `\"<file> (syntax error at <file>:<line>: <message>)\"` for a syntax\n * error, `\"<file> (threw: <message>)\"` for anything else.\n *\n * The two need different remedies. A file that *threw* usually read an\n * undefined env var at module scope; a file with a *syntax error* needs its\n * syntax fixed, and the loaders' advice is keyed on which one this is. Node\n * puts a syntax error's location only in the first line of its stack, so that\n * is where the line number comes from.\n */\nexport function describeLoadFailure(file: string, err: unknown): string {\n const message = err instanceof Error ? err.message : String(err);\n // By name, not `instanceof`: the error comes from whatever realm did the\n // import, and its SyntaxError is not this one's.\n if (err instanceof Error && err.name === \"SyntaxError\") {\n const firstLine = err.stack?.split(\"\\n\", 1)[0] ?? \"\";\n const at = /:(\\d+)(?::\\d+)?$/.exec(firstLine);\n const where = at ? ` at ${path.basename(file)}:${at[1]}` : \"\";\n return `${file} (syntax error${where}: ${message})`;\n }\n return `${file} (threw: ${message})`;\n}\n\n/** The advice for {@link describeLoadFailure}'s syntax-error entries. */\nexport const SYNTAX_ERROR_ADVICE =\n \" A file with a syntax error never ran: fix the syntax at the line shown \" +\n \"(`tsc --noEmit` or your editor will point at it too).\";\n"],"mappings":";;;;;;;;;;;AAKA,IAAM,6BAAa,IAAI,IAAY;;;;;;;;;;;;;;;;;;;;AAqBnC,SAAgB,eAAe,KAAa,SAAuB;CAC/D,IAAI,WAAW,IAAI,GAAG,GAAG;CACzB,WAAW,IAAI,GAAG;CAClB,QAAQ,KAAK,wBAAwB,SAAS;AAClD;;;;;;;;;;;;;ACJA,IAAa,6BAA6C;CACtD,MAAM,WAAW,IAAI,SAAS,OAAO,oBAAoB;CACzD,QAAQ,QAAgB,SAAS,GAAG;AACxC,EAAA,CAAG;;;;;;;;;;;;;;AChBH,SAAgB,oBAAoB,MAAc,KAAsB;CACpE,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;CAG/D,IAAI,eAAe,SAAS,IAAI,SAAS,eAAe;EACpD,MAAM,YAAY,IAAI,OAAO,MAAM,MAAM,CAAC,CAAC,CAAC,MAAM;EAClD,MAAM,KAAK,mBAAmB,KAAK,SAAS;EAE5C,OAAO,GAAG,KAAK,gBADD,KAAK,OAAO,OAAK,SAAS,IAAI,EAAE,GAAG,GAAG,OAAO,GACtB,IAAI,QAAQ;CACrD;CACA,OAAO,GAAG,KAAK,WAAW,QAAQ;AACtC;;AAGA,IAAa,sBACT"}
|
package/dist/mcp/mcp-routes.d.ts
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import { Hono } from "hono";
|
|
24
24
|
import type { AuthAdapter, CollectionConfig, DataDriver } from "@rebasepro/types";
|
|
25
25
|
import type { HonoEnv } from "../api/types.js";
|
|
26
|
+
import { type JwtService } from "../auth/jwt.js";
|
|
26
27
|
import { type DataRateLimitConfig } from "../auth/rate-limiter.js";
|
|
27
28
|
import type { ApiKeyIdentity, ApiKeyRefusal } from "../auth/api-keys/api-key-middleware.js";
|
|
28
29
|
/**
|
|
@@ -37,6 +38,11 @@ import type { ApiKeyIdentity, ApiKeyRefusal } from "../auth/api-keys/api-key-mid
|
|
|
37
38
|
*/
|
|
38
39
|
export declare const MAX_BATCH_MESSAGES = 20;
|
|
39
40
|
export interface McpRoutesConfig {
|
|
41
|
+
/**
|
|
42
|
+
* The backend whose MCP access tokens this endpoint accepts — its own key
|
|
43
|
+
* signed them. The process default when omitted.
|
|
44
|
+
*/
|
|
45
|
+
jwt?: JwtService;
|
|
40
46
|
/**
|
|
41
47
|
* Verify an `rk_` API key, for clients that are configured with a header
|
|
42
48
|
* rather than an OAuth flow. A key reaches the tools its `data:*` scopes
|
package/dist/mcp/mcp-tools.d.ts
CHANGED
|
@@ -51,6 +51,14 @@ export interface McpToolDefinition {
|
|
|
51
51
|
* to be listed, and on the collection a call names for it to run.
|
|
52
52
|
*/
|
|
53
53
|
requiredScope: McpScope;
|
|
54
|
+
/**
|
|
55
|
+
* Whether the reply carries the result a second time, as
|
|
56
|
+
* `structuredContent`. The text block always carries it — it is what a
|
|
57
|
+
* model reads. A result a program consumes (rows, a count, a write's
|
|
58
|
+
* outcome) also goes out as typed data; one only a model reads does not,
|
|
59
|
+
* so the reply is not twice its size. Defaults to true.
|
|
60
|
+
*/
|
|
61
|
+
structuredContent?: boolean;
|
|
54
62
|
run(args: Record<string, unknown>, ctx: McpToolContext): Promise<unknown>;
|
|
55
63
|
}
|
|
56
64
|
/** An error whose message is safe to hand back to the model. */
|
|
@@ -25,10 +25,17 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { Hono } from "hono";
|
|
27
27
|
import type { HonoEnv } from "../api/types.js";
|
|
28
|
+
import { type JwtService } from "../auth/jwt.js";
|
|
28
29
|
import type { AuthRepository } from "../auth/interfaces.js";
|
|
29
30
|
import type { OAuthStore } from "./oauth-store.js";
|
|
30
31
|
export interface OAuthRoutesConfig {
|
|
31
32
|
store: OAuthStore;
|
|
33
|
+
/**
|
|
34
|
+
* The backend this authorization server issues for: its key signs the
|
|
35
|
+
* MCP access tokens and the authorize-request token, and verifies the
|
|
36
|
+
* person's session at consent. The process default when omitted.
|
|
37
|
+
*/
|
|
38
|
+
jwt?: JwtService;
|
|
32
39
|
/** The externally reachable origin of this deployment. */
|
|
33
40
|
publicUrl: string;
|
|
34
41
|
/** Where `/mcp` is mounted, e.g. `/mcp`. */
|
package/dist/metrics/index.d.ts
CHANGED
|
@@ -31,11 +31,24 @@ export type MetricSurface = "data" | "auth" | "storage" | "functions" | "admin"
|
|
|
31
31
|
| "other";
|
|
32
32
|
/** Whether a path is the platform looking in, rather than a request served. */
|
|
33
33
|
export declare function isUncountedPath(pathname: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* How long a query waited for a pooled database connection, in milliseconds —
|
|
36
|
+
* opening one included, when the pool had to. Labelled by data source. See
|
|
37
|
+
* `pool-wait.ts`, which records it, and why it matters there.
|
|
38
|
+
*/
|
|
39
|
+
export declare const DB_POOL_WAIT_METRIC = "rebase_db_pool_wait_ms";
|
|
40
|
+
/**
|
|
41
|
+
* Buckets for {@link DB_POOL_WAIT_METRIC}. A checkout from a pool with an idle
|
|
42
|
+
* connection takes well under a millisecond, so the low end is finer than a
|
|
43
|
+
* request's: the interesting number is how much of the tail is above zero.
|
|
44
|
+
*/
|
|
45
|
+
export declare const POOL_WAIT_BUCKETS_MS: number[];
|
|
34
46
|
export declare class MetricsRegistry {
|
|
35
47
|
private requests;
|
|
36
48
|
private latency;
|
|
37
49
|
private gauges;
|
|
38
50
|
private counters;
|
|
51
|
+
private histograms;
|
|
39
52
|
readonly startedAt: number;
|
|
40
53
|
private static labelKey;
|
|
41
54
|
private static parseLabels;
|
|
@@ -51,6 +64,11 @@ export declare class MetricsRegistry {
|
|
|
51
64
|
recordRequest(labels: Record<string, string>, durationMs: number): void;
|
|
52
65
|
incrementCounter(name: string, labels?: Record<string, string>, by?: number): void;
|
|
53
66
|
setGauge(name: string, value: number, labels?: Record<string, string>): void;
|
|
67
|
+
/**
|
|
68
|
+
* Record one observation in a named histogram. `buckets` are its upper
|
|
69
|
+
* bounds, fixed by the first observation of the series.
|
|
70
|
+
*/
|
|
71
|
+
observe(name: string, value: number, labels?: Record<string, string>, buckets?: readonly number[]): void;
|
|
54
72
|
/** Prometheus escaping: backslash, quote and newline, in that order. */
|
|
55
73
|
private static formatLabels;
|
|
56
74
|
/** Group `name -> [[labelKey, value]]` for one-HELP-per-metric rendering. */
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long queries wait for a pooled database connection.
|
|
3
|
+
*
|
|
4
|
+
* A write holds its connection — and its transaction, and the locks it has
|
|
5
|
+
* taken — for as long as its callbacks run: `beforeSave` runs after the
|
|
6
|
+
* transaction has begun, `afterSave` before it commits. Callbacks that await
|
|
7
|
+
* something slow therefore take connections out of the pool for that long, and
|
|
8
|
+
* once enough of them run at once every other request queues for one. Measured
|
|
9
|
+
* with the default pool of 20: 25 concurrent saves whose `beforeSave` awaited
|
|
10
|
+
* 300 ms made an unrelated one-row read wait 290 ms for a connection.
|
|
11
|
+
*
|
|
12
|
+
* Nothing else shows that queue. Request latency rises, but so it does for a
|
|
13
|
+
* slow query; this histogram says how much of a request was spent waiting for
|
|
14
|
+
* a connection at all. Near zero is a healthy pool.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import { type MetricsRegistry } from "./index.js";
|
|
19
|
+
/** `pg.Pool`'s callback form of `connect`, which `pool.query` uses internally. */
|
|
20
|
+
type ConnectCallback = (err: Error | undefined, client: unknown, done: (release?: unknown) => void) => void;
|
|
21
|
+
/**
|
|
22
|
+
* `connect` as a `pg.Pool` has it: a promise without a callback, the callback
|
|
23
|
+
* with one. Both are timed — transactions check out with the first,
|
|
24
|
+
* `pool.query` with the second.
|
|
25
|
+
*/
|
|
26
|
+
export type PoolConnect = (callback?: ConnectCallback) => Promise<unknown> | void;
|
|
27
|
+
/**
|
|
28
|
+
* Time every checkout from `pool`, from the call to the moment a connection is
|
|
29
|
+
* handed over, and report it to `record` in milliseconds. A failed checkout is
|
|
30
|
+
* not recorded: it is an error, not a wait.
|
|
31
|
+
*
|
|
32
|
+
* Wraps `connect` on this pool object only. `false` when the pool has no
|
|
33
|
+
* `connect` to time (a driver that is not pool-based) or is timed already.
|
|
34
|
+
*/
|
|
35
|
+
export declare function timePoolCheckouts(pool: {
|
|
36
|
+
connect?: PoolConnect;
|
|
37
|
+
}, record: (waitMs: number) => void): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Time the checkouts of every pool-based data source into `registry`'s
|
|
40
|
+
* {@link DB_POOL_WAIT_METRIC} histogram, labelled by the source's key.
|
|
41
|
+
*/
|
|
42
|
+
export declare function recordPoolWaits(registry: MetricsRegistry, sources: readonly {
|
|
43
|
+
key: string;
|
|
44
|
+
connection: {
|
|
45
|
+
pool?: {
|
|
46
|
+
connect?: PoolConnect;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
}[]): void;
|
|
50
|
+
export {};
|