@palbase/backend 17.3.0 → 18.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 +1848 -0
- package/dist/bin/palbase-backend.cjs.map +1 -0
- package/dist/bin/palbase-backend.d.cts +1 -0
- package/dist/bin/palbase-backend.d.ts +1 -0
- package/dist/bin/palbase-backend.js +168 -0
- package/dist/bin/palbase-backend.js.map +1 -0
- package/dist/chunk-7D4SUZUM.js +38 -0
- package/dist/chunk-7D4SUZUM.js.map +1 -0
- package/dist/chunk-N32VDWKH.js +172 -0
- package/dist/chunk-N32VDWKH.js.map +1 -0
- package/dist/chunk-POYAFBLF.js +189 -0
- package/dist/chunk-POYAFBLF.js.map +1 -0
- package/dist/chunk-QMVK4X3V.js +200 -0
- package/dist/chunk-QMVK4X3V.js.map +1 -0
- package/dist/chunk-SSGAMC26.js +342 -0
- package/dist/chunk-SSGAMC26.js.map +1 -0
- package/dist/chunk-VYH4U7ZQ.js +1138 -0
- package/dist/chunk-VYH4U7ZQ.js.map +1 -0
- package/dist/{chunk-IBIME7N2.js → chunk-W5ODXPY3.js} +2 -321
- package/dist/chunk-W5ODXPY3.js.map +1 -0
- package/dist/chunk-YL4C5NRY.js +90 -0
- package/dist/chunk-YL4C5NRY.js.map +1 -0
- package/dist/db/env.cjs.map +1 -1
- package/dist/db/env.d.cts +21 -1
- package/dist/db/env.d.ts +21 -1
- package/dist/db/index.cjs +15 -0
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +2 -1
- package/dist/db/index.d.ts +2 -1
- package/dist/db/index.js +9 -6
- package/dist/{index-J1tnAnFj.d.ts → endpoint-B0LpZixz.d.cts} +124 -667
- package/dist/{index-D6mRh4yq.d.cts → endpoint-B0LpZixz.d.ts} +124 -667
- package/dist/engine/index.cjs +1797 -0
- package/dist/engine/index.cjs.map +1 -0
- package/dist/engine/index.d.cts +7 -0
- package/dist/engine/index.d.ts +7 -0
- package/dist/engine/index.js +43 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/index-B46CGNvx.d.cts +839 -0
- package/dist/index-BGSCWlUa.d.cts +674 -0
- package/dist/index-DZDUMth5.d.ts +839 -0
- package/dist/index-g-EzitI-.d.ts +674 -0
- package/dist/index.cjs +1046 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +290 -532
- package/dist/index.d.ts +290 -532
- package/dist/index.js +999 -509
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.cjs +6464 -0
- package/dist/openapi/index.cjs.map +1 -0
- package/dist/openapi/index.d.cts +170 -0
- package/dist/openapi/index.d.ts +170 -0
- package/dist/openapi/index.js +6248 -0
- package/dist/openapi/index.js.map +1 -0
- package/dist/registry-3BLYv4si.d.ts +338 -0
- package/dist/registry-Cw0YEYCg.d.cts +338 -0
- package/dist/test/index.js +2 -0
- package/dist/test/index.js.map +1 -1
- package/docs/database.md +16 -3
- package/docs/llms-full.txt +16 -3
- package/package.json +43 -13
- package/stager/package.json +4 -0
- package/stager/return_types.js +338 -0
- package/stager/stage.js +78 -0
- package/stager/throw_analysis.js +726 -0
- package/template/AGENTS.md +261 -0
- package/template/config/secrets.ts +24 -0
- package/template/controllers/health.controller.ts +30 -0
- package/template/db/schema.ts +35 -0
- package/template/package.json +18 -0
- package/template/tsconfig.json +30 -0
- package/LICENSE +0 -21
- package/dist/chunk-IBIME7N2.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -51,6 +51,7 @@ __export(src_exports, {
|
|
|
51
51
|
PALBASE_EXTENSIONS: () => PALBASE_EXTENSIONS,
|
|
52
52
|
PROVIDER_CATALOG: () => PROVIDER_CATALOG,
|
|
53
53
|
PalError: () => PalError,
|
|
54
|
+
PalbaseModuleError: () => PalbaseModuleError,
|
|
54
55
|
Param: () => Param,
|
|
55
56
|
Patch: () => Patch,
|
|
56
57
|
PolicyBuilder: () => PolicyBuilder,
|
|
@@ -65,7 +66,9 @@ __export(src_exports, {
|
|
|
65
66
|
RequestId: () => RequestId,
|
|
66
67
|
RequireEntitlement: () => RequireEntitlement,
|
|
67
68
|
Resource: () => Resource,
|
|
69
|
+
SECRETS_CONFIG_KIND: () => SECRETS_CONFIG_KIND,
|
|
68
70
|
STORAGE_CONFIG_KIND: () => STORAGE_CONFIG_KIND,
|
|
71
|
+
Secrets: () => Secrets,
|
|
69
72
|
Spend: () => Spend,
|
|
70
73
|
Storage: () => Storage,
|
|
71
74
|
TEST_USERS_CONFIG_KIND: () => TEST_USERS_CONFIG_KIND,
|
|
@@ -81,6 +84,7 @@ __export(src_exports, {
|
|
|
81
84
|
__getRuntime: () => __getRuntime,
|
|
82
85
|
__registerResource: () => __registerResource,
|
|
83
86
|
__requestALS: () => __requestALS,
|
|
87
|
+
__resetRegisteredControllers: () => __resetRegisteredControllers,
|
|
84
88
|
__runResourceBoot: () => __runResourceBoot,
|
|
85
89
|
__runWithRuntime: () => __runWithRuntime,
|
|
86
90
|
__setRuntime: () => __setRuntime,
|
|
@@ -89,6 +93,7 @@ __export(src_exports, {
|
|
|
89
93
|
bigint: () => bigint,
|
|
90
94
|
boolean: () => boolean,
|
|
91
95
|
bucket: () => bucket,
|
|
96
|
+
buildModuleClients: () => buildModuleClients,
|
|
92
97
|
buildProvider: () => buildProvider,
|
|
93
98
|
dec: () => dec,
|
|
94
99
|
defineEgress: () => defineEgress,
|
|
@@ -97,6 +102,7 @@ __export(src_exports, {
|
|
|
97
102
|
defineMiddleware: () => defineMiddleware,
|
|
98
103
|
defineNotifications: () => defineNotifications,
|
|
99
104
|
defineSchema: () => defineSchema,
|
|
105
|
+
defineSecrets: () => defineSecrets,
|
|
100
106
|
defineStorage: () => defineStorage,
|
|
101
107
|
defineTestUsers: () => defineTestUsers,
|
|
102
108
|
documents: () => documents,
|
|
@@ -106,6 +112,7 @@ __export(src_exports, {
|
|
|
106
112
|
flag: () => flag,
|
|
107
113
|
getErrorRegistry: () => getErrorRegistry,
|
|
108
114
|
getJobConfig: () => getJobConfig,
|
|
115
|
+
getRegisteredControllers: () => getRegisteredControllers,
|
|
109
116
|
getRoutes: () => getRoutes,
|
|
110
117
|
getWebhookConfig: () => getWebhookConfig,
|
|
111
118
|
inc: () => inc,
|
|
@@ -113,6 +120,7 @@ __export(src_exports, {
|
|
|
113
120
|
isPalbaseExtension: () => isPalbaseExtension,
|
|
114
121
|
jsonb: () => jsonb,
|
|
115
122
|
makeEnvDts: () => makeEnvDts,
|
|
123
|
+
makeHttpClient: () => makeHttpClient,
|
|
116
124
|
makePurchasesDts: () => makePurchasesDts,
|
|
117
125
|
makeTypedDB: () => makeTypedDB,
|
|
118
126
|
now: () => now,
|
|
@@ -122,11 +130,13 @@ __export(src_exports, {
|
|
|
122
130
|
raw: () => raw,
|
|
123
131
|
recordThrows: () => recordThrows,
|
|
124
132
|
reservedSecretKey: () => reservedSecretKey,
|
|
133
|
+
secret: () => secret,
|
|
125
134
|
spendFor: () => spendFor,
|
|
126
135
|
storage: () => storage,
|
|
127
136
|
testUser: () => testUser,
|
|
128
137
|
text: () => text,
|
|
129
138
|
timestamp: () => timestamp,
|
|
139
|
+
toSchemaJSON: () => toSchemaJSON,
|
|
130
140
|
uuid: () => uuid,
|
|
131
141
|
validateUploadAgainstStorage: () => validateUploadAgainstStorage,
|
|
132
142
|
z: () => import_zod2.z
|
|
@@ -635,8 +645,35 @@ var Database = Object.assign(makeTypedSurface(rawDatabase), {
|
|
|
635
645
|
}
|
|
636
646
|
});
|
|
637
647
|
var Documents = makeServiceProxy("Documents");
|
|
638
|
-
|
|
648
|
+
function makeBucketsAccessor(storage2) {
|
|
649
|
+
return new Proxy(
|
|
650
|
+
{},
|
|
651
|
+
{
|
|
652
|
+
get(_t, prop) {
|
|
653
|
+
if (typeof prop !== "string") return void 0;
|
|
654
|
+
return storage2().bucket(prop);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
var rawStorage = makeServiceProxy("Storage");
|
|
660
|
+
var Storage = Object.assign(
|
|
661
|
+
{
|
|
662
|
+
// FORWARDED explicitly, not assigned onto the service proxy.
|
|
663
|
+
//
|
|
664
|
+
// `Object.assign(rawStorage, {buckets})` writes onto the proxy's TARGET, and
|
|
665
|
+
// the proxy's only trap is `get`, which forwards every read to the module
|
|
666
|
+
// client — so the property landed somewhere nothing reads and
|
|
667
|
+
// `Storage.buckets.docs` was `undefined` in a deployed handler. It
|
|
668
|
+
// typechecked, every test passed, and the live call answered
|
|
669
|
+
// "TypeError: undefined is not an object". `Database` never had the bug
|
|
670
|
+
// because it builds a plain surface the same way this now does.
|
|
671
|
+
bucket: (name) => rawStorage.bucket(name)
|
|
672
|
+
},
|
|
673
|
+
{ buckets: makeBucketsAccessor(() => rawStorage) }
|
|
674
|
+
);
|
|
639
675
|
var Cache = makeServiceProxy("Cache");
|
|
676
|
+
var Secrets = makeServiceProxy("Secrets");
|
|
640
677
|
var Log = makeServiceProxy("Log");
|
|
641
678
|
var Notifications = makeServiceProxy("Notifications");
|
|
642
679
|
var Purchases = makeServiceProxy("Purchases");
|
|
@@ -652,6 +689,19 @@ var Flags = Object.assign(
|
|
|
652
689
|
getAll(context) {
|
|
653
690
|
return rawFlags.getAll(context);
|
|
654
691
|
},
|
|
692
|
+
/**
|
|
693
|
+
* Resolve a flag's value, with an optional fallback.
|
|
694
|
+
*
|
|
695
|
+
* FORWARDED as of 2026-08-15. This surface is written out by hand, method
|
|
696
|
+
* by method, and `get` was missing from it — so the client implemented it,
|
|
697
|
+
* thirty assertions covered it, and `Flags.get("x")` was `undefined` in a
|
|
698
|
+
* deployed handler. Exactly the shape of the `Storage.buckets` defect found
|
|
699
|
+
* the same day: a hand-maintained forwarding list is a list somebody has to
|
|
700
|
+
* remember to update.
|
|
701
|
+
*/
|
|
702
|
+
get(flagName, defaultOrContext, maybeContext) {
|
|
703
|
+
return rawFlags.get(flagName, defaultOrContext, maybeContext);
|
|
704
|
+
},
|
|
655
705
|
setOverride(key, value) {
|
|
656
706
|
return rawFlags.setOverride(key, value);
|
|
657
707
|
}
|
|
@@ -671,11 +721,14 @@ var Flags = Object.assign(
|
|
|
671
721
|
var Realtime = makeServiceProxy("Realtime");
|
|
672
722
|
|
|
673
723
|
// src/errors.ts
|
|
724
|
+
var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
|
|
674
725
|
var HttpError = class extends Error {
|
|
675
726
|
status;
|
|
676
727
|
error;
|
|
677
728
|
errorDescription;
|
|
678
729
|
data;
|
|
730
|
+
/** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
|
|
731
|
+
[HTTP_ERROR_BRAND] = true;
|
|
679
732
|
constructor(status, error, errorDescription, data) {
|
|
680
733
|
super(errorDescription);
|
|
681
734
|
this.name = "HttpError";
|
|
@@ -911,6 +964,795 @@ function Spend(key) {
|
|
|
911
964
|
};
|
|
912
965
|
}
|
|
913
966
|
|
|
967
|
+
// src/clients/http.ts
|
|
968
|
+
var PalbaseModuleError = class extends Error {
|
|
969
|
+
code;
|
|
970
|
+
status;
|
|
971
|
+
details;
|
|
972
|
+
constructor(code, message, status, details = {}) {
|
|
973
|
+
super(message);
|
|
974
|
+
this.name = "PalbaseModuleError";
|
|
975
|
+
this.code = code;
|
|
976
|
+
this.status = status;
|
|
977
|
+
this.details = details;
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
function makeHttpClient(cfg) {
|
|
981
|
+
async function request(method, path, options = {}) {
|
|
982
|
+
const headers = {
|
|
983
|
+
"Content-Type": "application/json",
|
|
984
|
+
...options.headers ?? {}
|
|
985
|
+
};
|
|
986
|
+
if (cfg.apiKey) headers["apikey"] = cfg.apiKey;
|
|
987
|
+
const init = { method, headers };
|
|
988
|
+
if (options.body !== void 0) {
|
|
989
|
+
if (typeof options.body === "string" || options.body instanceof Uint8Array) {
|
|
990
|
+
init.body = options.body;
|
|
991
|
+
} else if (typeof FormData !== "undefined" && options.body instanceof FormData) {
|
|
992
|
+
delete headers["Content-Type"];
|
|
993
|
+
init.body = options.body;
|
|
994
|
+
} else if (typeof Blob !== "undefined" && options.body instanceof Blob) {
|
|
995
|
+
delete headers["Content-Type"];
|
|
996
|
+
init.body = options.body;
|
|
997
|
+
} else {
|
|
998
|
+
init.body = JSON.stringify(options.body);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (options.signal) init.signal = options.signal;
|
|
1002
|
+
const doFetch = cfg.fetchImpl ?? globalThis.fetch;
|
|
1003
|
+
let response;
|
|
1004
|
+
try {
|
|
1005
|
+
response = await doFetch(`${cfg.baseUrl}${path}`, init);
|
|
1006
|
+
} catch (err) {
|
|
1007
|
+
return {
|
|
1008
|
+
data: null,
|
|
1009
|
+
error: new PalbaseModuleError(
|
|
1010
|
+
"network_error",
|
|
1011
|
+
err instanceof Error ? err.message : "Network request failed",
|
|
1012
|
+
0
|
|
1013
|
+
),
|
|
1014
|
+
status: 0
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
1018
|
+
let parsed = null;
|
|
1019
|
+
if (method !== "HEAD" && contentType.includes("json")) {
|
|
1020
|
+
parsed = await response.json().catch(() => null);
|
|
1021
|
+
} else if (method !== "HEAD" && contentType.startsWith("image/")) {
|
|
1022
|
+
parsed = new Uint8Array(await response.arrayBuffer());
|
|
1023
|
+
} else if (method !== "HEAD" && response.body) {
|
|
1024
|
+
parsed = await response.text().catch(() => null);
|
|
1025
|
+
}
|
|
1026
|
+
if (!response.ok) {
|
|
1027
|
+
const body = parsed && typeof parsed === "object" ? parsed : {};
|
|
1028
|
+
const code = typeof body.error === "string" && body.error ? body.error : "unknown_error";
|
|
1029
|
+
const message = typeof body.error_description === "string" && body.error_description || response.statusText || "request failed";
|
|
1030
|
+
return {
|
|
1031
|
+
data: null,
|
|
1032
|
+
error: new PalbaseModuleError(code, message, response.status, body),
|
|
1033
|
+
status: response.status
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
return { data: parsed, error: null, status: response.status };
|
|
1037
|
+
}
|
|
1038
|
+
return { baseUrl: cfg.baseUrl, request };
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// src/clients/documents.ts
|
|
1042
|
+
var SEGMENT_RE = /^[A-Za-z0-9_-]+$/;
|
|
1043
|
+
function validateSegment(segment, label) {
|
|
1044
|
+
if (!SEGMENT_RE.test(segment)) {
|
|
1045
|
+
throw new Error(`Invalid ${label}: "${segment}". Must match ${SEGMENT_RE.source}`);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
function buildDocumentRef(http, path) {
|
|
1049
|
+
return {
|
|
1050
|
+
path,
|
|
1051
|
+
async set(data) {
|
|
1052
|
+
return http.request("PUT", `/v1/docs/${path}`, { body: data });
|
|
1053
|
+
},
|
|
1054
|
+
async get() {
|
|
1055
|
+
const response = await http.request("GET", `/v1/docs/${path}`);
|
|
1056
|
+
if (response.error) return { data: null, error: response.error, status: response.status };
|
|
1057
|
+
const raw2 = response.data ?? {};
|
|
1058
|
+
const exists = raw2.exists !== void 0 ? raw2.exists : Boolean(raw2.data || raw2.id);
|
|
1059
|
+
const segments = path.split("/");
|
|
1060
|
+
const id = raw2.id || segments[segments.length - 1] || "";
|
|
1061
|
+
return {
|
|
1062
|
+
data: { id, exists, data: () => raw2.data, ref: { path } },
|
|
1063
|
+
error: null,
|
|
1064
|
+
status: response.status
|
|
1065
|
+
};
|
|
1066
|
+
},
|
|
1067
|
+
async update(data) {
|
|
1068
|
+
return http.request("PATCH", `/v1/docs/${path}`, { body: data });
|
|
1069
|
+
},
|
|
1070
|
+
async delete() {
|
|
1071
|
+
return http.request("DELETE", `/v1/docs/${path}`);
|
|
1072
|
+
},
|
|
1073
|
+
collection(name) {
|
|
1074
|
+
validateSegment(name, "subcollection name");
|
|
1075
|
+
return buildCollectionRef(http, `${path}/${name}`);
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
function buildCollectionRef(http, path, state = { where: [], orderBy: [] }) {
|
|
1080
|
+
function snapshot(doc) {
|
|
1081
|
+
return {
|
|
1082
|
+
id: doc.id,
|
|
1083
|
+
exists: true,
|
|
1084
|
+
data: () => doc.data,
|
|
1085
|
+
ref: { path: `${path}/${doc.id}` }
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
function querySnapshot(docs) {
|
|
1089
|
+
return {
|
|
1090
|
+
docs,
|
|
1091
|
+
empty: docs.length === 0,
|
|
1092
|
+
size: docs.length,
|
|
1093
|
+
docChanges: () => docs.map((doc) => ({ type: "added", doc }))
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
return {
|
|
1097
|
+
path,
|
|
1098
|
+
doc(id) {
|
|
1099
|
+
validateSegment(id, "document ID");
|
|
1100
|
+
return buildDocumentRef(http, `${path}/${id}`);
|
|
1101
|
+
},
|
|
1102
|
+
async add(data) {
|
|
1103
|
+
const resp = await http.request("POST", `/v1/docs/${path}`, { body: data });
|
|
1104
|
+
if (resp.error || !resp.data) {
|
|
1105
|
+
return { data: null, error: resp.error, status: resp.status };
|
|
1106
|
+
}
|
|
1107
|
+
return {
|
|
1108
|
+
data: buildDocumentRef(http, `${path}/${resp.data.id}`),
|
|
1109
|
+
error: null,
|
|
1110
|
+
status: resp.status
|
|
1111
|
+
};
|
|
1112
|
+
},
|
|
1113
|
+
// Each narrowing returns a NEW ref. A builder that mutated in place would
|
|
1114
|
+
// leak one caller's filter into a query another caller had already held.
|
|
1115
|
+
where(field, op, value) {
|
|
1116
|
+
return buildCollectionRef(http, path, {
|
|
1117
|
+
...state,
|
|
1118
|
+
where: [...state.where, { field, op, value }]
|
|
1119
|
+
});
|
|
1120
|
+
},
|
|
1121
|
+
orderBy(field, direction = "asc") {
|
|
1122
|
+
return buildCollectionRef(http, path, {
|
|
1123
|
+
...state,
|
|
1124
|
+
orderBy: [...state.orderBy, { field, direction }]
|
|
1125
|
+
});
|
|
1126
|
+
},
|
|
1127
|
+
limit(n) {
|
|
1128
|
+
return buildCollectionRef(http, path, { ...state, limit: n });
|
|
1129
|
+
},
|
|
1130
|
+
async get() {
|
|
1131
|
+
const narrowed = state.where.length > 0 || state.orderBy.length > 0 || state.limit !== void 0;
|
|
1132
|
+
const resp = narrowed ? await http.request(
|
|
1133
|
+
"POST",
|
|
1134
|
+
`/v1/docs/${path}/query`,
|
|
1135
|
+
{ body: queryBody(state) }
|
|
1136
|
+
) : await http.request(
|
|
1137
|
+
"GET",
|
|
1138
|
+
`/v1/docs/${path}`
|
|
1139
|
+
);
|
|
1140
|
+
if (resp.error) return { data: null, error: resp.error, status: resp.status };
|
|
1141
|
+
const docs = (resp.data?.documents ?? []).map(snapshot);
|
|
1142
|
+
return { data: querySnapshot(docs), error: null, status: resp.status };
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
function queryBody(state) {
|
|
1147
|
+
const body = {};
|
|
1148
|
+
if (state.where.length > 0) {
|
|
1149
|
+
body.where = state.where.map((w) => ({ field: w.field, op: w.op, value: w.value }));
|
|
1150
|
+
}
|
|
1151
|
+
if (state.orderBy.length > 0) {
|
|
1152
|
+
body.orderBy = state.orderBy.map((o) => ({ field: o.field, direction: o.direction }));
|
|
1153
|
+
}
|
|
1154
|
+
if (state.limit !== void 0) body.limit = state.limit;
|
|
1155
|
+
return body;
|
|
1156
|
+
}
|
|
1157
|
+
var MAX_BATCH = 500;
|
|
1158
|
+
function buildDocumentsClient(http) {
|
|
1159
|
+
return {
|
|
1160
|
+
/**
|
|
1161
|
+
* `Documents.doc("users/alice")` — segments are collection/documentId PAIRS,
|
|
1162
|
+
* so an odd count addresses a COLLECTION and is refused. Accepting it would
|
|
1163
|
+
* write a document whose id happens to be a collection's name.
|
|
1164
|
+
*/
|
|
1165
|
+
doc(path) {
|
|
1166
|
+
const segments = String(path).split("/").filter((s) => s.length > 0);
|
|
1167
|
+
if (segments.length === 0 || segments.length % 2 !== 0) {
|
|
1168
|
+
throw new Error(
|
|
1169
|
+
`Invalid document path: "${path}". Expected collection/documentId pairs, got ${segments.length} segment(s).`
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
segments.forEach((s, i) => validateSegment(s, i % 2 === 0 ? "collection name" : "document ID"));
|
|
1173
|
+
return buildDocumentRef(http, segments.join("/"));
|
|
1174
|
+
},
|
|
1175
|
+
collection(name) {
|
|
1176
|
+
validateSegment(name, "collection name");
|
|
1177
|
+
return buildCollectionRef(http, name);
|
|
1178
|
+
},
|
|
1179
|
+
async batch(operations) {
|
|
1180
|
+
if (operations.length > MAX_BATCH) {
|
|
1181
|
+
return {
|
|
1182
|
+
data: null,
|
|
1183
|
+
error: new PalbaseModuleError(
|
|
1184
|
+
"batch_too_large",
|
|
1185
|
+
`Batch size ${operations.length} exceeds maximum of ${MAX_BATCH}`,
|
|
1186
|
+
400
|
|
1187
|
+
),
|
|
1188
|
+
status: 400
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
if (operations.length === 0) return { data: null, error: null, status: 200 };
|
|
1192
|
+
return http.request("POST", "/v1/docs/batch", {
|
|
1193
|
+
body: operations.map((op) => ({ op: op.op, path: op.ref.path, data: op.data }))
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// src/clients/flags.ts
|
|
1200
|
+
var FLAG_NAME_RE = /^[A-Za-z0-9_.-]+$/;
|
|
1201
|
+
function assertFlagName(flagName) {
|
|
1202
|
+
if (!FLAG_NAME_RE.test(flagName)) {
|
|
1203
|
+
throw new Error(`Invalid flag name: "${flagName}". Flag names must match ${FLAG_NAME_RE.source}`);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
function flagEnabled(value) {
|
|
1207
|
+
if (typeof value === "boolean") return value;
|
|
1208
|
+
return value != null && value !== 0 && value !== "";
|
|
1209
|
+
}
|
|
1210
|
+
function flagVariant(value) {
|
|
1211
|
+
return typeof value === "string" ? { name: value } : null;
|
|
1212
|
+
}
|
|
1213
|
+
function isContextObject(v) {
|
|
1214
|
+
return v !== null && typeof v === "object" && !Array.isArray(v) && ("userId" in v || "properties" in v);
|
|
1215
|
+
}
|
|
1216
|
+
function buildFlagsClient(http, cfg) {
|
|
1217
|
+
function resolveUserId(context) {
|
|
1218
|
+
if (context && "userId" in context) return context.userId ?? null;
|
|
1219
|
+
return cfg.getCurrentUserId() || void 0;
|
|
1220
|
+
}
|
|
1221
|
+
function mergedPath(context) {
|
|
1222
|
+
const uid = resolveUserId(context);
|
|
1223
|
+
return uid ? `/v1/user-flags/users/${encodeURIComponent(uid)}` : "/v1/user-flags";
|
|
1224
|
+
}
|
|
1225
|
+
function fetchMerged(context) {
|
|
1226
|
+
return http.request("GET", mergedPath(context));
|
|
1227
|
+
}
|
|
1228
|
+
const service = {
|
|
1229
|
+
async setOverrideForUser(userId, key, value) {
|
|
1230
|
+
return http.request(
|
|
1231
|
+
"PUT",
|
|
1232
|
+
`/v1/user-flags/users/${encodeURIComponent(userId)}/${encodeURIComponent(key)}`,
|
|
1233
|
+
{ body: { value } }
|
|
1234
|
+
);
|
|
1235
|
+
},
|
|
1236
|
+
async setOverridesForUser(userId, values) {
|
|
1237
|
+
return http.request("PUT", `/v1/user-flags/users/${encodeURIComponent(userId)}`, {
|
|
1238
|
+
body: { values }
|
|
1239
|
+
});
|
|
1240
|
+
},
|
|
1241
|
+
async clearOverrideForUser(userId, key) {
|
|
1242
|
+
return http.request(
|
|
1243
|
+
"DELETE",
|
|
1244
|
+
`/v1/user-flags/users/${encodeURIComponent(userId)}/${encodeURIComponent(key)}`
|
|
1245
|
+
);
|
|
1246
|
+
},
|
|
1247
|
+
async clearAllOverridesForUser(userId) {
|
|
1248
|
+
return http.request("DELETE", `/v1/user-flags/users/${encodeURIComponent(userId)}`);
|
|
1249
|
+
},
|
|
1250
|
+
async batchSetOverrides(operations) {
|
|
1251
|
+
const ops = (operations ?? []).map((op) => {
|
|
1252
|
+
const raw2 = op;
|
|
1253
|
+
return { user_id: raw2.userId ?? raw2.user_id, values: raw2.values };
|
|
1254
|
+
});
|
|
1255
|
+
return http.request("POST", "/v1/user-flags/batch", { body: { operations: ops } });
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
1258
|
+
return {
|
|
1259
|
+
async isEnabled(flagName, context) {
|
|
1260
|
+
assertFlagName(flagName);
|
|
1261
|
+
const res = await fetchMerged(context);
|
|
1262
|
+
if (res.error || res.data == null) return { data: null, error: res.error, status: res.status };
|
|
1263
|
+
return { data: flagEnabled(res.data.values?.[flagName]), error: null, status: res.status };
|
|
1264
|
+
},
|
|
1265
|
+
async getVariant(flagName, context) {
|
|
1266
|
+
assertFlagName(flagName);
|
|
1267
|
+
const res = await fetchMerged(context);
|
|
1268
|
+
if (res.error || res.data == null) return { data: null, error: res.error, status: res.status };
|
|
1269
|
+
return { data: flagVariant(res.data.values?.[flagName]), error: null, status: res.status };
|
|
1270
|
+
},
|
|
1271
|
+
/**
|
|
1272
|
+
* `get(key)` · `get(key, default)` · `get(key, default, ctx)` · `get(key, ctx)`.
|
|
1273
|
+
*
|
|
1274
|
+
* The default is substituted when the flag is ABSENT or when the flags
|
|
1275
|
+
* service is unreachable — a product that hides a feature because a lookup
|
|
1276
|
+
* timed out is behaving correctly; one that crashes is not.
|
|
1277
|
+
*/
|
|
1278
|
+
async get(flagName, defaultOrContext, maybeContext) {
|
|
1279
|
+
assertFlagName(flagName);
|
|
1280
|
+
let defaultValue;
|
|
1281
|
+
let context;
|
|
1282
|
+
let hasDefault = false;
|
|
1283
|
+
if (maybeContext !== void 0) {
|
|
1284
|
+
defaultValue = defaultOrContext;
|
|
1285
|
+
hasDefault = true;
|
|
1286
|
+
context = maybeContext;
|
|
1287
|
+
} else if (isContextObject(defaultOrContext)) {
|
|
1288
|
+
context = defaultOrContext;
|
|
1289
|
+
} else if (defaultOrContext !== void 0) {
|
|
1290
|
+
defaultValue = defaultOrContext;
|
|
1291
|
+
hasDefault = true;
|
|
1292
|
+
}
|
|
1293
|
+
const resp = await fetchMerged(context);
|
|
1294
|
+
if (resp.error !== null) {
|
|
1295
|
+
if (hasDefault) return { data: defaultValue ?? null, error: null, status: resp.status };
|
|
1296
|
+
return { data: null, error: resp.error, status: resp.status };
|
|
1297
|
+
}
|
|
1298
|
+
const values = resp.data?.values ?? {};
|
|
1299
|
+
const value = values[flagName];
|
|
1300
|
+
if (value === void 0) {
|
|
1301
|
+
return { data: hasDefault ? defaultValue ?? null : null, error: null, status: resp.status };
|
|
1302
|
+
}
|
|
1303
|
+
return { data: value, error: null, status: resp.status };
|
|
1304
|
+
},
|
|
1305
|
+
async getAll(context) {
|
|
1306
|
+
const res = await fetchMerged(context);
|
|
1307
|
+
if (res.error || res.data == null) return { data: null, error: res.error, status: res.status };
|
|
1308
|
+
const values = res.data.values ?? {};
|
|
1309
|
+
return {
|
|
1310
|
+
data: Object.keys(values).map((name) => {
|
|
1311
|
+
const value = values[name];
|
|
1312
|
+
const flag2 = {
|
|
1313
|
+
name,
|
|
1314
|
+
enabled: flagEnabled(value)
|
|
1315
|
+
};
|
|
1316
|
+
if (typeof value === "string") flag2.variant = { name: value };
|
|
1317
|
+
return flag2;
|
|
1318
|
+
}),
|
|
1319
|
+
error: null,
|
|
1320
|
+
status: res.status
|
|
1321
|
+
};
|
|
1322
|
+
},
|
|
1323
|
+
/**
|
|
1324
|
+
* Override a flag for the CURRENT request user.
|
|
1325
|
+
*
|
|
1326
|
+
* On an anonymous request this does not silently no-op and does not write
|
|
1327
|
+
* for nobody: it refuses and names the call that does cross-user writes.
|
|
1328
|
+
*/
|
|
1329
|
+
async setOverride(key, value) {
|
|
1330
|
+
const uid = cfg.getCurrentUserId();
|
|
1331
|
+
if (!uid) {
|
|
1332
|
+
return {
|
|
1333
|
+
data: null,
|
|
1334
|
+
error: {
|
|
1335
|
+
message: "setOverride requires a signed-in user; use Flags.asService().setOverrideForUser(userId, key, value) for cross-user writes"
|
|
1336
|
+
},
|
|
1337
|
+
status: 400
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
return http.request(
|
|
1341
|
+
"PUT",
|
|
1342
|
+
`/v1/user-flags/users/${encodeURIComponent(uid)}/${encodeURIComponent(key)}`,
|
|
1343
|
+
{ body: { value } }
|
|
1344
|
+
);
|
|
1345
|
+
},
|
|
1346
|
+
asService() {
|
|
1347
|
+
return service;
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// src/clients/notifications.ts
|
|
1353
|
+
function mapEnvelope(res, map) {
|
|
1354
|
+
if (res.error !== null || res.data === null || res.data === void 0) {
|
|
1355
|
+
return { data: null, error: res.error, status: res.status };
|
|
1356
|
+
}
|
|
1357
|
+
return { data: map(res.data), error: null, status: res.status };
|
|
1358
|
+
}
|
|
1359
|
+
function toEmailTemplate(wire) {
|
|
1360
|
+
const view = {
|
|
1361
|
+
id: wire.id,
|
|
1362
|
+
slug: wire.slug,
|
|
1363
|
+
locale: wire.locale,
|
|
1364
|
+
subject: wire.subject,
|
|
1365
|
+
htmlBody: wire.html_body,
|
|
1366
|
+
variables: wire.variables ?? [],
|
|
1367
|
+
isDefault: wire.is_default,
|
|
1368
|
+
createdAt: wire.created_at,
|
|
1369
|
+
updatedAt: wire.updated_at
|
|
1370
|
+
};
|
|
1371
|
+
if (wire.text_body !== void 0) view.textBody = wire.text_body;
|
|
1372
|
+
return view;
|
|
1373
|
+
}
|
|
1374
|
+
function toSmsTemplate(wire) {
|
|
1375
|
+
return {
|
|
1376
|
+
id: wire.id,
|
|
1377
|
+
slug: wire.slug,
|
|
1378
|
+
locale: wire.locale,
|
|
1379
|
+
body: wire.body,
|
|
1380
|
+
variables: wire.variables ?? [],
|
|
1381
|
+
isDefault: wire.is_default,
|
|
1382
|
+
createdAt: wire.created_at,
|
|
1383
|
+
updatedAt: wire.updated_at
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
function buildNotificationsClient(http) {
|
|
1387
|
+
const push = {
|
|
1388
|
+
async send(params) {
|
|
1389
|
+
return http.request("POST", "/v1/notifications/push", { body: params });
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
const email = {
|
|
1393
|
+
async send(params) {
|
|
1394
|
+
const { templateSlug, html, text: text2, ...rest } = params;
|
|
1395
|
+
const body = { ...rest };
|
|
1396
|
+
if (templateSlug !== void 0) body.template_slug = templateSlug;
|
|
1397
|
+
if (html !== void 0) body.html_body = html;
|
|
1398
|
+
if (text2 !== void 0) body.text_body = text2;
|
|
1399
|
+
return http.request("POST", "/v1/notifications/email", { body });
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
const sms = {
|
|
1403
|
+
async send(params) {
|
|
1404
|
+
const { templateSlug, ...rest } = params;
|
|
1405
|
+
const body = templateSlug !== void 0 ? { ...rest, template_slug: templateSlug } : rest;
|
|
1406
|
+
return http.request("POST", "/v1/notifications/sms", { body });
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
const verifications = {
|
|
1410
|
+
async start(params) {
|
|
1411
|
+
return http.request("POST", "/v1/notifications/verifications", { body: params });
|
|
1412
|
+
},
|
|
1413
|
+
async check(params) {
|
|
1414
|
+
return http.request("POST", "/v1/notifications/verifications/check", { body: params });
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
const inbox = {
|
|
1418
|
+
async send(params) {
|
|
1419
|
+
return http.request("POST", "/v1/notifications/inbox", { body: params });
|
|
1420
|
+
},
|
|
1421
|
+
async list(options) {
|
|
1422
|
+
const opts = options ?? {};
|
|
1423
|
+
const params = new URLSearchParams();
|
|
1424
|
+
if (opts.cursor) params.set("cursor", opts.cursor);
|
|
1425
|
+
if (opts.limit !== void 0) params.set("limit", String(opts.limit));
|
|
1426
|
+
if (opts.is_read !== void 0) params.set("is_read", opts.is_read ? "true" : "false");
|
|
1427
|
+
if (opts.category) params.set("category", opts.category);
|
|
1428
|
+
if (opts.include_archived) params.set("include_archived", "true");
|
|
1429
|
+
const query = params.toString();
|
|
1430
|
+
return http.request("GET", `/v1/notifications/inbox${query ? `?${query}` : ""}`);
|
|
1431
|
+
},
|
|
1432
|
+
async unreadCount() {
|
|
1433
|
+
return http.request("GET", "/v1/notifications/inbox/unread-count");
|
|
1434
|
+
},
|
|
1435
|
+
async markRead(id) {
|
|
1436
|
+
return http.request("PATCH", `/v1/notifications/inbox/${encodeURIComponent(id)}/read`);
|
|
1437
|
+
},
|
|
1438
|
+
async markAllRead() {
|
|
1439
|
+
return http.request("POST", "/v1/notifications/inbox/read-all");
|
|
1440
|
+
},
|
|
1441
|
+
async archive(id) {
|
|
1442
|
+
return http.request("DELETE", `/v1/notifications/inbox/${encodeURIComponent(id)}`);
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
const preferences = {
|
|
1446
|
+
async get() {
|
|
1447
|
+
return http.request("GET", "/v1/notifications/preferences");
|
|
1448
|
+
},
|
|
1449
|
+
async update(params) {
|
|
1450
|
+
return http.request("PUT", "/v1/notifications/preferences", { body: params });
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
const emailTemplates = {
|
|
1454
|
+
async list() {
|
|
1455
|
+
const resp = await http.request("GET", "/v1/notifications/templates");
|
|
1456
|
+
return mapEnvelope(resp, (rows) => (rows ?? []).map(toEmailTemplate));
|
|
1457
|
+
},
|
|
1458
|
+
async get(id) {
|
|
1459
|
+
const resp = await http.request(
|
|
1460
|
+
"GET",
|
|
1461
|
+
`/v1/notifications/templates/${encodeURIComponent(id)}`
|
|
1462
|
+
);
|
|
1463
|
+
return mapEnvelope(resp, toEmailTemplate);
|
|
1464
|
+
},
|
|
1465
|
+
async create(input) {
|
|
1466
|
+
const body = {
|
|
1467
|
+
slug: input.slug,
|
|
1468
|
+
subject: input.subject,
|
|
1469
|
+
html_body: input.htmlBody
|
|
1470
|
+
};
|
|
1471
|
+
if (input.textBody !== void 0) body.text_body = input.textBody;
|
|
1472
|
+
if (input.variables !== void 0) body.variables = input.variables;
|
|
1473
|
+
const resp = await http.request("POST", "/v1/notifications/templates", { body });
|
|
1474
|
+
return mapEnvelope(resp, toEmailTemplate);
|
|
1475
|
+
},
|
|
1476
|
+
async update(id, input) {
|
|
1477
|
+
const body = {};
|
|
1478
|
+
if (input.subject !== void 0) body.subject = input.subject;
|
|
1479
|
+
if (input.htmlBody !== void 0) body.html_body = input.htmlBody;
|
|
1480
|
+
if (input.textBody !== void 0) body.text_body = input.textBody;
|
|
1481
|
+
if (input.variables !== void 0) body.variables = input.variables;
|
|
1482
|
+
const resp = await http.request(
|
|
1483
|
+
"PUT",
|
|
1484
|
+
`/v1/notifications/templates/${encodeURIComponent(id)}`,
|
|
1485
|
+
{ body }
|
|
1486
|
+
);
|
|
1487
|
+
return mapEnvelope(resp, toEmailTemplate);
|
|
1488
|
+
},
|
|
1489
|
+
async delete(id) {
|
|
1490
|
+
return http.request("DELETE", `/v1/notifications/templates/${encodeURIComponent(id)}`);
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
const smsTemplates = {
|
|
1494
|
+
async list() {
|
|
1495
|
+
const resp = await http.request("GET", "/v1/notifications/sms-templates");
|
|
1496
|
+
return mapEnvelope(resp, (rows) => (rows ?? []).map(toSmsTemplate));
|
|
1497
|
+
},
|
|
1498
|
+
async get(id) {
|
|
1499
|
+
const resp = await http.request(
|
|
1500
|
+
"GET",
|
|
1501
|
+
`/v1/notifications/sms-templates/${encodeURIComponent(id)}`
|
|
1502
|
+
);
|
|
1503
|
+
return mapEnvelope(resp, toSmsTemplate);
|
|
1504
|
+
},
|
|
1505
|
+
async create(input) {
|
|
1506
|
+
const body = { slug: input.slug, body: input.body };
|
|
1507
|
+
if (input.variables !== void 0) body.variables = input.variables;
|
|
1508
|
+
const resp = await http.request("POST", "/v1/notifications/sms-templates", { body });
|
|
1509
|
+
return mapEnvelope(resp, toSmsTemplate);
|
|
1510
|
+
},
|
|
1511
|
+
async update(id, input) {
|
|
1512
|
+
const body = {};
|
|
1513
|
+
if (input.body !== void 0) body.body = input.body;
|
|
1514
|
+
if (input.variables !== void 0) body.variables = input.variables;
|
|
1515
|
+
const resp = await http.request(
|
|
1516
|
+
"PUT",
|
|
1517
|
+
`/v1/notifications/sms-templates/${encodeURIComponent(id)}`,
|
|
1518
|
+
{ body }
|
|
1519
|
+
);
|
|
1520
|
+
return mapEnvelope(resp, toSmsTemplate);
|
|
1521
|
+
},
|
|
1522
|
+
async delete(id) {
|
|
1523
|
+
return http.request("DELETE", `/v1/notifications/sms-templates/${encodeURIComponent(id)}`);
|
|
1524
|
+
}
|
|
1525
|
+
};
|
|
1526
|
+
return {
|
|
1527
|
+
push,
|
|
1528
|
+
email,
|
|
1529
|
+
sms,
|
|
1530
|
+
verifications,
|
|
1531
|
+
inbox,
|
|
1532
|
+
preferences,
|
|
1533
|
+
templates: { email: emailTemplates, sms: smsTemplates },
|
|
1534
|
+
async registerDevice(params) {
|
|
1535
|
+
return http.request("POST", "/v1/notifications/devices", { body: params });
|
|
1536
|
+
},
|
|
1537
|
+
async unregisterDevice(deviceId) {
|
|
1538
|
+
return http.request("DELETE", `/v1/notifications/devices/${encodeURIComponent(deviceId)}`);
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// src/clients/realtime.ts
|
|
1544
|
+
var import_node_crypto = require("crypto");
|
|
1545
|
+
function mintToken(secret2) {
|
|
1546
|
+
const now2 = Math.floor(Date.now() / 1e3);
|
|
1547
|
+
const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url");
|
|
1548
|
+
const payload = Buffer.from(
|
|
1549
|
+
JSON.stringify({ iss: "backend-runtime", role: "service_role", iat: now2, exp: now2 + 60 })
|
|
1550
|
+
).toString("base64url");
|
|
1551
|
+
const sig = (0, import_node_crypto.createHmac)("sha256", secret2).update(`${header}.${payload}`).digest("base64url");
|
|
1552
|
+
return `${header}.${payload}.${sig}`;
|
|
1553
|
+
}
|
|
1554
|
+
function buildRealtimeClient(cfg) {
|
|
1555
|
+
const url = `${cfg.baseUrl}/realtime/api/broadcast`;
|
|
1556
|
+
return {
|
|
1557
|
+
async broadcast(channel, event, payload) {
|
|
1558
|
+
if (typeof channel !== "string" || channel.length === 0) {
|
|
1559
|
+
return {
|
|
1560
|
+
data: null,
|
|
1561
|
+
error: new PalbaseModuleError("invalid_argument", "channel must be a non-empty string", 400)
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
if (typeof event !== "string" || event.length === 0) {
|
|
1565
|
+
return {
|
|
1566
|
+
data: null,
|
|
1567
|
+
error: new PalbaseModuleError("invalid_argument", "event must be a non-empty string", 400)
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
if (!cfg.apiJwtSecret) {
|
|
1571
|
+
return {
|
|
1572
|
+
data: null,
|
|
1573
|
+
error: new PalbaseModuleError(
|
|
1574
|
+
"realtime_unconfigured",
|
|
1575
|
+
"Realtime.broadcast is unavailable: realtime is not provisioned for this Environment (PALBASE_REALTIME_API_JWT_SECRET unset).",
|
|
1576
|
+
503
|
|
1577
|
+
)
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
let token;
|
|
1581
|
+
try {
|
|
1582
|
+
token = mintToken(cfg.apiJwtSecret);
|
|
1583
|
+
} catch (err) {
|
|
1584
|
+
return {
|
|
1585
|
+
data: null,
|
|
1586
|
+
error: new PalbaseModuleError(
|
|
1587
|
+
"realtime_token_error",
|
|
1588
|
+
err instanceof Error ? err.message : "token mint failed",
|
|
1589
|
+
500
|
|
1590
|
+
)
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
const body = JSON.stringify({
|
|
1594
|
+
messages: [{ topic: channel, event, payload: payload ?? {}, private: false }]
|
|
1595
|
+
});
|
|
1596
|
+
const doFetch = cfg.fetchImpl ?? globalThis.fetch;
|
|
1597
|
+
let response;
|
|
1598
|
+
try {
|
|
1599
|
+
response = await doFetch(url, {
|
|
1600
|
+
method: "POST",
|
|
1601
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
1602
|
+
body
|
|
1603
|
+
});
|
|
1604
|
+
} catch (err) {
|
|
1605
|
+
return {
|
|
1606
|
+
data: null,
|
|
1607
|
+
error: new PalbaseModuleError(
|
|
1608
|
+
"network_error",
|
|
1609
|
+
err instanceof Error ? err.message : "broadcast request failed",
|
|
1610
|
+
0
|
|
1611
|
+
)
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
if (response.status === 202 || response.status === 200) {
|
|
1615
|
+
return { data: void 0, error: null };
|
|
1616
|
+
}
|
|
1617
|
+
const detail = await response.text().catch(() => "");
|
|
1618
|
+
return {
|
|
1619
|
+
data: null,
|
|
1620
|
+
error: new PalbaseModuleError(
|
|
1621
|
+
"realtime_broadcast_failed",
|
|
1622
|
+
`broadcast returned ${response.status}: ${detail.slice(0, 200)}`,
|
|
1623
|
+
response.status
|
|
1624
|
+
)
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
// src/clients/storage.ts
|
|
1631
|
+
var BUCKET_NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
1632
|
+
var STORAGE_PATH_RE = /^[a-zA-Z0-9_./-]+$/;
|
|
1633
|
+
var STORAGE_TRAVERSAL_RE = /(?:^|\/)\.\.(?:\/|$)/;
|
|
1634
|
+
function validateStoragePath(p) {
|
|
1635
|
+
if (!STORAGE_PATH_RE.test(p) || STORAGE_TRAVERSAL_RE.test(p)) {
|
|
1636
|
+
throw new Error(
|
|
1637
|
+
`Invalid file path: "${p}". Paths must not contain traversal sequences and must match ${STORAGE_PATH_RE.source}`
|
|
1638
|
+
);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
function toFileObject(bucket2, row) {
|
|
1642
|
+
return {
|
|
1643
|
+
name: row.path ?? "",
|
|
1644
|
+
path: row.path ?? "",
|
|
1645
|
+
bucket: row.bucket ?? bucket2,
|
|
1646
|
+
size: typeof row.size === "number" ? row.size : 0,
|
|
1647
|
+
contentType: row.contentType ?? "",
|
|
1648
|
+
checksum: row.checksum ?? "",
|
|
1649
|
+
width: row.width,
|
|
1650
|
+
height: row.height,
|
|
1651
|
+
thumbhash: row.thumbhash,
|
|
1652
|
+
variants: row.variants ?? {}
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
function mapResponse(res, map) {
|
|
1656
|
+
if (res.error || res.data === null || res.data === void 0) {
|
|
1657
|
+
return { data: null, error: res.error, status: res.status };
|
|
1658
|
+
}
|
|
1659
|
+
return { data: map(res.data), error: null, status: res.status };
|
|
1660
|
+
}
|
|
1661
|
+
function buildBucketClient(http, bucketName) {
|
|
1662
|
+
const objectPath = (path) => `/v1/storage/object/${bucketName}/${path}`;
|
|
1663
|
+
return {
|
|
1664
|
+
async upload(path, file, options) {
|
|
1665
|
+
validateStoragePath(path);
|
|
1666
|
+
const headers = {
|
|
1667
|
+
"Content-Type": options?.contentType ?? "application/octet-stream"
|
|
1668
|
+
};
|
|
1669
|
+
if (options?.upsert) headers["x-upsert"] = "true";
|
|
1670
|
+
const body = file instanceof ArrayBuffer ? new Uint8Array(file) : file;
|
|
1671
|
+
const res = await http.request("PUT", objectPath(path), {
|
|
1672
|
+
body,
|
|
1673
|
+
headers
|
|
1674
|
+
});
|
|
1675
|
+
return mapResponse(res, (r) => toFileObject(bucketName, r ?? {}));
|
|
1676
|
+
},
|
|
1677
|
+
async download(path) {
|
|
1678
|
+
validateStoragePath(path);
|
|
1679
|
+
return http.request("GET", objectPath(path), { headers: { Accept: "*/*" } });
|
|
1680
|
+
},
|
|
1681
|
+
getPublicUrl(path, options) {
|
|
1682
|
+
validateStoragePath(path);
|
|
1683
|
+
const variant = options?.variant ? `?variant=${encodeURIComponent(options.variant)}` : "";
|
|
1684
|
+
return `/v1/files/${bucketName}/${path}${variant}`;
|
|
1685
|
+
},
|
|
1686
|
+
async createSignedUrl(path, options) {
|
|
1687
|
+
validateStoragePath(path);
|
|
1688
|
+
return http.request("POST", `/v1/storage/sign/${bucketName}/${path}`, {
|
|
1689
|
+
body: { expiresIn: options.expiresIn }
|
|
1690
|
+
});
|
|
1691
|
+
},
|
|
1692
|
+
async list(prefix, options) {
|
|
1693
|
+
if (prefix) validateStoragePath(prefix);
|
|
1694
|
+
const params = new URLSearchParams();
|
|
1695
|
+
if (prefix) params.set("prefix", prefix);
|
|
1696
|
+
if (options?.limit !== void 0) params.set("limit", String(options.limit));
|
|
1697
|
+
const query = params.toString();
|
|
1698
|
+
const res = await http.request(
|
|
1699
|
+
"GET",
|
|
1700
|
+
`/v1/storage/bucket/${bucketName}${query ? `?${query}` : ""}`
|
|
1701
|
+
);
|
|
1702
|
+
return mapResponse(res, (body) => (body?.objects ?? []).map((row) => toFileObject(bucketName, row)));
|
|
1703
|
+
},
|
|
1704
|
+
async remove(paths) {
|
|
1705
|
+
for (const p of paths) validateStoragePath(p);
|
|
1706
|
+
const removed = [];
|
|
1707
|
+
for (const p of paths) {
|
|
1708
|
+
const res = await http.request("DELETE", objectPath(p));
|
|
1709
|
+
if (res.error) return { data: null, error: res.error, status: res.status };
|
|
1710
|
+
removed.push(toFileObject(bucketName, { path: p }));
|
|
1711
|
+
}
|
|
1712
|
+
return { data: removed, error: null, status: 200 };
|
|
1713
|
+
},
|
|
1714
|
+
async move(from, to) {
|
|
1715
|
+
validateStoragePath(from);
|
|
1716
|
+
validateStoragePath(to);
|
|
1717
|
+
return http.request("POST", `/v1/storage/move/${bucketName}`, { body: { from, to } });
|
|
1718
|
+
},
|
|
1719
|
+
async copy(from, to) {
|
|
1720
|
+
validateStoragePath(from);
|
|
1721
|
+
validateStoragePath(to);
|
|
1722
|
+
return http.request("POST", `/v1/storage/copy/${bucketName}`, { body: { from, to } });
|
|
1723
|
+
}
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
function buildStorageClient(http) {
|
|
1727
|
+
return {
|
|
1728
|
+
bucket(name) {
|
|
1729
|
+
if (!BUCKET_NAME_RE.test(name)) {
|
|
1730
|
+
throw new Error(`Invalid bucket name: "${name}". Bucket names must match ${BUCKET_NAME_RE.source}`);
|
|
1731
|
+
}
|
|
1732
|
+
return buildBucketClient(http, name);
|
|
1733
|
+
}
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
// src/clients/index.ts
|
|
1738
|
+
function buildModuleClients(cfg) {
|
|
1739
|
+
const baseUrl = (cfg.baseUrl || "").replace(/\/+$/, "");
|
|
1740
|
+
if (!baseUrl) return {};
|
|
1741
|
+
const apiKey = cfg.serviceRoleKey || cfg.apiKey || "";
|
|
1742
|
+
const http = makeHttpClient({ baseUrl, apiKey, fetchImpl: cfg.fetchImpl });
|
|
1743
|
+
return {
|
|
1744
|
+
Documents: buildDocumentsClient(http),
|
|
1745
|
+
Storage: buildStorageClient(http),
|
|
1746
|
+
Notifications: buildNotificationsClient(http),
|
|
1747
|
+
Flags: buildFlagsClient(http, { getCurrentUserId: cfg.getCurrentUserId }),
|
|
1748
|
+
Realtime: buildRealtimeClient({
|
|
1749
|
+
baseUrl,
|
|
1750
|
+
apiJwtSecret: cfg.realtimeApiJwtSecret,
|
|
1751
|
+
fetchImpl: cfg.fetchImpl
|
|
1752
|
+
})
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
|
|
914
1756
|
// src/purchases/keys-gen.ts
|
|
915
1757
|
function liveKeys(entries) {
|
|
916
1758
|
return Object.entries(entries ?? {}).filter(([, def]) => def?.removed !== true).map(([key]) => key).sort();
|
|
@@ -1020,6 +1862,89 @@ function defineSchema(input) {
|
|
|
1020
1862
|
return { tables, extensions };
|
|
1021
1863
|
}
|
|
1022
1864
|
|
|
1865
|
+
// src/db/schema-json.ts
|
|
1866
|
+
function defOf(column) {
|
|
1867
|
+
return "_def" in column ? column._def : column;
|
|
1868
|
+
}
|
|
1869
|
+
function columnToJSON(column) {
|
|
1870
|
+
const def = defOf(column);
|
|
1871
|
+
const out = {
|
|
1872
|
+
type: def.type,
|
|
1873
|
+
nullable: def.nullable,
|
|
1874
|
+
primaryKey: def.primaryKey
|
|
1875
|
+
};
|
|
1876
|
+
if (def.defaultValue !== void 0) out.defaultValue = def.defaultValue;
|
|
1877
|
+
if (def.defaultRandom === true) out.defaultRandom = true;
|
|
1878
|
+
if (def.defaultNow === true) out.defaultNow = true;
|
|
1879
|
+
if (def.renamedFrom !== void 0) out.renamedFrom = def.renamedFrom;
|
|
1880
|
+
if (def.references !== void 0) {
|
|
1881
|
+
out.references = { table: def.references.table, column: def.references.column };
|
|
1882
|
+
}
|
|
1883
|
+
if (def.onDeleteAction !== void 0) out.onDeleteAction = def.onDeleteAction;
|
|
1884
|
+
if (def.enumName !== void 0) out.enumName = def.enumName;
|
|
1885
|
+
if (def.enumValues !== void 0) out.enumValues = [...def.enumValues];
|
|
1886
|
+
if (def.unique === true) out.unique = true;
|
|
1887
|
+
return out;
|
|
1888
|
+
}
|
|
1889
|
+
function policyToJSON(policy2) {
|
|
1890
|
+
return {
|
|
1891
|
+
name: policy2.name,
|
|
1892
|
+
command: policy2.command ?? "all",
|
|
1893
|
+
roles: policy2.roles ? [...policy2.roles] : [],
|
|
1894
|
+
// null rather than omitted: a policy with no USING clause is a different
|
|
1895
|
+
// thing from one whose clause the emitter forgot, and Go reads the
|
|
1896
|
+
// difference.
|
|
1897
|
+
using: policy2.using ?? null,
|
|
1898
|
+
withCheck: policy2.withCheck ?? null,
|
|
1899
|
+
permissive: policy2.permissive !== false
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
function tableToJSON(table) {
|
|
1903
|
+
const columns = {};
|
|
1904
|
+
for (const [name, column] of Object.entries(table.columns)) {
|
|
1905
|
+
columns[name] = columnToJSON(column);
|
|
1906
|
+
}
|
|
1907
|
+
const out = {
|
|
1908
|
+
name: table.name,
|
|
1909
|
+
columns,
|
|
1910
|
+
// Read, not re-derived. `defineSchema` already resolves the fail-closed
|
|
1911
|
+
// default (RLS on unless the author wrote `rls: false`, and forced on by any
|
|
1912
|
+
// policy), and a second copy of a SECURITY default is exactly the thing that
|
|
1913
|
+
// drifts — the direction it drifted last time was "expose everything", and
|
|
1914
|
+
// the live proof was one user reading another's rows.
|
|
1915
|
+
rls: table.rls,
|
|
1916
|
+
policies: (table.policies ?? []).map(policyToJSON)
|
|
1917
|
+
};
|
|
1918
|
+
if (table.primaryKey !== void 0 && table.primaryKey.length > 0) {
|
|
1919
|
+
out.primaryKey = [...table.primaryKey];
|
|
1920
|
+
}
|
|
1921
|
+
if (table.unique !== void 0 && table.unique.length > 0) {
|
|
1922
|
+
out.uniqueConstraints = table.unique.map((u) => ({ name: u.name, columns: [...u.columns] }));
|
|
1923
|
+
}
|
|
1924
|
+
if (table.raw !== void 0 && table.raw.length > 0) {
|
|
1925
|
+
out.rawConstraints = table.raw.map((r) => ({
|
|
1926
|
+
name: r.name,
|
|
1927
|
+
up: r.up,
|
|
1928
|
+
down: r.down ?? null
|
|
1929
|
+
}));
|
|
1930
|
+
}
|
|
1931
|
+
if (table.checks !== void 0 && table.checks.length > 0) {
|
|
1932
|
+
out.checks = table.checks.map((c) => ({ name: c.name, expr: c.expr }));
|
|
1933
|
+
}
|
|
1934
|
+
if (table.indexes !== void 0 && table.indexes.length > 0) {
|
|
1935
|
+
out.indexes = table.indexes.map((i) => ({ name: i.name, columns: [...i.columns] }));
|
|
1936
|
+
}
|
|
1937
|
+
return out;
|
|
1938
|
+
}
|
|
1939
|
+
function toSchemaJSON(schema) {
|
|
1940
|
+
const tables = {};
|
|
1941
|
+
for (const table of Object.values(schema.tables)) {
|
|
1942
|
+
const json = tableToJSON(table);
|
|
1943
|
+
tables[json.name] = json;
|
|
1944
|
+
}
|
|
1945
|
+
return { tables, extensions: [...new Set(schema.extensions ?? [])] };
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1023
1948
|
// src/db/extensions.ts
|
|
1024
1949
|
var PALBASE_EXTENSIONS = [
|
|
1025
1950
|
// Search & text
|
|
@@ -1105,6 +2030,21 @@ var ColumnBuilder = class _ColumnBuilder {
|
|
|
1105
2030
|
return new _ColumnBuilder(this._def.type, this._def);
|
|
1106
2031
|
}
|
|
1107
2032
|
/** Add a foreign key reference. */
|
|
2033
|
+
/**
|
|
2034
|
+
* Declares that this column used to be called `previous`.
|
|
2035
|
+
*
|
|
2036
|
+
* A schema diff sees one name gone and another present; it cannot know whether
|
|
2037
|
+
* you renamed a column or dropped one and added another, and the two are very
|
|
2038
|
+
* different — the second loses every value. Saying so here turns the plan into
|
|
2039
|
+
* `ALTER TABLE … RENAME COLUMN` instead.
|
|
2040
|
+
*
|
|
2041
|
+
* Once the rename has been applied the annotation is inert (the old name is no
|
|
2042
|
+
* longer there to rename), so it can be deleted at your leisure.
|
|
2043
|
+
*/
|
|
2044
|
+
renamedFrom(previous) {
|
|
2045
|
+
this._def.renamedFrom = previous;
|
|
2046
|
+
return this;
|
|
2047
|
+
}
|
|
1108
2048
|
references(table, column) {
|
|
1109
2049
|
this._def.references = { table, column };
|
|
1110
2050
|
return new _ColumnBuilder(this._def.type, this._def);
|
|
@@ -1372,6 +2312,7 @@ export {};
|
|
|
1372
2312
|
// src/config/storage.ts
|
|
1373
2313
|
var STORAGE_CONFIG_KIND = "storage";
|
|
1374
2314
|
var VARIANT_FORMATS = ["webp", "avif", "jpeg", "png"];
|
|
2315
|
+
var VARIANT_FITS = ["cover", "contain", "inside"];
|
|
1375
2316
|
function normalizeVariants(input) {
|
|
1376
2317
|
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
1377
2318
|
throw new Error("bucket variants must be an object of { <name>: { width?, height?, format? } }");
|
|
@@ -1401,10 +2342,26 @@ function normalizeVariants(input) {
|
|
|
1401
2342
|
`bucket variant "${name}" format must be one of ${VARIANT_FORMATS.join(", ")}, got "${String(spec.format)}"`
|
|
1402
2343
|
);
|
|
1403
2344
|
}
|
|
2345
|
+
if (spec.fit !== void 0 && !VARIANT_FITS.includes(spec.fit)) {
|
|
2346
|
+
throw new Error(
|
|
2347
|
+
`bucket variant "${name}" fit must be one of ${VARIANT_FITS.join(", ")}, got "${String(spec.fit)}"`
|
|
2348
|
+
);
|
|
2349
|
+
}
|
|
2350
|
+
if (spec.quality !== void 0) {
|
|
2351
|
+
const q = spec.quality;
|
|
2352
|
+
if (typeof q !== "number" || !Number.isInteger(q) || q < 1 || q > 100) {
|
|
2353
|
+
throw new Error(`bucket variant "${name}" quality must be an integer 1\u2013100, got ${String(q)}`);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
if (spec.fit === "cover" && (spec.width === void 0 || spec.height === void 0)) {
|
|
2357
|
+
throw new Error(`bucket variant "${name}" uses fit "cover", which needs BOTH width and height`);
|
|
2358
|
+
}
|
|
1404
2359
|
out[name] = {
|
|
1405
2360
|
...spec.width !== void 0 ? { width: spec.width } : {},
|
|
1406
2361
|
...spec.height !== void 0 ? { height: spec.height } : {},
|
|
1407
|
-
...spec.
|
|
2362
|
+
...spec.fit !== void 0 ? { fit: spec.fit } : {},
|
|
2363
|
+
...spec.format !== void 0 ? { format: spec.format } : {},
|
|
2364
|
+
...spec.quality !== void 0 ? { quality: spec.quality } : {}
|
|
1408
2365
|
};
|
|
1409
2366
|
}
|
|
1410
2367
|
return out;
|
|
@@ -1935,6 +2892,57 @@ function defineFlags(input) {
|
|
|
1935
2892
|
return { __config: FLAGS_CONFIG_KIND, flags };
|
|
1936
2893
|
}
|
|
1937
2894
|
|
|
2895
|
+
// src/config/secrets.ts
|
|
2896
|
+
var SECRETS_CONFIG_KIND = "secrets";
|
|
2897
|
+
var SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
2898
|
+
function secret(name, opts = {}) {
|
|
2899
|
+
if (typeof name !== "string" || !SECRET_NAME_RE.test(name)) {
|
|
2900
|
+
throw new Error(
|
|
2901
|
+
`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")`
|
|
2902
|
+
);
|
|
2903
|
+
}
|
|
2904
|
+
if (opts === null || typeof opts !== "object") {
|
|
2905
|
+
throw new Error(`secret("${name}") expects { required?, description? }`);
|
|
2906
|
+
}
|
|
2907
|
+
let required = true;
|
|
2908
|
+
if (opts.required !== void 0) {
|
|
2909
|
+
if (typeof opts.required !== "boolean") {
|
|
2910
|
+
throw new Error(`secret("${name}") required must be a boolean`);
|
|
2911
|
+
}
|
|
2912
|
+
required = opts.required;
|
|
2913
|
+
}
|
|
2914
|
+
let description = null;
|
|
2915
|
+
if (opts.description !== void 0) {
|
|
2916
|
+
if (typeof opts.description !== "string") {
|
|
2917
|
+
throw new Error(`secret("${name}") description must be a string`);
|
|
2918
|
+
}
|
|
2919
|
+
const trimmed = opts.description.trim();
|
|
2920
|
+
description = trimmed.length > 0 ? trimmed : null;
|
|
2921
|
+
}
|
|
2922
|
+
return { name, required, description };
|
|
2923
|
+
}
|
|
2924
|
+
function defineSecrets(input) {
|
|
2925
|
+
if (input === null || typeof input !== "object" || !Array.isArray(input.secrets)) {
|
|
2926
|
+
throw new Error('defineSecrets expects { secrets: [secret("NAME"), ...] }');
|
|
2927
|
+
}
|
|
2928
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2929
|
+
for (const def of input.secrets) {
|
|
2930
|
+
if (def === null || typeof def !== "object" || typeof def.name !== "string" || typeof def.required !== "boolean") {
|
|
2931
|
+
throw new Error(
|
|
2932
|
+
`defineSecrets: every entry must come from secret() \u2014 got ${JSON.stringify(def)}`
|
|
2933
|
+
);
|
|
2934
|
+
}
|
|
2935
|
+
if (byName.has(def.name)) {
|
|
2936
|
+
throw new Error(
|
|
2937
|
+
`secret "${def.name}" is declared twice in config/secrets.ts \u2014 remove one; a name has exactly one requirement`
|
|
2938
|
+
);
|
|
2939
|
+
}
|
|
2940
|
+
byName.set(def.name, def);
|
|
2941
|
+
}
|
|
2942
|
+
const secrets = [...byName.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
2943
|
+
return { __config: SECRETS_CONFIG_KIND, secrets };
|
|
2944
|
+
}
|
|
2945
|
+
|
|
1938
2946
|
// src/decorators/registry.ts
|
|
1939
2947
|
var ROUTES = /* @__PURE__ */ Symbol.for("palbase.backend.routes");
|
|
1940
2948
|
var PARAM_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.paramBuffer");
|
|
@@ -2029,6 +3037,21 @@ function getRoutes(ctor) {
|
|
|
2029
3037
|
|
|
2030
3038
|
// src/decorators/controller.ts
|
|
2031
3039
|
var CONTROLLER_META = /* @__PURE__ */ Symbol.for("palbase.backend.controllerMeta");
|
|
3040
|
+
var REGISTRY = /* @__PURE__ */ Symbol.for("palbase.backend.allControllers");
|
|
3041
|
+
function registry() {
|
|
3042
|
+
const g = globalThis;
|
|
3043
|
+
const existing = g[REGISTRY];
|
|
3044
|
+
if (existing) return existing;
|
|
3045
|
+
const fresh = [];
|
|
3046
|
+
g[REGISTRY] = fresh;
|
|
3047
|
+
return fresh;
|
|
3048
|
+
}
|
|
3049
|
+
function getRegisteredControllers() {
|
|
3050
|
+
return registry().slice();
|
|
3051
|
+
}
|
|
3052
|
+
function __resetRegisteredControllers() {
|
|
3053
|
+
registry().length = 0;
|
|
3054
|
+
}
|
|
2032
3055
|
var RESERVED_FIRST_SEGMENT = "webhooks";
|
|
2033
3056
|
function assertNotReserved(path, subject) {
|
|
2034
3057
|
const [first] = path.split("/").filter(Boolean);
|
|
@@ -2065,6 +3088,8 @@ function Controller(basePath, options = {}) {
|
|
|
2065
3088
|
configurable: true,
|
|
2066
3089
|
writable: false
|
|
2067
3090
|
});
|
|
3091
|
+
const all = registry();
|
|
3092
|
+
if (!all.includes(ctor)) all.push(ctor);
|
|
2068
3093
|
return ctor;
|
|
2069
3094
|
};
|
|
2070
3095
|
}
|
|
@@ -2246,8 +3271,8 @@ function defineError(code, status, dataSchema) {
|
|
|
2246
3271
|
`defineError: status for "${code}" must be a 4xx/5xx integer, got ${status} \u2014 error responses must not clobber success responses in the project spec.`
|
|
2247
3272
|
);
|
|
2248
3273
|
}
|
|
2249
|
-
const
|
|
2250
|
-
const existing =
|
|
3274
|
+
const registry3 = getErrorRegistry();
|
|
3275
|
+
const existing = registry3.get(code);
|
|
2251
3276
|
const className = defaultClassName(code);
|
|
2252
3277
|
const dataDigest = dataSchema ? digestOf(code, dataSchema) : void 0;
|
|
2253
3278
|
if (existing) {
|
|
@@ -2276,7 +3301,7 @@ function defineError(code, status, dataSchema) {
|
|
|
2276
3301
|
};
|
|
2277
3302
|
const cls = dataSchema ? makeWithData(dataSchema) : makeWithoutData();
|
|
2278
3303
|
Object.defineProperty(cls, "name", { value: className });
|
|
2279
|
-
|
|
3304
|
+
registry3.set(code, {
|
|
2280
3305
|
code,
|
|
2281
3306
|
status,
|
|
2282
3307
|
className,
|
|
@@ -2533,16 +3558,16 @@ function hasSecrets(value) {
|
|
|
2533
3558
|
const secrets = value.secrets;
|
|
2534
3559
|
return secrets === void 0 || Array.isArray(secrets);
|
|
2535
3560
|
}
|
|
2536
|
-
var
|
|
3561
|
+
var registry2 = [];
|
|
2537
3562
|
function __registerResource(resource) {
|
|
2538
|
-
|
|
3563
|
+
registry2.push({ resource, booted: false });
|
|
2539
3564
|
}
|
|
2540
3565
|
function declaredSecrets(resource) {
|
|
2541
3566
|
const ctor = resource.constructor;
|
|
2542
3567
|
return hasSecrets(ctor) ? ctor.secrets ?? [] : [];
|
|
2543
3568
|
}
|
|
2544
3569
|
async function __runResourceBoot(envMap) {
|
|
2545
|
-
for (const entry of
|
|
3570
|
+
for (const entry of registry2) {
|
|
2546
3571
|
if (entry.booted) continue;
|
|
2547
3572
|
const secrets = declaredSecrets(entry.resource);
|
|
2548
3573
|
const env = {};
|
|
@@ -2560,13 +3585,13 @@ async function __runResourceBoot(envMap) {
|
|
|
2560
3585
|
}
|
|
2561
3586
|
}
|
|
2562
3587
|
async function __shutdownResources() {
|
|
2563
|
-
for (let i =
|
|
2564
|
-
const entry =
|
|
3588
|
+
for (let i = registry2.length - 1; i >= 0; i -= 1) {
|
|
3589
|
+
const entry = registry2[i];
|
|
2565
3590
|
if (entry.booted && entry.resource.shutdown) {
|
|
2566
3591
|
await entry.resource.shutdown();
|
|
2567
3592
|
}
|
|
2568
3593
|
}
|
|
2569
|
-
|
|
3594
|
+
registry2.length = 0;
|
|
2570
3595
|
}
|
|
2571
3596
|
|
|
2572
3597
|
// src/hooks.ts
|
|
@@ -2639,6 +3664,7 @@ var import_zod2 = require("zod");
|
|
|
2639
3664
|
PALBASE_EXTENSIONS,
|
|
2640
3665
|
PROVIDER_CATALOG,
|
|
2641
3666
|
PalError,
|
|
3667
|
+
PalbaseModuleError,
|
|
2642
3668
|
Param,
|
|
2643
3669
|
Patch,
|
|
2644
3670
|
PolicyBuilder,
|
|
@@ -2653,7 +3679,9 @@ var import_zod2 = require("zod");
|
|
|
2653
3679
|
RequestId,
|
|
2654
3680
|
RequireEntitlement,
|
|
2655
3681
|
Resource,
|
|
3682
|
+
SECRETS_CONFIG_KIND,
|
|
2656
3683
|
STORAGE_CONFIG_KIND,
|
|
3684
|
+
Secrets,
|
|
2657
3685
|
Spend,
|
|
2658
3686
|
Storage,
|
|
2659
3687
|
TEST_USERS_CONFIG_KIND,
|
|
@@ -2669,6 +3697,7 @@ var import_zod2 = require("zod");
|
|
|
2669
3697
|
__getRuntime,
|
|
2670
3698
|
__registerResource,
|
|
2671
3699
|
__requestALS,
|
|
3700
|
+
__resetRegisteredControllers,
|
|
2672
3701
|
__runResourceBoot,
|
|
2673
3702
|
__runWithRuntime,
|
|
2674
3703
|
__setRuntime,
|
|
@@ -2677,6 +3706,7 @@ var import_zod2 = require("zod");
|
|
|
2677
3706
|
bigint,
|
|
2678
3707
|
boolean,
|
|
2679
3708
|
bucket,
|
|
3709
|
+
buildModuleClients,
|
|
2680
3710
|
buildProvider,
|
|
2681
3711
|
dec,
|
|
2682
3712
|
defineEgress,
|
|
@@ -2685,6 +3715,7 @@ var import_zod2 = require("zod");
|
|
|
2685
3715
|
defineMiddleware,
|
|
2686
3716
|
defineNotifications,
|
|
2687
3717
|
defineSchema,
|
|
3718
|
+
defineSecrets,
|
|
2688
3719
|
defineStorage,
|
|
2689
3720
|
defineTestUsers,
|
|
2690
3721
|
documents,
|
|
@@ -2694,6 +3725,7 @@ var import_zod2 = require("zod");
|
|
|
2694
3725
|
flag,
|
|
2695
3726
|
getErrorRegistry,
|
|
2696
3727
|
getJobConfig,
|
|
3728
|
+
getRegisteredControllers,
|
|
2697
3729
|
getRoutes,
|
|
2698
3730
|
getWebhookConfig,
|
|
2699
3731
|
inc,
|
|
@@ -2701,6 +3733,7 @@ var import_zod2 = require("zod");
|
|
|
2701
3733
|
isPalbaseExtension,
|
|
2702
3734
|
jsonb,
|
|
2703
3735
|
makeEnvDts,
|
|
3736
|
+
makeHttpClient,
|
|
2704
3737
|
makePurchasesDts,
|
|
2705
3738
|
makeTypedDB,
|
|
2706
3739
|
now,
|
|
@@ -2710,11 +3743,13 @@ var import_zod2 = require("zod");
|
|
|
2710
3743
|
raw,
|
|
2711
3744
|
recordThrows,
|
|
2712
3745
|
reservedSecretKey,
|
|
3746
|
+
secret,
|
|
2713
3747
|
spendFor,
|
|
2714
3748
|
storage,
|
|
2715
3749
|
testUser,
|
|
2716
3750
|
text,
|
|
2717
3751
|
timestamp,
|
|
3752
|
+
toSchemaJSON,
|
|
2718
3753
|
uuid,
|
|
2719
3754
|
validateUploadAgainstStorage,
|
|
2720
3755
|
z
|