@vertebr/admin 0.1.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.
Files changed (48) hide show
  1. package/dist/database/configStore.d.ts +10 -0
  2. package/dist/database/configStore.d.ts.map +1 -0
  3. package/dist/database/configStore.js +34 -0
  4. package/dist/database/configStore.js.map +1 -0
  5. package/dist/database/connector.d.ts +31 -0
  6. package/dist/database/connector.d.ts.map +1 -0
  7. package/dist/database/connector.js +187 -0
  8. package/dist/database/connector.js.map +1 -0
  9. package/dist/database/firestore.d.ts +71 -0
  10. package/dist/database/firestore.d.ts.map +1 -0
  11. package/dist/database/firestore.js +115 -0
  12. package/dist/database/firestore.js.map +1 -0
  13. package/dist/database/index.d.ts +8 -0
  14. package/dist/database/index.d.ts.map +1 -0
  15. package/dist/database/index.js +20 -0
  16. package/dist/database/index.js.map +1 -0
  17. package/dist/database/memory.d.ts +12 -0
  18. package/dist/database/memory.d.ts.map +1 -0
  19. package/dist/database/memory.js +116 -0
  20. package/dist/database/memory.js.map +1 -0
  21. package/dist/database/mongo.d.ts +41 -0
  22. package/dist/database/mongo.d.ts.map +1 -0
  23. package/dist/database/mongo.js +151 -0
  24. package/dist/database/mongo.js.map +1 -0
  25. package/dist/database/postgres.d.ts +46 -0
  26. package/dist/database/postgres.d.ts.map +1 -0
  27. package/dist/database/postgres.js +262 -0
  28. package/dist/database/postgres.js.map +1 -0
  29. package/dist/database/types.d.ts +70 -0
  30. package/dist/database/types.d.ts.map +1 -0
  31. package/dist/database/types.js +3 -0
  32. package/dist/database/types.js.map +1 -0
  33. package/dist/index.d.ts +210 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +650 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/schema.d.ts +18 -0
  38. package/dist/schema.d.ts.map +1 -0
  39. package/dist/schema.js +4 -0
  40. package/dist/schema.js.map +1 -0
  41. package/dist/store.d.ts +12 -0
  42. package/dist/store.d.ts.map +1 -0
  43. package/dist/store.js +36 -0
  44. package/dist/store.js.map +1 -0
  45. package/package.json +45 -0
  46. package/static/assets/index-CSsz_d7k.css +1 -0
  47. package/static/assets/index-D5rhtylJ.js +52 -0
  48. package/static/index.html +29 -0
