@fjall/payload 0.87.4

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 ADDED
@@ -0,0 +1,135 @@
1
+ # @fjall/payload
2
+
3
+ AWS Lambda adapters for Payload CMS. This package provides drop-in replacements for Payload's database and storage adapters, optimized for serverless deployments on AWS.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @fjall/payload @payloadcms/storage-s3
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **`fjallPostgresAdapter`** - PostgreSQL adapter with AWS Secrets Manager integration
14
+ - **`awsS3Storage`** - S3 storage plugin for media uploads
15
+ - **`fjallDefaults`** - Sensible CORS/CSRF defaults for Lambda
16
+ - **`isProduction`** / **`isLambda`** - Environment detection helpers
17
+
18
+ ## Usage
19
+
20
+ ### Database Adapter
21
+
22
+ ```typescript
23
+ // payload.config.ts
24
+ import { buildConfig } from "payload";
25
+ import { fjallPostgresAdapter, fjallDefaults } from "@fjall/payload";
26
+ import { migrations } from "./migrations";
27
+
28
+ export default buildConfig({
29
+ ...fjallDefaults(),
30
+ db: await fjallPostgresAdapter({
31
+ migrationDir: "./src/migrations",
32
+ prodMigrations: migrations,
33
+ }),
34
+ // ... rest of config
35
+ });
36
+ ```
37
+
38
+ The adapter automatically:
39
+
40
+ - Fetches database credentials from AWS Secrets Manager in Lambda
41
+ - Falls back to `DATABASE_URL` for local development
42
+ - Configures SSL for Aurora PostgreSQL
43
+
44
+ ### S3 Storage Plugin
45
+
46
+ ```typescript
47
+ // plugins/index.ts
48
+ import { awsS3Storage } from "@fjall/payload";
49
+ import { Plugin } from "payload";
50
+
51
+ export const plugins: Plugin[] = [awsS3Storage()];
52
+ ```
53
+
54
+ The plugin automatically:
55
+
56
+ - Uses `MEDIA_BUCKET_NAME` environment variable set by Fjall infrastructure
57
+ - Handles presigned URLs for uploads
58
+ - Falls back to local filesystem in development
59
+
60
+ ### Environment Detection
61
+
62
+ ```typescript
63
+ import { isProduction, isLambda } from "@fjall/payload";
64
+
65
+ // Conditional logic based on environment
66
+ if (isProduction()) {
67
+ // Running in AWS with Fjall infrastructure
68
+ }
69
+
70
+ if (isLambda()) {
71
+ // Running in AWS Lambda runtime
72
+ }
73
+ ```
74
+
75
+ ### Conditional Static Directory
76
+
77
+ For Media collections that need local uploads in dev but S3 in production:
78
+
79
+ ```typescript
80
+ import { isProduction } from "@fjall/payload";
81
+ import path from "path";
82
+
83
+ export const Media: CollectionConfig = {
84
+ slug: "media",
85
+ upload: {
86
+ ...(isProduction()
87
+ ? {}
88
+ : {
89
+ staticDir: path.resolve(__dirname, "../../public/media"),
90
+ }),
91
+ },
92
+ };
93
+ ```
94
+
95
+ ## Environment Variables
96
+
97
+ ### Production (Lambda)
98
+
99
+ Set automatically by Fjall infrastructure:
100
+
101
+ | Variable | Description |
102
+ | ------------------- | ----------------------------- |
103
+ | `DATABASE_HOST` | Aurora PostgreSQL endpoint |
104
+ | `DATABASE_PORT` | Database port (default: 5432) |
105
+ | `DATABASE_NAME` | Database name |
106
+ | `DATABASE_SSL` | Enable SSL (`true`) |
107
+ | `MEDIA_BUCKET_NAME` | S3 bucket for uploads |
108
+
109
+ Credentials fetched from Secrets Manager:
110
+
111
+ - `DATABASE_USERNAME`
112
+ - `DATABASE_PASSWORD`
113
+
114
+ ### Development
115
+
116
+ | Variable | Description |
117
+ | -------------- | --------------------------------- |
118
+ | `DATABASE_URL` | Full PostgreSQL connection string |
119
+
120
+ ## Auto-Configuration
121
+
122
+ When you run `fjall deploy` on a Payload project, the CLI automatically:
123
+
124
+ 1. Detects the Payload pattern
125
+ 2. Installs `@fjall/payload` and `@payloadcms/storage-s3`
126
+ 3. Patches `payload.config.ts` to use `fjallPostgresAdapter`
127
+ 4. Patches or creates `plugins/index.ts` with S3 storage
128
+ 5. Configures `next.config.js` for Lambda (standalone output)
129
+ 6. Generates `open-next.config.ts` for OpenNext
130
+
131
+ No manual configuration required - just run `fjall deploy`.
132
+
133
+ ## License
134
+
135
+ MIT
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Fjall PostgreSQL adapter for Payload CMS.
3
+ *
4
+ * Wraps @payloadcms/db-postgres with AWS-specific features:
5
+ * - Fetches database credentials from AWS Secrets Manager via Lambda Extension
6
+ * - Constructs connection string from Fjall environment variables
7
+ * - Handles SSL configuration for Aurora PostgreSQL
8
+ * - Falls back to DATABASE_URL for local development
9
+ */
10
+ import { postgresAdapter } from "@payloadcms/db-postgres";
11
+ /** The return type of postgresAdapter */
12
+ type PostgresAdapterResult = ReturnType<typeof postgresAdapter>;
13
+ /**
14
+ * Options for the Fjall PostgreSQL adapter.
15
+ */
16
+ export interface FjallPostgresAdapterOptions {
17
+ /**
18
+ * Path to migrations directory (relative to project root).
19
+ * @default './src/migrations'
20
+ */
21
+ migrationDir?: string;
22
+ /**
23
+ * Pre-compiled migrations for production.
24
+ * Required for Lambda deployments where filesystem migrations aren't available.
25
+ */
26
+ prodMigrations?: Array<{
27
+ up: (args: {
28
+ db: unknown;
29
+ payload: unknown;
30
+ req: unknown;
31
+ }) => Promise<void>;
32
+ down: (args: {
33
+ db: unknown;
34
+ payload: unknown;
35
+ req: unknown;
36
+ }) => Promise<void>;
37
+ name: string;
38
+ }>;
39
+ }
40
+ /**
41
+ * Create a Fjall-configured PostgreSQL adapter for Payload CMS.
42
+ *
43
+ * This is an async function because it needs to fetch credentials from
44
+ * Secrets Manager. Call it with top-level await in your payload.config.ts.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * import { fjallPostgresAdapter } from '@fjall/payload'
49
+ * import { migrations } from './migrations'
50
+ *
51
+ * const db = await fjallPostgresAdapter({
52
+ * migrationDir: './src/migrations',
53
+ * prodMigrations: migrations,
54
+ * })
55
+ *
56
+ * export default buildConfig({
57
+ * db,
58
+ * // ...
59
+ * })
60
+ * ```
61
+ */
62
+ export declare function fjallPostgresAdapter(options?: FjallPostgresAdapterOptions): Promise<PostgresAdapterResult>;
63
+ export {};
64
+ //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/adapters/postgres.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG1D,yCAAyC;AACzC,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAmDhE;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,cAAc,CAAC,EAAE,KAAK,CAAC;QACrB,EAAE,EAAE,CAAC,IAAI,EAAE;YACT,EAAE,EAAE,OAAO,CAAC;YACZ,OAAO,EAAE,OAAO,CAAC;YACjB,GAAG,EAAE,OAAO,CAAC;SACd,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QACpB,IAAI,EAAE,CAAC,IAAI,EAAE;YACX,EAAE,EAAE,OAAO,CAAC;YACZ,OAAO,EAAE,OAAO,CAAC;YACjB,GAAG,EAAE,OAAO,CAAC;SACd,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,qBAAqB,CAAC,CAahC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Fjall PostgreSQL adapter for Payload CMS.
3
+ *
4
+ * Wraps @payloadcms/db-postgres with AWS-specific features:
5
+ * - Fetches database credentials from AWS Secrets Manager via Lambda Extension
6
+ * - Constructs connection string from Fjall environment variables
7
+ * - Handles SSL configuration for Aurora PostgreSQL
8
+ * - Falls back to DATABASE_URL for local development
9
+ */
10
+ import { postgresAdapter } from "@payloadcms/db-postgres";
11
+ import { fetchSecretField } from "./secrets.js";
12
+ // Cache for resolved database URL (resolved once at Lambda cold start).
13
+ // Module-level state is intentional here: Lambda reuses the module across
14
+ // invocations, so caching avoids repeated Secrets Manager calls.
15
+ let cachedDbUrl = null;
16
+ /**
17
+ * Build the database connection URL from Fjall environment variables.
18
+ *
19
+ * In production (Lambda), uses:
20
+ * - DATABASE_HOST, DATABASE_PORT, DATABASE_NAME from environment
21
+ * - DATABASE_USERNAME/PASSWORD from Secrets Manager via Lambda Extension
22
+ *
23
+ * In development, falls back to DATABASE_URL environment variable.
24
+ */
25
+ async function getDatabaseUrl() {
26
+ // Return cached URL if already resolved
27
+ if (cachedDbUrl)
28
+ return cachedDbUrl;
29
+ // If Fjall env vars are present (production Lambda environment)
30
+ if (process.env.DATABASE_HOST) {
31
+ const host = process.env.DATABASE_HOST;
32
+ const port = process.env.DATABASE_PORT || "5432";
33
+ const name = process.env.DATABASE_NAME;
34
+ // Fetch credentials from Lambda Extension
35
+ const user = (await fetchSecretField("DATABASE_USERNAME")) || "postgres";
36
+ const pass = (await fetchSecretField("DATABASE_PASSWORD")) || "";
37
+ cachedDbUrl = `postgresql://${user}:${encodeURIComponent(pass)}@${host}:${port}/${name}`;
38
+ return cachedDbUrl;
39
+ }
40
+ // Fallback to DATABASE_URL for local development
41
+ cachedDbUrl = process.env.DATABASE_URL || "";
42
+ return cachedDbUrl;
43
+ }
44
+ /**
45
+ * Get SSL configuration for Aurora PostgreSQL.
46
+ *
47
+ * In production (DATABASE_SSL=true), enables SSL but skips certificate
48
+ * verification since Aurora uses Amazon's internal CA.
49
+ */
50
+ function getSslConfig() {
51
+ return process.env.DATABASE_SSL === "true"
52
+ ? { rejectUnauthorized: false }
53
+ : false;
54
+ }
55
+ /**
56
+ * Create a Fjall-configured PostgreSQL adapter for Payload CMS.
57
+ *
58
+ * This is an async function because it needs to fetch credentials from
59
+ * Secrets Manager. Call it with top-level await in your payload.config.ts.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * import { fjallPostgresAdapter } from '@fjall/payload'
64
+ * import { migrations } from './migrations'
65
+ *
66
+ * const db = await fjallPostgresAdapter({
67
+ * migrationDir: './src/migrations',
68
+ * prodMigrations: migrations,
69
+ * })
70
+ *
71
+ * export default buildConfig({
72
+ * db,
73
+ * // ...
74
+ * })
75
+ * ```
76
+ */
77
+ export async function fjallPostgresAdapter(options = {}) {
78
+ const { migrationDir = "./src/migrations", prodMigrations } = options;
79
+ const databaseUrl = await getDatabaseUrl();
80
+ return postgresAdapter({
81
+ pool: {
82
+ connectionString: databaseUrl,
83
+ ssl: getSslConfig(),
84
+ },
85
+ migrationDir,
86
+ prodMigrations,
87
+ });
88
+ }
89
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/adapters/postgres.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKhD,wEAAwE;AACxE,0EAA0E;AAC1E,iEAAiE;AACjE,IAAI,WAAW,GAAkB,IAAI,CAAC;AAEtC;;;;;;;;GAQG;AACH,KAAK,UAAU,cAAc;IAC3B,wCAAwC;IACxC,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IAEpC,gEAAgE;IAChE,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;QACvC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,MAAM,CAAC;QACjD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;QAEvC,0CAA0C;QAC1C,MAAM,IAAI,GAAG,CAAC,MAAM,gBAAgB,CAAC,mBAAmB,CAAC,CAAC,IAAI,UAAU,CAAC;QACzE,MAAM,IAAI,GAAG,CAAC,MAAM,gBAAgB,CAAC,mBAAmB,CAAC,CAAC,IAAI,EAAE,CAAC;QAEjE,WAAW,GAAG,gBAAgB,IAAI,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QACzF,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,iDAAiD;IACjD,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;IAC7C,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;;;GAKG;AACH,SAAS,YAAY;IACnB,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,MAAM;QACxC,CAAC,CAAC,EAAE,kBAAkB,EAAE,KAAK,EAAE;QAC/B,CAAC,CAAC,KAAK,CAAC;AACZ,CAAC;AA+BD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,UAAuC,EAAE;IAEzC,MAAM,EAAE,YAAY,GAAG,kBAAkB,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC;IAEtE,MAAM,WAAW,GAAG,MAAM,cAAc,EAAE,CAAC;IAE3C,OAAO,eAAe,CAAC;QACrB,IAAI,EAAE;YACJ,gBAAgB,EAAE,WAAW;YAC7B,GAAG,EAAE,YAAY,EAAE;SACpB;QACD,YAAY;QACZ,cAAc;KACf,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * AWS Secrets Manager helper for Lambda environments.
3
+ *
4
+ * Fetches secrets via the Lambda Extension which runs on port 2773.
5
+ * The extension caches secrets automatically for performance.
6
+ *
7
+ * @see https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_lambda.html
8
+ */
9
+ /**
10
+ * Fetch a specific field from an AWS Secret stored in Secrets Manager.
11
+ *
12
+ * Uses the Lambda Parameters and Secrets Extension for secure, cached access.
13
+ * The extension expects:
14
+ * - `${envKey}_SECRET_ARN` - The ARN of the secret in Secrets Manager
15
+ * - `${envKey}_SECRET_FIELD` - (Optional) The field name within the JSON secret
16
+ *
17
+ * @param envKey - The environment variable prefix (e.g., 'DATABASE_USERNAME')
18
+ * @returns The secret value, or undefined if not available
19
+ */
20
+ export declare function fetchSecretField(envKey: string): Promise<string | undefined>;
21
+ //# sourceMappingURL=secrets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/adapters/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAqC7B"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * AWS Secrets Manager helper for Lambda environments.
3
+ *
4
+ * Fetches secrets via the Lambda Extension which runs on port 2773.
5
+ * The extension caches secrets automatically for performance.
6
+ *
7
+ * @see https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_lambda.html
8
+ */
9
+ /**
10
+ * Fetch a specific field from an AWS Secret stored in Secrets Manager.
11
+ *
12
+ * Uses the Lambda Parameters and Secrets Extension for secure, cached access.
13
+ * The extension expects:
14
+ * - `${envKey}_SECRET_ARN` - The ARN of the secret in Secrets Manager
15
+ * - `${envKey}_SECRET_FIELD` - (Optional) The field name within the JSON secret
16
+ *
17
+ * @param envKey - The environment variable prefix (e.g., 'DATABASE_USERNAME')
18
+ * @returns The secret value, or undefined if not available
19
+ */
20
+ export async function fetchSecretField(envKey) {
21
+ const secretArn = process.env[`${envKey}_SECRET_ARN`];
22
+ const secretField = process.env[`${envKey}_SECRET_FIELD`];
23
+ if (!secretArn)
24
+ return undefined;
25
+ const sessionToken = process.env.AWS_SESSION_TOKEN;
26
+ if (!sessionToken) {
27
+ // AWS_SESSION_TOKEN is set by Lambda runtime - if missing, we're not in Lambda
28
+ console.error(`fetchSecretField: AWS_SESSION_TOKEN not set (not running in Lambda?)`);
29
+ return undefined;
30
+ }
31
+ try {
32
+ const url = `http://localhost:2773/secretsmanager/get?secretId=${encodeURIComponent(secretArn)}`;
33
+ const response = await fetch(url, {
34
+ headers: {
35
+ "X-Aws-Parameters-Secrets-Token": sessionToken,
36
+ },
37
+ });
38
+ if (!response.ok) {
39
+ console.error(`Failed to fetch secret ${envKey}: ${response.status}`);
40
+ return undefined;
41
+ }
42
+ const data = (await response.json());
43
+ if (!data.SecretString)
44
+ return undefined;
45
+ const parsed = JSON.parse(data.SecretString);
46
+ return secretField ? parsed[secretField] : data.SecretString;
47
+ }
48
+ catch (err) {
49
+ console.error(`Error fetching secret ${envKey}:`, err);
50
+ return undefined;
51
+ }
52
+ }
53
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/adapters/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAAc;IAEd,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,aAAa,CAAC,CAAC;IACtD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,eAAe,CAAC,CAAC;IAE1D,IAAI,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IAEjC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IACnD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,+EAA+E;QAC/E,OAAO,CAAC,KAAK,CACX,sEAAsE,CACvE,CAAC;QACF,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,qDAAqD,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC;QACjG,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,OAAO,EAAE;gBACP,gCAAgC,EAAE,YAAY;aAC/C;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,OAAO,CAAC,KAAK,CAAC,0BAA0B,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YACtE,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA8B,CAAC;QAClE,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO,SAAS,CAAC;QAEzC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC7C,OAAO,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC;IAC/D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,yBAAyB,MAAM,GAAG,EAAE,GAAG,CAAC,CAAC;QACvD,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Fjall default configuration helpers for Payload CMS.
3
+ *
4
+ * Provides sensible defaults for AWS Lambda deployments:
5
+ * - CORS configuration based on environment
6
+ * - CSRF configuration for security
7
+ * - Environment detection helpers
8
+ */
9
+ /**
10
+ * Check if running in a production AWS environment.
11
+ *
12
+ * Detection is based on the presence of MEDIA_BUCKET_NAME which is set
13
+ * by Fjall infrastructure in Lambda environments.
14
+ */
15
+ export declare function isProduction(): boolean;
16
+ /**
17
+ * Check if running in AWS Lambda.
18
+ *
19
+ * Uses AWS_LAMBDA_FUNCTION_NAME which is set by the Lambda runtime.
20
+ */
21
+ export declare function isLambda(): boolean;
22
+ /**
23
+ * Fjall default configuration spread for Payload buildConfig.
24
+ *
25
+ * Provides:
26
+ * - CORS: Locked to NEXT_PUBLIC_SERVER_URL in production, permissive otherwise
27
+ * - CSRF: Enabled in production with NEXT_PUBLIC_SERVER_URL
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * import { fjallDefaults } from '@fjall/payload'
32
+ *
33
+ * export default buildConfig({
34
+ * ...fjallDefaults(),
35
+ * // your config...
36
+ * })
37
+ * ```
38
+ */
39
+ export declare function fjallDefaults(): {
40
+ cors: string[];
41
+ csrf: string[];
42
+ };
43
+ /**
44
+ * Get the conditional staticDir configuration for Media collections.
45
+ *
46
+ * In production (Lambda), S3 storage is used and staticDir should be omitted.
47
+ * In local development, uploads go to the local filesystem.
48
+ *
49
+ * @param localPath - The local path for development (e.g., './public/media')
50
+ * @returns Object with staticDir for local dev, empty object for production
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * import { conditionalStaticDir } from '@fjall/payload'
55
+ * import path from 'path'
56
+ *
57
+ * export const Media: CollectionConfig = {
58
+ * slug: 'media',
59
+ * upload: {
60
+ * ...conditionalStaticDir(path.resolve(__dirname, '../../public/media')),
61
+ * // other upload config...
62
+ * },
63
+ * }
64
+ * ```
65
+ */
66
+ export declare function conditionalStaticDir(localPath: string): {
67
+ staticDir: string;
68
+ } | Record<string, never>;
69
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,IAAI,OAAO,CAElC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,IAAI;IAC/B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAUA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,GAChB;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAE/C"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Fjall default configuration helpers for Payload CMS.
3
+ *
4
+ * Provides sensible defaults for AWS Lambda deployments:
5
+ * - CORS configuration based on environment
6
+ * - CSRF configuration for security
7
+ * - Environment detection helpers
8
+ */
9
+ /**
10
+ * Check if running in a production AWS environment.
11
+ *
12
+ * Detection is based on the presence of MEDIA_BUCKET_NAME which is set
13
+ * by Fjall infrastructure in Lambda environments.
14
+ */
15
+ export function isProduction() {
16
+ return Boolean(process.env.MEDIA_BUCKET_NAME);
17
+ }
18
+ /**
19
+ * Check if running in AWS Lambda.
20
+ *
21
+ * Uses AWS_LAMBDA_FUNCTION_NAME which is set by the Lambda runtime.
22
+ */
23
+ export function isLambda() {
24
+ return Boolean(process.env.AWS_LAMBDA_FUNCTION_NAME);
25
+ }
26
+ /**
27
+ * Fjall default configuration spread for Payload buildConfig.
28
+ *
29
+ * Provides:
30
+ * - CORS: Locked to NEXT_PUBLIC_SERVER_URL in production, permissive otherwise
31
+ * - CSRF: Enabled in production with NEXT_PUBLIC_SERVER_URL
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * import { fjallDefaults } from '@fjall/payload'
36
+ *
37
+ * export default buildConfig({
38
+ * ...fjallDefaults(),
39
+ * // your config...
40
+ * })
41
+ * ```
42
+ */
43
+ export function fjallDefaults() {
44
+ const serverUrl = process.env.NEXT_PUBLIC_SERVER_URL;
45
+ return {
46
+ // When custom domain is set (via Lambda env), lock CORS to that domain
47
+ // Otherwise permissive for CloudFront deployments
48
+ cors: serverUrl ? [serverUrl] : ["*"],
49
+ // CSRF protection - only enable when we have a known origin
50
+ csrf: serverUrl ? [serverUrl] : [],
51
+ };
52
+ }
53
+ /**
54
+ * Get the conditional staticDir configuration for Media collections.
55
+ *
56
+ * In production (Lambda), S3 storage is used and staticDir should be omitted.
57
+ * In local development, uploads go to the local filesystem.
58
+ *
59
+ * @param localPath - The local path for development (e.g., './public/media')
60
+ * @returns Object with staticDir for local dev, empty object for production
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * import { conditionalStaticDir } from '@fjall/payload'
65
+ * import path from 'path'
66
+ *
67
+ * export const Media: CollectionConfig = {
68
+ * slug: 'media',
69
+ * upload: {
70
+ * ...conditionalStaticDir(path.resolve(__dirname, '../../public/media')),
71
+ * // other upload config...
72
+ * },
73
+ * }
74
+ * ```
75
+ */
76
+ export function conditionalStaticDir(localPath) {
77
+ return isProduction() ? {} : { staticDir: localPath };
78
+ }
79
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;GAKG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;AAChD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ;IACtB,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,aAAa;IAI3B,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;IAErD,OAAO;QACL,uEAAuE;QACvE,kDAAkD;QAClD,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACrC,4DAA4D;QAC5D,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE;KACnC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,oBAAoB,CAClC,SAAiB;IAEjB,OAAO,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;AACxD,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @fjall/payload - Fjall AWS adapters and utilities for Payload CMS
3
+ *
4
+ * This package provides AWS-specific adapters and helpers for deploying
5
+ * Payload CMS applications to AWS Lambda via Fjall.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // payload.config.ts
10
+ * import {
11
+ * fjallPostgresAdapter,
12
+ * awsS3Storage,
13
+ * fjallDefaults,
14
+ * isProduction
15
+ * } from '@fjall/payload'
16
+ * import { migrations } from './migrations'
17
+ *
18
+ * const db = await fjallPostgresAdapter({
19
+ * migrationDir: './src/migrations',
20
+ * prodMigrations: migrations,
21
+ * })
22
+ *
23
+ * export default buildConfig({
24
+ * ...fjallDefaults(),
25
+ * db,
26
+ * plugins: [
27
+ * awsS3Storage(),
28
+ * // other plugins...
29
+ * ],
30
+ * // your config...
31
+ * })
32
+ * ```
33
+ */
34
+ export { fjallPostgresAdapter, type FjallPostgresAdapterOptions, } from "./adapters/postgres.js";
35
+ export { fetchSecretField } from "./adapters/secrets.js";
36
+ export { awsS3Storage, type AwsS3StorageOptions } from "./storage/s3.js";
37
+ export { fjallDefaults, isProduction, isLambda, conditionalStaticDir, } from "./config/defaults.js";
38
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAGH,OAAO,EACL,oBAAoB,EACpB,KAAK,2BAA2B,GACjC,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAGzE,OAAO,EACL,aAAa,EACb,YAAY,EACZ,QAAQ,EACR,oBAAoB,GACrB,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @fjall/payload - Fjall AWS adapters and utilities for Payload CMS
3
+ *
4
+ * This package provides AWS-specific adapters and helpers for deploying
5
+ * Payload CMS applications to AWS Lambda via Fjall.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // payload.config.ts
10
+ * import {
11
+ * fjallPostgresAdapter,
12
+ * awsS3Storage,
13
+ * fjallDefaults,
14
+ * isProduction
15
+ * } from '@fjall/payload'
16
+ * import { migrations } from './migrations'
17
+ *
18
+ * const db = await fjallPostgresAdapter({
19
+ * migrationDir: './src/migrations',
20
+ * prodMigrations: migrations,
21
+ * })
22
+ *
23
+ * export default buildConfig({
24
+ * ...fjallDefaults(),
25
+ * db,
26
+ * plugins: [
27
+ * awsS3Storage(),
28
+ * // other plugins...
29
+ * ],
30
+ * // your config...
31
+ * })
32
+ * ```
33
+ */
34
+ // Database adapter
35
+ export { fjallPostgresAdapter, } from "./adapters/postgres.js";
36
+ // AWS Secrets helper
37
+ export { fetchSecretField } from "./adapters/secrets.js";
38
+ // S3 storage plugin
39
+ export { awsS3Storage } from "./storage/s3.js";
40
+ // Configuration helpers
41
+ export { fjallDefaults, isProduction, isLambda, conditionalStaticDir, } from "./config/defaults.js";
42
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,mBAAmB;AACnB,OAAO,EACL,oBAAoB,GAErB,MAAM,wBAAwB,CAAC;AAEhC,qBAAqB;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,oBAAoB;AACpB,OAAO,EAAE,YAAY,EAA4B,MAAM,iBAAiB,CAAC;AAEzE,wBAAwB;AACxB,OAAO,EACL,aAAa,EACb,YAAY,EACZ,QAAQ,EACR,oBAAoB,GACrB,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Fjall S3 storage configuration for Payload CMS.
3
+ *
4
+ * Wraps @payloadcms/storage-s3 with AWS Lambda-specific defaults:
5
+ * - Uses MEDIA_BUCKET_NAME environment variable from Fjall infrastructure
6
+ * - Enables/disables local storage based on environment
7
+ * - Uses AWS_REGION for automatic region configuration
8
+ */
9
+ import type { Plugin } from "payload";
10
+ /**
11
+ * Options for the Fjall S3 storage plugin.
12
+ */
13
+ export interface AwsS3StorageOptions {
14
+ /**
15
+ * Collection slugs to enable S3 storage for.
16
+ * @default ['media']
17
+ */
18
+ collections?: string[];
19
+ /**
20
+ * S3 key prefix for uploaded files.
21
+ * @default 'media'
22
+ */
23
+ prefix?: string;
24
+ /**
25
+ * Custom bucket name. If not provided, uses MEDIA_BUCKET_NAME env var.
26
+ */
27
+ bucket?: string;
28
+ /**
29
+ * AWS region. If not provided, uses AWS_REGION env var.
30
+ * @default 'us-east-1'
31
+ */
32
+ region?: string;
33
+ }
34
+ /**
35
+ * Create an S3 storage plugin configured for Fjall/AWS Lambda.
36
+ *
37
+ * This plugin is always registered so Payload includes S3 client components
38
+ * in the importMap at build time. In local development (when MEDIA_BUCKET_NAME
39
+ * is not set), uploads fall back to disk storage.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * import { awsS3Storage } from '@fjall/payload'
44
+ *
45
+ * export const plugins: Plugin[] = [
46
+ * awsS3Storage(),
47
+ * // other plugins...
48
+ * ]
49
+ * ```
50
+ */
51
+ export declare function awsS3Storage(options?: AwsS3StorageOptions): Plugin;
52
+ //# sourceMappingURL=s3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../src/storage/s3.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAEvB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,CA0BtE"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Fjall S3 storage configuration for Payload CMS.
3
+ *
4
+ * Wraps @payloadcms/storage-s3 with AWS Lambda-specific defaults:
5
+ * - Uses MEDIA_BUCKET_NAME environment variable from Fjall infrastructure
6
+ * - Enables/disables local storage based on environment
7
+ * - Uses AWS_REGION for automatic region configuration
8
+ */
9
+ import { s3Storage } from "@payloadcms/storage-s3";
10
+ /**
11
+ * Create an S3 storage plugin configured for Fjall/AWS Lambda.
12
+ *
13
+ * This plugin is always registered so Payload includes S3 client components
14
+ * in the importMap at build time. In local development (when MEDIA_BUCKET_NAME
15
+ * is not set), uploads fall back to disk storage.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { awsS3Storage } from '@fjall/payload'
20
+ *
21
+ * export const plugins: Plugin[] = [
22
+ * awsS3Storage(),
23
+ * // other plugins...
24
+ * ]
25
+ * ```
26
+ */
27
+ export function awsS3Storage(options = {}) {
28
+ const { collections = ["media"], prefix = "media", bucket = process.env.MEDIA_BUCKET_NAME, region = process.env.AWS_REGION || "us-east-1", } = options;
29
+ // Build collection config - disable local storage when bucket is configured
30
+ const collectionConfig = Object.fromEntries(collections.map((slug) => [
31
+ slug,
32
+ { prefix, disableLocalStorage: !!bucket },
33
+ ]));
34
+ return s3Storage({
35
+ collections: collectionConfig,
36
+ // Use placeholder bucket for local dev - plugin is registered but won't be used
37
+ bucket: bucket || "placeholder",
38
+ config: {
39
+ // When running in Lambda with IAM role, credentials are automatic
40
+ // For local testing with S3, set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
41
+ region,
42
+ },
43
+ });
44
+ }
45
+ //# sourceMappingURL=s3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3.js","sourceRoot":"","sources":["../../src/storage/s3.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AA+BnD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,YAAY,CAAC,UAA+B,EAAE;IAC5D,MAAM,EACJ,WAAW,GAAG,CAAC,OAAO,CAAC,EACvB,MAAM,GAAG,OAAO,EAChB,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,EACtC,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,WAAW,GAC/C,GAAG,OAAO,CAAC;IAEZ,4EAA4E;IAC5E,MAAM,gBAAgB,GAAG,MAAM,CAAC,WAAW,CACzC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACxB,IAAI;QACJ,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE;KAC1C,CAAC,CACH,CAAC;IAEF,OAAO,SAAS,CAAC;QACf,WAAW,EAAE,gBAAgB;QAC7B,gFAAgF;QAChF,MAAM,EAAE,MAAM,IAAI,aAAa;QAC/B,MAAM,EAAE;YACN,kEAAkE;YAClE,6EAA6E;YAC7E,MAAM;SACP;KACF,CAAC,CAAC;AACL,CAAC"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@fjall/payload",
3
+ "version": "0.87.4",
4
+ "description": "Fjall AWS adapters and utilities for Payload CMS",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist/"
16
+ ],
17
+ "scripts": {
18
+ "clean": "rm -rf ./dist",
19
+ "build": "npm run clean && npx tsc",
20
+ "watch": "npm run build && npx tsc-watch",
21
+ "typecheck": "tsc --noEmit",
22
+ "format": "prettier --write \"src/**/*.ts\"",
23
+ "format:check": "prettier --check \"src/**/*.ts\"",
24
+ "lint": "eslint src/",
25
+ "lint:fix": "eslint src/ --fix"
26
+ },
27
+ "engines": {
28
+ "node": ">=18.0.0"
29
+ },
30
+ "author": "",
31
+ "license": "ISC",
32
+ "peerDependencies": {
33
+ "@payloadcms/db-postgres": "^3.0.0",
34
+ "@payloadcms/storage-s3": "^3.0.0",
35
+ "payload": "^3.0.0"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "@payloadcms/db-postgres": {
39
+ "optional": false
40
+ },
41
+ "@payloadcms/storage-s3": {
42
+ "optional": true
43
+ }
44
+ },
45
+ "devDependencies": {
46
+ "@payloadcms/db-postgres": "^3.73.0",
47
+ "@payloadcms/storage-s3": "^3.73.0",
48
+ "@types/node": "^22.0.0",
49
+ "payload": "^3.73.0",
50
+ "typescript": "^5.8.2"
51
+ },
52
+ "gitHead": "0f9a9509a197b92e469b05719551e63c61b044f3"
53
+ }