@palbase/backend 17.4.0 → 18.0.1
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-AAN642N5.js → chunk-W5ODXPY3.js} +2 -336
- 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.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-VLrU7rSW.d.ts → endpoint-B0LpZixz.d.cts} +124 -685
- package/dist/{index-BA_oFAz9.d.cts → endpoint-B0LpZixz.d.ts} +124 -685
- 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 +1031 -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-AAN642N5.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
|
|
@@ -1387,6 +2312,7 @@ export {};
|
|
|
1387
2312
|
// src/config/storage.ts
|
|
1388
2313
|
var STORAGE_CONFIG_KIND = "storage";
|
|
1389
2314
|
var VARIANT_FORMATS = ["webp", "avif", "jpeg", "png"];
|
|
2315
|
+
var VARIANT_FITS = ["cover", "contain", "inside"];
|
|
1390
2316
|
function normalizeVariants(input) {
|
|
1391
2317
|
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
1392
2318
|
throw new Error("bucket variants must be an object of { <name>: { width?, height?, format? } }");
|
|
@@ -1416,10 +2342,26 @@ function normalizeVariants(input) {
|
|
|
1416
2342
|
`bucket variant "${name}" format must be one of ${VARIANT_FORMATS.join(", ")}, got "${String(spec.format)}"`
|
|
1417
2343
|
);
|
|
1418
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
|
+
}
|
|
1419
2359
|
out[name] = {
|
|
1420
2360
|
...spec.width !== void 0 ? { width: spec.width } : {},
|
|
1421
2361
|
...spec.height !== void 0 ? { height: spec.height } : {},
|
|
1422
|
-
...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 } : {}
|
|
1423
2365
|
};
|
|
1424
2366
|
}
|
|
1425
2367
|
return out;
|
|
@@ -1950,6 +2892,57 @@ function defineFlags(input) {
|
|
|
1950
2892
|
return { __config: FLAGS_CONFIG_KIND, flags };
|
|
1951
2893
|
}
|
|
1952
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
|
+
|
|
1953
2946
|
// src/decorators/registry.ts
|
|
1954
2947
|
var ROUTES = /* @__PURE__ */ Symbol.for("palbase.backend.routes");
|
|
1955
2948
|
var PARAM_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.paramBuffer");
|
|
@@ -2044,6 +3037,21 @@ function getRoutes(ctor) {
|
|
|
2044
3037
|
|
|
2045
3038
|
// src/decorators/controller.ts
|
|
2046
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
|
+
}
|
|
2047
3055
|
var RESERVED_FIRST_SEGMENT = "webhooks";
|
|
2048
3056
|
function assertNotReserved(path, subject) {
|
|
2049
3057
|
const [first] = path.split("/").filter(Boolean);
|
|
@@ -2080,6 +3088,8 @@ function Controller(basePath, options = {}) {
|
|
|
2080
3088
|
configurable: true,
|
|
2081
3089
|
writable: false
|
|
2082
3090
|
});
|
|
3091
|
+
const all = registry();
|
|
3092
|
+
if (!all.includes(ctor)) all.push(ctor);
|
|
2083
3093
|
return ctor;
|
|
2084
3094
|
};
|
|
2085
3095
|
}
|
|
@@ -2261,8 +3271,8 @@ function defineError(code, status, dataSchema) {
|
|
|
2261
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.`
|
|
2262
3272
|
);
|
|
2263
3273
|
}
|
|
2264
|
-
const
|
|
2265
|
-
const existing =
|
|
3274
|
+
const registry3 = getErrorRegistry();
|
|
3275
|
+
const existing = registry3.get(code);
|
|
2266
3276
|
const className = defaultClassName(code);
|
|
2267
3277
|
const dataDigest = dataSchema ? digestOf(code, dataSchema) : void 0;
|
|
2268
3278
|
if (existing) {
|
|
@@ -2291,7 +3301,7 @@ function defineError(code, status, dataSchema) {
|
|
|
2291
3301
|
};
|
|
2292
3302
|
const cls = dataSchema ? makeWithData(dataSchema) : makeWithoutData();
|
|
2293
3303
|
Object.defineProperty(cls, "name", { value: className });
|
|
2294
|
-
|
|
3304
|
+
registry3.set(code, {
|
|
2295
3305
|
code,
|
|
2296
3306
|
status,
|
|
2297
3307
|
className,
|
|
@@ -2548,16 +3558,16 @@ function hasSecrets(value) {
|
|
|
2548
3558
|
const secrets = value.secrets;
|
|
2549
3559
|
return secrets === void 0 || Array.isArray(secrets);
|
|
2550
3560
|
}
|
|
2551
|
-
var
|
|
3561
|
+
var registry2 = [];
|
|
2552
3562
|
function __registerResource(resource) {
|
|
2553
|
-
|
|
3563
|
+
registry2.push({ resource, booted: false });
|
|
2554
3564
|
}
|
|
2555
3565
|
function declaredSecrets(resource) {
|
|
2556
3566
|
const ctor = resource.constructor;
|
|
2557
3567
|
return hasSecrets(ctor) ? ctor.secrets ?? [] : [];
|
|
2558
3568
|
}
|
|
2559
3569
|
async function __runResourceBoot(envMap) {
|
|
2560
|
-
for (const entry of
|
|
3570
|
+
for (const entry of registry2) {
|
|
2561
3571
|
if (entry.booted) continue;
|
|
2562
3572
|
const secrets = declaredSecrets(entry.resource);
|
|
2563
3573
|
const env = {};
|
|
@@ -2575,13 +3585,13 @@ async function __runResourceBoot(envMap) {
|
|
|
2575
3585
|
}
|
|
2576
3586
|
}
|
|
2577
3587
|
async function __shutdownResources() {
|
|
2578
|
-
for (let i =
|
|
2579
|
-
const entry =
|
|
3588
|
+
for (let i = registry2.length - 1; i >= 0; i -= 1) {
|
|
3589
|
+
const entry = registry2[i];
|
|
2580
3590
|
if (entry.booted && entry.resource.shutdown) {
|
|
2581
3591
|
await entry.resource.shutdown();
|
|
2582
3592
|
}
|
|
2583
3593
|
}
|
|
2584
|
-
|
|
3594
|
+
registry2.length = 0;
|
|
2585
3595
|
}
|
|
2586
3596
|
|
|
2587
3597
|
// src/hooks.ts
|
|
@@ -2654,6 +3664,7 @@ var import_zod2 = require("zod");
|
|
|
2654
3664
|
PALBASE_EXTENSIONS,
|
|
2655
3665
|
PROVIDER_CATALOG,
|
|
2656
3666
|
PalError,
|
|
3667
|
+
PalbaseModuleError,
|
|
2657
3668
|
Param,
|
|
2658
3669
|
Patch,
|
|
2659
3670
|
PolicyBuilder,
|
|
@@ -2668,7 +3679,9 @@ var import_zod2 = require("zod");
|
|
|
2668
3679
|
RequestId,
|
|
2669
3680
|
RequireEntitlement,
|
|
2670
3681
|
Resource,
|
|
3682
|
+
SECRETS_CONFIG_KIND,
|
|
2671
3683
|
STORAGE_CONFIG_KIND,
|
|
3684
|
+
Secrets,
|
|
2672
3685
|
Spend,
|
|
2673
3686
|
Storage,
|
|
2674
3687
|
TEST_USERS_CONFIG_KIND,
|
|
@@ -2684,6 +3697,7 @@ var import_zod2 = require("zod");
|
|
|
2684
3697
|
__getRuntime,
|
|
2685
3698
|
__registerResource,
|
|
2686
3699
|
__requestALS,
|
|
3700
|
+
__resetRegisteredControllers,
|
|
2687
3701
|
__runResourceBoot,
|
|
2688
3702
|
__runWithRuntime,
|
|
2689
3703
|
__setRuntime,
|
|
@@ -2692,6 +3706,7 @@ var import_zod2 = require("zod");
|
|
|
2692
3706
|
bigint,
|
|
2693
3707
|
boolean,
|
|
2694
3708
|
bucket,
|
|
3709
|
+
buildModuleClients,
|
|
2695
3710
|
buildProvider,
|
|
2696
3711
|
dec,
|
|
2697
3712
|
defineEgress,
|
|
@@ -2700,6 +3715,7 @@ var import_zod2 = require("zod");
|
|
|
2700
3715
|
defineMiddleware,
|
|
2701
3716
|
defineNotifications,
|
|
2702
3717
|
defineSchema,
|
|
3718
|
+
defineSecrets,
|
|
2703
3719
|
defineStorage,
|
|
2704
3720
|
defineTestUsers,
|
|
2705
3721
|
documents,
|
|
@@ -2709,6 +3725,7 @@ var import_zod2 = require("zod");
|
|
|
2709
3725
|
flag,
|
|
2710
3726
|
getErrorRegistry,
|
|
2711
3727
|
getJobConfig,
|
|
3728
|
+
getRegisteredControllers,
|
|
2712
3729
|
getRoutes,
|
|
2713
3730
|
getWebhookConfig,
|
|
2714
3731
|
inc,
|
|
@@ -2716,6 +3733,7 @@ var import_zod2 = require("zod");
|
|
|
2716
3733
|
isPalbaseExtension,
|
|
2717
3734
|
jsonb,
|
|
2718
3735
|
makeEnvDts,
|
|
3736
|
+
makeHttpClient,
|
|
2719
3737
|
makePurchasesDts,
|
|
2720
3738
|
makeTypedDB,
|
|
2721
3739
|
now,
|
|
@@ -2725,11 +3743,13 @@ var import_zod2 = require("zod");
|
|
|
2725
3743
|
raw,
|
|
2726
3744
|
recordThrows,
|
|
2727
3745
|
reservedSecretKey,
|
|
3746
|
+
secret,
|
|
2728
3747
|
spendFor,
|
|
2729
3748
|
storage,
|
|
2730
3749
|
testUser,
|
|
2731
3750
|
text,
|
|
2732
3751
|
timestamp,
|
|
3752
|
+
toSchemaJSON,
|
|
2733
3753
|
uuid,
|
|
2734
3754
|
validateUploadAgainstStorage,
|
|
2735
3755
|
z
|