@pramen/server 0.0.1 → 0.0.2

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/src/worker.ts CHANGED
@@ -171,9 +171,9 @@ export function makeWorker(app: PramenApp) {
171
171
 
172
172
  // --- admin: list known tenants ---
173
173
  if (url.pathname === "/tenants") {
174
- if (!isAdmin(identity)) return forbidden("tenants");
174
+ if (!isAdmin(identity)) return withCors(forbidden("tenants"), cors);
175
175
  const list = await env.KV.list({ prefix: "tenant:" });
176
- return json({ ok: true, result: list.keys.map((k) => k.name.slice("tenant:".length)) });
176
+ return withCors(json({ ok: true, result: list.keys.map((k) => k.name.slice("tenant:".length)) }), cors);
177
177
  }
178
178
 
179
179
  // --- admin: point-in-time recovery for a tenant ---
@@ -193,10 +193,29 @@ export function makeWorker(app: PramenApp) {
193
193
 
194
194
  // --- admin: a tenant's applied schema (hash + tables) ---
195
195
  if (url.pathname === "/admin/schema") {
196
- if (!isAdmin(identity)) return forbidden("schema");
196
+ if (!isAdmin(identity)) return withCors(forbidden("schema"), cors);
197
197
  const tenant = url.searchParams.get("tenant") ?? "main";
198
198
  const stub = env.PRAMEN.get(env.PRAMEN.idFromName(tenant));
199
- return stub.fetch(new Request("https://do/__schema", { headers: { "x-pramen-tenant": tenant } }));
199
+ const res = await stub.fetch(new Request("https://do/__schema", { headers: { "x-pramen-tenant": tenant } }));
200
+ return withCors(res, cors);
201
+ }
202
+
203
+ // --- admin: generic data ops over a tenant's tables (browse/edit any row).
204
+ // Body: { tenant, table, op: list|get|create|update|delete|count, ... }. Runs
205
+ // in the DO under SYSTEM scope (ACL bypassed) — gated to admins here. ---
206
+ if (url.pathname === "/admin/data" && request.method === "POST") {
207
+ if (!isAdmin(identity)) return forbidden("data");
208
+ const body = (await request.json().catch(() => ({}))) as { tenant?: unknown };
209
+ const tenant = typeof body.tenant === "string" && body.tenant ? body.tenant : "main";
210
+ const stub = env.PRAMEN.get(env.PRAMEN.idFromName(tenant));
211
+ const res = await stub.fetch(
212
+ new Request("https://do/__admin/data", {
213
+ method: "POST",
214
+ headers: { "content-type": "application/json", "x-pramen-tenant": tenant },
215
+ body: JSON.stringify(body),
216
+ }),
217
+ );
218
+ return withCors(res, cors);
200
219
  }
201
220
 
202
221
  const isRpc = url.pathname.startsWith("/rpc/");
@@ -206,7 +225,8 @@ export function makeWorker(app: PramenApp) {
206
225
  return new Response(
207
226
  "pramen — POST /rpc/<handler> (JSON body), or WebSocket /live for live queries. " +
208
227
  "Header X-Pramen-Tenant selects the store (default: main). " +
209
- "Admin: GET /tenants, POST /admin/recover {tenant,timestamp}, GET /admin/schema?tenant=.\n",
228
+ "Admin: GET /tenants, POST /admin/recover {tenant,timestamp}, GET /admin/schema?tenant=, " +
229
+ "POST /admin/data {tenant,table,op}.\n",
210
230
  { headers: { "content-type": "text/plain" } },
211
231
  );
212
232
  }