@everystack/server 0.4.4 → 0.4.5
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/package.json +2 -2
- package/src/db.ts +48 -24
- package/src/plugin.ts +10 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everystack/server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "Server runtime primitives for Lambda — event adapters, routing, SSR, image processing",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"author": "Scalable Technology, Inc. <licensing@scalable.technology>",
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
"ts-jest": "29.4.9",
|
|
153
153
|
"typescript": "5.9.3",
|
|
154
154
|
"@everystack/auth": "0.4.0",
|
|
155
|
-
"@everystack/cli": "0.4.
|
|
155
|
+
"@everystack/cli": "0.4.19",
|
|
156
156
|
"@everystack/pg-tools": "0.4.0"
|
|
157
157
|
},
|
|
158
158
|
"scripts": {
|
package/src/db.ts
CHANGED
|
@@ -7,19 +7,26 @@
|
|
|
7
7
|
* transparently. Works with createSecrets() or manual sst.Secret declarations.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { Resource } from 'sst';
|
|
11
10
|
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
12
11
|
import postgres from 'postgres';
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Read an SST-linked resource WITHOUT importing the `sst` package. SST injects
|
|
15
|
+
* every link as `process.env.SST_RESOURCE_<Name>` (a JSON blob) — SST's own
|
|
16
|
+
* Resource proxy just parses that env var (see sst/dist/resource/shared.js:
|
|
17
|
+
* `raw[name] = JSON.parse(process.env['SST_RESOURCE_' + name])`). Reading it
|
|
18
|
+
* directly means this module never imports `sst`, which is a deploy-time
|
|
19
|
+
* construct: Metro/esbuild cannot bundle it, and SST's own build-time
|
|
20
|
+
* version-check plugin mis-reads a node_module's `sst` import and fails the
|
|
21
|
+
* build. So the API/SSR helpers stayed unbundleable in the exact runtime half
|
|
22
|
+
* the framework targets. Returns undefined when the link is absent — every
|
|
23
|
+
* getter then falls through to process.env, unchanged.
|
|
17
24
|
*/
|
|
18
|
-
function
|
|
25
|
+
function sstLink<T = Record<string, any>>(name: string): T | undefined {
|
|
26
|
+
const raw = process.env[`SST_RESOURCE_${name}`];
|
|
27
|
+
if (!raw) return undefined;
|
|
19
28
|
try {
|
|
20
|
-
|
|
21
|
-
// SST's Resource proxy may return undefined for missing keys
|
|
22
|
-
return val === undefined ? undefined : val;
|
|
29
|
+
return JSON.parse(raw) as T;
|
|
23
30
|
} catch {
|
|
24
31
|
return undefined;
|
|
25
32
|
}
|
|
@@ -32,11 +39,11 @@ export function getDatabaseUrl(): string {
|
|
|
32
39
|
// resolved first, setting DATABASE_URL to a least-privilege role would be silently
|
|
33
40
|
// ignored. See docs/plans/credential-split-rls-everywhere.md.
|
|
34
41
|
|
|
35
|
-
// SST Secret — PascalCase (
|
|
36
|
-
const pascal =
|
|
42
|
+
// SST Secret — PascalCase (SST_RESOURCE_DatabaseUrl → { value })
|
|
43
|
+
const pascal = sstLink('DatabaseUrl')?.value as string | undefined;
|
|
37
44
|
if (pascal) return pascal;
|
|
38
|
-
// SST Secret — raw env name (
|
|
39
|
-
const raw =
|
|
45
|
+
// SST Secret — raw env name (SST_RESOURCE_DATABASE_URL → { value })
|
|
46
|
+
const raw = sstLink('DATABASE_URL')?.value as string | undefined;
|
|
40
47
|
if (raw) return raw;
|
|
41
48
|
// process.env fallback
|
|
42
49
|
if (process.env.DATABASE_URL) return process.env.DATABASE_URL;
|
|
@@ -54,8 +61,25 @@ export function getDatabaseUrl(): string {
|
|
|
54
61
|
* no dedicated ADMIN_DATABASE_URL is provisioned — kept separate from getDatabaseUrl() so the
|
|
55
62
|
* operator path never accidentally resolves the least-privilege DATABASE_URL secret.
|
|
56
63
|
*/
|
|
64
|
+
export interface DatabaseComponentInfo {
|
|
65
|
+
username: string;
|
|
66
|
+
password: string;
|
|
67
|
+
host: string;
|
|
68
|
+
port: number;
|
|
69
|
+
database: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The linked SST Postgres component's connection fields, or null when none is linked.
|
|
74
|
+
* The single reader of the `Database` link — callers that only need host/port/database
|
|
75
|
+
* (db:psql, provision probe) use this instead of importing `sst`.
|
|
76
|
+
*/
|
|
77
|
+
export function getDatabaseComponentInfo(): DatabaseComponentInfo | null {
|
|
78
|
+
return sstLink<DatabaseComponentInfo>('Database') ?? null;
|
|
79
|
+
}
|
|
80
|
+
|
|
57
81
|
export function getMasterDatabaseUrl(): string | null {
|
|
58
|
-
const db =
|
|
82
|
+
const db = getDatabaseComponentInfo();
|
|
59
83
|
if (db?.host) {
|
|
60
84
|
return `postgresql://${db.username}:${db.password}@${db.host}:${db.port}/${db.database}`;
|
|
61
85
|
}
|
|
@@ -73,11 +97,11 @@ export function getMasterDatabaseUrl(): string | null {
|
|
|
73
97
|
* Returns null when no admin URL is configured (single-credential setup).
|
|
74
98
|
*/
|
|
75
99
|
export function getAdminDatabaseUrl(): string | null {
|
|
76
|
-
// SST Secret — PascalCase (
|
|
77
|
-
const pascal =
|
|
100
|
+
// SST Secret — PascalCase (SST_RESOURCE_AdminDatabaseUrl → { value })
|
|
101
|
+
const pascal = sstLink('AdminDatabaseUrl')?.value as string | undefined;
|
|
78
102
|
if (pascal) return pascal;
|
|
79
|
-
// SST Secret — raw env name (
|
|
80
|
-
const raw =
|
|
103
|
+
// SST Secret — raw env name (SST_RESOURCE_ADMIN_DATABASE_URL → { value })
|
|
104
|
+
const raw = sstLink('ADMIN_DATABASE_URL')?.value as string | undefined;
|
|
81
105
|
if (raw) return raw;
|
|
82
106
|
// process.env fallback
|
|
83
107
|
if (process.env.ADMIN_DATABASE_URL) return process.env.ADMIN_DATABASE_URL;
|
|
@@ -85,11 +109,11 @@ export function getAdminDatabaseUrl(): string | null {
|
|
|
85
109
|
}
|
|
86
110
|
|
|
87
111
|
export function getJwtSecret(): Uint8Array {
|
|
88
|
-
// PascalCase
|
|
89
|
-
const pascal =
|
|
112
|
+
// PascalCase Secret (SST_RESOURCE_JwtSecret → { value })
|
|
113
|
+
const pascal = sstLink('JwtSecret')?.value as string | undefined;
|
|
90
114
|
if (pascal) return new TextEncoder().encode(pascal);
|
|
91
|
-
// Raw env name
|
|
92
|
-
const raw =
|
|
115
|
+
// Raw env name Secret (SST_RESOURCE_JWT_SECRET → { value })
|
|
116
|
+
const raw = sstLink('JWT_SECRET')?.value as string | undefined;
|
|
93
117
|
if (raw) return new TextEncoder().encode(raw);
|
|
94
118
|
// process.env fallback
|
|
95
119
|
if (process.env.JWT_SECRET) return new TextEncoder().encode(process.env.JWT_SECRET);
|
|
@@ -288,11 +312,11 @@ export function getAdminSql(): ReturnType<typeof postgres> {
|
|
|
288
312
|
* different host. Returns null when no replica is configured.
|
|
289
313
|
*/
|
|
290
314
|
export function getReadDatabaseUrl(): string | null {
|
|
291
|
-
// SST Secret — PascalCase (
|
|
292
|
-
const pascal =
|
|
315
|
+
// SST Secret — PascalCase (SST_RESOURCE_ReadDatabaseUrl → { value })
|
|
316
|
+
const pascal = sstLink('ReadDatabaseUrl')?.value as string | undefined;
|
|
293
317
|
if (pascal) return pascal;
|
|
294
|
-
// SST Secret — raw env name (
|
|
295
|
-
const raw =
|
|
318
|
+
// SST Secret — raw env name (SST_RESOURCE_READ_DATABASE_URL → { value })
|
|
319
|
+
const raw = sstLink('READ_DATABASE_URL')?.value as string | undefined;
|
|
296
320
|
if (raw) return raw;
|
|
297
321
|
// process.env fallback
|
|
298
322
|
if (process.env.READ_DATABASE_URL) return process.env.READ_DATABASE_URL;
|
package/src/plugin.ts
CHANGED
|
@@ -42,8 +42,8 @@ export interface PluginContext {
|
|
|
42
42
|
verifyToken: (token: string) => Promise<Record<string, unknown> | null>;
|
|
43
43
|
/** Runtime environment */
|
|
44
44
|
environment: string;
|
|
45
|
-
/** Publish a background job */
|
|
46
|
-
publishJob
|
|
45
|
+
/** Publish a background job. Optional — a lean app with no @everystack/jobs never sets it. */
|
|
46
|
+
publishJob?: (type: string, payload: unknown, options?: { schedulable?: boolean; runAt?: Date }) => Promise<string>;
|
|
47
47
|
/** Cancel a scheduled job */
|
|
48
48
|
cancelJob?: (jobId: string) => Promise<void>;
|
|
49
49
|
/** Reschedule a pending job */
|
|
@@ -596,18 +596,12 @@ export function dbPlugin(options: DbPluginOptions): Plugin {
|
|
|
596
596
|
if (options.connectionInfo) {
|
|
597
597
|
return await options.connectionInfo();
|
|
598
598
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
host: (Resource as any).Database.host,
|
|
603
|
-
port: (Resource as any).Database.port,
|
|
604
|
-
database: (Resource as any).Database.database,
|
|
605
|
-
username: (Resource as any).Database.username,
|
|
606
|
-
password: (Resource as any).Database.password,
|
|
607
|
-
};
|
|
608
|
-
} catch {
|
|
599
|
+
const { getDatabaseComponentInfo } = await import('./db');
|
|
600
|
+
const db = getDatabaseComponentInfo();
|
|
601
|
+
if (!db) {
|
|
609
602
|
return { error: 'No connectionInfo callback provided and SST Resource.Database not available' };
|
|
610
603
|
}
|
|
604
|
+
return { host: db.host, port: db.port, database: db.database, username: db.username, password: db.password };
|
|
611
605
|
};
|
|
612
606
|
|
|
613
607
|
// --- db:query ---
|
|
@@ -778,13 +772,10 @@ export function dbPlugin(options: DbPluginOptions): Plugin {
|
|
|
778
772
|
connection = { host: info.host, port: info.port, database: info.database };
|
|
779
773
|
}
|
|
780
774
|
} else {
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
} catch {
|
|
786
|
-
// No linked component and no callback — the admin login lands unverified.
|
|
787
|
-
}
|
|
775
|
+
const { getDatabaseComponentInfo } = await import('./db');
|
|
776
|
+
const db = getDatabaseComponentInfo();
|
|
777
|
+
// No linked component and no callback — the admin login lands unverified.
|
|
778
|
+
if (db?.host) connection = { host: db.host, port: db.port, database: db.database };
|
|
788
779
|
}
|
|
789
780
|
|
|
790
781
|
return await runProvision(
|