@rebasepro/server 0.16.0 → 0.16.1-canary.g041c925
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/{GCSStorageController-BEmDYFKc.js → GCSStorageController-Bl7nmhdv.js} +7 -6
- package/dist/{GCSStorageController-BEmDYFKc.js.map → GCSStorageController-Bl7nmhdv.js.map} +1 -1
- package/dist/{S3StorageController-B2EDXXMH.js → S3StorageController-CWvvrRpF.js} +7 -6
- package/dist/{S3StorageController-B2EDXXMH.js.map → S3StorageController-CWvvrRpF.js.map} +1 -1
- package/dist/{admin_block-DLILvzle.js → admin_block-BeypnEfb.js} +6 -6
- package/dist/admin_block-BeypnEfb.js.map +1 -0
- package/dist/api/ast-schema-editor.d.ts +23 -0
- package/dist/api/contract-routes.d.ts +1 -1
- package/dist/api/errors.d.ts +1 -1
- package/dist/api/index.d.ts +3 -3
- package/dist/api/live-schema-routes.d.ts +122 -0
- package/dist/api/logs-routes.d.ts +1 -1
- package/dist/api/mount.d.ts +41 -0
- package/dist/api/rest/api-generator.d.ts +2 -2
- package/dist/api/rest/index.d.ts +1 -1
- package/dist/api/rest/query-parser.d.ts +1 -1
- package/dist/api/schema-editor-routes.d.ts +1 -1
- package/dist/api/types.d.ts +12 -4
- package/dist/{schema-editor-routes-CV9k0w3G.js → ast-schema-editor-CDGgrIn7.js} +37 -45
- package/dist/ast-schema-editor-CDGgrIn7.js.map +1 -0
- package/dist/auth/adapter-middleware.d.ts +2 -2
- package/dist/auth/admin-roles-route.d.ts +2 -2
- package/dist/auth/admin-user-ops.d.ts +3 -3
- package/dist/auth/admin-users-route.d.ts +4 -6
- package/dist/auth/api-keys/api-key-middleware.d.ts +2 -2
- package/dist/auth/api-keys/api-key-permission-guard.d.ts +1 -1
- package/dist/auth/api-keys/api-key-routes.d.ts +2 -2
- package/dist/auth/api-keys/api-key-store.d.ts +1 -1
- package/dist/auth/api-keys/index.d.ts +9 -9
- package/dist/auth/apple-oauth.d.ts +2 -2
- package/dist/auth/auth-hooks.d.ts +4 -4
- package/dist/auth/bitbucket-oauth.d.ts +2 -2
- package/dist/auth/builtin-auth-adapter.d.ts +8 -4
- package/dist/auth/capabilities.d.ts +51 -0
- package/dist/auth/captcha.d.ts +86 -0
- package/dist/auth/cookie-utils.d.ts +2 -2
- package/dist/auth/discord-oauth.d.ts +2 -2
- package/dist/auth/facebook-oauth.d.ts +2 -2
- package/dist/auth/github-oauth.d.ts +2 -2
- package/dist/auth/gitlab-oauth.d.ts +2 -2
- package/dist/auth/google-oauth.d.ts +1 -1
- package/dist/auth/index.d.ts +58 -54
- package/dist/auth/jwks-routes.d.ts +1 -1
- package/dist/auth/jwt.d.ts +1 -1
- package/dist/auth/linkedin-oauth.d.ts +2 -2
- package/dist/auth/magic-link-routes.d.ts +10 -3
- package/dist/auth/mfa-gate.d.ts +1 -1
- package/dist/auth/mfa-routes.d.ts +3 -3
- package/dist/auth/microsoft-oauth.d.ts +2 -2
- package/dist/auth/middleware.d.ts +6 -7
- package/dist/auth/otp-routes.d.ts +89 -0
- package/dist/auth/rate-limiter.d.ts +2 -2
- package/dist/auth/require-auth.d.ts +1 -1
- package/dist/auth/reset-password-admin.d.ts +4 -4
- package/dist/auth/routes.d.ts +18 -10
- package/dist/auth/session-routes.d.ts +3 -3
- package/dist/auth/slack-oauth.d.ts +2 -2
- package/dist/auth/spotify-oauth.d.ts +2 -2
- package/dist/auth/token-revocation.d.ts +2 -2
- package/dist/auth/twitter-oauth.d.ts +2 -2
- package/dist/{auth-5Et5mnUA.js → auth-DzarZhLS.js} +603 -4376
- package/dist/auth-DzarZhLS.js.map +1 -0
- package/dist/backup/backup-common.d.ts +1 -1
- package/dist/backup/backup-routes.d.ts +3 -3
- package/dist/backup/index.d.ts +3 -3
- package/dist/{backup-C6ljYVTp.js → backup-CRdZkA6c.js} +7 -6
- package/dist/{backup-C6ljYVTp.js.map → backup-CRdZkA6c.js.map} +1 -1
- package/dist/boot/bindings.d.ts +81 -0
- package/dist/boot/boot.d.ts +5 -5
- package/dist/boot/bundle.d.ts +10 -1
- package/dist/boot/driver.d.ts +1 -1
- package/dist/boot/env.d.ts +28 -1
- package/dist/boot/fetch-bundle.d.ts +144 -1
- package/dist/boot/local-provisioner.d.ts +30 -0
- package/dist/boot/options.d.ts +30 -6
- package/dist/boot/resource-adapters.d.ts +31 -0
- package/dist/boot/resource-loading.d.ts +10 -0
- package/dist/boot/role.d.ts +3 -2
- package/dist/boot/sources.d.ts +1 -1
- package/dist/boot/version-skew.d.ts +1 -1
- package/dist/collections/BackendCollectionRegistry.d.ts +1 -1
- package/dist/collections/loader.d.ts +1 -1
- package/dist/{contract-routes-DZ-LBpSL.js → contract-routes-DDSRyGSl.js} +8 -7
- package/dist/contract-routes-DDSRyGSl.js.map +1 -0
- package/dist/cron/cron-routes.d.ts +2 -2
- package/dist/cron/cron-scheduler.d.ts +3 -3
- package/dist/cron/index.d.ts +8 -8
- package/dist/{cron-loader-YhhQeVBM.js → cron-loader-d9WMFENB.js} +8 -7
- package/dist/{cron-loader-YhhQeVBM.js.map → cron-loader-d9WMFENB.js.map} +1 -1
- package/dist/{cron-routes-maM_RlUu.js → cron-routes-B_GKclr5.js} +9 -8
- package/dist/cron-routes-B_GKclr5.js.map +1 -0
- package/dist/{cron-scheduler-DIpYBmZP.js → cron-scheduler-BB82dWuU.js} +9 -9
- package/dist/cron-scheduler-BB82dWuU.js.map +1 -0
- package/dist/{cron-store-DfH_4Cd9.js → cron-store-BTZqwkM1.js} +10 -9
- package/dist/{cron-store-DfH_4Cd9.js.map → cron-store-BTZqwkM1.js.map} +1 -1
- package/dist/{ddl-bootstrap-Cywoj8Ta.js → ddl-bootstrap-euwN8FOy.js} +21 -6
- package/dist/ddl-bootstrap-euwN8FOy.js.map +1 -0
- package/dist/deploy/pod-contract.d.ts +139 -0
- package/dist/dev-secrets.d.ts +52 -0
- package/dist/{dynamic-import-Dvh-K5fl.js → dynamic-import-X40pTZUQ.js} +5 -4
- package/dist/{dynamic-import-Dvh-K5fl.js.map → dynamic-import-X40pTZUQ.js.map} +1 -1
- package/dist/email/dev-sink.d.ts +88 -0
- package/dist/email/index.d.ts +9 -7
- package/dist/email/link-base.d.ts +1 -1
- package/dist/email/smtp-email-service.d.ts +2 -2
- package/dist/email/templates.d.ts +12 -0
- package/dist/email/types.d.ts +18 -6
- package/dist/env.d.ts +1 -1
- package/dist/{errors-EBYiaJ2E.js → errors-DBwpj9N8.js} +8 -7
- package/dist/errors-DBwpj9N8.js.map +1 -0
- package/dist/{function-loader-DDS1v7YX.js → function-loader-LLdmBFoL.js} +9 -8
- package/dist/{function-loader-DDS1v7YX.js.map → function-loader-LLdmBFoL.js.map} +1 -1
- package/dist/{function-routes-Btcez1T-.js → function-routes-ClT6UQpD.js} +8 -7
- package/dist/function-routes-ClT6UQpD.js.map +1 -0
- package/dist/functions/context.d.ts +141 -0
- package/dist/functions/define-function.d.ts +1 -1
- package/dist/functions/function-routes.d.ts +9 -3
- package/dist/functions/guards.d.ts +76 -0
- package/dist/functions/index.d.ts +96 -5
- package/dist/functions/index.js +919 -0
- package/dist/functions/index.js.map +1 -0
- package/dist/functions/internal.d.ts +26 -0
- package/dist/functions/proxy.d.ts +1 -1
- package/dist/functions/request-timeout.d.ts +9 -1
- package/dist/functions/runtime-env.d.ts +92 -0
- package/dist/functions/wait-until.d.ts +74 -0
- package/dist/history/history-routes.d.ts +2 -2
- package/dist/history/index.d.ts +1 -1
- package/dist/index.d.ts +65 -54
- package/dist/index.es.js +3498 -305
- package/dist/index.es.js.map +1 -1
- package/dist/init/docs.d.ts +1 -1
- package/dist/init/middlewares.d.ts +1 -1
- package/dist/init/shutdown.d.ts +4 -0
- package/dist/init/storage.d.ts +1 -1
- package/dist/init/surfaces.d.ts +10 -0
- package/dist/init.d.ts +171 -22
- package/dist/jobs/index.d.ts +5 -5
- package/dist/{jobs-CyOKXXlu.js → jobs-CeZzdeR8.js} +10 -9
- package/dist/{jobs-CyOKXXlu.js.map → jobs-CeZzdeR8.js.map} +1 -1
- package/dist/{jwt-DxH9fLPt.js → jwt-CpMqxVct.js} +9 -8
- package/dist/{jwt-DxH9fLPt.js.map → jwt-CpMqxVct.js.map} +1 -1
- package/dist/{logger-DfvF_8r-.js → logger-DS03e908.js} +104 -10
- package/dist/logger-DS03e908.js.map +1 -0
- package/dist/{logs-routes-CWBLQj2l.js → logs-routes-DB72iQSr.js} +36 -9
- package/dist/logs-routes-DB72iQSr.js.map +1 -0
- package/dist/metrics/index.d.ts +1 -1
- package/dist/{openapi-generator-BCKJRUS4.js → openapi-generator-wFJo22gg.js} +7 -11
- package/dist/{openapi-generator-BCKJRUS4.js.map → openapi-generator-wFJo22gg.js.map} +1 -1
- package/dist/{proxy-Bj5DVllb.js → proxy-CMymhnwG.js} +9 -6
- package/dist/{proxy-Bj5DVllb.js.map → proxy-CMymhnwG.js.map} +1 -1
- package/dist/query-parser-C3hHu17g.js +355 -0
- package/dist/query-parser-C3hHu17g.js.map +1 -0
- package/dist/{request-timeout-BuFoEKwT.js → request-timeout-C8gkc-j7.js} +21 -6
- package/dist/request-timeout-C8gkc-j7.js.map +1 -0
- package/dist/rls-audit/index.d.ts +111 -0
- package/dist/{rolldown-runtime-DSJWtz9O.js → rolldown-runtime-dW7B1o5h.js} +4 -3
- package/dist/schema-edit/apply-schema-change.d.ts +141 -0
- package/dist/schema-edit/github-repository.d.ts +67 -0
- package/dist/schema-edit/local-git-repository.d.ts +28 -0
- package/dist/schema-edit/project-root.d.ts +43 -0
- package/dist/schema-edit/remote-source.d.ts +25 -0
- package/dist/schema-edit/schema-edit-permissions.d.ts +129 -0
- package/dist/schema-editor-routes-CRKMZnXu.js +87 -0
- package/dist/schema-editor-routes-CRKMZnXu.js.map +1 -0
- package/dist/schemas-DBxgjM9A.js +4153 -0
- package/dist/schemas-DBxgjM9A.js.map +1 -0
- package/dist/{selection-_z6TM1DB.js → selection-CRpqKUbt.js} +6 -5
- package/dist/{selection-_z6TM1DB.js.map → selection-CRpqKUbt.js.map} +1 -1
- package/dist/services/webhook-service.d.ts +1 -1
- package/dist/singleton.d.ts +7 -0
- package/dist/{src-CrCxd8km.js → src-6VqGNeDB.js} +122 -5
- package/dist/src-6VqGNeDB.js.map +1 -0
- package/dist/{src-BPfYOeN4.js → src-C5vFEj-t.js} +8 -6
- package/dist/src-C5vFEj-t.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +1 -1
- package/dist/storage/LocalStorageController.d.ts +1 -1
- package/dist/storage/S3StorageController.d.ts +1 -1
- package/dist/storage/cache-headers.d.ts +87 -0
- package/dist/storage/index.d.ts +11 -11
- package/dist/storage/path-pattern.d.ts +58 -0
- package/dist/storage/policies.d.ts +88 -0
- package/dist/storage/range.d.ts +63 -0
- package/dist/storage/rendition-cache.d.ts +45 -0
- package/dist/storage/routes.d.ts +17 -5
- package/dist/storage/storage-registry.d.ts +1 -1
- package/dist/storage/triggers.d.ts +66 -0
- package/dist/storage/tus-handler.d.ts +34 -3
- package/dist/topics/runtime.d.ts +68 -0
- package/dist/{types-DSnOC4mF.js → types-BfKcm9do.js} +5 -4
- package/dist/{types-DSnOC4mF.js.map → types-BfKcm9do.js.map} +1 -1
- package/dist/utils/host.d.ts +58 -0
- package/dist/utils/logger.d.ts +0 -15
- package/dist/utils/request-id.d.ts +1 -1
- package/functions/package.json +24 -0
- package/package.json +13 -7
- package/dist/admin_block-DLILvzle.js.map +0 -1
- package/dist/auth-5Et5mnUA.js.map +0 -1
- package/dist/contract-routes-DZ-LBpSL.js.map +0 -1
- package/dist/cron-routes-maM_RlUu.js.map +0 -1
- package/dist/cron-scheduler-DIpYBmZP.js.map +0 -1
- package/dist/ddl-bootstrap-Cywoj8Ta.js.map +0 -1
- package/dist/errors-EBYiaJ2E.js.map +0 -1
- package/dist/function-routes-Btcez1T-.js.map +0 -1
- package/dist/logger-DfvF_8r-.js.map +0 -1
- package/dist/logs-routes-CWBLQj2l.js.map +0 -1
- package/dist/request-timeout-BuFoEKwT.js.map +0 -1
- package/dist/schema-editor-routes-CV9k0w3G.js.map +0 -1
- package/dist/src-BPfYOeN4.js.map +0 -1
- package/dist/src-CrCxd8km.js.map +0 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* optional peer dependency — users who don't need GCS never pay for the
|
|
6
6
|
* import cost or need it installed.
|
|
7
7
|
*/
|
|
8
|
-
import { StorageController, GCSStorageConfig } from "./types";
|
|
8
|
+
import { StorageController, GCSStorageConfig } from "./types.js";
|
|
9
9
|
import { UploadFileProps, UploadFileResult, DownloadConfig, StorageListResult } from "@rebasepro/types";
|
|
10
10
|
/**
|
|
11
11
|
* Google Cloud Storage implementation of `StorageController`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Local filesystem storage controller
|
|
3
3
|
*/
|
|
4
|
-
import { StorageController, LocalStorageConfig } from "./types";
|
|
4
|
+
import { StorageController, LocalStorageConfig } from "./types.js";
|
|
5
5
|
import { UploadFileProps, UploadFileResult, DownloadConfig, StorageListResult } from "@rebasepro/types";
|
|
6
6
|
/**
|
|
7
7
|
* Bucket used when a call names none.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* S3-compatible storage controller (works with AWS S3 and MinIO)
|
|
3
3
|
*/
|
|
4
|
-
import { S3StorageConfig, StorageController } from "./types";
|
|
4
|
+
import { S3StorageConfig, StorageController } from "./types.js";
|
|
5
5
|
import { DownloadConfig, StorageListResult, UploadFileProps, UploadFileResult } from "@rebasepro/types";
|
|
6
6
|
/**
|
|
7
7
|
* S3-compatible storage implementation
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache and revalidation headers for served objects.
|
|
3
|
+
*
|
|
4
|
+
* ## Why every byte still goes through the process
|
|
5
|
+
*
|
|
6
|
+
* `routes.ts` deliberately proxies rather than redirecting to a signed URL —
|
|
7
|
+
* mixed content and unreachable VPC endpoints, both documented there. That
|
|
8
|
+
* decision stands. What it cost was caching: a proxied response only caches as
|
|
9
|
+
* well as its headers say, and the local path sent none at all, so every load
|
|
10
|
+
* of every image re-read the file and re-sent the body.
|
|
11
|
+
*
|
|
12
|
+
* This module is the headers. It does not change where bytes come from; it
|
|
13
|
+
* lets the client stop asking for them.
|
|
14
|
+
*
|
|
15
|
+
* ## Why `immutable` is wrong here, and was
|
|
16
|
+
*
|
|
17
|
+
* The remote path sent `public, max-age=3600, immutable`, and the transform
|
|
18
|
+
* paths sent it with `max-age=31536000`. `immutable` is a promise that the body
|
|
19
|
+
* at this URL will never change. A storage key can be overwritten — `putObject`
|
|
20
|
+
* on an existing key is an ordinary operation — so the promise is false for
|
|
21
|
+
* every object this server serves, and the symptom is an upload that replaces a
|
|
22
|
+
* file which then does not visibly change for an hour, or a year.
|
|
23
|
+
*
|
|
24
|
+
* Revalidation replaces it. With a validator the client asks "still this one?"
|
|
25
|
+
* and gets a 304 with no body, which costs a round trip instead of a transfer.
|
|
26
|
+
*
|
|
27
|
+
* ## Why `public` was a leak
|
|
28
|
+
*
|
|
29
|
+
* The same header said `public` for every object, including one that required
|
|
30
|
+
* authentication to fetch. `public` is explicit permission for a *shared* cache
|
|
31
|
+
* — a CDN, a corporate proxy — to store the response and serve it to somebody
|
|
32
|
+
* else. Private objects are now `private`, which is the directive that says a
|
|
33
|
+
* browser may keep it but a shared cache may not.
|
|
34
|
+
*/
|
|
35
|
+
import type { Context } from "hono";
|
|
36
|
+
/**
|
|
37
|
+
* A weak validator built from size and modification time.
|
|
38
|
+
*
|
|
39
|
+
* Weak (`W/`) because it is not a digest of the body: two different bodies of
|
|
40
|
+
* the same length written in the same millisecond would collide. That is the
|
|
41
|
+
* same trade nginx makes for static files, and the alternative — hashing every
|
|
42
|
+
* object on every request — costs more than it saves for content this server is
|
|
43
|
+
* already streaming from somewhere else.
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildEntityTag(size: number, lastModifiedMs: number): string;
|
|
46
|
+
export interface ObjectValidators {
|
|
47
|
+
etag: string;
|
|
48
|
+
/** RFC 9110 date, second-resolution. */
|
|
49
|
+
lastModified: string;
|
|
50
|
+
lastModifiedMs: number;
|
|
51
|
+
}
|
|
52
|
+
export declare function objectValidators(size: number, lastModifiedMs: number): ObjectValidators;
|
|
53
|
+
export interface CacheControlOptions {
|
|
54
|
+
/**
|
|
55
|
+
* Whether a *shared* cache may store this. True only for objects that need
|
|
56
|
+
* no credentials to fetch.
|
|
57
|
+
*/
|
|
58
|
+
isPublic: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* How long a client may reuse the response without asking. Short by
|
|
61
|
+
* default, because the object behind it is mutable.
|
|
62
|
+
*/
|
|
63
|
+
maxAgeSeconds: number;
|
|
64
|
+
/**
|
|
65
|
+
* Public responses only: how long a stale copy may be served while a
|
|
66
|
+
* revalidation happens in the background. Shared caches honour it; it turns
|
|
67
|
+
* the revalidation round trip into something the user does not wait for.
|
|
68
|
+
*/
|
|
69
|
+
staleWhileRevalidateSeconds?: number;
|
|
70
|
+
}
|
|
71
|
+
export declare function cacheControl(options: CacheControlOptions): string;
|
|
72
|
+
/**
|
|
73
|
+
* Whether the request already holds this exact representation.
|
|
74
|
+
*
|
|
75
|
+
* `If-None-Match` wins outright when present: it is an exact validator, whereas
|
|
76
|
+
* `If-Modified-Since` has second resolution and is only consulted in its
|
|
77
|
+
* absence — again the order RFC 9110 requires.
|
|
78
|
+
*/
|
|
79
|
+
export declare function isNotModified(c: Context, validators: ObjectValidators): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Put the validators and the cache policy on a response.
|
|
82
|
+
*
|
|
83
|
+
* Applied to the 304 as well as the 200: a 304 that omits them tells the client
|
|
84
|
+
* nothing about how long the answer holds, so the next load revalidates again
|
|
85
|
+
* immediately and the round trip repeats forever.
|
|
86
|
+
*/
|
|
87
|
+
export declare function applyCacheHeaders(c: Context, validators: ObjectValidators, options: CacheControlOptions): void;
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
* For other providers (Azure Blob, etc.), implement the
|
|
12
12
|
* `StorageController` interface and pass the instance directly to the `storage` config.
|
|
13
13
|
*/
|
|
14
|
-
export * from "./types";
|
|
15
|
-
export { LocalStorageController } from "./LocalStorageController";
|
|
16
|
-
export { S3StorageController } from "./S3StorageController";
|
|
17
|
-
export { GCSStorageController } from "./GCSStorageController";
|
|
18
|
-
export { createStorageRoutes } from "./routes";
|
|
19
|
-
export type { StorageRoutesConfig } from "./routes";
|
|
20
|
-
export * from "./storage-registry";
|
|
21
|
-
export { parseTransformOptions, transformImage, isTransformableImage, TransformCache } from "./image-transform";
|
|
22
|
-
export type { ImageTransformOptions } from "./image-transform";
|
|
23
|
-
export { TusHandler } from "./tus-handler";
|
|
24
|
-
import { BackendStorageConfig, StorageController } from "./types";
|
|
14
|
+
export * from "./types.js";
|
|
15
|
+
export { LocalStorageController } from "./LocalStorageController.js";
|
|
16
|
+
export { S3StorageController } from "./S3StorageController.js";
|
|
17
|
+
export { GCSStorageController } from "./GCSStorageController.js";
|
|
18
|
+
export { createStorageRoutes } from "./routes.js";
|
|
19
|
+
export type { StorageRoutesConfig } from "./routes.js";
|
|
20
|
+
export * from "./storage-registry.js";
|
|
21
|
+
export { parseTransformOptions, transformImage, isTransformableImage, TransformCache } from "./image-transform.js";
|
|
22
|
+
export type { ImageTransformOptions } from "./image-transform.js";
|
|
23
|
+
export { TusHandler } from "./tus-handler.js";
|
|
24
|
+
import { BackendStorageConfig, StorageController } from "./types.js";
|
|
25
25
|
/**
|
|
26
26
|
* Create a storage controller from a config object.
|
|
27
27
|
*
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The storage key pattern language, in one place.
|
|
3
|
+
*
|
|
4
|
+
* `storagePolicies` invented it and `storageTriggers` needs exactly the same
|
|
5
|
+
* thing: match a key segment by segment, capture the named segments, and refuse
|
|
6
|
+
* a pattern that would silently do something other than it says. Two copies of
|
|
7
|
+
* a matcher used for access control is one copy too many — the day they drift
|
|
8
|
+
* is the day a policy and a trigger disagree about what `users/:uid/**` means,
|
|
9
|
+
* and only one of them is enforcing anything.
|
|
10
|
+
*
|
|
11
|
+
* The rules:
|
|
12
|
+
*
|
|
13
|
+
* - a literal segment matches itself
|
|
14
|
+
* - `*` matches exactly one segment
|
|
15
|
+
* - `:name` matches one segment and captures it
|
|
16
|
+
* - `**` matches the rest of the key, including nothing, and is only allowed as
|
|
17
|
+
* the final segment
|
|
18
|
+
*
|
|
19
|
+
* Compilation is strict and happens at boot: a `**` in the middle, a `:` with
|
|
20
|
+
* no name, or the same capture twice are all errors, because each of them is a
|
|
21
|
+
* pattern whose author believed something the matcher does not do.
|
|
22
|
+
*/
|
|
23
|
+
/** A pattern that cannot be compiled. Thrown at boot, never per request. */
|
|
24
|
+
export declare class StoragePatternError extends Error {
|
|
25
|
+
constructor(message: string);
|
|
26
|
+
}
|
|
27
|
+
export interface CompiledStoragePattern {
|
|
28
|
+
segments: string[];
|
|
29
|
+
/** True when the last segment is `**`. */
|
|
30
|
+
trailing: boolean;
|
|
31
|
+
/** The pattern as written, for error messages and logs. */
|
|
32
|
+
source: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Split a key into segments, dropping empty ones.
|
|
36
|
+
*
|
|
37
|
+
* Empty segments come from a leading slash or a `//`, and treating them as real
|
|
38
|
+
* would let `users//x` match a pattern expecting a captured segment with the
|
|
39
|
+
* empty string. Keys reaching here are already sanitized against traversal; this
|
|
40
|
+
* is about matching, not safety.
|
|
41
|
+
*/
|
|
42
|
+
export declare function segmentsOf(value: string): string[];
|
|
43
|
+
/**
|
|
44
|
+
* Compile a pattern, or throw {@link StoragePatternError}.
|
|
45
|
+
*
|
|
46
|
+
* `label` names the thing being compiled in any error — `storagePolicies[2]`,
|
|
47
|
+
* `storageTriggers[0]` — so a boot failure points at the line to edit.
|
|
48
|
+
*/
|
|
49
|
+
export declare function compileStoragePattern(path: string, label: string): CompiledStoragePattern;
|
|
50
|
+
/**
|
|
51
|
+
* Match a key against a compiled pattern.
|
|
52
|
+
*
|
|
53
|
+
* Returns the captured segments on a match — an empty object for a pattern that
|
|
54
|
+
* captures nothing — and `undefined` on a miss. The distinction matters: a
|
|
55
|
+
* pattern with no `:name` still matches, and `{}` is a match while `undefined`
|
|
56
|
+
* is not.
|
|
57
|
+
*/
|
|
58
|
+
export declare function matchStoragePattern(compiled: CompiledStoragePattern, key: string): Record<string, string> | undefined;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative access control for storage.
|
|
3
|
+
*
|
|
4
|
+
* Storage is not under row-level security, so `storageAuthorize` — a hook, a
|
|
5
|
+
* piece of imperative code — was the whole access-control model. That is the one
|
|
6
|
+
* place the claim "security lives in the database" does not hold, and it shows
|
|
7
|
+
* in the shape of the alternative: without a hook, **any authenticated caller
|
|
8
|
+
* may read, overwrite, delete or list any key they can name**, because storage
|
|
9
|
+
* keys share one flat namespace.
|
|
10
|
+
*
|
|
11
|
+
* A policy list says the same things declaratively, and can be read without
|
|
12
|
+
* running it:
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* storagePolicies: [
|
|
16
|
+
* { path: "public/**", operations: ["read"], allow: "public" },
|
|
17
|
+
* { path: "users/:uid/**", allow: ({ params, user }) => user?.uid === params.uid }
|
|
18
|
+
* ]
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* ## Deny by default, and why that is the whole design
|
|
22
|
+
*
|
|
23
|
+
* A key matched by no policy is refused. That makes every widening an explicit,
|
|
24
|
+
* reviewable line, and it makes a mistake in this module fail *closed* — a
|
|
25
|
+
* pattern that fails to match denies access rather than granting it. Authz code
|
|
26
|
+
* that fails open is worth more to an attacker than the feature is to anyone
|
|
27
|
+
* else, so the bias is deliberate and absolute.
|
|
28
|
+
*
|
|
29
|
+
* ## The hook remains, as an escape hatch and not as a fallback
|
|
30
|
+
*
|
|
31
|
+
* Ownership frequently lives in a row rather than in a key, which no pattern can
|
|
32
|
+
* express. So when policies match nothing, an explicit `storageAuthorize` is
|
|
33
|
+
* consulted before the request is refused. Present, it can widen; absent, the
|
|
34
|
+
* answer is no. Policies never *narrow* what the hook allowed and the hook never
|
|
35
|
+
* overrides a policy denial — each can only say yes, which keeps "who allowed
|
|
36
|
+
* this?" answerable by reading either one alone.
|
|
37
|
+
*/
|
|
38
|
+
import type { StorageAuthorize, StorageAuthorizeContext, StorageOperation } from "@rebasepro/types";
|
|
39
|
+
/** Everything a predicate is told, plus whatever the path captured. */
|
|
40
|
+
export interface StoragePolicyContext extends StorageAuthorizeContext {
|
|
41
|
+
/** Segments captured by `:name` placeholders in the matched path. */
|
|
42
|
+
params: Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
export type StoragePolicyPredicate = (ctx: StoragePolicyContext) => boolean | Promise<boolean>;
|
|
45
|
+
export interface StoragePolicy {
|
|
46
|
+
/**
|
|
47
|
+
* A key pattern, matched segment by segment against the sanitized key.
|
|
48
|
+
*
|
|
49
|
+
* - a literal segment matches itself
|
|
50
|
+
* - `*` matches exactly one segment
|
|
51
|
+
* - `:name` matches one segment and captures it
|
|
52
|
+
* - `**` matches the rest of the key, including nothing, and is only
|
|
53
|
+
* allowed as the final segment
|
|
54
|
+
*/
|
|
55
|
+
path: string;
|
|
56
|
+
/** Which operations this grants. Defaults to every one of them. */
|
|
57
|
+
operations?: StorageOperation[];
|
|
58
|
+
/**
|
|
59
|
+
* Who it grants to.
|
|
60
|
+
*
|
|
61
|
+
* - `"public"` — anyone, signed in or not
|
|
62
|
+
* - `"authenticated"` — any caller with a uid
|
|
63
|
+
* - a predicate — anything else, given the captured params and the user
|
|
64
|
+
*/
|
|
65
|
+
allow: "public" | "authenticated" | StoragePolicyPredicate;
|
|
66
|
+
}
|
|
67
|
+
export declare class StoragePolicyError extends Error {
|
|
68
|
+
constructor(message: string);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Compile a policy list into a {@link StorageAuthorize}.
|
|
72
|
+
*
|
|
73
|
+
* Compiled once, at boot, so a malformed pattern fails the start rather than the
|
|
74
|
+
* first upload. `fallback` is an explicit `storageAuthorize`, consulted only
|
|
75
|
+
* when no policy matched.
|
|
76
|
+
*/
|
|
77
|
+
export declare function compileStoragePolicies(policies: StoragePolicy[], fallback?: StorageAuthorize): StorageAuthorize;
|
|
78
|
+
/**
|
|
79
|
+
* The access-control function for a configuration, or `undefined` when it
|
|
80
|
+
* declares none.
|
|
81
|
+
*
|
|
82
|
+
* Kept separate from {@link compileStoragePolicies} so `init` has one call to
|
|
83
|
+
* make and one thing to reason about: policies, a hook, both, or neither.
|
|
84
|
+
*/
|
|
85
|
+
export declare function resolveStorageAccessControl(config: {
|
|
86
|
+
storagePolicies?: StoragePolicy[];
|
|
87
|
+
storageAuthorize?: StorageAuthorize;
|
|
88
|
+
}): StorageAuthorize | undefined;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Byte-range requests for served objects.
|
|
3
|
+
*
|
|
4
|
+
* Without `Accept-Ranges`, a browser will not offer to seek in an audio or
|
|
5
|
+
* video element served from here: it has no way to ask for the middle of a file,
|
|
6
|
+
* so scrubbing means fetching the whole thing from the start, and on a long
|
|
7
|
+
* recording it means fetching it again for every seek. Safari goes further and
|
|
8
|
+
* refuses to play a `<video>` at all unless the first response is a `206`.
|
|
9
|
+
*
|
|
10
|
+
* So this is not a performance nicety. For media it is the difference between a
|
|
11
|
+
* player that works and one that does not.
|
|
12
|
+
*
|
|
13
|
+
* ## What is implemented, and what is refused
|
|
14
|
+
*
|
|
15
|
+
* A single range — `bytes=0-499`, `bytes=500-`, `bytes=-500`. That is what every
|
|
16
|
+
* browser sends for media playback.
|
|
17
|
+
*
|
|
18
|
+
* Multiple ranges in one request (`bytes=0-99,200-299`) are **not** served.
|
|
19
|
+
* Answering them means composing a `multipart/byteranges` body, and nothing that
|
|
20
|
+
* matters sends them; a server may always answer a range request with the whole
|
|
21
|
+
* `200` instead, which is what happens here. Refusing to guess is better than a
|
|
22
|
+
* half-implemented multipart encoder.
|
|
23
|
+
*
|
|
24
|
+
* An unsatisfiable range — one starting past the end of the object — is a `416`
|
|
25
|
+
* with a `Content-Range: bytes /<size>`, as RFC 9110 requires. Answering `200`
|
|
26
|
+
* there would hand a player the whole file when it asked for a byte that does
|
|
27
|
+
* not exist, and it would never notice.
|
|
28
|
+
*/
|
|
29
|
+
/** A resolved, inclusive byte range within an object of known size. */
|
|
30
|
+
export interface ByteRange {
|
|
31
|
+
start: number;
|
|
32
|
+
/** Inclusive, as the header is. */
|
|
33
|
+
end: number;
|
|
34
|
+
/** `end - start + 1`. */
|
|
35
|
+
length: number;
|
|
36
|
+
}
|
|
37
|
+
export type RangeParse =
|
|
38
|
+
/** No `Range` header, or one this server declines to honour. Serve `200`. */
|
|
39
|
+
{
|
|
40
|
+
kind: "none";
|
|
41
|
+
}
|
|
42
|
+
/** Serve `206` with this range. */
|
|
43
|
+
| {
|
|
44
|
+
kind: "range";
|
|
45
|
+
range: ByteRange;
|
|
46
|
+
}
|
|
47
|
+
/** Serve `416`. The range cannot be satisfied for an object of this size. */
|
|
48
|
+
| {
|
|
49
|
+
kind: "unsatisfiable";
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Parse a `Range` header against an object of `size` bytes.
|
|
53
|
+
*
|
|
54
|
+
* Deliberately conservative: anything malformed, multi-range, or in a unit other
|
|
55
|
+
* than bytes resolves to `none`, which serves the whole object. A range request
|
|
56
|
+
* answered with `200` is always legal; a range answered wrongly is a corrupted
|
|
57
|
+
* download.
|
|
58
|
+
*/
|
|
59
|
+
export declare function parseRange(header: string | undefined, size: number): RangeParse;
|
|
60
|
+
/** The `Content-Range` for a served range. */
|
|
61
|
+
export declare function contentRange(range: ByteRange, size: number): string;
|
|
62
|
+
/** The `Content-Range` for a `416`, which names the size and no range. */
|
|
63
|
+
export declare function unsatisfiableContentRange(size: number): string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { StorageController } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Where renditions live inside the source bucket.
|
|
4
|
+
*
|
|
5
|
+
* Fixed rather than configurable: it is the target of a lifecycle rule and of
|
|
6
|
+
* the route-layer refusal below, and both of those are worth being able to
|
|
7
|
+
* write down once, in the documentation, for every deployment.
|
|
8
|
+
*/
|
|
9
|
+
export declare const RENDITION_PREFIX = "_rebase/renditions/";
|
|
10
|
+
/** True for a key that names the reserved rendition space. */
|
|
11
|
+
export declare function isRenditionKey(key: string): boolean;
|
|
12
|
+
export interface RenditionCacheConfig {
|
|
13
|
+
/** Off unless set. See the module comment for why. */
|
|
14
|
+
enabled?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface DurableRendition {
|
|
17
|
+
data: Buffer;
|
|
18
|
+
contentType: string;
|
|
19
|
+
}
|
|
20
|
+
export interface DurableRenditionCache {
|
|
21
|
+
/** The stored rendition for this cache key, or null. Never throws. */
|
|
22
|
+
get(controller: StorageController, cacheKey: string, bucket: string | undefined): Promise<DurableRendition | null>;
|
|
23
|
+
/** Store a rendition. Never throws: a failed write is a slower cache. */
|
|
24
|
+
put(controller: StorageController, cacheKey: string, bucket: string | undefined, rendition: DurableRendition): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The object key for a rendition.
|
|
28
|
+
*
|
|
29
|
+
* Hashed rather than derived from the source path: the source key can be up to
|
|
30
|
+
* 1024 characters and the transform options are a JSON object, so a readable
|
|
31
|
+
* composition would exceed key limits on exactly the paths people use — deep
|
|
32
|
+
* per-user prefixes. The source key is kept in the object's metadata, where it
|
|
33
|
+
* is available to anyone auditing the prefix and to nothing on the hot path.
|
|
34
|
+
*/
|
|
35
|
+
export declare function renditionKey(cacheKey: string, contentType: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* A rendition's key when reading, where the content type is not yet known.
|
|
38
|
+
*
|
|
39
|
+
* Reads try the known extensions rather than storing an index: four `getObject`
|
|
40
|
+
* calls at worst is still one round trip in the common case — the first
|
|
41
|
+
* candidate is the format the request asked for — and an index is a second
|
|
42
|
+
* thing to keep consistent with the objects it describes.
|
|
43
|
+
*/
|
|
44
|
+
export declare function renditionKeyCandidates(cacheKey: string, preferred?: string): string[];
|
|
45
|
+
export declare function createDurableRenditionCache(): DurableRenditionCache;
|
package/dist/storage/routes.d.ts
CHANGED
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
* is used.
|
|
8
8
|
*/
|
|
9
9
|
import { Hono } from "hono";
|
|
10
|
-
import { StorageController, type StorageAuthorize, type StorageAuthorizeData } from "./types";
|
|
11
|
-
import { type StorageRegistry } from "./storage-registry";
|
|
10
|
+
import { StorageController, type StorageAuthorize, type StorageAuthorizeData } from "./types.js";
|
|
11
|
+
import { type StorageRegistry } from "./storage-registry.js";
|
|
12
12
|
import { type StorageSourceDefinition, type AuthAdapter } from "@rebasepro/types";
|
|
13
|
-
import { HonoEnv } from "../api/types";
|
|
13
|
+
import { HonoEnv } from "../api/types.js";
|
|
14
|
+
import { type StorageTrigger } from "./triggers.js";
|
|
15
|
+
import { type RenditionCacheConfig } from "./rendition-cache.js";
|
|
14
16
|
/**
|
|
15
17
|
* Decide what to actually serve for a stored content type.
|
|
16
18
|
*
|
|
@@ -24,8 +26,8 @@ export declare function resolveServedContentType(storedContentType: string): {
|
|
|
24
26
|
};
|
|
25
27
|
export interface StorageRoutesConfig {
|
|
26
28
|
/**
|
|
27
|
-
*
|
|
28
|
-
* Used
|
|
29
|
+
* A single storage controller, for a backend with one storage source.
|
|
30
|
+
* Used when no `registry` is provided.
|
|
29
31
|
*/
|
|
30
32
|
controller?: StorageController;
|
|
31
33
|
/**
|
|
@@ -71,6 +73,16 @@ export interface StorageRoutesConfig {
|
|
|
71
73
|
* resolved.
|
|
72
74
|
*/
|
|
73
75
|
authorizeData?: () => StorageAuthorizeData | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Keep derived image renditions in the storage source instead of only in
|
|
78
|
+
* this process's memory. Off unless enabled — see `rendition-cache.ts` for
|
|
79
|
+
* why a read that writes to somebody's bucket is not a default.
|
|
80
|
+
*/
|
|
81
|
+
renditionCache?: RenditionCacheConfig;
|
|
82
|
+
/**
|
|
83
|
+
* Run something when an object lands, or when one goes. See `triggers.ts`.
|
|
84
|
+
*/
|
|
85
|
+
triggers?: StorageTrigger[];
|
|
74
86
|
}
|
|
75
87
|
/**
|
|
76
88
|
* Extract the wildcard portion of a route path from the full request path.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - String properties use `storageId` in their config to specify which storage to use
|
|
11
11
|
* - Properties without `storageId` fallback to "(default)"
|
|
12
12
|
*/
|
|
13
|
-
import { StorageController } from "./types";
|
|
13
|
+
import { StorageController } from "./types.js";
|
|
14
14
|
/**
|
|
15
15
|
* The default storage identifier used when:
|
|
16
16
|
* - A single storage controller is provided (not a map)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** What happened to the object. */
|
|
2
|
+
export type StorageTriggerEvent = "finalize" | "delete";
|
|
3
|
+
export interface StorageEventContext {
|
|
4
|
+
/** Which event this is. */
|
|
5
|
+
event: StorageTriggerEvent;
|
|
6
|
+
/** The canonical storage key, as written. */
|
|
7
|
+
key: string;
|
|
8
|
+
/** The bucket, when the request named one. */
|
|
9
|
+
bucket?: string;
|
|
10
|
+
/** The storage source the object lives in. */
|
|
11
|
+
storageId: string;
|
|
12
|
+
/** Size in bytes. Present on `finalize`, absent on `delete`. */
|
|
13
|
+
size?: number;
|
|
14
|
+
/** The uploader's declared content type, when there was one. */
|
|
15
|
+
contentType?: string;
|
|
16
|
+
/** Segments captured by `:name` placeholders in the matched path. */
|
|
17
|
+
params: Record<string, string>;
|
|
18
|
+
/** Who made the request, when it was authenticated. */
|
|
19
|
+
user?: {
|
|
20
|
+
uid: string;
|
|
21
|
+
email?: string;
|
|
22
|
+
roles?: string[];
|
|
23
|
+
} | null;
|
|
24
|
+
/** When the event was raised, ISO 8601. */
|
|
25
|
+
at: string;
|
|
26
|
+
}
|
|
27
|
+
export type StorageTriggerHandler = (ctx: StorageEventContext) => void | Promise<void>;
|
|
28
|
+
export interface StorageTrigger {
|
|
29
|
+
/**
|
|
30
|
+
* A key pattern, matched segment by segment against the canonical key.
|
|
31
|
+
*
|
|
32
|
+
* - a literal segment matches itself
|
|
33
|
+
* - `*` matches exactly one segment
|
|
34
|
+
* - `:name` matches one segment and captures it
|
|
35
|
+
* - `**` matches the rest of the key, including nothing, and is only
|
|
36
|
+
* allowed as the final segment
|
|
37
|
+
*/
|
|
38
|
+
path: string;
|
|
39
|
+
/** Which events fire it. Defaults to `["finalize"]`. */
|
|
40
|
+
events?: StorageTriggerEvent[];
|
|
41
|
+
/** A name for the logs. Defaults to the pattern. */
|
|
42
|
+
name?: string;
|
|
43
|
+
handler: StorageTriggerHandler;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A request's principal, in the shape a trigger is documented to receive.
|
|
47
|
+
*
|
|
48
|
+
* Hono's `user` variable is typed `AuthResult`, which admits `true` — some
|
|
49
|
+
* middleware sets a bare boolean to mean "authenticated, with no identity
|
|
50
|
+
* attached". A trigger asking "who did this?" is better told `null` than
|
|
51
|
+
* `true`, and better told `null` than a `{}` with no `uid`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function triggerUser(raw: unknown): StorageEventContext["user"];
|
|
54
|
+
export declare class StorageTriggerError extends Error {
|
|
55
|
+
constructor(message: string);
|
|
56
|
+
}
|
|
57
|
+
/** Fires the triggers a key and event match. Never throws. */
|
|
58
|
+
export type StorageTriggerDispatcher = (ctx: Omit<StorageEventContext, "params">) => Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Compile a trigger list into a dispatcher, or throw
|
|
61
|
+
* {@link StorageTriggerError}.
|
|
62
|
+
*
|
|
63
|
+
* Compiled at boot so a malformed pattern fails the start rather than the first
|
|
64
|
+
* upload — and, worse, rather than failing *silently* by matching nothing.
|
|
65
|
+
*/
|
|
66
|
+
export declare function compileStorageTriggers(triggers: StorageTrigger[]): StorageTriggerDispatcher;
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* @see https://tus.io/protocols/resumable-upload
|
|
9
9
|
*/
|
|
10
10
|
import type { Context } from "hono";
|
|
11
|
-
import type { StorageController } from "./types";
|
|
12
|
-
import { type StorageRegistry } from "./storage-registry";
|
|
11
|
+
import type { StorageController } from "./types.js";
|
|
12
|
+
import { type StorageRegistry } from "./storage-registry.js";
|
|
13
13
|
/**
|
|
14
14
|
* TUS resumable upload handler.
|
|
15
15
|
*
|
|
@@ -33,6 +33,16 @@ export declare class TusHandler {
|
|
|
33
33
|
* can only answer for the destination it is told about.
|
|
34
34
|
*/
|
|
35
35
|
private authorizeUpload?;
|
|
36
|
+
/**
|
|
37
|
+
* Called once the bytes are stored, so storage triggers fire for the
|
|
38
|
+
* resumable path too. A trigger wired only to `POST /upload` would miss
|
|
39
|
+
* exactly the uploads big enough to be worth reacting to.
|
|
40
|
+
*
|
|
41
|
+
* Never throws: the dispatcher swallows handler errors, because the
|
|
42
|
+
* object is already written and failing here would tell the client to
|
|
43
|
+
* repeat a write that succeeded.
|
|
44
|
+
*/
|
|
45
|
+
private onFinalized?;
|
|
36
46
|
private uploads;
|
|
37
47
|
private tusDir;
|
|
38
48
|
private cleanupTimer?;
|
|
@@ -49,7 +59,28 @@ export declare class TusHandler {
|
|
|
49
59
|
* Every routing value the write will use is passed in, because the hook
|
|
50
60
|
* can only answer for the destination it is told about.
|
|
51
61
|
*/
|
|
52
|
-
authorizeUpload?: ((c: Context, key: string, bucket: string, storageId?: string) => Promise<void>) | undefined
|
|
62
|
+
authorizeUpload?: ((c: Context, key: string, bucket: string, storageId?: string) => Promise<void>) | undefined,
|
|
63
|
+
/**
|
|
64
|
+
* Called once the bytes are stored, so storage triggers fire for the
|
|
65
|
+
* resumable path too. A trigger wired only to `POST /upload` would miss
|
|
66
|
+
* exactly the uploads big enough to be worth reacting to.
|
|
67
|
+
*
|
|
68
|
+
* Never throws: the dispatcher swallows handler errors, because the
|
|
69
|
+
* object is already written and failing here would tell the client to
|
|
70
|
+
* repeat a write that succeeded.
|
|
71
|
+
*/
|
|
72
|
+
onFinalized?: ((event: {
|
|
73
|
+
key: string;
|
|
74
|
+
bucket?: string;
|
|
75
|
+
storageId?: string;
|
|
76
|
+
size: number;
|
|
77
|
+
contentType?: string;
|
|
78
|
+
user?: {
|
|
79
|
+
uid: string;
|
|
80
|
+
email?: string;
|
|
81
|
+
roles?: string[];
|
|
82
|
+
} | null;
|
|
83
|
+
}) => Promise<void>) | undefined);
|
|
53
84
|
/** Ensure the temp directory exists. */
|
|
54
85
|
private ensureDir;
|
|
55
86
|
/** Start periodic cleanup of stale uploads. */
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Topics, on the durable job queue.
|
|
3
|
+
*
|
|
4
|
+
* ## Why fan out at publish time
|
|
5
|
+
*
|
|
6
|
+
* Publishing writes **one job row per subscription**, not one row for the
|
|
7
|
+
* event. That is the shape SNS→SQS has and the reason is the same: each
|
|
8
|
+
* subscriber then retries on its own schedule, and a subscription that is
|
|
9
|
+
* broken or slow neither blocks the others nor causes them to run again. A
|
|
10
|
+
* single row per event forces one retry policy across every consumer, so the
|
|
11
|
+
* fourth attempt of a failing audit log re-delivers a welcome email that
|
|
12
|
+
* already went out.
|
|
13
|
+
*
|
|
14
|
+
* It also makes "did this subscriber get it" a row somebody can look at, which
|
|
15
|
+
* is the property the job queue was built for: work that gives up is *kept*,
|
|
16
|
+
* because a queue that silently drops what it could not deliver is
|
|
17
|
+
* indistinguishable from one with nothing to do.
|
|
18
|
+
*
|
|
19
|
+
* ## What at-least-once means here
|
|
20
|
+
*
|
|
21
|
+
* A handler can see the same event twice — a worker that dies holding a job
|
|
22
|
+
* releases it after the visibility timeout, and the next worker starts the
|
|
23
|
+
* handler again from the top. Handlers must tolerate that. It is the honest
|
|
24
|
+
* name for what a retrying queue does, which is why `at-most-once` is refused
|
|
25
|
+
* at declaration rather than quietly given the other guarantee.
|
|
26
|
+
*
|
|
27
|
+
* ## Transactional publish
|
|
28
|
+
*
|
|
29
|
+
* `enqueue` is a row insert, so a publish inside a transaction that rolls back
|
|
30
|
+
* was never published — the difference between "email sent for an order that
|
|
31
|
+
* does not exist" and nothing having happened at all.
|
|
32
|
+
*/
|
|
33
|
+
import { type ResourceDeclaration, type TopicRuntime } from "@rebasepro/types";
|
|
34
|
+
import type { JobHandler, JobQueueClient } from "./../jobs/types.js";
|
|
35
|
+
/** The job task name a subscription's deliveries are recorded under. */
|
|
36
|
+
export declare function topicTaskName(topic: string, subscription: string): string;
|
|
37
|
+
/** Attempts a subscription gets before its delivery is left failed. */
|
|
38
|
+
export declare const DEFAULT_TOPIC_ATTEMPTS = 5;
|
|
39
|
+
/**
|
|
40
|
+
* Build the job handlers every declared subscription needs.
|
|
41
|
+
*
|
|
42
|
+
* Returned rather than registered, because the queue takes its whole task map
|
|
43
|
+
* at construction — and a task map assembled in one place is one somebody can
|
|
44
|
+
* print.
|
|
45
|
+
*/
|
|
46
|
+
export declare function topicJobHandlers(): Record<string, JobHandler<never>>;
|
|
47
|
+
export interface TopicRuntimeOptions {
|
|
48
|
+
queue: JobQueueClient;
|
|
49
|
+
/** Declared topics, so publishing to an undeclared one can be refused. */
|
|
50
|
+
topics: ResourceDeclaration[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Build the runtime topics publish through.
|
|
54
|
+
*
|
|
55
|
+
* Publishing to a topic nothing declared throws. The handle API makes it hard
|
|
56
|
+
* to reach, but a string name from an ejected project or a dynamic caller can —
|
|
57
|
+
* and enqueueing under a task no worker handles would write rows nobody ever
|
|
58
|
+
* runs.
|
|
59
|
+
*/
|
|
60
|
+
export declare function createTopicRuntime(options: TopicRuntimeOptions): TopicRuntime;
|
|
61
|
+
/**
|
|
62
|
+
* Install the runtime, and hand back a teardown.
|
|
63
|
+
*
|
|
64
|
+
* Publishing without one throws rather than resolving — an event that
|
|
65
|
+
* disappears into a resolved promise is the failure a queue exists to
|
|
66
|
+
* prevent — so it is installed for exactly as long as a backend is up.
|
|
67
|
+
*/
|
|
68
|
+
export declare function installTopicRuntime(options: TopicRuntimeOptions): () => void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { createRequire as
|
|
2
|
-
import "process";
|
|
3
|
-
|
|
1
|
+
import { createRequire as __rebaseCreateRequire } from "module";
|
|
2
|
+
import __rebaseProcess from "process";
|
|
3
|
+
globalThis.process ??= __rebaseProcess;
|
|
4
|
+
__rebaseCreateRequire(import.meta.url);
|
|
4
5
|
//#region src/storage/types.ts
|
|
5
6
|
/**
|
|
6
7
|
* Default maximum file size (50MB)
|
|
@@ -35,4 +36,4 @@ var DOCUMENT_MIME_TYPES = [
|
|
|
35
36
|
//#endregion
|
|
36
37
|
export { DOCUMENT_MIME_TYPES as n, IMAGE_MIME_TYPES as r, DEFAULT_MAX_FILE_SIZE as t };
|
|
37
38
|
|
|
38
|
-
//# sourceMappingURL=types-
|
|
39
|
+
//# sourceMappingURL=types-BfKcm9do.js.map
|