handoff-app 2.0.0-12 → 2.0.0-13
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/{Changelog.md → CHANGELOG.md} +84 -0
- package/{License.md → LICENSE.md} +1 -1
- package/README.md +760 -801
- package/RECIPES.md +975 -0
- package/components.json +20 -20
- package/config/docs/foundations/colors/guidelines.md +0 -1
- package/config/docs/foundations/colors.md +0 -1
- package/config/docs/foundations/effects/guidelines.md +0 -1
- package/config/docs/foundations/effects/resources.md +0 -1
- package/config/docs/foundations/effects.md +0 -1
- package/config/docs/foundations/fonts.md +0 -1
- package/config/docs/foundations/grid.md +0 -1
- package/config/docs/foundations/icon.md +0 -1
- package/config/docs/foundations/icons/guidelines.md +0 -1
- package/config/docs/foundations/icons.md +0 -1
- package/config/docs/foundations/images/guidelines.md +0 -1
- package/config/docs/foundations/images/resources.md +0 -1
- package/config/docs/foundations/images.md +0 -1
- package/config/docs/foundations/logo/resources.md +0 -1
- package/config/docs/foundations/logo.md +0 -1
- package/config/docs/foundations/typography/guidelines.md +0 -1
- package/config/docs/foundations/typography/resources.md +0 -1
- package/config/docs/foundations/typography.md +0 -1
- package/config/docs/foundations.md +0 -1
- package/config/docs/guidelines.md +0 -1
- package/config/docs/index.md +0 -1
- package/config/docs/system/tokens/foundations/colors.md +0 -4
- package/config/docs/system/tokens/foundations/effects.md +0 -4
- package/config/docs/system/tokens/foundations/typography.md +0 -2
- package/config/docs/system.md +0 -2
- package/config/templates/page.md +2 -1
- package/config/templates/page.mdx +2 -1
- package/dist/ai/define.d.ts +1 -1
- package/dist/ai/define.js +1 -1
- package/dist/ai/types.d.ts +1 -1
- package/dist/ai/types.js +1 -1
- package/dist/app-builder/build.js +14 -14
- package/dist/app-builder/client-config.js +1 -1
- package/dist/config/loader.js +1 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/nav/index.d.ts +2 -2
- package/dist/nav/registry.d.ts +3 -3
- package/dist/nav/registry.js +1 -1
- package/dist/nav/workspace.d.ts +3 -3
- package/dist/nav/workspace.js +1 -1
- package/dist/providers.d.ts +10 -0
- package/dist/{define.js → providers.js} +4 -4
- package/dist/registry/asset-storage/define.d.ts +5 -5
- package/dist/registry/asset-storage/define.js +4 -4
- package/dist/registry/asset-storage/resolve.d.ts +8 -11
- package/dist/registry/asset-storage/resolve.js +12 -13
- package/dist/registry/asset-storage/types.d.ts +12 -12
- package/dist/registry/asset-storage/types.js +2 -2
- package/dist/registry/db/schema.d.ts +2 -2
- package/dist/registry/db/schema.js +2 -2
- package/dist/registry/email/define.d.ts +1 -1
- package/dist/registry/email/define.js +1 -1
- package/dist/registry/email/resolve.d.ts +7 -8
- package/dist/registry/email/resolve.js +21 -23
- package/dist/registry/email/types.d.ts +1 -1
- package/dist/registry/email/types.js +1 -1
- package/dist/store/registry.d.ts +3 -3
- package/dist/store/registry.js +4 -4
- package/dist/types/config.d.ts +4 -13
- package/docs/cli.md +275 -54
- package/package.json +7 -7
- package/src/ai/define.ts +1 -1
- package/src/ai/types.ts +1 -1
- package/src/app/components/Auth/AccountLayout.tsx +1 -1
- package/src/app/components/Auth/AuthShell.tsx +10 -4
- package/src/app/components/Foundations/ColorGrid.tsx +13 -14
- package/src/app/components/Layout/Main.tsx +2 -2
- package/src/app/components/McpIntegration/McpConfigDialog.tsx +12 -51
- package/src/app/components/Navigation/MobileNav.tsx +0 -2
- package/src/app/components/util/index.ts +0 -11
- package/src/app/lib/asset-storage.ts +25 -25
- package/src/app/lib/auth/email.ts +4 -5
- package/src/app/lib/docs-api/page-rendering.ts +1 -1
- package/src/app/lib/docs-api/registry.ts +2 -3
- package/src/app/lib/docs-api/runtime-config.ts +8 -8
- package/src/app/lib/registry-api/asset-transfer.ts +25 -25
- package/src/app/lib/server-module.ts +1 -1
- package/src/app/lib/utils.ts +7 -0
- package/src/app/next.config.mjs +2 -2
- package/src/app/pages/[...slug]/index.tsx +2 -1
- package/src/app/pages/account/ai.tsx +1 -1
- package/src/app/pages/account/index.tsx +1 -1
- package/src/app/pages/account/tokens.tsx +1 -1
- package/src/app/pages/account/users.tsx +1 -1
- package/src/app/pages/api/docs/nav.json.ts +1 -1
- package/src/app/pages/api/mcp.ts +6 -0
- package/src/app/pages/api/registry/transfer/assets/blobs/[hash].ts +1 -1
- package/src/app/pages/cli/device.tsx +1 -1
- package/src/app/pages/foundations/colors.tsx +9 -8
- package/src/app/pages/foundations/index.tsx +3 -3
- package/src/app/pages/index.tsx +2 -2
- package/src/app/pages/login.tsx +10 -5
- package/src/app/pages/reset-password.tsx +7 -2
- package/src/app/pages/system/component/[component]/index.tsx +2 -5
- package/src/app/pages/system/index.tsx +11 -11
- package/src/app/pages/system/pattern/[pattern]/index.tsx +2 -2
- package/src/app/pages/system/tokens/components/[component]/index.tsx +0 -4
- package/src/app-builder/build.ts +10 -10
- package/src/app-builder/client-config.ts +1 -1
- package/src/config/loader.ts +1 -2
- package/src/index.ts +6 -6
- package/src/nav/index.ts +2 -2
- package/src/nav/registry.ts +3 -3
- package/src/nav/workspace.ts +3 -3
- package/src/{define.ts → providers.ts} +8 -8
- package/src/registry/asset-storage/define.ts +6 -6
- package/src/registry/asset-storage/resolve.ts +16 -20
- package/src/registry/asset-storage/types.ts +12 -12
- package/src/registry/db/schema.ts +3 -3
- package/src/registry/email/define.ts +1 -1
- package/src/registry/email/resolve.ts +23 -23
- package/src/registry/email/types.ts +1 -1
- package/src/store/registry.ts +7 -7
- package/src/types/config.ts +4 -14
- package/.editorconfig +0 -19
- package/.eslintrc.json +0 -4
- package/.prettierignore +0 -0
- package/.prettierrc.json +0 -8
- package/.vscode/settings.json +0 -17
- package/dist/define.d.ts +0 -10
- package/dist/registry/email/adapters/resend.d.ts +0 -7
- package/dist/registry/email/adapters/resend.js +0 -35
- package/src/app/components/old/Header.tsx +0 -89
- package/src/app/pages/assets/fonts.tsx +0 -129
- package/src/app/pages/assets/index.tsx +0 -91
- package/src/app/pages/assets/logos.tsx +0 -75
- package/src/registry/email/adapters/resend.ts +0 -22
- /package/dist/registry/email/{adapters → providers}/smtp.d.ts +0 -0
- /package/dist/registry/email/{adapters → providers}/smtp.js +0 -0
- /package/src/registry/email/{adapters → providers}/smtp.ts +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Typed identity helper for custom asset storage
|
|
3
|
+
* Typed identity helper for custom asset storage providers (mirrors `defineCatalogItem`): a consumer
|
|
4
4
|
* default-exports its result from a **server-only** module and points
|
|
5
5
|
* `runtime.registry.assetStorage.module` at it. It must be its own module (not an inline
|
|
6
6
|
* `defineConfig` function) so the build can trace it into the deployed registry. The default export
|
|
7
|
-
* may be the
|
|
7
|
+
* may be the provider object, or a factory `(context) => StorageProvider` when it needs its options/env.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.
|
|
11
|
-
function
|
|
10
|
+
exports.defineStorageProvider = defineStorageProvider;
|
|
11
|
+
function defineStorageProvider(input) {
|
|
12
12
|
return input;
|
|
13
13
|
}
|
|
@@ -2,34 +2,31 @@
|
|
|
2
2
|
* Asset storage settings resolution.
|
|
3
3
|
*
|
|
4
4
|
* `runtime.registry.assetStorage` selects the active provider for **new** uploads: the built-in
|
|
5
|
-
* `database` inline default or a `custom` module. The build bakes the
|
|
5
|
+
* `database` inline default or a `custom` module. The build bakes the provider, the module location,
|
|
6
6
|
* and the options. An option literal is baked as it is. An environment reference stays `{ $env }`
|
|
7
|
-
* and is resolved at request time, before the custom
|
|
7
|
+
* and is resolved at request time, before the custom provider factory runs.
|
|
8
8
|
*/
|
|
9
9
|
import type { ResolvedConfig } from '../../types/config';
|
|
10
10
|
import type { AssetStorageProvider } from '../db/schema';
|
|
11
|
-
/** Which adapter implementation is active. Recorded on each blob row as its storage provider. */
|
|
12
|
-
export type AssetStorageAdapterKind = AssetStorageProvider;
|
|
13
11
|
/** The raw, authored settings block (a structural copy of the config type, resolvable from bake). */
|
|
14
12
|
export interface AssetStorageSettings {
|
|
15
|
-
|
|
13
|
+
provider?: AssetStorageProvider;
|
|
16
14
|
module?: string;
|
|
17
15
|
maxInlineBytes?: number;
|
|
18
16
|
options?: Record<string, unknown>;
|
|
19
17
|
}
|
|
20
|
-
|
|
21
|
-
export declare const DEFAULT_ASSET_STORAGE_ADAPTER: AssetStorageAdapterKind;
|
|
18
|
+
export declare const DEFAULT_ASSET_STORAGE_PROVIDER: AssetStorageProvider;
|
|
22
19
|
/** Default max bytes kept inline in Postgres `bytea` (4 MB, under Vercel's ~4.5 MB function limit). */
|
|
23
20
|
export declare const DEFAULT_MAX_INLINE_BYTES: number;
|
|
24
21
|
/** Fully resolved asset storage settings. */
|
|
25
22
|
export interface ResolvedAssetStorage {
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
/** For `custom`: the server-only
|
|
23
|
+
/** Recorded on each new blob row as its storage provider. */
|
|
24
|
+
provider: AssetStorageProvider;
|
|
25
|
+
/** For `custom`: the server-only provider module path. */
|
|
29
26
|
module?: string;
|
|
30
27
|
/** For `database`: the inline-content size ceiling. */
|
|
31
28
|
maxInlineBytes: number;
|
|
32
|
-
/**
|
|
29
|
+
/** Provider options: literals and `{ $env }` references, never secret values. */
|
|
33
30
|
options: Record<string, unknown>;
|
|
34
31
|
}
|
|
35
32
|
/** Resolve raw asset storage settings into a fully-defaulted shape. */
|
|
@@ -3,25 +3,24 @@
|
|
|
3
3
|
* Asset storage settings resolution.
|
|
4
4
|
*
|
|
5
5
|
* `runtime.registry.assetStorage` selects the active provider for **new** uploads: the built-in
|
|
6
|
-
* `database` inline default or a `custom` module. The build bakes the
|
|
6
|
+
* `database` inline default or a `custom` module. The build bakes the provider, the module location,
|
|
7
7
|
* and the options. An option literal is baked as it is. An environment reference stays `{ $env }`
|
|
8
|
-
* and is resolved at request time, before the custom
|
|
8
|
+
* and is resolved at request time, before the custom provider factory runs.
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.resolveAssetStorageFromConfig = exports.resolveAssetStorageSettings = exports.DEFAULT_MAX_INLINE_BYTES = exports.
|
|
11
|
+
exports.resolveAssetStorageFromConfig = exports.resolveAssetStorageSettings = exports.DEFAULT_MAX_INLINE_BYTES = exports.DEFAULT_ASSET_STORAGE_PROVIDER = void 0;
|
|
12
12
|
const errors_1 = require("../../config/errors");
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
exports.DEFAULT_ASSET_STORAGE_ADAPTER = 'database';
|
|
13
|
+
const PROVIDERS = ['database', 'custom'];
|
|
14
|
+
exports.DEFAULT_ASSET_STORAGE_PROVIDER = 'database';
|
|
16
15
|
/** Default max bytes kept inline in Postgres `bytea` (4 MB, under Vercel's ~4.5 MB function limit). */
|
|
17
16
|
exports.DEFAULT_MAX_INLINE_BYTES = 4 * 1024 * 1024;
|
|
18
17
|
/** Resolve raw asset storage settings into a fully-defaulted shape. */
|
|
19
18
|
const resolveAssetStorageSettings = (settings) => {
|
|
20
19
|
var _a, _b, _c;
|
|
21
|
-
const
|
|
20
|
+
const provider = (_a = settings === null || settings === void 0 ? void 0 : settings.provider) !== null && _a !== void 0 ? _a : exports.DEFAULT_ASSET_STORAGE_PROVIDER;
|
|
22
21
|
const options = (_b = settings === null || settings === void 0 ? void 0 : settings.options) !== null && _b !== void 0 ? _b : {};
|
|
23
22
|
const maxInlineBytes = typeof (settings === null || settings === void 0 ? void 0 : settings.maxInlineBytes) === 'number' && settings.maxInlineBytes > 0 ? settings.maxInlineBytes : exports.DEFAULT_MAX_INLINE_BYTES;
|
|
24
|
-
return {
|
|
23
|
+
return { provider, module: ((_c = settings === null || settings === void 0 ? void 0 : settings.module) === null || _c === void 0 ? void 0 : _c.trim()) || undefined, maxInlineBytes, options };
|
|
25
24
|
};
|
|
26
25
|
exports.resolveAssetStorageSettings = resolveAssetStorageSettings;
|
|
27
26
|
/** Resolve and validate the asset storage block from a loaded config (CLI/build side). An unusable block fails the build. */
|
|
@@ -31,17 +30,17 @@ const resolveAssetStorageFromConfig = (config) => {
|
|
|
31
30
|
const fail = (problem) => {
|
|
32
31
|
throw new errors_1.HandoffConfigError(`Config "runtime.registry.assetStorage": ${problem}`);
|
|
33
32
|
};
|
|
34
|
-
const
|
|
35
|
-
if (!
|
|
36
|
-
fail(`unknown
|
|
33
|
+
const provider = (_c = settings === null || settings === void 0 ? void 0 : settings.provider) !== null && _c !== void 0 ? _c : exports.DEFAULT_ASSET_STORAGE_PROVIDER;
|
|
34
|
+
if (!PROVIDERS.includes(provider)) {
|
|
35
|
+
fail(`unknown provider ${JSON.stringify(provider)}. Use "database" or "custom".`);
|
|
37
36
|
}
|
|
38
37
|
const options = settings === null || settings === void 0 ? void 0 : settings.options;
|
|
39
38
|
if (options !== undefined && (options === null || typeof options !== 'object' || Array.isArray(options))) {
|
|
40
39
|
fail('"options" must be an object.');
|
|
41
40
|
}
|
|
42
41
|
const module = settings === null || settings === void 0 ? void 0 : settings.module;
|
|
43
|
-
if (
|
|
44
|
-
fail('
|
|
42
|
+
if (provider === 'custom' && (typeof module !== 'string' || !module.trim())) {
|
|
43
|
+
fail('provider "custom" needs a "module" that default-exports a defineStorageProvider() result.');
|
|
45
44
|
}
|
|
46
45
|
const maxInlineBytes = settings === null || settings === void 0 ? void 0 : settings.maxInlineBytes;
|
|
47
46
|
if (maxInlineBytes !== undefined && (typeof maxInlineBytes !== 'number' || !Number.isInteger(maxInlineBytes) || maxInlineBytes <= 0)) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Server-only asset storage contract for custom {@link
|
|
2
|
+
* Server-only asset storage contract for custom {@link defineStorageProvider} object storage modules.
|
|
3
3
|
* The built-in `database` provider is the inline default: bytes live in the `asset_blobs.content`
|
|
4
|
-
* bytea column, read/written directly by the registry, so it needs no
|
|
4
|
+
* bytea column, read/written directly by the registry, so it needs no module. Object providers
|
|
5
5
|
* record a `storageRef` + provider id so reads resolve back through whichever provider stored the
|
|
6
6
|
* blob.
|
|
7
7
|
*/
|
|
8
8
|
/** Input for storing one content-addressed blob. */
|
|
9
|
-
export interface
|
|
9
|
+
export interface StorageInput {
|
|
10
10
|
/** SHA-256 of the bytes (hex), the content identity, usable as a stable object key. */
|
|
11
11
|
hash: string;
|
|
12
12
|
bytes: Buffer;
|
|
@@ -17,7 +17,7 @@ export interface AssetStorageInput {
|
|
|
17
17
|
* How a provider returns stored content: raw bytes, a readable stream, or a signed/redirect URL the
|
|
18
18
|
* client can fetch directly (used to avoid routing large payloads through the serverless function).
|
|
19
19
|
*/
|
|
20
|
-
export type
|
|
20
|
+
export type StorageReadResult = {
|
|
21
21
|
kind: 'bytes';
|
|
22
22
|
bytes: Buffer;
|
|
23
23
|
contentType?: string;
|
|
@@ -43,10 +43,10 @@ export interface AssetUpload {
|
|
|
43
43
|
/** The reference to persist on the blob's `asset_blobs` row after the upload. */
|
|
44
44
|
storageRef: string;
|
|
45
45
|
}
|
|
46
|
-
/** The pluggable storage
|
|
47
|
-
export interface
|
|
46
|
+
/** The pluggable storage contract for object-backed asset providers. */
|
|
47
|
+
export interface StorageProvider {
|
|
48
48
|
/** Store one blob and return the reference to persist on its `asset_blobs` row. */
|
|
49
|
-
put(input:
|
|
49
|
+
put(input: StorageInput): Promise<{
|
|
50
50
|
storageRef: string;
|
|
51
51
|
}>;
|
|
52
52
|
/**
|
|
@@ -56,16 +56,16 @@ export interface AssetStorage {
|
|
|
56
56
|
*/
|
|
57
57
|
createUpload?(input: AssetUploadInput): Promise<AssetUpload>;
|
|
58
58
|
/** Resolve stored content by its `storageRef`. */
|
|
59
|
-
get(storageRef: string): Promise<
|
|
59
|
+
get(storageRef: string): Promise<StorageReadResult>;
|
|
60
60
|
/** Delete stored content by its `storageRef`. Should be retryable/idempotent. */
|
|
61
61
|
delete(storageRef: string): Promise<void>;
|
|
62
62
|
}
|
|
63
|
-
/** Context passed to a custom
|
|
64
|
-
export interface
|
|
63
|
+
/** Context passed to a custom provider factory: its config options and the process env. */
|
|
64
|
+
export interface StorageProviderContext {
|
|
65
65
|
/** `runtime.registry.assetStorage.options`, with each `fromEnv()` reference replaced by its value. */
|
|
66
66
|
options: Record<string, unknown>;
|
|
67
67
|
/** The process environment. */
|
|
68
68
|
env: NodeJS.ProcessEnv;
|
|
69
69
|
}
|
|
70
|
-
/** A custom
|
|
71
|
-
export type
|
|
70
|
+
/** A custom provider module may export the provider directly or a factory that builds it from its context. */
|
|
71
|
+
export type StorageProviderFactory = (context: StorageProviderContext) => StorageProvider | Promise<StorageProvider>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Server-only asset storage contract for custom {@link
|
|
3
|
+
* Server-only asset storage contract for custom {@link defineStorageProvider} object storage modules.
|
|
4
4
|
* The built-in `database` provider is the inline default: bytes live in the `asset_blobs.content`
|
|
5
|
-
* bytea column, read/written directly by the registry, so it needs no
|
|
5
|
+
* bytea column, read/written directly by the registry, so it needs no module. Object providers
|
|
6
6
|
* record a `storageRef` + provider id so reads resolve back through whichever provider stored the
|
|
7
7
|
* blob.
|
|
8
8
|
*/
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
import type { ArtifactBuildStatus, ArtifactKind, ArtifactOwnerKind, ArtifactReference } from '../../artifacts/types';
|
|
25
25
|
import type { RegistryTextFileKind } from '../../store/types';
|
|
26
26
|
import type { TokenSetKind } from '../tokens/sets';
|
|
27
|
-
/** Where an asset blob's bytes live: inline in the database (`database`) or in
|
|
27
|
+
/** Where an asset blob's bytes live: inline in the database (`database`) or in a custom provider (`custom`). */
|
|
28
28
|
export type AssetStorageProvider = 'database' | 'custom';
|
|
29
29
|
import type { ComponentListObject, PageListObject, PatternComponentEntry, PatternListObject } from '../../transformers/preview/types';
|
|
30
30
|
/** Registry account role. Administrators may issue write-scoped credentials. */
|
|
@@ -2439,7 +2439,7 @@ export declare const assets: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
|
2439
2439
|
/**
|
|
2440
2440
|
* Content-addressed binary blob store, shared across assets/collections (the `hash` is the blob
|
|
2441
2441
|
* identity, so an unchanged file re-references the same row). Exactly one location is active per row:
|
|
2442
|
-
* inline `content` (bytea, default
|
|
2442
|
+
* inline `content` (bytea, default `database` provider) or an external `storageRef` resolved through the
|
|
2443
2443
|
* recorded `storageProvider`. Both nullable so DB- and object-backed blobs coexist.
|
|
2444
2444
|
*/
|
|
2445
2445
|
export declare const assetBlobs: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
@@ -28,7 +28,7 @@ const drizzle_orm_1 = require("drizzle-orm");
|
|
|
28
28
|
const pg_core_1 = require("drizzle-orm/pg-core");
|
|
29
29
|
/**
|
|
30
30
|
* PostgreSQL `bytea` column. Drizzle pg-core has no built-in binary type, so declare it once here.
|
|
31
|
-
* Reads/writes are Node `Buffer`s. The default asset storage
|
|
31
|
+
* Reads/writes are Node `Buffer`s. The default asset storage provider keeps small/ordinary asset
|
|
32
32
|
* bytes inline in this column; larger assets are offloaded to an object-storage `storageRef` instead.
|
|
33
33
|
*/
|
|
34
34
|
const bytea = (0, pg_core_1.customType)({
|
|
@@ -321,7 +321,7 @@ exports.assets = (0, pg_core_1.pgTable)('assets', {
|
|
|
321
321
|
/**
|
|
322
322
|
* Content-addressed binary blob store, shared across assets/collections (the `hash` is the blob
|
|
323
323
|
* identity, so an unchanged file re-references the same row). Exactly one location is active per row:
|
|
324
|
-
* inline `content` (bytea, default
|
|
324
|
+
* inline `content` (bytea, default `database` provider) or an external `storageRef` resolved through the
|
|
325
325
|
* recorded `storageProvider`. Both nullable so DB- and object-backed blobs coexist.
|
|
326
326
|
*/
|
|
327
327
|
exports.assetBlobs = (0, pg_core_1.pgTable)('asset_blobs', {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Typed identity helper for custom email providers (mirrors `
|
|
2
|
+
* Typed identity helper for custom email providers (mirrors `defineStorageProvider`): a consumer
|
|
3
3
|
* default-exports its result from a **server-only** module and points `runtime.registry.email.module`
|
|
4
4
|
* at it. It must be its own module (not an inline `defineConfig` function) so the build can trace it
|
|
5
5
|
* into the deployed registry. The default export may be the provider object, or a factory
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Typed identity helper for custom email providers (mirrors `
|
|
3
|
+
* Typed identity helper for custom email providers (mirrors `defineStorageProvider`): a consumer
|
|
4
4
|
* default-exports its result from a **server-only** module and points `runtime.registry.email.module`
|
|
5
5
|
* at it. It must be its own module (not an inline `defineConfig` function) so the build can trace it
|
|
6
6
|
* into the deployed registry. The default export may be the provider object, or a factory
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Registry email settings resolution.
|
|
3
3
|
*
|
|
4
|
-
* `runtime.registry.email.provider` selects the built-in `
|
|
5
|
-
*
|
|
6
|
-
* baked as it is. An environment reference stays `{ $env }` and is
|
|
4
|
+
* `runtime.registry.email.provider` selects the built-in `smtp` provider or a `custom` module. Without
|
|
5
|
+
* an email block, email delivery is off. The build bakes the provider, the module location, and the
|
|
6
|
+
* options. An option literal is baked as it is. An environment reference stays `{ $env }` and is
|
|
7
|
+
* resolved at request time.
|
|
7
8
|
*/
|
|
8
9
|
import type { ResolvedConfig } from '../../types/config';
|
|
9
|
-
export type EmailProviderKind = '
|
|
10
|
-
export declare const DEFAULT_EMAIL_PROVIDER: EmailProviderKind;
|
|
11
|
-
/** Default env-var name holding the Resend API key. */
|
|
12
|
-
export declare const DEFAULT_RESEND_API_KEY_ENV = "RESEND_API_KEY";
|
|
10
|
+
export type EmailProviderKind = 'smtp' | 'custom';
|
|
13
11
|
/** Email settings as they are baked: literals and `{ $env }` references, never secret values. */
|
|
14
12
|
export interface EmailSettings {
|
|
15
13
|
from?: string;
|
|
16
|
-
|
|
14
|
+
/** Unset when the config has no email block. */
|
|
15
|
+
provider?: EmailProviderKind;
|
|
17
16
|
/** For `custom`: the server-only provider module path. */
|
|
18
17
|
module?: string;
|
|
19
18
|
options: Record<string, unknown>;
|
|
@@ -2,45 +2,46 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Registry email settings resolution.
|
|
4
4
|
*
|
|
5
|
-
* `runtime.registry.email.provider` selects the built-in `
|
|
6
|
-
*
|
|
7
|
-
* baked as it is. An environment reference stays `{ $env }` and is
|
|
5
|
+
* `runtime.registry.email.provider` selects the built-in `smtp` provider or a `custom` module. Without
|
|
6
|
+
* an email block, email delivery is off. The build bakes the provider, the module location, and the
|
|
7
|
+
* options. An option literal is baked as it is. An environment reference stays `{ $env }` and is
|
|
8
|
+
* resolved at request time.
|
|
8
9
|
*/
|
|
9
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.parseEmailSettings = exports.resolveEmailFromConfig =
|
|
11
|
+
exports.parseEmailSettings = exports.resolveEmailFromConfig = void 0;
|
|
11
12
|
const errors_1 = require("../../config/errors");
|
|
12
|
-
|
|
13
|
-
/** Default env-var name holding the Resend API key. */
|
|
14
|
-
exports.DEFAULT_RESEND_API_KEY_ENV = 'RESEND_API_KEY';
|
|
15
|
-
const PROVIDERS = ['resend', 'smtp', 'custom'];
|
|
13
|
+
const PROVIDERS = ['smtp', 'custom'];
|
|
16
14
|
/** The options each built-in provider accepts. A custom provider accepts any. */
|
|
17
15
|
const BUILT_IN_OPTIONS = {
|
|
18
|
-
resend: ['apiKey'],
|
|
19
16
|
smtp: ['host', 'port', 'secure', 'user', 'password'],
|
|
20
17
|
};
|
|
21
18
|
const isPlainRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
22
19
|
/** Resolve and validate the email block from a loaded config (CLI/build side). An unusable block fails the build. */
|
|
23
20
|
const resolveEmailFromConfig = (config) => {
|
|
24
|
-
var _a, _b, _c
|
|
21
|
+
var _a, _b, _c;
|
|
25
22
|
const email = (_b = (_a = config === null || config === void 0 ? void 0 : config.runtime) === null || _a === void 0 ? void 0 : _a.registry) === null || _b === void 0 ? void 0 : _b.email;
|
|
26
23
|
const fail = (problem) => {
|
|
27
24
|
throw new errors_1.HandoffConfigError(`Config "runtime.registry.email": ${problem}`);
|
|
28
25
|
};
|
|
29
|
-
|
|
26
|
+
if (email === undefined)
|
|
27
|
+
return { options: {} };
|
|
28
|
+
const provider = email.provider;
|
|
30
29
|
if (!PROVIDERS.includes(provider)) {
|
|
31
|
-
fail(
|
|
30
|
+
fail(email.provider === undefined
|
|
31
|
+
? '"provider" is required. Use "smtp" or "custom".'
|
|
32
|
+
: `unknown provider ${JSON.stringify(provider)}. Use "smtp" or "custom".`);
|
|
32
33
|
}
|
|
33
|
-
if (
|
|
34
|
+
if (email.options !== undefined && !isPlainRecord(email.options)) {
|
|
34
35
|
fail('"options" must be an object.');
|
|
35
36
|
}
|
|
36
|
-
const options = Object.assign({}, ((
|
|
37
|
-
const module = typeof
|
|
37
|
+
const options = Object.assign({}, ((_c = email.options) !== null && _c !== void 0 ? _c : {}));
|
|
38
|
+
const module = typeof email.module === 'string' ? email.module.trim() : undefined;
|
|
38
39
|
if (provider === 'custom') {
|
|
39
40
|
if (!module)
|
|
40
41
|
fail('provider "custom" needs a "module" that default-exports a defineEmailProvider() result.');
|
|
41
42
|
}
|
|
42
43
|
else {
|
|
43
|
-
if (
|
|
44
|
+
if (email.module !== undefined)
|
|
44
45
|
fail(`"module" is used only by provider "custom", not "${provider}".`);
|
|
45
46
|
const allowed = BUILT_IN_OPTIONS[provider];
|
|
46
47
|
const unknown = Object.keys(options).filter((key) => !allowed.includes(key));
|
|
@@ -51,26 +52,23 @@ const resolveEmailFromConfig = (config) => {
|
|
|
51
52
|
}
|
|
52
53
|
if (provider === 'smtp' && options.host === undefined)
|
|
53
54
|
fail('provider "smtp" needs "options.host".');
|
|
54
|
-
|
|
55
|
-
options.apiKey = { $env: exports.DEFAULT_RESEND_API_KEY_ENV };
|
|
56
|
-
const from = typeof (email === null || email === void 0 ? void 0 : email.from) === 'string' ? email.from.trim() || undefined : undefined;
|
|
55
|
+
const from = typeof email.from === 'string' ? email.from.trim() || undefined : undefined;
|
|
57
56
|
return Object.assign(Object.assign({ from, provider }, (module ? { module } : {})), { options });
|
|
58
57
|
};
|
|
59
58
|
exports.resolveEmailFromConfig = resolveEmailFromConfig;
|
|
60
59
|
/** Read baked settings back leniently (server side): a malformed value must not take the server down. */
|
|
61
60
|
const parseEmailSettings = (raw) => {
|
|
62
|
-
var _a;
|
|
63
61
|
let options = raw.options;
|
|
64
62
|
if (typeof options === 'string') {
|
|
65
63
|
try {
|
|
66
64
|
options = options.trim() ? JSON.parse(options) : {};
|
|
67
65
|
}
|
|
68
|
-
catch (
|
|
66
|
+
catch (_a) {
|
|
69
67
|
options = {};
|
|
70
68
|
}
|
|
71
69
|
}
|
|
72
|
-
const provider =
|
|
70
|
+
const provider = PROVIDERS.find((kind) => kind === raw.provider);
|
|
73
71
|
const module = typeof raw.module === 'string' ? raw.module.trim() : '';
|
|
74
|
-
return Object.assign(Object.assign({ from: typeof raw.from === 'string' ? raw.from.trim() || undefined : undefined, provider }, (module ? { module } : {})), { options: isPlainRecord(options) ? options : {} });
|
|
72
|
+
return Object.assign(Object.assign(Object.assign({ from: typeof raw.from === 'string' ? raw.from.trim() || undefined : undefined }, (provider ? { provider } : {})), (module ? { module } : {})), { options: isPlainRecord(options) ? options : {} });
|
|
75
73
|
};
|
|
76
74
|
exports.parseEmailSettings = parseEmailSettings;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Server-only email provider contract. Handoff renders every message, and a provider only delivers it.
|
|
3
|
-
*
|
|
3
|
+
* SMTP is built in. Anything else is a {@link defineEmailProvider} module.
|
|
4
4
|
*/
|
|
5
5
|
/** One rendered message. */
|
|
6
6
|
export interface EmailMessage {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Server-only email provider contract. Handoff renders every message, and a provider only delivers it.
|
|
4
|
-
*
|
|
4
|
+
* SMTP is built in. Anything else is a {@link defineEmailProvider} module.
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
package/dist/store/registry.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - Reads never materialize source files or run the build pipeline — every record and file is
|
|
14
14
|
* returned exactly as it was published.
|
|
15
15
|
*/
|
|
16
|
-
import type {
|
|
16
|
+
import type { StorageProvider } from '../registry/asset-storage/types';
|
|
17
17
|
import type { RegistryDatabase } from '../registry/db/client';
|
|
18
18
|
import { type AssetStorageProvider } from '../registry/db/schema';
|
|
19
19
|
import type { ComponentListObject, PageListObject, PatternListObject } from '../transformers/preview/types';
|
|
@@ -22,11 +22,11 @@ import type { AssetContentResource, AssetMetadata, AssetStore, ComponentStore, H
|
|
|
22
22
|
export interface RegistryStoreContext {
|
|
23
23
|
db: RegistryDatabase;
|
|
24
24
|
/**
|
|
25
|
-
* Resolve the {@link
|
|
25
|
+
* Resolve the {@link StorageProvider} for a blob's recorded provider (or `null` for the inline
|
|
26
26
|
* `database` provider). Injected by the app runtime so `src/store` stays free of app-side imports;
|
|
27
27
|
* when omitted, only inline DB-backed asset content is resolvable.
|
|
28
28
|
*/
|
|
29
|
-
|
|
29
|
+
resolveStorageProvider?: (provider: AssetStorageProvider) => Promise<StorageProvider | null>;
|
|
30
30
|
}
|
|
31
31
|
export declare class RegistryComponentStore implements ComponentStore {
|
|
32
32
|
private readonly context;
|
package/dist/store/registry.js
CHANGED
|
@@ -404,14 +404,14 @@ class RegistryAssetStore {
|
|
|
404
404
|
return Object.assign(Object.assign({}, base), { body: Buffer.from(row.blobContent) });
|
|
405
405
|
}
|
|
406
406
|
// Object-backed - resolve through the provider that stored it.
|
|
407
|
-
if (!row.storageRef || !this.context.
|
|
407
|
+
if (!row.storageRef || !this.context.resolveStorageProvider) {
|
|
408
408
|
return null;
|
|
409
409
|
}
|
|
410
|
-
const
|
|
411
|
-
if (!
|
|
410
|
+
const storage = yield this.context.resolveStorageProvider(row.storageProvider);
|
|
411
|
+
if (!storage) {
|
|
412
412
|
return null;
|
|
413
413
|
}
|
|
414
|
-
const result = yield
|
|
414
|
+
const result = yield storage.get(row.storageRef);
|
|
415
415
|
if (result.kind === 'redirect') {
|
|
416
416
|
return Object.assign(Object.assign({}, base), { redirectUrl: result.url });
|
|
417
417
|
}
|
package/dist/types/config.d.ts
CHANGED
|
@@ -126,15 +126,6 @@ interface HandoffEmailBase {
|
|
|
126
126
|
/** Sender address, such as `Handoff <no-reply@example.com>`. Use a profile for per-environment values. */
|
|
127
127
|
from?: string;
|
|
128
128
|
}
|
|
129
|
-
/** Send through the Resend HTTP API. */
|
|
130
|
-
export interface HandoffResendEmail extends HandoffEmailBase {
|
|
131
|
-
/** @default "resend" */
|
|
132
|
-
provider?: 'resend';
|
|
133
|
-
options?: {
|
|
134
|
-
/** Environment reference to the Resend API key. @default fromEnv('RESEND_API_KEY') */
|
|
135
|
-
apiKey?: RuntimeEnvReference<string>;
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
129
|
/** Send through an SMTP server, such as Amazon SES, SendGrid, Postmark, Mailgun or Microsoft 365. */
|
|
139
130
|
export interface HandoffSmtpEmail extends HandoffEmailBase {
|
|
140
131
|
provider: 'smtp';
|
|
@@ -164,7 +155,7 @@ export interface HandoffCustomEmail extends HandoffEmailBase {
|
|
|
164
155
|
options?: Record<string, unknown>;
|
|
165
156
|
}
|
|
166
157
|
/** Registry email settings. `provider` selects the shape of `options`. */
|
|
167
|
-
export type HandoffEmailConfig =
|
|
158
|
+
export type HandoffEmailConfig = HandoffSmtpEmail | HandoffCustomEmail;
|
|
168
159
|
/**
|
|
169
160
|
* One AI connection the deployment declares. Connections are the whole surface: a reader can add a
|
|
170
161
|
* key for a declared connection and nothing else, so the server never calls a URL a reader chose.
|
|
@@ -268,13 +259,13 @@ export interface HandoffRuntimeConfig {
|
|
|
268
259
|
* `custom` module for object storage.
|
|
269
260
|
* @default "database"
|
|
270
261
|
*/
|
|
271
|
-
|
|
272
|
-
/** For `
|
|
262
|
+
provider?: 'database' | 'custom';
|
|
263
|
+
/** For `provider: "custom"` - server-only module path default-exporting a `defineStorageProvider()` result. */
|
|
273
264
|
module?: string;
|
|
274
265
|
/** Max bytes kept inline in the database `bytea` column (larger uploads are rejected). @default 4194304 */
|
|
275
266
|
maxInlineBytes?: number;
|
|
276
267
|
/**
|
|
277
|
-
* Options passed to the
|
|
268
|
+
* Options passed to the provider factory. Literals are baked. Environment references are resolved
|
|
278
269
|
* at request time, so secrets must be environment references.
|
|
279
270
|
*/
|
|
280
271
|
options?: Record<string, unknown>;
|