@palbase/backend 22.0.1 → 23.0.0
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/bin/palbase-backend.cjs +764 -25
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +4 -4
- package/dist/{chunk-POYAFBLF.js → chunk-FSGSB42K.js} +13 -6
- package/dist/chunk-FSGSB42K.js.map +1 -0
- package/dist/{chunk-SSGAMC26.js → chunk-HAF67F2H.js} +114 -5
- package/dist/chunk-HAF67F2H.js.map +1 -0
- package/dist/{chunk-QMVK4X3V.js → chunk-OMRTHM4X.js} +98 -98
- package/dist/chunk-OMRTHM4X.js.map +1 -0
- package/dist/{chunk-QYOHMVUW.js → chunk-REZU6UKT.js} +755 -24
- package/dist/chunk-REZU6UKT.js.map +1 -0
- package/dist/{chunk-YL4C5NRY.js → chunk-Y5HXVUMP.js} +2 -2
- package/dist/{chunk-N32VDWKH.js → chunk-ZC6Q2BRD.js} +4 -59
- package/dist/chunk-ZC6Q2BRD.js.map +1 -0
- package/dist/db/env.cjs.map +1 -1
- package/dist/db/env.d.cts +3 -21
- package/dist/db/env.d.ts +3 -21
- package/dist/db/index.cjs +117 -6
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +3 -2
- package/dist/db/index.d.ts +3 -2
- package/dist/db/index.js +7 -3
- package/dist/{endpoint-B0LpZixz.d.cts → endpoint-BavvbW4P.d.ts} +116 -14
- package/dist/{endpoint-B0LpZixz.d.ts → endpoint-i8TTCohk.d.cts} +116 -14
- package/dist/engine/index.cjs +764 -25
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +6 -5
- package/dist/engine/index.d.ts +6 -5
- package/dist/engine/index.js +3 -3
- package/dist/{index-g-EzitI-.d.ts → index-B3jmmItD.d.ts} +203 -7
- package/dist/{index-BCNtlG1w.d.ts → index-B7YBEG5w.d.ts} +117 -93
- package/dist/{index-BGSCWlUa.d.cts → index-Bmvx1EvJ.d.cts} +203 -7
- package/dist/{index-B4W6d2VJ.d.cts → index-E7OscPJT.d.cts} +117 -93
- package/dist/index.cjs +509 -1085
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +203 -1133
- package/dist/index.d.ts +203 -1133
- package/dist/index.js +288 -900
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.cjs +3 -57
- package/dist/openapi/index.cjs.map +1 -1
- package/dist/openapi/index.d.cts +3 -2
- package/dist/openapi/index.d.ts +3 -2
- package/dist/openapi/index.js +5 -22
- package/dist/openapi/index.js.map +1 -1
- package/dist/{registry-3BLYv4si.d.ts → registry-C3H2uPeZ.d.cts} +13 -169
- package/dist/{registry-Cw0YEYCg.d.cts → registry-DY3d9l1k.d.ts} +13 -169
- package/dist/{purchases/keys.cjs → stack.cjs} +4 -4
- package/dist/stack.cjs.map +1 -0
- package/dist/stack.d.cts +76 -0
- package/dist/stack.d.ts +76 -0
- package/dist/stack.js +1 -0
- package/docs/README.md +7 -6
- package/docs/endpoints.md +1 -1
- package/docs/errors.md +9 -0
- package/docs/llms-full.txt +17 -261
- package/docs/llms.txt +0 -2
- package/package.json +7 -7
- package/template/package.json +1 -1
- package/dist/chunk-N32VDWKH.js.map +0 -1
- package/dist/chunk-POYAFBLF.js.map +0 -1
- package/dist/chunk-QMVK4X3V.js.map +0 -1
- package/dist/chunk-QYOHMVUW.js.map +0 -1
- package/dist/chunk-SSGAMC26.js.map +0 -1
- package/dist/purchases/keys.cjs.map +0 -1
- package/dist/purchases/keys.d.cts +0 -42
- package/dist/purchases/keys.d.ts +0 -42
- package/dist/purchases/keys.js +0 -1
- package/docs/config.md +0 -147
- package/docs/resources.md +0 -97
- package/template/config/secrets.ts +0 -24
- /package/dist/{chunk-YL4C5NRY.js.map → chunk-Y5HXVUMP.js.map} +0 -0
- /package/dist/{purchases/keys.js.map → stack.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -11,20 +11,18 @@ import {
|
|
|
11
11
|
jsonb,
|
|
12
12
|
makeTypedDB,
|
|
13
13
|
numeric,
|
|
14
|
+
openai,
|
|
14
15
|
policy,
|
|
15
16
|
raw,
|
|
16
17
|
text,
|
|
17
18
|
timestamp,
|
|
18
|
-
uuid
|
|
19
|
-
|
|
19
|
+
uuid,
|
|
20
|
+
vector
|
|
21
|
+
} from "./chunk-HAF67F2H.js";
|
|
20
22
|
import {
|
|
21
23
|
defineError,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
recordEntitlement,
|
|
25
|
-
recordSpend,
|
|
26
|
-
spendFor
|
|
27
|
-
} from "./chunk-N32VDWKH.js";
|
|
24
|
+
getErrorRegistry
|
|
25
|
+
} from "./chunk-ZC6Q2BRD.js";
|
|
28
26
|
import {
|
|
29
27
|
Cache,
|
|
30
28
|
Database,
|
|
@@ -32,7 +30,6 @@ import {
|
|
|
32
30
|
Flags,
|
|
33
31
|
Log,
|
|
34
32
|
Notifications,
|
|
35
|
-
Purchases,
|
|
36
33
|
Realtime,
|
|
37
34
|
Secrets,
|
|
38
35
|
Storage,
|
|
@@ -40,7 +37,7 @@ import {
|
|
|
40
37
|
__requestALS,
|
|
41
38
|
__runWithRuntime,
|
|
42
39
|
__setRuntime
|
|
43
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-FSGSB42K.js";
|
|
44
41
|
import {
|
|
45
42
|
TxPlanError,
|
|
46
43
|
TxRefError,
|
|
@@ -52,7 +49,7 @@ import {
|
|
|
52
49
|
Controller,
|
|
53
50
|
__resetRegisteredControllers,
|
|
54
51
|
getRegisteredControllers
|
|
55
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-Y5HXVUMP.js";
|
|
56
53
|
import {
|
|
57
54
|
BadRequest,
|
|
58
55
|
Conflict,
|
|
@@ -66,141 +63,9 @@ import {
|
|
|
66
63
|
recordParam,
|
|
67
64
|
recordRoute,
|
|
68
65
|
recordThrows
|
|
69
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-OMRTHM4X.js";
|
|
70
67
|
import "./chunk-7D4SUZUM.js";
|
|
71
68
|
|
|
72
|
-
// src/purchases/errors.ts
|
|
73
|
-
function asPalstoreError(err) {
|
|
74
|
-
if (!(err instanceof Error)) return null;
|
|
75
|
-
const e = err;
|
|
76
|
-
if (typeof e.code !== "string" || typeof e.status !== "number") return null;
|
|
77
|
-
return { code: e.code, status: e.status, message: e.message };
|
|
78
|
-
}
|
|
79
|
-
function toHttpError(err) {
|
|
80
|
-
if (err instanceof HttpError) return err;
|
|
81
|
-
const p = asPalstoreError(err);
|
|
82
|
-
if (!p) return err;
|
|
83
|
-
switch (p.code) {
|
|
84
|
-
case "entitlement_required": {
|
|
85
|
-
const entitlement = err.entitlement ?? null;
|
|
86
|
-
return new PalError(403, "entitlement_required", p.message, { entitlement });
|
|
87
|
-
}
|
|
88
|
-
case "quota_exceeded":
|
|
89
|
-
case "credit_insufficient": {
|
|
90
|
-
const state = err.state;
|
|
91
|
-
return new PalError(429, p.code, p.message, state);
|
|
92
|
-
}
|
|
93
|
-
default:
|
|
94
|
-
return err;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// src/purchases/subject.ts
|
|
99
|
-
var subjectByRequest = /* @__PURE__ */ new WeakMap();
|
|
100
|
-
function storeEnv() {
|
|
101
|
-
return process.env.PALSTORE_STORE_ENV === "sandbox" ? "sandbox" : "production";
|
|
102
|
-
}
|
|
103
|
-
async function currentSubjectId() {
|
|
104
|
-
const store = __requestALS.getStore();
|
|
105
|
-
if (!store) {
|
|
106
|
-
throw new Error(
|
|
107
|
-
"Purchases decorators used outside a request scope. @RequireEntitlement/@Spend resolve the subject from the request's authenticated user, so they only work inside an endpoint handler."
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
const memoized = subjectByRequest.get(store);
|
|
111
|
-
if (memoized) return memoized;
|
|
112
|
-
const userId = store.userId;
|
|
113
|
-
if (!userId) {
|
|
114
|
-
throw new Unauthorized(
|
|
115
|
-
"This endpoint requires an authenticated user: purchases entitlements and quota are per-user, and the subject is resolved from the request's identity."
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
const resolving = Purchases.resolveSubject({ userRef: userId, storeEnv: storeEnv() }).then(
|
|
119
|
-
(r) => r.subjectId
|
|
120
|
-
);
|
|
121
|
-
subjectByRequest.set(store, resolving);
|
|
122
|
-
resolving.catch(() => subjectByRequest.delete(store));
|
|
123
|
-
return resolving;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// src/purchases/decorators.ts
|
|
127
|
-
var WRAPPED = /* @__PURE__ */ Symbol.for("palbase.backend.purchases.wrapped");
|
|
128
|
-
function idempotencyKeyFor(key) {
|
|
129
|
-
const store = __requestALS.getStore();
|
|
130
|
-
const base = store?.idempotencyKey || store?.requestId;
|
|
131
|
-
if (!base) {
|
|
132
|
-
throw new Error(
|
|
133
|
-
"@Spend needs a request-scoped idempotency key, and the runtime supplied neither an Idempotency-Key header nor a request id. Spending without one would let a retried request charge the user twice."
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
return `${base}:${key}`;
|
|
137
|
-
}
|
|
138
|
-
function ensureWrapper(target, fnName, descriptor) {
|
|
139
|
-
const original = descriptor.value;
|
|
140
|
-
if (typeof original !== "function") {
|
|
141
|
-
throw new Error(
|
|
142
|
-
`@RequireEntitlement/@Spend can only decorate a method; "${fnName}" is not one.`
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
if (original[WRAPPED]) return;
|
|
146
|
-
const wrapper = async function(...args) {
|
|
147
|
-
const ctor = this.constructor;
|
|
148
|
-
const entitlement = entitlementFor(ctor, fnName);
|
|
149
|
-
const spend = spendFor(ctor, fnName);
|
|
150
|
-
let subjectId;
|
|
151
|
-
try {
|
|
152
|
-
subjectId = await currentSubjectId();
|
|
153
|
-
} catch (err) {
|
|
154
|
-
throw toHttpError(err);
|
|
155
|
-
}
|
|
156
|
-
if (entitlement !== void 0) {
|
|
157
|
-
try {
|
|
158
|
-
await Purchases.require(subjectId, entitlement);
|
|
159
|
-
} catch (err) {
|
|
160
|
-
throw toHttpError(err);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if (spend === void 0) {
|
|
164
|
-
return original.apply(this, args);
|
|
165
|
-
}
|
|
166
|
-
let handlerFailure;
|
|
167
|
-
try {
|
|
168
|
-
return await Purchases.withSpend(
|
|
169
|
-
subjectId,
|
|
170
|
-
spend.key,
|
|
171
|
-
{ count: spend.count, idempotencyKey: idempotencyKeyFor(spend.key) },
|
|
172
|
-
async () => {
|
|
173
|
-
try {
|
|
174
|
-
return await original.apply(this, args);
|
|
175
|
-
} catch (err) {
|
|
176
|
-
handlerFailure = { err };
|
|
177
|
-
throw err;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
);
|
|
181
|
-
} catch (err) {
|
|
182
|
-
if (handlerFailure !== void 0 && handlerFailure.err === err) throw err;
|
|
183
|
-
throw toHttpError(err);
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
wrapper[WRAPPED] = true;
|
|
187
|
-
descriptor.value = wrapper;
|
|
188
|
-
}
|
|
189
|
-
function RequireEntitlement(key) {
|
|
190
|
-
return function(target, propertyKey, descriptor) {
|
|
191
|
-
const fnName = String(propertyKey);
|
|
192
|
-
recordEntitlement(target, fnName, key);
|
|
193
|
-
ensureWrapper(target, fnName, descriptor);
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
function Spend(key) {
|
|
197
|
-
return function(target, propertyKey, descriptor) {
|
|
198
|
-
const fnName = String(propertyKey);
|
|
199
|
-
recordSpend(target, fnName, { key, count: 1 });
|
|
200
|
-
ensureWrapper(target, fnName, descriptor);
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
69
|
// src/clients/http.ts
|
|
205
70
|
var PalbaseModuleError = class extends Error {
|
|
206
71
|
code;
|
|
@@ -546,12 +411,12 @@ function buildFlagsClient(http, cfg) {
|
|
|
546
411
|
return {
|
|
547
412
|
data: Object.keys(values).map((name) => {
|
|
548
413
|
const value = values[name];
|
|
549
|
-
const
|
|
414
|
+
const flag = {
|
|
550
415
|
name,
|
|
551
416
|
enabled: flagEnabled(value)
|
|
552
417
|
};
|
|
553
|
-
if (typeof value === "string")
|
|
554
|
-
return
|
|
418
|
+
if (typeof value === "string") flag.variant = { name: value };
|
|
419
|
+
return flag;
|
|
555
420
|
}),
|
|
556
421
|
error: null,
|
|
557
422
|
status: res.status
|
|
@@ -779,18 +644,99 @@ function buildNotificationsClient(http) {
|
|
|
779
644
|
|
|
780
645
|
// src/clients/realtime.ts
|
|
781
646
|
import { createHmac } from "crypto";
|
|
782
|
-
function mintToken(
|
|
647
|
+
function mintToken(secret) {
|
|
783
648
|
const now2 = Math.floor(Date.now() / 1e3);
|
|
784
649
|
const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url");
|
|
785
650
|
const payload = Buffer.from(
|
|
786
651
|
JSON.stringify({ iss: "backend-runtime", role: "service_role", iat: now2, exp: now2 + 60 })
|
|
787
652
|
).toString("base64url");
|
|
788
|
-
const sig = createHmac("sha256",
|
|
653
|
+
const sig = createHmac("sha256", secret).update(`${header}.${payload}`).digest("base64url");
|
|
789
654
|
return `${header}.${payload}.${sig}`;
|
|
790
655
|
}
|
|
791
656
|
function buildRealtimeClient(cfg) {
|
|
792
657
|
const url = `${cfg.baseUrl}/realtime/api/broadcast`;
|
|
658
|
+
const stateUrl = `${cfg.baseUrl}/realtime/api/state`;
|
|
659
|
+
async function writeState(topic, key, value, del) {
|
|
660
|
+
if (typeof topic !== "string" || topic.length === 0) {
|
|
661
|
+
return {
|
|
662
|
+
data: null,
|
|
663
|
+
error: new PalbaseModuleError("invalid_argument", "topic must be a non-empty string", 400)
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
667
|
+
return {
|
|
668
|
+
data: null,
|
|
669
|
+
error: new PalbaseModuleError("invalid_argument", "key must be a non-empty string", 400)
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
if (!cfg.apiJwtSecret) {
|
|
673
|
+
return {
|
|
674
|
+
data: null,
|
|
675
|
+
error: new PalbaseModuleError(
|
|
676
|
+
"realtime_unconfigured",
|
|
677
|
+
"Realtime.state is unavailable: realtime is not provisioned for this Environment (PALBASE_REALTIME_API_JWT_SECRET unset).",
|
|
678
|
+
503
|
|
679
|
+
)
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
let token;
|
|
683
|
+
try {
|
|
684
|
+
token = mintToken(cfg.apiJwtSecret);
|
|
685
|
+
} catch (err) {
|
|
686
|
+
return {
|
|
687
|
+
data: null,
|
|
688
|
+
error: new PalbaseModuleError(
|
|
689
|
+
"realtime_token_error",
|
|
690
|
+
err instanceof Error ? err.message : "token mint failed",
|
|
691
|
+
500
|
|
692
|
+
)
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
const op = del ? { topic, key, del: true } : { topic, key, value: value ?? {} };
|
|
696
|
+
const doFetch = cfg.fetchImpl ?? globalThis.fetch;
|
|
697
|
+
let response;
|
|
698
|
+
try {
|
|
699
|
+
response = await doFetch(stateUrl, {
|
|
700
|
+
method: "POST",
|
|
701
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
702
|
+
body: JSON.stringify({ ops: [op] })
|
|
703
|
+
});
|
|
704
|
+
} catch (err) {
|
|
705
|
+
return {
|
|
706
|
+
data: null,
|
|
707
|
+
error: new PalbaseModuleError(
|
|
708
|
+
"network_error",
|
|
709
|
+
err instanceof Error ? err.message : "state request failed",
|
|
710
|
+
0
|
|
711
|
+
)
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
if (response.status === 202 || response.status === 200) {
|
|
715
|
+
return { data: void 0, error: null };
|
|
716
|
+
}
|
|
717
|
+
const detail = await response.text().catch(() => "");
|
|
718
|
+
return {
|
|
719
|
+
data: null,
|
|
720
|
+
error: new PalbaseModuleError(
|
|
721
|
+
"realtime_state_failed",
|
|
722
|
+
`realtime state write returned ${response.status}${detail ? `: ${detail}` : ""}`,
|
|
723
|
+
response.status
|
|
724
|
+
)
|
|
725
|
+
};
|
|
726
|
+
}
|
|
793
727
|
return {
|
|
728
|
+
state: {
|
|
729
|
+
/** Write one DURABLE entry on a channel's shared state.
|
|
730
|
+
*
|
|
731
|
+
* The difference from broadcast is WHO it reaches: a broadcast reaches
|
|
732
|
+
* whoever is listening at that instant, while state is also handed to
|
|
733
|
+
* whoever joins afterwards. "This value is now X" wants the second — a
|
|
734
|
+
* client connecting a second later should not have to wait for the next
|
|
735
|
+
* change to learn X. */
|
|
736
|
+
set: (topic, key, value) => writeState(topic, key, value, false),
|
|
737
|
+
/** Remove one entry. */
|
|
738
|
+
clear: (topic, key) => writeState(topic, key, void 0, true)
|
|
739
|
+
},
|
|
794
740
|
async broadcast(channel, event, payload) {
|
|
795
741
|
if (typeof channel !== "string" || channel.length === 0) {
|
|
796
742
|
return {
|
|
@@ -875,11 +821,11 @@ function validateStoragePath(p) {
|
|
|
875
821
|
);
|
|
876
822
|
}
|
|
877
823
|
}
|
|
878
|
-
function toFileObject(
|
|
824
|
+
function toFileObject(bucket, row) {
|
|
879
825
|
return {
|
|
880
826
|
name: row.path ?? "",
|
|
881
827
|
path: row.path ?? "",
|
|
882
|
-
bucket: row.bucket ??
|
|
828
|
+
bucket: row.bucket ?? bucket,
|
|
883
829
|
size: typeof row.size === "number" ? row.size : 0,
|
|
884
830
|
contentType: row.contentType ?? "",
|
|
885
831
|
checksum: row.checksum ?? "",
|
|
@@ -995,35 +941,6 @@ function buildModuleClients(cfg) {
|
|
|
995
941
|
};
|
|
996
942
|
}
|
|
997
943
|
|
|
998
|
-
// src/purchases/keys-gen.ts
|
|
999
|
-
function liveKeys(entries) {
|
|
1000
|
-
return Object.entries(entries ?? {}).filter(([, def]) => def?.removed !== true).map(([key]) => key).sort();
|
|
1001
|
-
}
|
|
1002
|
-
function memberName(key) {
|
|
1003
|
-
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
|
|
1004
|
-
}
|
|
1005
|
-
function members(keys) {
|
|
1006
|
-
if (keys.length === 0) return "";
|
|
1007
|
-
return `
|
|
1008
|
-
${keys.map((k) => ` ${memberName(k)}: true;`).join("\n")}
|
|
1009
|
-
`;
|
|
1010
|
-
}
|
|
1011
|
-
function makePurchasesDts(manifest) {
|
|
1012
|
-
const entitlements = liveKeys(manifest.entitlements);
|
|
1013
|
-
const limits = liveKeys({ ...manifest.limits, ...manifest.credits });
|
|
1014
|
-
return `// palbase-purchases.d.ts \u2014 GENERATED by @palbase/backend. Do not edit.
|
|
1015
|
-
// Source: the project's palstore catalog revision.
|
|
1016
|
-
|
|
1017
|
-
declare module "@palbase/backend/purchases" {
|
|
1018
|
-
interface Entitlements {${members(entitlements)}}
|
|
1019
|
-
|
|
1020
|
-
interface Limits {${members(limits)}}
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
export {};
|
|
1024
|
-
`;
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
944
|
// src/db/schema-json.ts
|
|
1028
945
|
function defOf(column) {
|
|
1029
946
|
return "_def" in column ? column._def : column;
|
|
@@ -1046,6 +963,7 @@ function columnToJSON(column) {
|
|
|
1046
963
|
if (def.enumName !== void 0) out.enumName = def.enumName;
|
|
1047
964
|
if (def.enumValues !== void 0) out.enumValues = [...def.enumValues];
|
|
1048
965
|
if (def.unique === true) out.unique = true;
|
|
966
|
+
if (def.dimensions !== void 0) out.dimensions = def.dimensions;
|
|
1049
967
|
return out;
|
|
1050
968
|
}
|
|
1051
969
|
function policyToJSON(policy2) {
|
|
@@ -1061,6 +979,75 @@ function policyToJSON(policy2) {
|
|
|
1061
979
|
permissive: policy2.permissive !== false
|
|
1062
980
|
};
|
|
1063
981
|
}
|
|
982
|
+
function commonSearchFields(search, out) {
|
|
983
|
+
if (search.synonyms !== void 0 && Object.keys(search.synonyms).length > 0) {
|
|
984
|
+
out.synonyms = Object.fromEntries(
|
|
985
|
+
Object.entries(search.synonyms).map(([word, alts]) => [word, [...alts]])
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
if (search.validity === true) out.validity = true;
|
|
989
|
+
}
|
|
990
|
+
function searchToJSON(search, vectorColumn) {
|
|
991
|
+
if (search.from !== void 0 && search.model !== void 0) {
|
|
992
|
+
const out2 = {};
|
|
993
|
+
const textCols = search.text === false ? void 0 : Array.isArray(search.text) && search.text.length > 0 ? search.text : search.from;
|
|
994
|
+
if (textCols !== void 0) out2.text = { columns: [...textCols] };
|
|
995
|
+
const v = { metric: search.metric ?? "cosine" };
|
|
996
|
+
if (vectorColumn !== void 0) v.column = vectorColumn;
|
|
997
|
+
v.embed = {
|
|
998
|
+
provider: search.model.provider,
|
|
999
|
+
model: search.model.model,
|
|
1000
|
+
from: [...search.from],
|
|
1001
|
+
...search.model.apiKeyName !== void 0 ? { apiKeyName: search.model.apiKeyName } : {},
|
|
1002
|
+
...search.model.dimensions !== void 0 ? { dimensions: search.model.dimensions } : {},
|
|
1003
|
+
...search.model.baseURL !== void 0 ? { baseURL: search.model.baseURL } : {}
|
|
1004
|
+
};
|
|
1005
|
+
if (search.staleness !== void 0) v.staleness = search.staleness;
|
|
1006
|
+
if (vectorColumn === void 0) {
|
|
1007
|
+
v.mode = "chunks";
|
|
1008
|
+
if (search.chunks !== void 0) {
|
|
1009
|
+
const c = {};
|
|
1010
|
+
if (search.chunks.size !== void 0 && search.chunks.size > 0) c.sizeChars = search.chunks.size;
|
|
1011
|
+
if (search.chunks.overlap !== void 0 && search.chunks.overlap > 0) c.overlapChars = search.chunks.overlap;
|
|
1012
|
+
if (Object.keys(c).length > 0) v.chunks = c;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
out2.vector = [v];
|
|
1016
|
+
commonSearchFields(search, out2);
|
|
1017
|
+
return out2;
|
|
1018
|
+
}
|
|
1019
|
+
const out = {};
|
|
1020
|
+
if (Array.isArray(search.text) && search.text.length > 0) out.text = { columns: [...search.text] };
|
|
1021
|
+
const legs = search.vector === void 0 ? [] : Array.isArray(search.vector) ? search.vector : [search.vector];
|
|
1022
|
+
if (legs.length > 0) {
|
|
1023
|
+
out.vector = legs.map((leg) => {
|
|
1024
|
+
const v = { metric: leg.metric ?? "cosine" };
|
|
1025
|
+
if (leg.column !== void 0) v.column = leg.column;
|
|
1026
|
+
if (leg.staleness !== void 0) v.staleness = leg.staleness;
|
|
1027
|
+
if (leg.model !== void 0) {
|
|
1028
|
+
v.embed = {
|
|
1029
|
+
provider: leg.model.provider,
|
|
1030
|
+
model: leg.model.model,
|
|
1031
|
+
from: [...leg.from ?? []],
|
|
1032
|
+
...leg.model.apiKeyName !== void 0 ? { apiKeyName: leg.model.apiKeyName } : {},
|
|
1033
|
+
...leg.model.dimensions !== void 0 ? { dimensions: leg.model.dimensions } : {},
|
|
1034
|
+
...leg.model.baseURL !== void 0 ? { baseURL: leg.model.baseURL } : {}
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
return v;
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
commonSearchFields(search, out);
|
|
1041
|
+
return out;
|
|
1042
|
+
}
|
|
1043
|
+
function memoryToJSON(m) {
|
|
1044
|
+
return {
|
|
1045
|
+
from: [...m.from],
|
|
1046
|
+
into: m.into,
|
|
1047
|
+
...m.subject !== void 0 ? { subject: m.subject } : {},
|
|
1048
|
+
extract: { provider: m.extract.provider, model: m.extract.model }
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1064
1051
|
function tableToJSON(table) {
|
|
1065
1052
|
const columns = {};
|
|
1066
1053
|
for (const [name, column] of Object.entries(table.columns)) {
|
|
@@ -1096,6 +1083,14 @@ function tableToJSON(table) {
|
|
|
1096
1083
|
if (table.indexes !== void 0 && table.indexes.length > 0) {
|
|
1097
1084
|
out.indexes = table.indexes.map((i) => ({ name: i.name, columns: [...i.columns] }));
|
|
1098
1085
|
}
|
|
1086
|
+
if (table.search !== void 0) {
|
|
1087
|
+
const vectorColumn = Object.entries(columns).find(([, c]) => c.dimensions !== void 0)?.[0];
|
|
1088
|
+
const sj = searchToJSON(table.search, vectorColumn);
|
|
1089
|
+
if (sj.text !== void 0 || sj.vector !== void 0) out.search = sj;
|
|
1090
|
+
}
|
|
1091
|
+
if (table.memory !== void 0) {
|
|
1092
|
+
out.memory = memoryToJSON(table.memory);
|
|
1093
|
+
}
|
|
1099
1094
|
return out;
|
|
1100
1095
|
}
|
|
1101
1096
|
function toSchemaJSON(schema) {
|
|
@@ -1107,6 +1102,63 @@ function toSchemaJSON(schema) {
|
|
|
1107
1102
|
return { tables, extensions: [...new Set(schema.extensions ?? [])] };
|
|
1108
1103
|
}
|
|
1109
1104
|
|
|
1105
|
+
// src/channels.ts
|
|
1106
|
+
var CHANNELS = /* @__PURE__ */ Symbol.for("palbase.backend.channels");
|
|
1107
|
+
function ownerOnly() {
|
|
1108
|
+
return { kind: "owner" };
|
|
1109
|
+
}
|
|
1110
|
+
function publicChannel(opts = {}) {
|
|
1111
|
+
return { kind: "public", publish: opts.publish ?? false, state: opts.state };
|
|
1112
|
+
}
|
|
1113
|
+
function paramCount(pattern) {
|
|
1114
|
+
return pattern.split(":").filter((s) => s.startsWith("{") && s.endsWith("}")).length;
|
|
1115
|
+
}
|
|
1116
|
+
function defineChannels(map) {
|
|
1117
|
+
const entries = [];
|
|
1118
|
+
for (const [pattern, entry] of Object.entries(map)) {
|
|
1119
|
+
if ("kind" in entry && entry.kind === "owner") {
|
|
1120
|
+
if (paramCount(pattern) !== 1) {
|
|
1121
|
+
throw new Error(
|
|
1122
|
+
`defineChannels: ownerOnly() pattern "${pattern}" must carry exactly one {param} (its value is compared to the token subject)`
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
entries.push({ pattern, kind: "owner" });
|
|
1126
|
+
} else if ("kind" in entry && entry.kind === "public") {
|
|
1127
|
+
entries.push({ pattern, kind: "public", publish: entry.publish, state: entry.state });
|
|
1128
|
+
} else {
|
|
1129
|
+
const c = entry;
|
|
1130
|
+
entries.push({ pattern, kind: "custom", authorize: c.authorize, handler: c.handler });
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
const def = { entries };
|
|
1134
|
+
const carrier = globalThis;
|
|
1135
|
+
const existing = carrier[CHANNELS];
|
|
1136
|
+
if (existing && !sameDeclaration(existing, def)) {
|
|
1137
|
+
throw new Error(
|
|
1138
|
+
`defineChannels: channels were already declared (${existing.entries.length} pattern(s): ${existing.entries.map((e) => e.pattern).join(", ")}). A project declares its channels ONCE \u2014 a second call would overwrite the first, and channels nobody declared are refused.`
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
carrier[CHANNELS] = def;
|
|
1142
|
+
return def;
|
|
1143
|
+
}
|
|
1144
|
+
function wireRow(e) {
|
|
1145
|
+
return JSON.stringify([
|
|
1146
|
+
e.pattern,
|
|
1147
|
+
e.kind,
|
|
1148
|
+
e.publish ?? false,
|
|
1149
|
+
e.state?.read ?? false,
|
|
1150
|
+
e.state?.write ?? false,
|
|
1151
|
+
// The handler is compared by SOURCE, like authorize: two evaluations of one
|
|
1152
|
+
// module give the same text, while different logic gives different text.
|
|
1153
|
+
e.handler ? e.handler.toString() : "",
|
|
1154
|
+
e.authorize ? e.authorize.toString() : ""
|
|
1155
|
+
]);
|
|
1156
|
+
}
|
|
1157
|
+
function sameDeclaration(a, b) {
|
|
1158
|
+
if (a.entries.length !== b.entries.length) return false;
|
|
1159
|
+
return a.entries.every((x, i) => wireRow(x) === wireRow(b.entries[i]));
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1110
1162
|
// src/db/env-gen.ts
|
|
1111
1163
|
function baseTsType(def) {
|
|
1112
1164
|
switch (def.type) {
|
|
@@ -1128,6 +1180,11 @@ function baseTsType(def) {
|
|
|
1128
1180
|
return "boolean";
|
|
1129
1181
|
case "jsonb":
|
|
1130
1182
|
return "unknown";
|
|
1183
|
+
// FR-008: the authoring type of a pgvector column. The default `unknown`
|
|
1184
|
+
// branch below STAYS — an unknown wire type is refused on the Go side
|
|
1185
|
+
// (FR-005), never papered over here.
|
|
1186
|
+
case "vector":
|
|
1187
|
+
return "number[]";
|
|
1131
1188
|
case "enum": {
|
|
1132
1189
|
const values = def.enumValues ?? [];
|
|
1133
1190
|
if (values.length === 0) return "string";
|
|
@@ -1161,10 +1218,10 @@ function buildChildEdges(schema) {
|
|
|
1161
1218
|
const ref = builder._def.references;
|
|
1162
1219
|
if (ref === void 0) continue;
|
|
1163
1220
|
if (ref.table === childName) continue;
|
|
1164
|
-
const
|
|
1165
|
-
if (
|
|
1166
|
-
if (
|
|
1167
|
-
|
|
1221
|
+
const bucket = edges.get(ref.table);
|
|
1222
|
+
if (bucket === void 0) continue;
|
|
1223
|
+
if (bucket.some((e) => e.child === childName)) continue;
|
|
1224
|
+
bucket.push({ child: childName, column: col });
|
|
1168
1225
|
}
|
|
1169
1226
|
}
|
|
1170
1227
|
return edges;
|
|
@@ -1209,6 +1266,10 @@ function tableBlock(table, children, indent) {
|
|
|
1209
1266
|
`${indent} };`,
|
|
1210
1267
|
`${indent} owner: ${owner === null ? "null" : JSON.stringify(owner)};`,
|
|
1211
1268
|
`${indent} children: {${childEntries === "" ? "" : ` ${childEntries} `}};`,
|
|
1269
|
+
// searchable: vector kolonu YA DA search beyanı → EnvTypedTable'da search()
|
|
1270
|
+
// üyesini açan yapısal bayrak (FR-013). Yokken satır hiç üretilmez ki
|
|
1271
|
+
// mevcut şemaların d.ts'i bayt-aynı kalsın.
|
|
1272
|
+
...cols.some(([, b]) => b._def.type === "vector") || table.search !== void 0 ? [`${indent} searchable: true;`] : [],
|
|
1212
1273
|
`${indent}};`
|
|
1213
1274
|
].join("\n");
|
|
1214
1275
|
}
|
|
@@ -1234,638 +1295,50 @@ export {};
|
|
|
1234
1295
|
`;
|
|
1235
1296
|
}
|
|
1236
1297
|
|
|
1237
|
-
// src/
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
var VARIANT_FITS = ["cover", "contain", "inside"];
|
|
1241
|
-
function normalizeVariants(input) {
|
|
1242
|
-
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
1243
|
-
throw new Error("bucket variants must be an object of { <name>: { width?, height?, format? } }");
|
|
1244
|
-
}
|
|
1245
|
-
const out = {};
|
|
1246
|
-
for (const [name, spec] of Object.entries(input)) {
|
|
1247
|
-
if (!/^[a-z0-9][a-z0-9_-]*$/i.test(name)) {
|
|
1248
|
-
throw new Error(
|
|
1249
|
-
`bucket variant name "${name}" must be alphanumeric with optional - or _ (e.g. "thumb", "card_2x")`
|
|
1250
|
-
);
|
|
1251
|
-
}
|
|
1252
|
-
if (spec === null || typeof spec !== "object" || Array.isArray(spec)) {
|
|
1253
|
-
throw new Error(`bucket variant "${name}" must be an object like { width: 100, height: 100 }`);
|
|
1254
|
-
}
|
|
1255
|
-
for (const dim of ["width", "height"]) {
|
|
1256
|
-
const value = spec[dim];
|
|
1257
|
-
if (value === void 0) continue;
|
|
1258
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
1259
|
-
throw new Error(`bucket variant "${name}" ${dim} must be a positive integer, got ${String(value)}`);
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
if (spec.width === void 0 && spec.height === void 0) {
|
|
1263
|
-
throw new Error(`bucket variant "${name}" must set width and/or height`);
|
|
1264
|
-
}
|
|
1265
|
-
if (spec.format !== void 0 && !VARIANT_FORMATS.includes(spec.format)) {
|
|
1266
|
-
throw new Error(
|
|
1267
|
-
`bucket variant "${name}" format must be one of ${VARIANT_FORMATS.join(", ")}, got "${String(spec.format)}"`
|
|
1268
|
-
);
|
|
1269
|
-
}
|
|
1270
|
-
if (spec.fit !== void 0 && !VARIANT_FITS.includes(spec.fit)) {
|
|
1271
|
-
throw new Error(
|
|
1272
|
-
`bucket variant "${name}" fit must be one of ${VARIANT_FITS.join(", ")}, got "${String(spec.fit)}"`
|
|
1273
|
-
);
|
|
1274
|
-
}
|
|
1275
|
-
if (spec.quality !== void 0) {
|
|
1276
|
-
const q = spec.quality;
|
|
1277
|
-
if (typeof q !== "number" || !Number.isInteger(q) || q < 1 || q > 100) {
|
|
1278
|
-
throw new Error(`bucket variant "${name}" quality must be an integer 1\u2013100, got ${String(q)}`);
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
if (spec.fit === "cover" && (spec.width === void 0 || spec.height === void 0)) {
|
|
1282
|
-
throw new Error(`bucket variant "${name}" uses fit "cover", which needs BOTH width and height`);
|
|
1283
|
-
}
|
|
1284
|
-
out[name] = {
|
|
1285
|
-
...spec.width !== void 0 ? { width: spec.width } : {},
|
|
1286
|
-
...spec.height !== void 0 ? { height: spec.height } : {},
|
|
1287
|
-
...spec.fit !== void 0 ? { fit: spec.fit } : {},
|
|
1288
|
-
...spec.format !== void 0 ? { format: spec.format } : {},
|
|
1289
|
-
...spec.quality !== void 0 ? { quality: spec.quality } : {}
|
|
1290
|
-
};
|
|
1291
|
-
}
|
|
1292
|
-
return out;
|
|
1298
|
+
// src/stack-gen.ts
|
|
1299
|
+
function liveNames(names) {
|
|
1300
|
+
return [...new Set(names)].sort();
|
|
1293
1301
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
kb: 1024,
|
|
1297
|
-
mb: 1024 ** 2,
|
|
1298
|
-
gb: 1024 ** 3,
|
|
1299
|
-
tb: 1024 ** 4
|
|
1300
|
-
};
|
|
1301
|
-
function parseFileSizeLimit(input) {
|
|
1302
|
-
if (typeof input === "number") {
|
|
1303
|
-
if (!Number.isFinite(input) || input < 0 || !Number.isInteger(input)) {
|
|
1304
|
-
throw new Error(
|
|
1305
|
-
`bucket fileSizeLimit must be a non-negative integer number of bytes, got ${input}`
|
|
1306
|
-
);
|
|
1307
|
-
}
|
|
1308
|
-
return input;
|
|
1309
|
-
}
|
|
1310
|
-
const trimmed = input.trim();
|
|
1311
|
-
const match = /^(\d+(?:\.\d+)?)\s*([a-zA-Z]+)?$/.exec(trimmed);
|
|
1312
|
-
if (!match) {
|
|
1313
|
-
throw new Error(
|
|
1314
|
-
`bucket fileSizeLimit string must be "<number><unit>" like "5MB" or "1GB", got "${input}"`
|
|
1315
|
-
);
|
|
1316
|
-
}
|
|
1317
|
-
const value = Number.parseFloat(match[1]);
|
|
1318
|
-
const unit = (match[2] ?? "b").toLowerCase();
|
|
1319
|
-
const multiplier = UNIT_BYTES[unit];
|
|
1320
|
-
if (multiplier === void 0) {
|
|
1321
|
-
throw new Error(
|
|
1322
|
-
`bucket fileSizeLimit has an unknown unit "${match[2]}" \u2014 use B, KB, MB, GB, or TB (e.g. "5MB")`
|
|
1323
|
-
);
|
|
1324
|
-
}
|
|
1325
|
-
const bytes = value * multiplier;
|
|
1326
|
-
if (!Number.isFinite(bytes) || bytes < 0) {
|
|
1327
|
-
throw new Error(`bucket fileSizeLimit resolved to an invalid byte count: ${bytes}`);
|
|
1328
|
-
}
|
|
1329
|
-
return Math.round(bytes);
|
|
1330
|
-
}
|
|
1331
|
-
var MIME_RE = /^[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*\/(?:\*|[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*)$/;
|
|
1332
|
-
function bucket(opts = {}) {
|
|
1333
|
-
const fileSizeLimit = opts.fileSizeLimit === void 0 ? null : parseFileSizeLimit(opts.fileSizeLimit);
|
|
1334
|
-
let allowedMimeTypes = null;
|
|
1335
|
-
if (opts.allowedMimeTypes !== void 0) {
|
|
1336
|
-
if (!Array.isArray(opts.allowedMimeTypes)) {
|
|
1337
|
-
throw new Error("bucket allowedMimeTypes must be an array of MIME-type strings");
|
|
1338
|
-
}
|
|
1339
|
-
for (const mime of opts.allowedMimeTypes) {
|
|
1340
|
-
if (typeof mime !== "string" || !MIME_RE.test(mime.trim())) {
|
|
1341
|
-
throw new Error(
|
|
1342
|
-
`bucket allowedMimeTypes entry "${mime}" is not a valid MIME type (expected "type/subtype", e.g. "image/png")`
|
|
1343
|
-
);
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
allowedMimeTypes = [...new Set(opts.allowedMimeTypes.map((m) => m.trim()))];
|
|
1347
|
-
}
|
|
1348
|
-
return {
|
|
1349
|
-
public: opts.public ?? false,
|
|
1350
|
-
fileSizeLimit,
|
|
1351
|
-
allowedMimeTypes,
|
|
1352
|
-
variants: opts.variants === void 0 ? null : normalizeVariants(opts.variants)
|
|
1353
|
-
};
|
|
1302
|
+
function memberName(name) {
|
|
1303
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
1354
1304
|
}
|
|
1355
|
-
function
|
|
1356
|
-
if (
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
for (const name of Object.keys(input.buckets)) {
|
|
1361
|
-
if (name.length === 0) {
|
|
1362
|
-
throw new Error("bucket name must be a non-empty string");
|
|
1363
|
-
}
|
|
1364
|
-
const def = input.buckets[name];
|
|
1365
|
-
if (def === void 0) continue;
|
|
1366
|
-
buckets[name] = def;
|
|
1367
|
-
}
|
|
1368
|
-
return { __config: STORAGE_CONFIG_KIND, buckets };
|
|
1305
|
+
function members(names) {
|
|
1306
|
+
if (names.length === 0) return "";
|
|
1307
|
+
return `
|
|
1308
|
+
${names.map((n) => ` ${memberName(n)}: true;`).join("\n")}
|
|
1309
|
+
`;
|
|
1369
1310
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
}
|
|
1385
|
-
hosts.push(h.trim().toLowerCase());
|
|
1386
|
-
}
|
|
1387
|
-
if (input.timeoutMs === void 0) {
|
|
1388
|
-
return { __config: EGRESS_CONFIG_KIND, hosts };
|
|
1389
|
-
}
|
|
1390
|
-
const timeoutMs = input.timeoutMs;
|
|
1391
|
-
if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs)) {
|
|
1392
|
-
throw new Error("defineEgress: timeoutMs must be an integer number of milliseconds");
|
|
1393
|
-
}
|
|
1394
|
-
if (timeoutMs < EGRESS_TIMEOUT_MIN_MS || timeoutMs > EGRESS_TIMEOUT_MAX_MS) {
|
|
1395
|
-
throw new Error(
|
|
1396
|
-
`defineEgress: timeoutMs must be between ${EGRESS_TIMEOUT_MIN_MS} and ${EGRESS_TIMEOUT_MAX_MS} (got ${timeoutMs})`
|
|
1397
|
-
);
|
|
1398
|
-
}
|
|
1399
|
-
return { __config: EGRESS_CONFIG_KIND, hosts, timeoutMs };
|
|
1311
|
+
function bucketMembers(buckets) {
|
|
1312
|
+
const normalized = buckets.map((b) => typeof b === "string" ? { name: b, variants: [] } : b);
|
|
1313
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1314
|
+
for (const b of normalized) byName.set(b.name, b.variants);
|
|
1315
|
+
const names = [...byName.keys()].sort();
|
|
1316
|
+
if (names.length === 0) return "";
|
|
1317
|
+
const rows = names.map((name) => {
|
|
1318
|
+
const variants = [...new Set(byName.get(name) ?? [])].sort();
|
|
1319
|
+
const union = variants.length === 0 ? "never" : variants.map((v) => JSON.stringify(v)).join(" | ");
|
|
1320
|
+
return ` ${memberName(name)}: { variants: ${union} };`;
|
|
1321
|
+
});
|
|
1322
|
+
return `
|
|
1323
|
+
${rows.join("\n")}
|
|
1324
|
+
`;
|
|
1400
1325
|
}
|
|
1326
|
+
function makeStackDts(names) {
|
|
1327
|
+
const secrets = liveNames(names.secrets);
|
|
1328
|
+
const flags = liveNames(names.flags);
|
|
1329
|
+
return `// palbase-stack.d.ts \u2014 GENERATED by @palbase/backend. Do not edit.
|
|
1330
|
+
// Source: the linked environment's stack.
|
|
1401
1331
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
var RESERVED_SECRET_PREFIX = "PB_NOTIFICATIONS";
|
|
1405
|
-
var PROVIDER_CATALOG = {
|
|
1406
|
-
apns: {
|
|
1407
|
-
channel: "push",
|
|
1408
|
-
required: ["teamId", "keyId", "bundleId"],
|
|
1409
|
-
optional: ["isProduction"],
|
|
1410
|
-
secrets: ["p8"]
|
|
1411
|
-
},
|
|
1412
|
-
fcm: {
|
|
1413
|
-
channel: "push",
|
|
1414
|
-
required: [],
|
|
1415
|
-
optional: [],
|
|
1416
|
-
secrets: ["serviceAccount"]
|
|
1417
|
-
},
|
|
1418
|
-
sendgrid: {
|
|
1419
|
-
channel: "email",
|
|
1420
|
-
required: ["fromDomain"],
|
|
1421
|
-
optional: [],
|
|
1422
|
-
secrets: ["apiKey"]
|
|
1423
|
-
},
|
|
1424
|
-
ses: {
|
|
1425
|
-
channel: "email",
|
|
1426
|
-
required: ["region", "accessKeyId", "fromDomain"],
|
|
1427
|
-
optional: [],
|
|
1428
|
-
secrets: ["secretAccessKey"]
|
|
1429
|
-
},
|
|
1430
|
-
smtp: {
|
|
1431
|
-
channel: "email",
|
|
1432
|
-
required: ["host", "port", "fromEmail"],
|
|
1433
|
-
optional: ["username", "useStarttls"],
|
|
1434
|
-
secrets: ["password"]
|
|
1435
|
-
},
|
|
1436
|
-
acs: {
|
|
1437
|
-
channel: "email",
|
|
1438
|
-
required: ["fromEmail"],
|
|
1439
|
-
optional: ["fromName"],
|
|
1440
|
-
secrets: ["connectionString"]
|
|
1441
|
-
},
|
|
1442
|
-
twilio: {
|
|
1443
|
-
channel: "sms",
|
|
1444
|
-
// account_sid required; one of from_number / messaging_service_sid required
|
|
1445
|
-
// (enforced by the refine in buildProvider, not by the flat `required` list).
|
|
1446
|
-
required: ["accountSid"],
|
|
1447
|
-
optional: ["fromNumber", "messagingServiceSid"],
|
|
1448
|
-
secrets: ["authToken"]
|
|
1449
|
-
}
|
|
1450
|
-
};
|
|
1451
|
-
var DEFAULT_TEMPLATE_LOCALE = "en";
|
|
1452
|
-
function extractVariables(...sources) {
|
|
1453
|
-
const found = /* @__PURE__ */ new Set();
|
|
1454
|
-
for (const src of sources) {
|
|
1455
|
-
if (!src) continue;
|
|
1456
|
-
for (const match of src.matchAll(/\{\{\{?\s*([^{}]+?)\s*\}?\}\}/g)) {
|
|
1457
|
-
const token = (match[1] ?? "").trim();
|
|
1458
|
-
if (token === "" || "#/^!>&".includes(token.charAt(0)) || token === "else") continue;
|
|
1459
|
-
if (/\s/.test(token)) continue;
|
|
1460
|
-
const root = token.split(".")[0] ?? "";
|
|
1461
|
-
if (root === "" || root === "this" || root.startsWith("@")) continue;
|
|
1462
|
-
found.add(root);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
return [...found].sort();
|
|
1466
|
-
}
|
|
1467
|
-
function buildEmailTemplate(slug, opts) {
|
|
1468
|
-
const row = (locale, c) => {
|
|
1469
|
-
if (!c.subject) throw new Error(`notifications: email template "${slug}" (${locale}) needs a subject`);
|
|
1470
|
-
if (!c.html) throw new Error(`notifications: email template "${slug}" (${locale}) needs an html body`);
|
|
1471
|
-
const def = {
|
|
1472
|
-
slug,
|
|
1473
|
-
locale,
|
|
1474
|
-
subject: c.subject,
|
|
1475
|
-
html_body: c.html,
|
|
1476
|
-
variables: c.variables ? [...c.variables] : extractVariables(c.subject, c.html, c.text)
|
|
1477
|
-
};
|
|
1478
|
-
if (c.text !== void 0) def.text_body = c.text;
|
|
1479
|
-
return def;
|
|
1480
|
-
};
|
|
1481
|
-
const rows = [row(opts.locale ?? DEFAULT_TEMPLATE_LOCALE, opts)];
|
|
1482
|
-
for (const [locale, content] of Object.entries(opts.locales ?? {})) {
|
|
1483
|
-
rows.push(row(locale, content));
|
|
1484
|
-
}
|
|
1485
|
-
return rows;
|
|
1486
|
-
}
|
|
1487
|
-
function buildSmsTemplate(slug, opts) {
|
|
1488
|
-
const row = (locale, c) => {
|
|
1489
|
-
if (!c.body) throw new Error(`notifications: sms template "${slug}" (${locale}) needs a body`);
|
|
1490
|
-
return {
|
|
1491
|
-
slug,
|
|
1492
|
-
locale,
|
|
1493
|
-
body: c.body,
|
|
1494
|
-
variables: c.variables ? [...c.variables] : extractVariables(c.body)
|
|
1495
|
-
};
|
|
1496
|
-
};
|
|
1497
|
-
const rows = [row(opts.locale ?? DEFAULT_TEMPLATE_LOCALE, opts)];
|
|
1498
|
-
for (const [locale, content] of Object.entries(opts.locales ?? {})) {
|
|
1499
|
-
rows.push(row(locale, content));
|
|
1500
|
-
}
|
|
1501
|
-
return rows;
|
|
1502
|
-
}
|
|
1503
|
-
function buildTemplates(input) {
|
|
1504
|
-
const out = { email: [], sms: [] };
|
|
1505
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1506
|
-
const claim = (channel, slug, locale) => {
|
|
1507
|
-
const key = `${channel}/${slug}/${locale}`;
|
|
1508
|
-
if (seen.has(key)) {
|
|
1509
|
-
throw new Error(`notifications: ${channel} template "${slug}" declares locale "${locale}" twice`);
|
|
1510
|
-
}
|
|
1511
|
-
seen.add(key);
|
|
1512
|
-
};
|
|
1513
|
-
for (const [slug, opts] of Object.entries(input.email ?? {})) {
|
|
1514
|
-
for (const row of buildEmailTemplate(slug, opts)) {
|
|
1515
|
-
claim("email", row.slug, row.locale);
|
|
1516
|
-
out.email.push(row);
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
for (const [slug, opts] of Object.entries(input.sms ?? {})) {
|
|
1520
|
-
for (const row of buildSmsTemplate(slug, opts)) {
|
|
1521
|
-
claim("sms", row.slug, row.locale);
|
|
1522
|
-
out.sms.push(row);
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
return out;
|
|
1526
|
-
}
|
|
1527
|
-
function buildProvider(name, opts) {
|
|
1528
|
-
const entry = PROVIDER_CATALOG[name];
|
|
1529
|
-
const src = { ...opts };
|
|
1530
|
-
const enabled = src.enabled === void 0 ? false : Boolean(src.enabled);
|
|
1531
|
-
if (!enabled) {
|
|
1532
|
-
return { enabled: false };
|
|
1533
|
-
}
|
|
1534
|
-
const def = { enabled: true };
|
|
1535
|
-
for (const field of entry.required) {
|
|
1536
|
-
const value = src[field];
|
|
1537
|
-
if (value === void 0 || value === null || value === "") {
|
|
1538
|
-
throw new Error(
|
|
1539
|
-
`notifications: provider "${name}" is enabled but missing required field "${field}"`
|
|
1540
|
-
);
|
|
1541
|
-
}
|
|
1542
|
-
def[field] = value;
|
|
1543
|
-
}
|
|
1544
|
-
for (const field of entry.optional) {
|
|
1545
|
-
if (src[field] !== void 0) {
|
|
1546
|
-
def[field] = src[field];
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
if (name === "twilio") {
|
|
1550
|
-
const hasFrom = Boolean(def.fromNumber);
|
|
1551
|
-
const hasMsg = Boolean(def.messagingServiceSid);
|
|
1552
|
-
if (!hasFrom && !hasMsg) {
|
|
1553
|
-
throw new Error(
|
|
1554
|
-
'notifications: provider "twilio" requires one of "fromNumber" or "messagingServiceSid"'
|
|
1555
|
-
);
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
return def;
|
|
1559
|
-
}
|
|
1560
|
-
function defineNotifications(input) {
|
|
1561
|
-
if (input === null || typeof input !== "object") {
|
|
1562
|
-
throw new Error("defineNotifications expects { push?, email?, sms? }");
|
|
1563
|
-
}
|
|
1564
|
-
const config = {
|
|
1565
|
-
__config: NOTIFICATIONS_CONFIG_KIND,
|
|
1566
|
-
push: {},
|
|
1567
|
-
email: {},
|
|
1568
|
-
sms: {},
|
|
1569
|
-
templates: buildTemplates(input.templates ?? {})
|
|
1570
|
-
};
|
|
1571
|
-
const push = input.push ?? {};
|
|
1572
|
-
if (push.apns !== void 0) config.push.apns = buildProvider("apns", push.apns);
|
|
1573
|
-
if (push.fcm !== void 0) config.push.fcm = buildProvider("fcm", push.fcm);
|
|
1574
|
-
const email = input.email ?? {};
|
|
1575
|
-
if (email.sendgrid !== void 0) config.email.sendgrid = buildProvider("sendgrid", email.sendgrid);
|
|
1576
|
-
if (email.ses !== void 0) config.email.ses = buildProvider("ses", email.ses);
|
|
1577
|
-
if (email.smtp !== void 0) config.email.smtp = buildProvider("smtp", email.smtp);
|
|
1578
|
-
if (email.acs !== void 0) config.email.acs = buildProvider("acs", email.acs);
|
|
1579
|
-
const sms = input.sms ?? {};
|
|
1580
|
-
if (sms.twilio !== void 0) config.sms.twilio = buildProvider("twilio", sms.twilio);
|
|
1581
|
-
return config;
|
|
1582
|
-
}
|
|
1583
|
-
function reservedSecretKey(provider, secretField) {
|
|
1584
|
-
return `${RESERVED_SECRET_PREFIX}_${camelToUpperSnake(provider)}_${camelToUpperSnake(secretField)}`;
|
|
1585
|
-
}
|
|
1586
|
-
function camelToUpperSnake(s) {
|
|
1587
|
-
return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase();
|
|
1588
|
-
}
|
|
1332
|
+
declare module "@palbase/backend/stack" {
|
|
1333
|
+
interface Secrets {${members(secrets)}}
|
|
1589
1334
|
|
|
1590
|
-
|
|
1591
|
-
var TEST_USERS_CONFIG_KIND = "test-users";
|
|
1592
|
-
var TEST_USER_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
1593
|
-
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1594
|
-
var E164_RE = /^\+[1-9]\d{1,14}$/;
|
|
1595
|
-
var OTP_LENGTH = 6;
|
|
1596
|
-
var OTP_RE = /^\d{6}$/;
|
|
1597
|
-
var MIN_PASSWORD_LENGTH = 8;
|
|
1598
|
-
function isPlainObject(value) {
|
|
1599
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1600
|
-
}
|
|
1601
|
-
function normalizeSeed(seed, userName) {
|
|
1602
|
-
if (seed === void 0) return {};
|
|
1603
|
-
if (!isPlainObject(seed)) {
|
|
1604
|
-
throw new Error(`testUser ${JSON.stringify(userName)}: seed must be { <table>: [ {...} ] }`);
|
|
1605
|
-
}
|
|
1606
|
-
const out = {};
|
|
1607
|
-
for (const [table, rows] of Object.entries(seed)) {
|
|
1608
|
-
if (!Array.isArray(rows)) {
|
|
1609
|
-
throw new Error(
|
|
1610
|
-
`testUser ${JSON.stringify(userName)}: seed.${table} must be an array of rows`
|
|
1611
|
-
);
|
|
1612
|
-
}
|
|
1613
|
-
if (rows.length === 0) {
|
|
1614
|
-
throw new Error(
|
|
1615
|
-
`testUser ${JSON.stringify(userName)}: seed.${table} is empty \u2014 remove it or add a row`
|
|
1616
|
-
);
|
|
1617
|
-
}
|
|
1618
|
-
for (const row of rows) {
|
|
1619
|
-
if (!isPlainObject(row)) {
|
|
1620
|
-
throw new Error(
|
|
1621
|
-
`testUser ${JSON.stringify(userName)}: every row in seed.${table} must be an object`
|
|
1622
|
-
);
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
out[table] = rows;
|
|
1626
|
-
}
|
|
1627
|
-
return out;
|
|
1628
|
-
}
|
|
1629
|
-
function testUser(opts = {}) {
|
|
1630
|
-
if (!isPlainObject(opts)) {
|
|
1631
|
-
throw new Error("testUser() expects { email?, password?, phone?, otp?, seed? }");
|
|
1632
|
-
}
|
|
1633
|
-
const hasEmail = opts.email !== void 0;
|
|
1634
|
-
const hasPassword = opts.password !== void 0;
|
|
1635
|
-
if (hasEmail !== hasPassword) {
|
|
1636
|
-
throw new Error(
|
|
1637
|
-
"testUser() needs email AND password together (a fixture with stable credentials) or neither (a template the server generates credentials for)"
|
|
1638
|
-
);
|
|
1639
|
-
}
|
|
1640
|
-
let email = null;
|
|
1641
|
-
let password = null;
|
|
1642
|
-
if (hasEmail) {
|
|
1643
|
-
if (typeof opts.email !== "string" || !EMAIL_RE.test(opts.email)) {
|
|
1644
|
-
throw new Error(`testUser email ${JSON.stringify(opts.email)} is not a valid address`);
|
|
1645
|
-
}
|
|
1646
|
-
if (typeof opts.password !== "string" || opts.password.length < MIN_PASSWORD_LENGTH) {
|
|
1647
|
-
throw new Error(
|
|
1648
|
-
`testUser password must be at least ${MIN_PASSWORD_LENGTH} characters (got ${typeof opts.password === "string" ? opts.password.length : 0})`
|
|
1649
|
-
);
|
|
1650
|
-
}
|
|
1651
|
-
email = opts.email;
|
|
1652
|
-
password = opts.password;
|
|
1653
|
-
}
|
|
1654
|
-
const hasPhone = opts.phone !== void 0;
|
|
1655
|
-
const hasOTP = opts.otp !== void 0;
|
|
1656
|
-
if (hasPhone !== hasOTP) {
|
|
1657
|
-
throw new Error(
|
|
1658
|
-
"testUser() needs phone AND otp together \u2014 nothing can receive an SMS at a fabricated number, so a phone fixture without its fixed code can never be signed in as"
|
|
1659
|
-
);
|
|
1660
|
-
}
|
|
1661
|
-
let phone = null;
|
|
1662
|
-
let otp = null;
|
|
1663
|
-
if (hasPhone) {
|
|
1664
|
-
if (typeof opts.phone !== "string" || !E164_RE.test(opts.phone)) {
|
|
1665
|
-
throw new Error(
|
|
1666
|
-
`testUser phone ${JSON.stringify(opts.phone)} must be E.164, e.g. "+905551112233"`
|
|
1667
|
-
);
|
|
1668
|
-
}
|
|
1669
|
-
if (typeof opts.otp !== "string" || !OTP_RE.test(opts.otp)) {
|
|
1670
|
-
throw new Error(
|
|
1671
|
-
`testUser otp ${JSON.stringify(opts.otp)} must be ${OTP_LENGTH} digits`
|
|
1672
|
-
);
|
|
1673
|
-
}
|
|
1674
|
-
phone = opts.phone;
|
|
1675
|
-
otp = opts.otp;
|
|
1676
|
-
}
|
|
1677
|
-
return { email, password, phone, otp, seed: normalizeSeed(opts.seed, email ?? "<template>") };
|
|
1678
|
-
}
|
|
1679
|
-
function defineTestUsers(input) {
|
|
1680
|
-
if (!isPlainObject(input) || !isPlainObject(input.users)) {
|
|
1681
|
-
throw new Error("defineTestUsers expects { users: { <name>: testUser({...}) } }");
|
|
1682
|
-
}
|
|
1683
|
-
const users = {};
|
|
1684
|
-
const emails = /* @__PURE__ */ new Map();
|
|
1685
|
-
for (const name of Object.keys(input.users)) {
|
|
1686
|
-
if (!TEST_USER_NAME_RE.test(name)) {
|
|
1687
|
-
throw new Error(
|
|
1688
|
-
`test user name ${JSON.stringify(name)} is invalid \u2014 must start with a letter and contain only letters, digits, and underscores`
|
|
1689
|
-
);
|
|
1690
|
-
}
|
|
1691
|
-
const def = input.users[name];
|
|
1692
|
-
if (def === void 0) continue;
|
|
1693
|
-
if (def.email !== null) {
|
|
1694
|
-
const lower = def.email.toLowerCase();
|
|
1695
|
-
const already = emails.get(lower);
|
|
1696
|
-
if (already !== void 0) {
|
|
1697
|
-
throw new Error(
|
|
1698
|
-
`test users ${JSON.stringify(already)} and ${JSON.stringify(name)} declare the same e-mail ${JSON.stringify(def.email)}`
|
|
1699
|
-
);
|
|
1700
|
-
}
|
|
1701
|
-
emails.set(lower, name);
|
|
1702
|
-
}
|
|
1703
|
-
users[name] = def;
|
|
1704
|
-
}
|
|
1705
|
-
return { __config: TEST_USERS_CONFIG_KIND, users };
|
|
1706
|
-
}
|
|
1335
|
+
interface Flags {${members(flags)}}
|
|
1707
1336
|
|
|
1708
|
-
|
|
1709
|
-
var FLAGS_CONFIG_KIND = "flags";
|
|
1710
|
-
var FLAG_KEY_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
1711
|
-
function valueMatchesType(type, value) {
|
|
1712
|
-
switch (type) {
|
|
1713
|
-
case "boolean":
|
|
1714
|
-
return typeof value === "boolean";
|
|
1715
|
-
case "number":
|
|
1716
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
1717
|
-
case "string":
|
|
1718
|
-
return typeof value === "string";
|
|
1719
|
-
case "json":
|
|
1720
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
function flag(opts) {
|
|
1724
|
-
if (opts === null || typeof opts !== "object") {
|
|
1725
|
-
throw new Error("flag() expects { type, default, variants?, description? }");
|
|
1726
|
-
}
|
|
1727
|
-
const { type } = opts;
|
|
1728
|
-
if (type !== "boolean" && type !== "number" && type !== "string" && type !== "json") {
|
|
1729
|
-
throw new Error(`flag type must be "boolean", "number", "string", or "json", got ${JSON.stringify(type)}`);
|
|
1730
|
-
}
|
|
1731
|
-
if (!valueMatchesType(type, opts.default)) {
|
|
1732
|
-
throw new Error(
|
|
1733
|
-
`flag default ${JSON.stringify(opts.default)} does not match type "${type}"`
|
|
1734
|
-
);
|
|
1735
|
-
}
|
|
1736
|
-
let variants = null;
|
|
1737
|
-
if (opts.variants !== void 0) {
|
|
1738
|
-
if (opts.type !== "string") {
|
|
1739
|
-
throw new Error(`flag variants are only valid for type "string" (got type "${type}")`);
|
|
1740
|
-
}
|
|
1741
|
-
if (!Array.isArray(opts.variants)) {
|
|
1742
|
-
throw new Error("flag variants must be an array of strings");
|
|
1743
|
-
}
|
|
1744
|
-
for (const v of opts.variants) {
|
|
1745
|
-
if (typeof v !== "string" || v.length === 0) {
|
|
1746
|
-
throw new Error(`flag variant ${JSON.stringify(v)} must be a non-empty string`);
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
variants = [...new Set(opts.variants)];
|
|
1750
|
-
if (variants.length === 0) {
|
|
1751
|
-
throw new Error("flag variants must be a non-empty list when supplied");
|
|
1752
|
-
}
|
|
1753
|
-
if (!variants.includes(opts.default)) {
|
|
1754
|
-
throw new Error(
|
|
1755
|
-
`flag default ${JSON.stringify(opts.default)} is not one of its variants [${variants.map((v) => JSON.stringify(v)).join(", ")}]`
|
|
1756
|
-
);
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
let conditions = null;
|
|
1760
|
-
if (opts.conditions !== void 0) {
|
|
1761
|
-
if (!Array.isArray(opts.conditions)) {
|
|
1762
|
-
throw new Error("flag conditions must be an array of { when, value }");
|
|
1763
|
-
}
|
|
1764
|
-
const compiled = [];
|
|
1765
|
-
for (const [i, branch] of opts.conditions.entries()) {
|
|
1766
|
-
if (branch === null || typeof branch !== "object") {
|
|
1767
|
-
throw new Error(`flag condition ${i} must be an object { when, value }`);
|
|
1768
|
-
}
|
|
1769
|
-
if (typeof branch.when !== "string" || branch.when.trim().length === 0) {
|
|
1770
|
-
throw new Error(`flag condition ${i} needs a non-empty "when" expression`);
|
|
1771
|
-
}
|
|
1772
|
-
if (!valueMatchesType(type, branch.value)) {
|
|
1773
|
-
throw new Error(
|
|
1774
|
-
`flag condition ${i} value ${JSON.stringify(branch.value)} does not match type "${type}"`
|
|
1775
|
-
);
|
|
1776
|
-
}
|
|
1777
|
-
if (variants !== null && !variants.includes(branch.value)) {
|
|
1778
|
-
throw new Error(
|
|
1779
|
-
`flag condition ${i} value ${JSON.stringify(branch.value)} is not one of its variants [${variants.map((v) => JSON.stringify(v)).join(", ")}]`
|
|
1780
|
-
);
|
|
1781
|
-
}
|
|
1782
|
-
compiled.push({ when: branch.when.trim(), value: branch.value });
|
|
1783
|
-
}
|
|
1784
|
-
conditions = compiled.length > 0 ? compiled : null;
|
|
1785
|
-
}
|
|
1786
|
-
let description = null;
|
|
1787
|
-
if (opts.description !== void 0) {
|
|
1788
|
-
if (typeof opts.description !== "string") {
|
|
1789
|
-
throw new Error("flag description must be a string");
|
|
1790
|
-
}
|
|
1791
|
-
const trimmed = opts.description.trim();
|
|
1792
|
-
description = trimmed.length > 0 ? trimmed : null;
|
|
1793
|
-
}
|
|
1794
|
-
return {
|
|
1795
|
-
type,
|
|
1796
|
-
default: opts.default,
|
|
1797
|
-
variants,
|
|
1798
|
-
conditions,
|
|
1799
|
-
description
|
|
1800
|
-
};
|
|
1801
|
-
}
|
|
1802
|
-
function defineFlags(input) {
|
|
1803
|
-
if (input === null || typeof input !== "object" || typeof input.flags !== "object") {
|
|
1804
|
-
throw new Error("defineFlags expects { flags: { <key>: flag({...}) } }");
|
|
1805
|
-
}
|
|
1806
|
-
const flags = {};
|
|
1807
|
-
for (const key of Object.keys(input.flags)) {
|
|
1808
|
-
if (!FLAG_KEY_RE.test(key)) {
|
|
1809
|
-
throw new Error(
|
|
1810
|
-
`flag key ${JSON.stringify(key)} is invalid \u2014 must start with a letter and contain only letters, digits, and underscores`
|
|
1811
|
-
);
|
|
1812
|
-
}
|
|
1813
|
-
const def = input.flags[key];
|
|
1814
|
-
if (def === void 0) continue;
|
|
1815
|
-
flags[key] = def;
|
|
1816
|
-
}
|
|
1817
|
-
return { __config: FLAGS_CONFIG_KIND, flags };
|
|
1337
|
+
interface Buckets {${bucketMembers(names.buckets)}}
|
|
1818
1338
|
}
|
|
1819
1339
|
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
var SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
1823
|
-
function secret(name, opts = {}) {
|
|
1824
|
-
if (typeof name !== "string" || !SECRET_NAME_RE.test(name)) {
|
|
1825
|
-
throw new Error(
|
|
1826
|
-
`secret name ${JSON.stringify(name)} is invalid \u2014 must be an environment-variable name: an uppercase letter, then uppercase letters, digits, and underscores (e.g. "NEO4J_PASSWORD")`
|
|
1827
|
-
);
|
|
1828
|
-
}
|
|
1829
|
-
if (opts === null || typeof opts !== "object") {
|
|
1830
|
-
throw new Error(`secret("${name}") expects { required?, description? }`);
|
|
1831
|
-
}
|
|
1832
|
-
let required = true;
|
|
1833
|
-
if (opts.required !== void 0) {
|
|
1834
|
-
if (typeof opts.required !== "boolean") {
|
|
1835
|
-
throw new Error(`secret("${name}") required must be a boolean`);
|
|
1836
|
-
}
|
|
1837
|
-
required = opts.required;
|
|
1838
|
-
}
|
|
1839
|
-
let description = null;
|
|
1840
|
-
if (opts.description !== void 0) {
|
|
1841
|
-
if (typeof opts.description !== "string") {
|
|
1842
|
-
throw new Error(`secret("${name}") description must be a string`);
|
|
1843
|
-
}
|
|
1844
|
-
const trimmed = opts.description.trim();
|
|
1845
|
-
description = trimmed.length > 0 ? trimmed : null;
|
|
1846
|
-
}
|
|
1847
|
-
return { name, required, description };
|
|
1848
|
-
}
|
|
1849
|
-
function defineSecrets(input) {
|
|
1850
|
-
if (input === null || typeof input !== "object" || !Array.isArray(input.secrets)) {
|
|
1851
|
-
throw new Error('defineSecrets expects { secrets: [secret("NAME"), ...] }');
|
|
1852
|
-
}
|
|
1853
|
-
const byName = /* @__PURE__ */ new Map();
|
|
1854
|
-
for (const def of input.secrets) {
|
|
1855
|
-
if (def === null || typeof def !== "object" || typeof def.name !== "string" || typeof def.required !== "boolean") {
|
|
1856
|
-
throw new Error(
|
|
1857
|
-
`defineSecrets: every entry must come from secret() \u2014 got ${JSON.stringify(def)}`
|
|
1858
|
-
);
|
|
1859
|
-
}
|
|
1860
|
-
if (byName.has(def.name)) {
|
|
1861
|
-
throw new Error(
|
|
1862
|
-
`secret "${def.name}" is declared twice in config/secrets.ts \u2014 remove one; a name has exactly one requirement`
|
|
1863
|
-
);
|
|
1864
|
-
}
|
|
1865
|
-
byName.set(def.name, def);
|
|
1866
|
-
}
|
|
1867
|
-
const secrets = [...byName.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
1868
|
-
return { __config: SECRETS_CONFIG_KIND, secrets };
|
|
1340
|
+
export {};
|
|
1341
|
+
`;
|
|
1869
1342
|
}
|
|
1870
1343
|
|
|
1871
1344
|
// src/decorators/methods.ts
|
|
@@ -2062,22 +1535,14 @@ function validateUploadConfigShape(c) {
|
|
|
2062
1535
|
if (c === null || typeof c !== "object") {
|
|
2063
1536
|
throw new Error("@Upload config must be an object { bucket, pathTemplate, ... }");
|
|
2064
1537
|
}
|
|
2065
|
-
|
|
1538
|
+
const bucket = c.bucket;
|
|
1539
|
+
if (typeof bucket !== "string" || bucket.length === 0) {
|
|
2066
1540
|
throw new Error("@Upload config.bucket must be a non-empty bucket name");
|
|
2067
1541
|
}
|
|
2068
1542
|
if (typeof c.pathTemplate !== "string" || c.pathTemplate.length === 0) {
|
|
2069
1543
|
throw new Error("@Upload config.pathTemplate must be a non-empty key template");
|
|
2070
1544
|
}
|
|
2071
1545
|
}
|
|
2072
|
-
function validateUploadAgainstStorage(uploadConfig, storage, routeLabel) {
|
|
2073
|
-
const def = storage.buckets[uploadConfig.bucket];
|
|
2074
|
-
if (def === void 0) {
|
|
2075
|
-
const known = Object.keys(storage.buckets);
|
|
2076
|
-
throw new Error(
|
|
2077
|
-
`@Upload route ${routeLabel} targets bucket "${uploadConfig.bucket}" which is not declared in defineStorage(...). ` + (known.length ? `Known buckets: ${known.join(", ")}.` : "No buckets are declared.")
|
|
2078
|
-
);
|
|
2079
|
-
}
|
|
2080
|
-
}
|
|
2081
1546
|
|
|
2082
1547
|
// src/decorators/params.ts
|
|
2083
1548
|
function makeParamDecorator(kind, extra) {
|
|
@@ -2253,52 +1718,6 @@ function getJobConfig(ctor) {
|
|
|
2253
1718
|
return { schedule: meta.schedule, timeout, retry, handler: run };
|
|
2254
1719
|
}
|
|
2255
1720
|
|
|
2256
|
-
// src/resource.ts
|
|
2257
|
-
var Resource = class {
|
|
2258
|
-
/** The env-var names this resource needs. Optional; omit for none. */
|
|
2259
|
-
static secrets;
|
|
2260
|
-
};
|
|
2261
|
-
function hasSecrets(value) {
|
|
2262
|
-
if (typeof value !== "object" && typeof value !== "function") return false;
|
|
2263
|
-
const secrets = value.secrets;
|
|
2264
|
-
return secrets === void 0 || Array.isArray(secrets);
|
|
2265
|
-
}
|
|
2266
|
-
var registry = [];
|
|
2267
|
-
function __registerResource(resource) {
|
|
2268
|
-
registry.push({ resource, booted: false });
|
|
2269
|
-
}
|
|
2270
|
-
function declaredSecrets(resource) {
|
|
2271
|
-
const ctor = resource.constructor;
|
|
2272
|
-
return hasSecrets(ctor) ? ctor.secrets ?? [] : [];
|
|
2273
|
-
}
|
|
2274
|
-
async function __runResourceBoot(envMap) {
|
|
2275
|
-
for (const entry of registry) {
|
|
2276
|
-
if (entry.booted) continue;
|
|
2277
|
-
const secrets = declaredSecrets(entry.resource);
|
|
2278
|
-
const env = {};
|
|
2279
|
-
for (const name of secrets) {
|
|
2280
|
-
const value = envMap[name];
|
|
2281
|
-
if (value === void 0) {
|
|
2282
|
-
throw new Error(
|
|
2283
|
-
`Resource ${entry.resource.constructor.name} requires secret "${name}" but it is not set. Set it with \`palbase secret set ${name} ...\` (or in Studio) and redeploy.`
|
|
2284
|
-
);
|
|
2285
|
-
}
|
|
2286
|
-
env[name] = value;
|
|
2287
|
-
}
|
|
2288
|
-
await entry.resource.init(env);
|
|
2289
|
-
entry.booted = true;
|
|
2290
|
-
}
|
|
2291
|
-
}
|
|
2292
|
-
async function __shutdownResources() {
|
|
2293
|
-
for (let i = registry.length - 1; i >= 0; i -= 1) {
|
|
2294
|
-
const entry = registry[i];
|
|
2295
|
-
if (entry.booted && entry.resource.shutdown) {
|
|
2296
|
-
await entry.resource.shutdown();
|
|
2297
|
-
}
|
|
2298
|
-
}
|
|
2299
|
-
registry.length = 0;
|
|
2300
|
-
}
|
|
2301
|
-
|
|
2302
1721
|
// src/index.ts
|
|
2303
1722
|
import { z } from "zod";
|
|
2304
1723
|
export {
|
|
@@ -2308,17 +1727,11 @@ export {
|
|
|
2308
1727
|
Client,
|
|
2309
1728
|
Conflict,
|
|
2310
1729
|
Controller,
|
|
2311
|
-
DEFAULT_TEMPLATE_LOCALE,
|
|
2312
1730
|
Database,
|
|
2313
1731
|
Delete,
|
|
2314
1732
|
Deny,
|
|
2315
1733
|
Documents,
|
|
2316
|
-
EGRESS_CONFIG_KIND,
|
|
2317
|
-
EGRESS_TIMEOUT_DEFAULT_MS,
|
|
2318
|
-
EGRESS_TIMEOUT_MAX_MS,
|
|
2319
|
-
EGRESS_TIMEOUT_MIN_MS,
|
|
2320
1734
|
EXTENSION_DEPENDENCIES,
|
|
2321
|
-
FLAGS_CONFIG_KIND,
|
|
2322
1735
|
Flags,
|
|
2323
1736
|
Forbidden,
|
|
2324
1737
|
Get,
|
|
@@ -2327,35 +1740,25 @@ export {
|
|
|
2327
1740
|
HttpError,
|
|
2328
1741
|
Job,
|
|
2329
1742
|
Log,
|
|
2330
|
-
NOTIFICATIONS_CONFIG_KIND,
|
|
2331
1743
|
NotFound,
|
|
2332
1744
|
Notifications,
|
|
2333
1745
|
On,
|
|
2334
1746
|
OptionalUser,
|
|
2335
1747
|
PALBASE_EXTENSIONS,
|
|
2336
|
-
PROVIDER_CATALOG,
|
|
2337
1748
|
PalError,
|
|
2338
1749
|
PalbaseModuleError,
|
|
2339
1750
|
Param,
|
|
2340
1751
|
Patch,
|
|
2341
1752
|
PolicyBuilder,
|
|
2342
1753
|
Post,
|
|
2343
|
-
Purchases,
|
|
2344
1754
|
Put,
|
|
2345
1755
|
Query,
|
|
2346
1756
|
QueryParams,
|
|
2347
|
-
RESERVED_SECRET_PREFIX,
|
|
2348
1757
|
Realtime,
|
|
2349
1758
|
Req,
|
|
2350
1759
|
RequestId,
|
|
2351
|
-
RequireEntitlement,
|
|
2352
|
-
Resource,
|
|
2353
|
-
SECRETS_CONFIG_KIND,
|
|
2354
|
-
STORAGE_CONFIG_KIND,
|
|
2355
1760
|
Secrets,
|
|
2356
|
-
Spend,
|
|
2357
1761
|
Storage,
|
|
2358
|
-
TEST_USERS_CONFIG_KIND,
|
|
2359
1762
|
TooManyRequests,
|
|
2360
1763
|
TraceId,
|
|
2361
1764
|
TxPlanError,
|
|
@@ -2366,32 +1769,19 @@ export {
|
|
|
2366
1769
|
User,
|
|
2367
1770
|
Webhook,
|
|
2368
1771
|
__getRuntime,
|
|
2369
|
-
__registerResource,
|
|
2370
1772
|
__requestALS,
|
|
2371
1773
|
__resetRegisteredControllers,
|
|
2372
|
-
__runResourceBoot,
|
|
2373
1774
|
__runWithRuntime,
|
|
2374
1775
|
__setRuntime,
|
|
2375
|
-
__shutdownResources,
|
|
2376
1776
|
bigint,
|
|
2377
1777
|
boolean,
|
|
2378
|
-
bucket,
|
|
2379
1778
|
buildModuleClients,
|
|
2380
|
-
buildProvider,
|
|
2381
1779
|
dec,
|
|
2382
|
-
|
|
1780
|
+
defineChannels,
|
|
2383
1781
|
defineError,
|
|
2384
|
-
defineFlags,
|
|
2385
1782
|
defineMiddleware,
|
|
2386
|
-
defineNotifications,
|
|
2387
1783
|
defineSchema,
|
|
2388
|
-
defineSecrets,
|
|
2389
|
-
defineStorage,
|
|
2390
|
-
defineTestUsers,
|
|
2391
|
-
entitlementFor,
|
|
2392
1784
|
enumType,
|
|
2393
|
-
extractVariables,
|
|
2394
|
-
flag,
|
|
2395
1785
|
getErrorRegistry,
|
|
2396
1786
|
getHookConfig,
|
|
2397
1787
|
getJobConfig,
|
|
@@ -2404,23 +1794,21 @@ export {
|
|
|
2404
1794
|
jsonb,
|
|
2405
1795
|
makeEnvDts,
|
|
2406
1796
|
makeHttpClient,
|
|
2407
|
-
|
|
1797
|
+
makeStackDts,
|
|
2408
1798
|
makeTypedDB,
|
|
2409
1799
|
now,
|
|
2410
1800
|
numeric,
|
|
2411
|
-
|
|
1801
|
+
openai,
|
|
1802
|
+
ownerOnly,
|
|
2412
1803
|
policy,
|
|
1804
|
+
publicChannel,
|
|
2413
1805
|
raw,
|
|
2414
1806
|
recordThrows,
|
|
2415
|
-
reservedSecretKey,
|
|
2416
|
-
secret,
|
|
2417
|
-
spendFor,
|
|
2418
|
-
testUser,
|
|
2419
1807
|
text,
|
|
2420
1808
|
timestamp,
|
|
2421
1809
|
toSchemaJSON,
|
|
2422
1810
|
uuid,
|
|
2423
|
-
|
|
1811
|
+
vector,
|
|
2424
1812
|
z
|
|
2425
1813
|
};
|
|
2426
1814
|
//# sourceMappingURL=index.js.map
|