package/dist/index.js ADDED
@@ -0,0 +1,650 @@
1
+ "use strict";
2
+ /**
3
+ * Vertebr — core admin control plane.
4
+ * Wire resources once; owners control them from the admin UI.
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.whereEq = exports.postgresJsonbTableDDL = exports.postgresAdapter = exports.mongoAdapter = exports.memoryAdapter = exports.firestoreAdapter = exports.databaseConnector = exports.adapterList = exports.adapterCount = exports.adapterConfigStore = exports.VertebrEnvError = exports.VertebrNotFoundError = exports.VertebrAuthError = exports.VERTEBR_ENV_HEADER = exports.mergeConfigValue = exports.memoryStore = void 0;
11
+ exports.createAdmin = createAdmin;
12
+ exports.stripeConnector = stripeConnector;
13
+ const node_fs_1 = __importDefault(require("node:fs"));
14
+ const node_path_1 = __importDefault(require("node:path"));
15
+ const store_1 = require("./store");
16
+ var store_2 = require("./store");
17
+ Object.defineProperty(exports, "memoryStore", { enumerable: true, get: function () { return store_2.memoryStore; } });
18
+ Object.defineProperty(exports, "mergeConfigValue", { enumerable: true, get: function () { return store_2.mergeConfigValue; } });
19
+ /** Header the dashboard sends to select the active environment. */
20
+ exports.VERTEBR_ENV_HEADER = "x-vertebr-env";
21
+ class VertebrAuthError extends Error {
22
+ statusCode = 401;
23
+ constructor(message = "Unauthorized") {
24
+ super(message);
25
+ this.name = "VertebrAuthError";
26
+ }
27
+ }
28
+ exports.VertebrAuthError = VertebrAuthError;
29
+ class VertebrNotFoundError extends Error {
30
+ statusCode = 404;
31
+ constructor(message = "Not found") {
32
+ super(message);
33
+ this.name = "VertebrNotFoundError";
34
+ }
35
+ }
36
+ exports.VertebrNotFoundError = VertebrNotFoundError;
37
+ class VertebrEnvError extends Error {
38
+ statusCode = 400;
39
+ constructor(message = "Invalid environment") {
40
+ super(message);
41
+ this.name = "VertebrEnvError";
42
+ }
43
+ }
44
+ exports.VertebrEnvError = VertebrEnvError;
45
+ function defaultStaticDir() {
46
+ const candidate = node_path_1.default.join(__dirname, "..", "static");
47
+ if (node_fs_1.default.existsSync(node_path_1.default.join(candidate, "index.html"))) {
48
+ return candidate;
49
+ }
50
+ return undefined;
51
+ }
52
+ function contentTypeFor(filePath) {
53
+ const ext = node_path_1.default.extname(filePath).toLowerCase();
54
+ switch (ext) {
55
+ case ".html":
56
+ return "text/html; charset=utf-8";
57
+ case ".js":
58
+ return "text/javascript; charset=utf-8";
59
+ case ".css":
60
+ return "text/css; charset=utf-8";
61
+ case ".svg":
62
+ return "image/svg+xml";
63
+ case ".json":
64
+ return "application/json";
65
+ case ".png":
66
+ return "image/png";
67
+ case ".woff2":
68
+ return "font/woff2";
69
+ default:
70
+ return "application/octet-stream";
71
+ }
72
+ }
73
+ function headerValue(headers, name) {
74
+ if (!headers)
75
+ return undefined;
76
+ const raw = headers[name] ?? headers[name.toLowerCase()];
77
+ if (Array.isArray(raw))
78
+ return raw[0];
79
+ return typeof raw === "string" ? raw : undefined;
80
+ }
81
+ function createAdmin(options) {
82
+ const basePath = (options.basePath ?? "/admin").replace(/\/$/, "") || "/admin";
83
+ const apiBase = `${basePath}/api`;
84
+ const resources = [];
85
+ const configs = [];
86
+ let metricsDef = null;
87
+ const controlMeta = new Map();
88
+ const staticDir = options.staticDir ?? defaultStaticDir();
89
+ const environments = options.environments && options.environments.length > 0
90
+ ? options.environments
91
+ : [{ id: "default", label: "Default", tone: "neutral" }];
92
+ const defaultEnvironment = options.defaultEnvironment ?? environments[0]?.id ?? "default";
93
+ if (!environments.some((env) => env.id === defaultEnvironment)) {
94
+ throw new Error(`defaultEnvironment "${defaultEnvironment}" is not in environments`);
95
+ }
96
+ function resolveDefaults(schema, defaults, env) {
97
+ const fromSchema = {};
98
+ for (const [key, field] of Object.entries(schema)) {
99
+ if (field.default !== undefined) {
100
+ fromSchema[key] = field.default;
101
+ }
102
+ }
103
+ const fromDef = typeof defaults === "function" ? defaults(env) : (defaults ?? {});
104
+ return { ...fromSchema, ...fromDef };
105
+ }
106
+ function resolveEnv(request) {
107
+ const header = headerValue(request.headers, exports.VERTEBR_ENV_HEADER);
108
+ const env = (header ?? defaultEnvironment).trim();
109
+ if (!environments.some((item) => item.id === env)) {
110
+ throw new VertebrEnvError(`Unknown environment "${env}". Expected one of: ${environments
111
+ .map((item) => item.id)
112
+ .join(", ")}`);
113
+ }
114
+ return env;
115
+ }
116
+ function publicAuth(env) {
117
+ return {
118
+ required: Boolean(env.auth?.required),
119
+ ...(env.auth?.claimKey ? { claimKey: env.auth.claimKey } : {}),
120
+ ...(env.auth?.roles?.length ? { roles: [...env.auth.roles] } : {}),
121
+ ...(env.auth?.client ? { client: env.auth.client } : {}),
122
+ };
123
+ }
124
+ function claimSatisfied(claims, key, expected) {
125
+ const value = claims?.[key];
126
+ if (value == null)
127
+ return false;
128
+ if (Array.isArray(value)) {
129
+ return value.map(String).includes(expected);
130
+ }
131
+ if (typeof value === "string") {
132
+ return (value === expected ||
133
+ value
134
+ .split(",")
135
+ .map((part) => part.trim())
136
+ .includes(expected));
137
+ }
138
+ return false;
139
+ }
140
+ async function assertAuth(request, envId) {
141
+ const environment = environments.find((item) => item.id === envId);
142
+ if (!environment) {
143
+ throw new VertebrEnvError(`Unknown environment "${envId}"`);
144
+ }
145
+ const user = options.auth.getUser
146
+ ? await options.auth.getUser(request)
147
+ : null;
148
+ const envAuth = environment.auth;
149
+ if (envAuth?.required) {
150
+ if (!user) {
151
+ throw new VertebrAuthError("Sign in required for this environment");
152
+ }
153
+ if (envAuth.claimKey) {
154
+ const expected = envAuth.claimIncludes ?? environment.id;
155
+ if (!claimSatisfied(user.claims, envAuth.claimKey, expected)) {
156
+ throw new VertebrAuthError(`Missing claim ${envAuth.claimKey}=${expected} for environment "${environment.id}"`);
157
+ }
158
+ }
159
+ if (envAuth.roles?.length) {
160
+ const roles = user.roles ?? [];
161
+ const ok = envAuth.roles.some((role) => roles.includes(role));
162
+ if (!ok) {
163
+ throw new VertebrAuthError(`Requires one of roles: ${envAuth.roles.join(", ")}`);
164
+ }
165
+ }
166
+ }
167
+ const allowed = await options.auth.check(request, {
168
+ env: envId,
169
+ user,
170
+ environment,
171
+ });
172
+ if (!allowed) {
173
+ throw new VertebrAuthError();
174
+ }
175
+ }
176
+ async function withEnvContext(request, run) {
177
+ const env = resolveEnv(request);
178
+ await assertAuth(request, env);
179
+ const ctx = { env, request };
180
+ const execute = () => Promise.resolve(run(ctx));
181
+ if (options.onEnvironment) {
182
+ return options.onEnvironment(env, execute);
183
+ }
184
+ return execute();
185
+ }
186
+ /** Public storefront reads — resolve env, no auth. */
187
+ async function withPublicEnvContext(request, run) {
188
+ const env = resolveEnv(request);
189
+ const ctx = { env, request };
190
+ const execute = () => Promise.resolve(run(ctx));
191
+ if (options.onEnvironment) {
192
+ return options.onEnvironment(env, execute);
193
+ }
194
+ return execute();
195
+ }
196
+ function findResource(id) {
197
+ const resource = resources.find((r) => r.id === id);
198
+ if (!resource) {
199
+ throw new VertebrNotFoundError(`Resource "${id}" not found`);
200
+ }
201
+ return resource;
202
+ }
203
+ function findConfig(id) {
204
+ const config = configs.find((c) => c.id === id);
205
+ if (!config) {
206
+ throw new VertebrNotFoundError(`Config "${id}" not found`);
207
+ }
208
+ return config;
209
+ }
210
+ async function readConfigValue(id, env) {
211
+ const meta = controlMeta.get(id);
212
+ if (meta) {
213
+ const stored = await meta.store.get(id, env);
214
+ return (0, store_1.mergeConfigValue)(resolveDefaults(meta.schema, meta.defaults, env), stored);
215
+ }
216
+ const config = findConfig(id);
217
+ return config.get({ env, request: null });
218
+ }
219
+ const bootstrapHandler = async () => ({
220
+ ok: true,
221
+ brand: "Vertebr",
222
+ defaultEnvironment,
223
+ environments: environments.map((env) => ({
224
+ id: env.id,
225
+ label: env.label,
226
+ tone: env.tone ?? "neutral",
227
+ auth: publicAuth(env),
228
+ })),
229
+ });
230
+ const catalogHandler = async (request) => withEnvContext(request, async (ctx) => ({
231
+ ok: true,
232
+ brand: "Vertebr",
233
+ environment: ctx.env,
234
+ environments: environments.map((env) => ({
235
+ id: env.id,
236
+ label: env.label,
237
+ tone: env.tone ?? "neutral",
238
+ auth: publicAuth(env),
239
+ })),
240
+ defaultEnvironment,
241
+ resources: resources.map((r) => ({
242
+ id: r.id,
243
+ label: r.label,
244
+ schema: r.schema,
245
+ permissions: {
246
+ create: r.permissions?.create ?? Boolean(r.create),
247
+ update: r.permissions?.update ?? Boolean(r.update),
248
+ delete: r.permissions?.delete ?? Boolean(r.remove),
249
+ },
250
+ })),
251
+ configs: configs.map((c) => ({
252
+ id: c.id,
253
+ label: c.label,
254
+ schema: c.schema,
255
+ })),
256
+ metrics: metricsDef
257
+ ? {
258
+ id: metricsDef.id ?? "overview",
259
+ label: metricsDef.label ?? "Overview",
260
+ }
261
+ : null,
262
+ }));
263
+ const listResourceHandler = async (request) => withEnvContext(request, async (ctx) => {
264
+ const id = request.params?.id;
265
+ if (!id) {
266
+ throw new VertebrNotFoundError("Missing resource id");
267
+ }
268
+ const resource = findResource(id);
269
+ const items = resource.list ? await resource.list(ctx) : [];
270
+ return {
271
+ id: resource.id,
272
+ label: resource.label,
273
+ schema: resource.schema,
274
+ environment: ctx.env,
275
+ permissions: {
276
+ create: resource.permissions?.create ?? Boolean(resource.create),
277
+ update: resource.permissions?.update ?? Boolean(resource.update),
278
+ delete: resource.permissions?.delete ?? Boolean(resource.remove),
279
+ },
280
+ actions: resource.actions
281
+ ? Object.fromEntries(Object.entries(resource.actions).map(([key, action]) => [
282
+ key,
283
+ { label: action.label, input: action.input ?? {} },
284
+ ]))
285
+ : {},
286
+ items,
287
+ };
288
+ });
289
+ const updateResourceHandler = async (request) => withEnvContext(request, async (ctx) => {
290
+ const id = request.params?.id;
291
+ const rowId = request.params?.rowId;
292
+ if (!id || !rowId) {
293
+ throw new VertebrNotFoundError("Missing resource or row id");
294
+ }
295
+ const resource = findResource(id);
296
+ if (!resource.update) {
297
+ throw new VertebrNotFoundError(`Resource "${id}" is not updatable`);
298
+ }
299
+ const payload = request.payload;
300
+ const value = payload && typeof payload === "object"
301
+ ? payload
302
+ : {};
303
+ const item = await resource.update(rowId, value, ctx);
304
+ return { ok: true, id, rowId, environment: ctx.env, item };
305
+ });
306
+ const createResourceHandler = async (request) => withEnvContext(request, async (ctx) => {
307
+ const id = request.params?.id;
308
+ if (!id) {
309
+ throw new VertebrNotFoundError("Missing resource id");
310
+ }
311
+ const resource = findResource(id);
312
+ if (!resource.create) {
313
+ throw new VertebrNotFoundError(`Resource "${id}" cannot create`);
314
+ }
315
+ const payload = request.payload;
316
+ const value = payload && typeof payload === "object"
317
+ ? payload
318
+ : {};
319
+ const item = await resource.create(value, ctx);
320
+ return { ok: true, id, environment: ctx.env, item };
321
+ });
322
+ const deleteResourceHandler = async (request) => withEnvContext(request, async (ctx) => {
323
+ const id = request.params?.id;
324
+ const rowId = request.params?.rowId;
325
+ if (!id || !rowId) {
326
+ throw new VertebrNotFoundError("Missing resource or row id");
327
+ }
328
+ const resource = findResource(id);
329
+ if (!resource.remove) {
330
+ throw new VertebrNotFoundError(`Resource "${id}" cannot delete`);
331
+ }
332
+ await resource.remove(rowId, ctx);
333
+ return { ok: true, id, rowId, environment: ctx.env };
334
+ });
335
+ const resourceActionHandler = async (request) => withEnvContext(request, async (ctx) => {
336
+ const id = request.params?.id;
337
+ const rowId = request.params?.rowId;
338
+ const actionId = request.params?.actionId;
339
+ if (!id || !rowId || !actionId) {
340
+ throw new VertebrNotFoundError("Missing resource, row, or action id");
341
+ }
342
+ const resource = findResource(id);
343
+ const action = resource.actions?.[actionId];
344
+ if (!action) {
345
+ throw new VertebrNotFoundError(`Action "${actionId}" not found`);
346
+ }
347
+ const row = resource.get
348
+ ? await resource.get(rowId, ctx)
349
+ : (await (resource.list?.(ctx) ?? [])).find((item) => String(item.id ?? "") === rowId ||
350
+ String(item.slug ?? "") === rowId ||
351
+ String(item.uid ?? "") === rowId ||
352
+ String(item.email ?? "") === rowId);
353
+ if (!row) {
354
+ throw new VertebrNotFoundError(`Row "${rowId}" not found`);
355
+ }
356
+ const payload = request.payload;
357
+ const input = payload && typeof payload === "object"
358
+ ? payload
359
+ : undefined;
360
+ const result = await action.run(row, input, ctx);
361
+ return { ok: true, id, rowId, actionId, environment: ctx.env, result };
362
+ });
363
+ const metricsHandler = async (request) => withEnvContext(request, async (ctx) => {
364
+ if (!metricsDef) {
365
+ return {
366
+ ok: true,
367
+ id: "overview",
368
+ label: "Overview",
369
+ environment: ctx.env,
370
+ metrics: [],
371
+ };
372
+ }
373
+ const metrics = await metricsDef.load(ctx);
374
+ return {
375
+ ok: true,
376
+ id: metricsDef.id ?? "overview",
377
+ label: metricsDef.label ?? "Overview",
378
+ environment: ctx.env,
379
+ metrics,
380
+ };
381
+ });
382
+ const getConfigHandler = async (request) => withEnvContext(request, async (ctx) => {
383
+ const id = request.params?.id;
384
+ if (!id) {
385
+ throw new VertebrNotFoundError("Missing config id");
386
+ }
387
+ const config = findConfig(id);
388
+ const value = await config.get(ctx);
389
+ return {
390
+ id: config.id,
391
+ label: config.label,
392
+ schema: config.schema,
393
+ environment: ctx.env,
394
+ value,
395
+ };
396
+ });
397
+ const setConfigHandler = async (request) => withEnvContext(request, async (ctx) => {
398
+ const id = request.params?.id;
399
+ if (!id) {
400
+ throw new VertebrNotFoundError("Missing config id");
401
+ }
402
+ const config = findConfig(id);
403
+ const payload = request.payload;
404
+ const value = payload && typeof payload === "object"
405
+ ? payload
406
+ : {};
407
+ await config.set(value, ctx);
408
+ return {
409
+ ok: true,
410
+ id: config.id,
411
+ environment: ctx.env,
412
+ value: await config.get(ctx),
413
+ };
414
+ });
415
+ const publicControlsHandler = async (request) => withPublicEnvContext(request, async (ctx) => {
416
+ const entries = await Promise.all([...controlMeta.entries()]
417
+ .filter(([, meta]) => meta.public)
418
+ .map(async ([id]) => {
419
+ const value = await readConfigValue(id, ctx.env);
420
+ return [id, value];
421
+ }));
422
+ return {
423
+ ok: true,
424
+ environment: ctx.env,
425
+ configs: Object.fromEntries(entries),
426
+ };
427
+ });
428
+ const publicConfigHandler = async (request) => withPublicEnvContext(request, async (ctx) => {
429
+ const id = request.params?.id;
430
+ if (!id) {
431
+ throw new VertebrNotFoundError("Missing config id");
432
+ }
433
+ const meta = controlMeta.get(id);
434
+ if (!meta?.public) {
435
+ throw new VertebrNotFoundError(`Public control "${id}" not found`);
436
+ }
437
+ const config = findConfig(id);
438
+ return {
439
+ ok: true,
440
+ id,
441
+ label: config.label,
442
+ environment: ctx.env,
443
+ value: await readConfigValue(id, ctx.env),
444
+ };
445
+ });
446
+ const admin = {
447
+ resource(def) {
448
+ resources.push(def);
449
+ return admin;
450
+ },
451
+ config(def) {
452
+ configs.push(def);
453
+ return admin;
454
+ },
455
+ control(def) {
456
+ controlMeta.set(def.id, {
457
+ public: Boolean(def.public),
458
+ store: def.store,
459
+ defaults: def.defaults,
460
+ schema: def.schema,
461
+ });
462
+ configs.push({
463
+ id: def.id,
464
+ label: def.label,
465
+ schema: def.schema,
466
+ get: async (ctx) => readConfigValue(def.id, ctx.env),
467
+ set: async (value, ctx) => {
468
+ const next = (0, store_1.mergeConfigValue)(resolveDefaults(def.schema, def.defaults, ctx.env), await def.store.get(def.id, ctx.env), value);
469
+ await def.store.set(def.id, ctx.env, next);
470
+ },
471
+ });
472
+ return admin;
473
+ },
474
+ metrics(def) {
475
+ metricsDef = def;
476
+ return admin;
477
+ },
478
+ async readConfig(id, env) {
479
+ if (!environments.some((item) => item.id === env)) {
480
+ throw new VertebrEnvError(`Unknown environment "${env}"`);
481
+ }
482
+ const run = () => readConfigValue(id, env);
483
+ if (options.onEnvironment) {
484
+ return options.onEnvironment(env, run);
485
+ }
486
+ return run();
487
+ },
488
+ use(connector) {
489
+ connector.register(admin);
490
+ return admin;
491
+ },
492
+ routes() {
493
+ return [
494
+ {
495
+ method: "GET",
496
+ path: `${apiBase}/bootstrap`,
497
+ handler: bootstrapHandler,
498
+ },
499
+ {
500
+ method: "GET",
501
+ path: `${apiBase}/public/controls`,
502
+ handler: publicControlsHandler,
503
+ },
504
+ {
505
+ method: "GET",
506
+ path: `${apiBase}/public/configs/{id}`,
507
+ handler: publicConfigHandler,
508
+ },
509
+ {
510
+ method: "GET",
511
+ path: `${apiBase}/metrics`,
512
+ handler: metricsHandler,
513
+ },
514
+ { method: "GET", path: apiBase, handler: catalogHandler },
515
+ {
516
+ method: "GET",
517
+ path: `${apiBase}/resources/{id}`,
518
+ handler: listResourceHandler,
519
+ },
520
+ {
521
+ method: "POST",
522
+ path: `${apiBase}/resources/{id}`,
523
+ handler: createResourceHandler,
524
+ },
525
+ {
526
+ method: "PUT",
527
+ path: `${apiBase}/resources/{id}/{rowId}`,
528
+ handler: updateResourceHandler,
529
+ },
530
+ {
531
+ method: "DELETE",
532
+ path: `${apiBase}/resources/{id}/{rowId}`,
533
+ handler: deleteResourceHandler,
534
+ },
535
+ {
536
+ method: "POST",
537
+ path: `${apiBase}/resources/{id}/{rowId}/actions/{actionId}`,
538
+ handler: resourceActionHandler,
539
+ },
540
+ {
541
+ method: "GET",
542
+ path: `${apiBase}/configs/{id}`,
543
+ handler: getConfigHandler,
544
+ },
545
+ {
546
+ method: "PUT",
547
+ path: `${apiBase}/configs/{id}`,
548
+ handler: setConfigHandler,
549
+ },
550
+ ];
551
+ },
552
+ hapiRoutes() {
553
+ const apiRoutes = admin.routes().map((route) => ({
554
+ method: route.method,
555
+ path: route.path,
556
+ options: { auth: false },
557
+ handler: async (request, h) => {
558
+ const toolkit = h;
559
+ try {
560
+ return await route.handler(request);
561
+ }
562
+ catch (error) {
563
+ if (error instanceof VertebrAuthError ||
564
+ error instanceof VertebrNotFoundError ||
565
+ error instanceof VertebrEnvError) {
566
+ return toolkit
567
+ .response({ ok: false, error: error.message })
568
+ .code(error.statusCode);
569
+ }
570
+ throw error;
571
+ }
572
+ },
573
+ }));
574
+ if (!staticDir) {
575
+ return apiRoutes;
576
+ }
577
+ const spaHandler = async (request, h) => {
578
+ const toolkit = h;
579
+ const req = request;
580
+ const reqPath = req.path ?? req.url?.pathname ?? "";
581
+ const relative = reqPath.slice(basePath.length).replace(/^\//, "");
582
+ if (relative === "api" || relative.startsWith("api/")) {
583
+ return toolkit.response({ ok: false, error: "Not found" }).code(404);
584
+ }
585
+ const safeRelative = relative.replace(/\.\./g, "");
586
+ const candidate = node_path_1.default.join(staticDir, safeRelative === "" ? "index.html" : safeRelative);
587
+ const resolved = node_path_1.default.resolve(candidate);
588
+ if (!resolved.startsWith(node_path_1.default.resolve(staticDir))) {
589
+ return toolkit.response({ ok: false, error: "Forbidden" }).code(403);
590
+ }
591
+ let filePath = resolved;
592
+ if (!node_fs_1.default.existsSync(filePath) || node_fs_1.default.statSync(filePath).isDirectory()) {
593
+ filePath = node_path_1.default.join(staticDir, "index.html");
594
+ }
595
+ const body = node_fs_1.default.readFileSync(filePath);
596
+ return toolkit.response(body).type(contentTypeFor(filePath)).code(200);
597
+ };
598
+ const staticRoutes = [
599
+ {
600
+ method: "GET",
601
+ path: basePath,
602
+ options: { auth: false },
603
+ handler: spaHandler,
604
+ },
605
+ {
606
+ method: "GET",
607
+ path: `${basePath}/{path*}`,
608
+ options: { auth: false },
609
+ handler: spaHandler,
610
+ },
611
+ ];
612
+ return [...apiRoutes, ...staticRoutes];
613
+ },
614
+ resources: () => [...resources],
615
+ configs: () => [...configs],
616
+ environments: () => [...environments],
617
+ };
618
+ return admin;
619
+ }
620
+ /** Example connector shape — Stripe implementation TBD. */
621
+ function stripeConnector(options) {
622
+ return {
623
+ id: options.id ?? "stripe",
624
+ register(admin) {
625
+ admin.resource({
626
+ id: "stripe-sessions",
627
+ label: "Stripe sessions",
628
+ schema: {
629
+ id: { type: "string", readOnly: true },
630
+ status: { type: "string", readOnly: true },
631
+ amountTotal: { type: "number", readOnly: true },
632
+ },
633
+ list: async () => [],
634
+ permissions: { create: false, update: false, delete: false },
635
+ });
636
+ },
637
+ };
638
+ }
639
+ var database_1 = require("./database");
640
+ Object.defineProperty(exports, "adapterConfigStore", { enumerable: true, get: function () { return database_1.adapterConfigStore; } });
641
+ Object.defineProperty(exports, "adapterCount", { enumerable: true, get: function () { return database_1.adapterCount; } });
642
+ Object.defineProperty(exports, "adapterList", { enumerable: true, get: function () { return database_1.adapterList; } });
643
+ Object.defineProperty(exports, "databaseConnector", { enumerable: true, get: function () { return database_1.databaseConnector; } });
644
+ Object.defineProperty(exports, "firestoreAdapter", { enumerable: true, get: function () { return database_1.firestoreAdapter; } });
645
+ Object.defineProperty(exports, "memoryAdapter", { enumerable: true, get: function () { return database_1.memoryAdapter; } });
646
+ Object.defineProperty(exports, "mongoAdapter", { enumerable: true, get: function () { return database_1.mongoAdapter; } });
647
+ Object.defineProperty(exports, "postgresAdapter", { enumerable: true, get: function () { return database_1.postgresAdapter; } });
648
+ Object.defineProperty(exports, "postgresJsonbTableDDL", { enumerable: true, get: function () { return database_1.postgresJsonbTableDDL; } });
649
+ Object.defineProperty(exports, "whereEq", { enumerable: true, get: function () { return database_1.whereEq; } });
650
+ //# sourceMappingURL=index.js.map