@helipod/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.
- package/dist/index.d.ts +126 -0
- package/dist/index.js +229 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { JSONValue, ValidatorJSON } from '@helipod/values';
|
|
2
|
+
import { MigrationDump } from '@helipod/docstore';
|
|
3
|
+
import { EmbeddedRuntime } from '@helipod/runtime-embedded';
|
|
4
|
+
import { RegisteredFunction, LogSink, IndexCatalog, LogFilter, ExecutionLogEntry } from '@helipod/executor';
|
|
5
|
+
import { ComparisonOp } from '@helipod/query-engine';
|
|
6
|
+
|
|
7
|
+
/** A fresh url-safe admin key (192 bits of entropy). */
|
|
8
|
+
declare function generateAdminKey(): string;
|
|
9
|
+
/** Constant-time comparison; false for a missing or wrong-length key. */
|
|
10
|
+
declare function verifyAdminKey(expected: string, presented: string | undefined): boolean;
|
|
11
|
+
|
|
12
|
+
interface FilterCond {
|
|
13
|
+
field: string;
|
|
14
|
+
op: ComparisonOp;
|
|
15
|
+
value: JSONValue;
|
|
16
|
+
}
|
|
17
|
+
/** Privileged, subscribable table browser. Reads any full-named table via cursor paginate + filters. */
|
|
18
|
+
declare const browseTableModule: RegisteredFunction;
|
|
19
|
+
|
|
20
|
+
type SchemaJsonLike = {
|
|
21
|
+
tables: Record<string, {
|
|
22
|
+
indexes: {
|
|
23
|
+
indexDescriptor: string;
|
|
24
|
+
}[];
|
|
25
|
+
shardKey?: string | null;
|
|
26
|
+
documentType?: ValidatorJSON;
|
|
27
|
+
}>;
|
|
28
|
+
};
|
|
29
|
+
type ManifestLike = {
|
|
30
|
+
path: string;
|
|
31
|
+
functions: {
|
|
32
|
+
name: string;
|
|
33
|
+
type: string;
|
|
34
|
+
}[];
|
|
35
|
+
}[];
|
|
36
|
+
interface AdminDeps {
|
|
37
|
+
runtime: EmbeddedRuntime;
|
|
38
|
+
schemaJson: SchemaJsonLike;
|
|
39
|
+
tableNumbers: Record<string, number>;
|
|
40
|
+
manifest: ManifestLike;
|
|
41
|
+
logSink: LogSink;
|
|
42
|
+
/** Optional — when provided, component tables (not in schemaJson) are enumerated. */
|
|
43
|
+
catalog?: IndexCatalog;
|
|
44
|
+
}
|
|
45
|
+
interface TableInfo {
|
|
46
|
+
name: string;
|
|
47
|
+
indexes: string[];
|
|
48
|
+
shardKey?: string;
|
|
49
|
+
documentCount: number;
|
|
50
|
+
}
|
|
51
|
+
interface TableDataPage {
|
|
52
|
+
documents: JSONValue[];
|
|
53
|
+
nextCursor: string | null;
|
|
54
|
+
hasMore: boolean;
|
|
55
|
+
scanCapped: boolean;
|
|
56
|
+
}
|
|
57
|
+
declare class AdminApi {
|
|
58
|
+
private readonly deps;
|
|
59
|
+
constructor(deps: AdminDeps);
|
|
60
|
+
private tableId;
|
|
61
|
+
listTables(): Promise<TableInfo[]>;
|
|
62
|
+
getTableData(table: string, opts?: {
|
|
63
|
+
cursor?: string | null;
|
|
64
|
+
pageSize?: number;
|
|
65
|
+
filter?: FilterCond[];
|
|
66
|
+
}): Promise<TableDataPage>;
|
|
67
|
+
listFunctions(): {
|
|
68
|
+
path: string;
|
|
69
|
+
kind: string;
|
|
70
|
+
}[];
|
|
71
|
+
queryLogs(filter?: LogFilter): ExecutionLogEntry[];
|
|
72
|
+
runFunction(path: string, args: JSONValue): Promise<{
|
|
73
|
+
value: JSONValue;
|
|
74
|
+
committed: boolean;
|
|
75
|
+
}>;
|
|
76
|
+
patchDocument(id: string, fields: Record<string, JSONValue>): Promise<JSONValue>;
|
|
77
|
+
deleteDocument(id: string): Promise<void>;
|
|
78
|
+
createDocument(table: string, fields: Record<string, JSONValue>): Promise<JSONValue>;
|
|
79
|
+
/** Swap the schema/tableNumbers/manifest the data browser + validation read — after a live deploy. */
|
|
80
|
+
setSchema(schemaJson: AdminDeps["schemaJson"], tableNumbers: Record<string, number>, manifest: AdminDeps["manifest"]): void;
|
|
81
|
+
/** The live schema + tableNumbers — a deploy diffs its new schema against this. */
|
|
82
|
+
getSchema(): {
|
|
83
|
+
schemaJson: AdminDeps["schemaJson"];
|
|
84
|
+
tableNumbers: Record<string, number>;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Export this deployment's full current materialized state to a portable {@link MigrationDump}
|
|
88
|
+
* (every live document + every current index row + the table-number map). Reachable via
|
|
89
|
+
* `GET /_admin/export` on BOTH the container `serve` path AND the Cloudflare DO host (both funnel
|
|
90
|
+
* `/_admin/*` through the same handler). Uses the store's `dumpCurrentState()` primitive — the same
|
|
91
|
+
* one the R2 snapshot mechanism uses — so ids and `_creationTime` round-trip verbatim.
|
|
92
|
+
*/
|
|
93
|
+
exportDump(): Promise<MigrationDump>;
|
|
94
|
+
/**
|
|
95
|
+
* Import a {@link MigrationDump} into this deployment. Runs the table-number collision guard FIRST
|
|
96
|
+
* (refuses a dump whose numbers would serve rows under the wrong table), applies the rows via the
|
|
97
|
+
* store's `write(..., "Overwrite")` overlay, then advances the runtime's timestamp oracle to the
|
|
98
|
+
* new high-water mark — without which a freshly-booted runtime keeps reading at `ts <= 0` and sees
|
|
99
|
+
* nothing. Intended for a FRESH target deployment (see `applyDumpToStore`'s note on merge semantics).
|
|
100
|
+
*/
|
|
101
|
+
importDump(dumpJson: string | unknown): Promise<{
|
|
102
|
+
ok: true;
|
|
103
|
+
imported: {
|
|
104
|
+
documents: number;
|
|
105
|
+
indexUpdates: number;
|
|
106
|
+
};
|
|
107
|
+
}>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Built-in privileged mutations the admin API invokes by id. Registered under `_system:*`. */
|
|
111
|
+
declare function systemModules(): Record<string, RegisteredFunction>;
|
|
112
|
+
|
|
113
|
+
interface AdminRequest {
|
|
114
|
+
method: string;
|
|
115
|
+
path: string;
|
|
116
|
+
query: Record<string, string>;
|
|
117
|
+
body?: string;
|
|
118
|
+
authorization?: string;
|
|
119
|
+
}
|
|
120
|
+
interface AdminResponse {
|
|
121
|
+
status: number;
|
|
122
|
+
body: JSONValue;
|
|
123
|
+
}
|
|
124
|
+
declare function handleAdminRequest(api: AdminApi, adminKey: string, req: AdminRequest): Promise<AdminResponse>;
|
|
125
|
+
|
|
126
|
+
export { AdminApi, type AdminDeps, type AdminRequest, type AdminResponse, type FilterCond, type ManifestLike, type SchemaJsonLike, type TableDataPage, type TableInfo, browseTableModule, generateAdminKey, handleAdminRequest, systemModules, verifyAdminKey };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// src/auth.ts
|
|
2
|
+
import { randomBytes, timingSafeEqual } from "crypto";
|
|
3
|
+
function generateAdminKey() {
|
|
4
|
+
return randomBytes(24).toString("base64url");
|
|
5
|
+
}
|
|
6
|
+
function verifyAdminKey(expected, presented) {
|
|
7
|
+
if (presented === void 0) return false;
|
|
8
|
+
const a = Buffer.from(expected);
|
|
9
|
+
const b = Buffer.from(presented);
|
|
10
|
+
if (a.length !== b.length) return false;
|
|
11
|
+
return timingSafeEqual(a, b);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/admin-api.ts
|
|
15
|
+
import { encodeStorageTableId } from "@helipod/id-codec";
|
|
16
|
+
import { convexToJson } from "@helipod/values";
|
|
17
|
+
import {
|
|
18
|
+
applyDumpToStore,
|
|
19
|
+
exportDumpFromStore,
|
|
20
|
+
parseDump
|
|
21
|
+
} from "@helipod/docstore";
|
|
22
|
+
var AdminApi = class {
|
|
23
|
+
constructor(deps) {
|
|
24
|
+
this.deps = deps;
|
|
25
|
+
}
|
|
26
|
+
deps;
|
|
27
|
+
tableId(table) {
|
|
28
|
+
const n = this.deps.tableNumbers[table];
|
|
29
|
+
if (n === void 0) throw new Error(`unknown table: ${table}`);
|
|
30
|
+
return encodeStorageTableId(n);
|
|
31
|
+
}
|
|
32
|
+
async listTables() {
|
|
33
|
+
const out = [];
|
|
34
|
+
for (const name of Object.keys(this.deps.tableNumbers).sort()) {
|
|
35
|
+
const schemaDef = this.deps.schemaJson.tables[name];
|
|
36
|
+
let indexes;
|
|
37
|
+
let shardKey;
|
|
38
|
+
if (schemaDef) {
|
|
39
|
+
indexes = schemaDef.indexes.map((i) => i.indexDescriptor);
|
|
40
|
+
shardKey = schemaDef.shardKey ?? void 0;
|
|
41
|
+
} else {
|
|
42
|
+
indexes = this.deps.catalog ? this.deps.catalog.indexesForTable(name).map((spec) => spec.index) : [];
|
|
43
|
+
shardKey = void 0;
|
|
44
|
+
}
|
|
45
|
+
out.push({
|
|
46
|
+
name,
|
|
47
|
+
indexes,
|
|
48
|
+
shardKey,
|
|
49
|
+
documentCount: await this.deps.runtime.store.count(this.tableId(name))
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
async getTableData(table, opts = {}) {
|
|
55
|
+
const args = { table, cursor: opts.cursor ?? null };
|
|
56
|
+
if (opts.pageSize !== void 0) args.pageSize = opts.pageSize;
|
|
57
|
+
if (opts.filter !== void 0) args.filter = opts.filter;
|
|
58
|
+
const r = await this.deps.runtime.runAdmin("_admin:browseTable", args);
|
|
59
|
+
return r.value;
|
|
60
|
+
}
|
|
61
|
+
listFunctions() {
|
|
62
|
+
return this.deps.manifest.flatMap(
|
|
63
|
+
(m) => m.functions.map((f) => ({ path: `${m.path}:${f.name}`, kind: f.type }))
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
queryLogs(filter) {
|
|
67
|
+
return this.deps.logSink.query(filter);
|
|
68
|
+
}
|
|
69
|
+
async runFunction(path, args) {
|
|
70
|
+
const r = await this.deps.runtime.run(path, args);
|
|
71
|
+
return { value: convexToJson(r.value), committed: r.committed };
|
|
72
|
+
}
|
|
73
|
+
async patchDocument(id, fields) {
|
|
74
|
+
const r = await this.deps.runtime.runSystem("_system:patchDocument", { id, fields });
|
|
75
|
+
return convexToJson(r.value);
|
|
76
|
+
}
|
|
77
|
+
async deleteDocument(id) {
|
|
78
|
+
await this.deps.runtime.runSystem("_system:deleteDocument", { id });
|
|
79
|
+
}
|
|
80
|
+
async createDocument(table, fields) {
|
|
81
|
+
const r = await this.deps.runtime.runSystem("_system:insertDocument", { table, fields });
|
|
82
|
+
return convexToJson(r.value);
|
|
83
|
+
}
|
|
84
|
+
/** Swap the schema/tableNumbers/manifest the data browser + validation read — after a live deploy. */
|
|
85
|
+
setSchema(schemaJson, tableNumbers, manifest) {
|
|
86
|
+
this.deps.schemaJson = schemaJson;
|
|
87
|
+
this.deps.tableNumbers = tableNumbers;
|
|
88
|
+
this.deps.manifest = manifest;
|
|
89
|
+
}
|
|
90
|
+
/** The live schema + tableNumbers — a deploy diffs its new schema against this. */
|
|
91
|
+
getSchema() {
|
|
92
|
+
return { schemaJson: this.deps.schemaJson, tableNumbers: this.deps.tableNumbers };
|
|
93
|
+
}
|
|
94
|
+
// ── Data migration (Slice 5 — portable ⇄ DO-native) ───────────────────────────────────────────
|
|
95
|
+
/**
|
|
96
|
+
* Export this deployment's full current materialized state to a portable {@link MigrationDump}
|
|
97
|
+
* (every live document + every current index row + the table-number map). Reachable via
|
|
98
|
+
* `GET /_admin/export` on BOTH the container `serve` path AND the Cloudflare DO host (both funnel
|
|
99
|
+
* `/_admin/*` through the same handler). Uses the store's `dumpCurrentState()` primitive — the same
|
|
100
|
+
* one the R2 snapshot mechanism uses — so ids and `_creationTime` round-trip verbatim.
|
|
101
|
+
*/
|
|
102
|
+
async exportDump() {
|
|
103
|
+
const deploymentId = await this.deps.runtime.store.getGlobal("fleet:deploymentId");
|
|
104
|
+
return exportDumpFromStore(this.deps.runtime.store, { tableNumbers: this.deps.tableNumbers, deploymentId });
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Import a {@link MigrationDump} into this deployment. Runs the table-number collision guard FIRST
|
|
108
|
+
* (refuses a dump whose numbers would serve rows under the wrong table), applies the rows via the
|
|
109
|
+
* store's `write(..., "Overwrite")` overlay, then advances the runtime's timestamp oracle to the
|
|
110
|
+
* new high-water mark — without which a freshly-booted runtime keeps reading at `ts <= 0` and sees
|
|
111
|
+
* nothing. Intended for a FRESH target deployment (see `applyDumpToStore`'s note on merge semantics).
|
|
112
|
+
*/
|
|
113
|
+
async importDump(dumpJson) {
|
|
114
|
+
const dump = parseDump(dumpJson);
|
|
115
|
+
const imported = await applyDumpToStore(
|
|
116
|
+
this.deps.runtime.store,
|
|
117
|
+
dump,
|
|
118
|
+
this.deps.tableNumbers
|
|
119
|
+
);
|
|
120
|
+
this.deps.runtime.observeTimestamp(await this.deps.runtime.store.maxTimestamp());
|
|
121
|
+
return { ok: true, imported };
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// src/browse.ts
|
|
126
|
+
import { query } from "@helipod/executor";
|
|
127
|
+
import { convexToJson as convexToJson2 } from "@helipod/values";
|
|
128
|
+
var MAX_SCAN = 1e3;
|
|
129
|
+
var PAGE_SIZE = 50;
|
|
130
|
+
var browseTableModule = query(async (ctx, args) => {
|
|
131
|
+
const b = ctx.db.query(args.table, "by_creation");
|
|
132
|
+
for (const c of args.filter ?? []) b.where(c.op, c.field, c.value);
|
|
133
|
+
const res = await b.paginate({ cursor: args.cursor ?? null, pageSize: args.pageSize ?? PAGE_SIZE, maxScan: MAX_SCAN });
|
|
134
|
+
return {
|
|
135
|
+
documents: res.page.map((d) => convexToJson2(d)),
|
|
136
|
+
nextCursor: res.nextCursor,
|
|
137
|
+
hasMore: res.hasMore,
|
|
138
|
+
scanCapped: res.scanCapped
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// src/system-functions.ts
|
|
143
|
+
import { mutation } from "@helipod/executor";
|
|
144
|
+
import { DocumentNotFoundError } from "@helipod/errors";
|
|
145
|
+
function systemModules() {
|
|
146
|
+
return {
|
|
147
|
+
"_system:patchDocument": mutation(async (ctx, args) => {
|
|
148
|
+
const existing = await ctx.db.get(args.id);
|
|
149
|
+
if (!existing) throw new DocumentNotFoundError(`cannot edit missing document ${args.id}`);
|
|
150
|
+
await ctx.db.replace(args.id, args.fields);
|
|
151
|
+
return await ctx.db.get(args.id);
|
|
152
|
+
}),
|
|
153
|
+
"_system:deleteDocument": mutation(async (ctx, args) => {
|
|
154
|
+
await ctx.db.delete(args.id);
|
|
155
|
+
return null;
|
|
156
|
+
}),
|
|
157
|
+
"_system:insertDocument": mutation(async (ctx, args) => {
|
|
158
|
+
const id = await ctx.db.insert(args.table, args.fields);
|
|
159
|
+
return await ctx.db.get(id);
|
|
160
|
+
})
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/router.ts
|
|
165
|
+
function bearer(authorization) {
|
|
166
|
+
if (!authorization) return void 0;
|
|
167
|
+
const m = /^Bearer (.+)$/.exec(authorization);
|
|
168
|
+
return m ? m[1] : void 0;
|
|
169
|
+
}
|
|
170
|
+
async function handleAdminRequest(api, adminKey, req) {
|
|
171
|
+
if (!verifyAdminKey(adminKey, bearer(req.authorization))) {
|
|
172
|
+
return { status: 401, body: { error: "unauthorized" } };
|
|
173
|
+
}
|
|
174
|
+
const parts = req.path.split("/").filter(Boolean);
|
|
175
|
+
const seg = parts.slice(1);
|
|
176
|
+
try {
|
|
177
|
+
if (req.method === "GET" && seg.length === 1 && seg[0] === "tables") {
|
|
178
|
+
return { status: 200, body: await api.listTables() };
|
|
179
|
+
}
|
|
180
|
+
if (req.method === "GET" && seg.length === 3 && seg[0] === "tables" && seg[2] === "data") {
|
|
181
|
+
const cursor = req.query.cursor ?? null;
|
|
182
|
+
const pageSize = req.query.pageSize ? Number(req.query.pageSize) : void 0;
|
|
183
|
+
const data = await api.getTableData(decodeURIComponent(seg[1]), { cursor, pageSize });
|
|
184
|
+
return { status: 200, body: data };
|
|
185
|
+
}
|
|
186
|
+
if (req.method === "GET" && seg.length === 1 && seg[0] === "functions") {
|
|
187
|
+
return { status: 200, body: api.listFunctions() };
|
|
188
|
+
}
|
|
189
|
+
if (req.method === "POST" && seg.length === 1 && seg[0] === "run") {
|
|
190
|
+
const { path, args } = JSON.parse(req.body ?? "{}");
|
|
191
|
+
if (!path) return { status: 400, body: { error: "missing path" } };
|
|
192
|
+
return { status: 200, body: await api.runFunction(path, args ?? {}) };
|
|
193
|
+
}
|
|
194
|
+
if (req.method === "GET" && seg.length === 1 && seg[0] === "logs") {
|
|
195
|
+
const since = req.query.since ? Number(req.query.since) : void 0;
|
|
196
|
+
return { status: 200, body: api.queryLogs({ since }) };
|
|
197
|
+
}
|
|
198
|
+
if (req.method === "PATCH" && seg.length === 4 && seg[0] === "tables" && seg[2] === "docs") {
|
|
199
|
+
const fields = JSON.parse(req.body ?? "{}");
|
|
200
|
+
return { status: 200, body: await api.patchDocument(seg[3], fields) };
|
|
201
|
+
}
|
|
202
|
+
if (req.method === "DELETE" && seg.length === 4 && seg[0] === "tables" && seg[2] === "docs") {
|
|
203
|
+
await api.deleteDocument(seg[3]);
|
|
204
|
+
return { status: 200, body: { ok: true } };
|
|
205
|
+
}
|
|
206
|
+
if (req.method === "POST" && seg.length === 3 && seg[0] === "tables" && seg[2] === "docs") {
|
|
207
|
+
const fields = JSON.parse(req.body ?? "{}");
|
|
208
|
+
return { status: 200, body: await api.createDocument(decodeURIComponent(seg[1]), fields) };
|
|
209
|
+
}
|
|
210
|
+
if (req.method === "GET" && seg.length === 1 && seg[0] === "export") {
|
|
211
|
+
return { status: 200, body: await api.exportDump() };
|
|
212
|
+
}
|
|
213
|
+
if (req.method === "POST" && seg.length === 1 && seg[0] === "import") {
|
|
214
|
+
return { status: 200, body: await api.importDump(req.body ?? "") };
|
|
215
|
+
}
|
|
216
|
+
return { status: 404, body: { error: "not found" } };
|
|
217
|
+
} catch (e) {
|
|
218
|
+
return { status: 400, body: { error: e instanceof Error ? e.message : String(e) } };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
export {
|
|
222
|
+
AdminApi,
|
|
223
|
+
browseTableModule,
|
|
224
|
+
generateAdminKey,
|
|
225
|
+
handleAdminRequest,
|
|
226
|
+
systemModules,
|
|
227
|
+
verifyAdminKey
|
|
228
|
+
};
|
|
229
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth.ts","../src/admin-api.ts","../src/browse.ts","../src/system-functions.ts","../src/router.ts"],"sourcesContent":["// packages/admin/src/auth.ts\nimport { randomBytes, timingSafeEqual } from \"node:crypto\";\n\n/** A fresh url-safe admin key (192 bits of entropy). */\nexport function generateAdminKey(): string {\n return randomBytes(24).toString(\"base64url\");\n}\n\n/** Constant-time comparison; false for a missing or wrong-length key. */\nexport function verifyAdminKey(expected: string, presented: string | undefined): boolean {\n if (presented === undefined) return false;\n const a = Buffer.from(expected);\n const b = Buffer.from(presented);\n if (a.length !== b.length) return false;\n return timingSafeEqual(a, b);\n}\n","import { encodeStorageTableId } from \"@helipod/id-codec\";\nimport { convexToJson, type JSONValue, type Value, type ValidatorJSON } from \"@helipod/values\";\nimport {\n applyDumpToStore,\n exportDumpFromStore,\n parseDump,\n type ImportableDocStore,\n type MigrationDump,\n} from \"@helipod/docstore\";\nimport type { EmbeddedRuntime } from \"@helipod/runtime-embedded\";\nimport type { ExecutionLogEntry, IndexCatalog, LogFilter, LogSink } from \"@helipod/executor\";\nimport type { FilterCond } from \"./browse\";\n\nexport type SchemaJsonLike = {\n tables: Record<\n string,\n {\n indexes: { indexDescriptor: string }[];\n shardKey?: string | null;\n // The real schema always sets this (see SchemaDefinitionJSON in @helipod/values); optional\n // here only because callers that build a SchemaJsonLike by hand (tests, component tables with\n // no app-level schema entry) don't always carry it. `helipod deploy`'s schema diff reads it\n // off the live `AdminApi.getSchema()` snapshot — see DeployDeps[\"current\"] in deploy-apply.ts.\n documentType?: ValidatorJSON;\n }\n >;\n};\nexport type ManifestLike = { path: string; functions: { name: string; type: string }[] }[];\n\nexport interface AdminDeps {\n runtime: EmbeddedRuntime;\n schemaJson: SchemaJsonLike;\n tableNumbers: Record<string, number>;\n manifest: ManifestLike;\n logSink: LogSink;\n /** Optional — when provided, component tables (not in schemaJson) are enumerated. */\n catalog?: IndexCatalog;\n}\n\nexport interface TableInfo {\n name: string;\n indexes: string[];\n shardKey?: string;\n documentCount: number;\n}\n\nexport interface TableDataPage {\n documents: JSONValue[];\n nextCursor: string | null;\n hasMore: boolean;\n scanCapped: boolean;\n}\n\nexport class AdminApi {\n constructor(private readonly deps: AdminDeps) {}\n\n private tableId(table: string): string {\n const n = this.deps.tableNumbers[table];\n if (n === undefined) throw new Error(`unknown table: ${table}`);\n return encodeStorageTableId(n);\n }\n\n async listTables(): Promise<TableInfo[]> {\n const out: TableInfo[] = [];\n for (const name of Object.keys(this.deps.tableNumbers).sort()) {\n const schemaDef = this.deps.schemaJson.tables[name];\n let indexes: string[];\n let shardKey: string | undefined;\n if (schemaDef) {\n // App table — use the schema definition.\n indexes = schemaDef.indexes.map((i) => i.indexDescriptor);\n shardKey = schemaDef.shardKey ?? undefined;\n } else {\n // Component table — derive index info from the catalog.\n indexes = this.deps.catalog\n ? this.deps.catalog.indexesForTable(name).map((spec) => spec.index)\n : [];\n shardKey = undefined;\n }\n out.push({\n name,\n indexes,\n shardKey,\n documentCount: await this.deps.runtime.store.count(this.tableId(name)),\n });\n }\n return out;\n }\n\n async getTableData(\n table: string,\n opts: { cursor?: string | null; pageSize?: number; filter?: FilterCond[] } = {},\n ): Promise<TableDataPage> {\n const args: Record<string, unknown> = { table, cursor: opts.cursor ?? null };\n if (opts.pageSize !== undefined) args.pageSize = opts.pageSize;\n if (opts.filter !== undefined) args.filter = opts.filter;\n const r = await this.deps.runtime.runAdmin(\"_admin:browseTable\", args as JSONValue);\n return r.value as TableDataPage;\n }\n\n listFunctions(): { path: string; kind: string }[] {\n return this.deps.manifest.flatMap((m) =>\n m.functions.map((f) => ({ path: `${m.path}:${f.name}`, kind: f.type })),\n );\n }\n\n queryLogs(filter?: LogFilter): ExecutionLogEntry[] {\n return this.deps.logSink.query(filter);\n }\n\n async runFunction(path: string, args: JSONValue): Promise<{ value: JSONValue; committed: boolean }> {\n const r = await this.deps.runtime.run(path, args);\n return { value: convexToJson(r.value as Value), committed: r.committed };\n }\n\n async patchDocument(id: string, fields: Record<string, JSONValue>): Promise<JSONValue> {\n const r = await this.deps.runtime.runSystem(\"_system:patchDocument\", { id, fields });\n return convexToJson(r.value as Value);\n }\n\n async deleteDocument(id: string): Promise<void> {\n await this.deps.runtime.runSystem(\"_system:deleteDocument\", { id });\n }\n\n async createDocument(table: string, fields: Record<string, JSONValue>): Promise<JSONValue> {\n const r = await this.deps.runtime.runSystem(\"_system:insertDocument\", { table, fields });\n return convexToJson(r.value as Value);\n }\n\n /** Swap the schema/tableNumbers/manifest the data browser + validation read — after a live deploy. */\n setSchema(schemaJson: AdminDeps[\"schemaJson\"], tableNumbers: Record<string, number>, manifest: AdminDeps[\"manifest\"]): void {\n this.deps.schemaJson = schemaJson;\n this.deps.tableNumbers = tableNumbers;\n this.deps.manifest = manifest;\n }\n\n /** The live schema + tableNumbers — a deploy diffs its new schema against this. */\n getSchema(): { schemaJson: AdminDeps[\"schemaJson\"]; tableNumbers: Record<string, number> } {\n return { schemaJson: this.deps.schemaJson, tableNumbers: this.deps.tableNumbers };\n }\n\n // ── Data migration (Slice 5 — portable ⇄ DO-native) ───────────────────────────────────────────\n\n /**\n * Export this deployment's full current materialized state to a portable {@link MigrationDump}\n * (every live document + every current index row + the table-number map). Reachable via\n * `GET /_admin/export` on BOTH the container `serve` path AND the Cloudflare DO host (both funnel\n * `/_admin/*` through the same handler). Uses the store's `dumpCurrentState()` primitive — the same\n * one the R2 snapshot mechanism uses — so ids and `_creationTime` round-trip verbatim.\n */\n async exportDump(): Promise<MigrationDump> {\n const deploymentId = (await this.deps.runtime.store.getGlobal(\"fleet:deploymentId\")) as string | null;\n return exportDumpFromStore(this.deps.runtime.store, { tableNumbers: this.deps.tableNumbers, deploymentId });\n }\n\n /**\n * Import a {@link MigrationDump} into this deployment. Runs the table-number collision guard FIRST\n * (refuses a dump whose numbers would serve rows under the wrong table), applies the rows via the\n * store's `write(..., \"Overwrite\")` overlay, then advances the runtime's timestamp oracle to the\n * new high-water mark — without which a freshly-booted runtime keeps reading at `ts <= 0` and sees\n * nothing. Intended for a FRESH target deployment (see `applyDumpToStore`'s note on merge semantics).\n */\n async importDump(dumpJson: string | unknown): Promise<{ ok: true; imported: { documents: number; indexUpdates: number } }> {\n const dump = parseDump(dumpJson);\n const imported = await applyDumpToStore(\n this.deps.runtime.store as unknown as ImportableDocStore,\n dump,\n this.deps.tableNumbers,\n );\n // Re-floor the read/write snapshot at the imported high-water mark (the same seeding a boot does).\n this.deps.runtime.observeTimestamp(await this.deps.runtime.store.maxTimestamp());\n return { ok: true, imported };\n }\n}\n","import { query, type RegisteredFunction } from \"@helipod/executor\";\nimport { convexToJson, type JSONValue, type Value } from \"@helipod/values\";\nimport type { ComparisonOp } from \"@helipod/query-engine\";\n\nexport interface FilterCond { field: string; op: ComparisonOp; value: JSONValue }\nconst MAX_SCAN = 1000;\nconst PAGE_SIZE = 50;\n\n/** Privileged, subscribable table browser. Reads any full-named table via cursor paginate + filters. */\nexport const browseTableModule: RegisteredFunction = query(async (ctx, args: {\n table: string; cursor?: string | null; pageSize?: number; filter?: FilterCond[];\n}) => {\n const b = (ctx as unknown as { db: { query(t: string, i: string): { where(op: ComparisonOp, f: string, v: Value): unknown; paginate(o: { cursor?: string | null; pageSize: number; maxScan: number }): Promise<{ page: unknown[]; nextCursor: string | null; hasMore: boolean; scanCapped: boolean }> } } })\n .db.query(args.table, \"by_creation\");\n for (const c of args.filter ?? []) (b as { where(op: ComparisonOp, f: string, v: Value): unknown }).where(c.op, c.field, c.value as Value);\n const res = await (b as { paginate(o: { cursor?: string | null; pageSize: number; maxScan: number }): Promise<{ page: unknown[]; nextCursor: string | null; hasMore: boolean; scanCapped: boolean }> })\n .paginate({ cursor: args.cursor ?? null, pageSize: args.pageSize ?? PAGE_SIZE, maxScan: MAX_SCAN });\n return {\n documents: (res.page as Value[]).map((d) => convexToJson(d)),\n nextCursor: res.nextCursor, hasMore: res.hasMore, scanCapped: res.scanCapped,\n } as JSONValue;\n});\n","import { mutation, type RegisteredFunction } from \"@helipod/executor\";\nimport { DocumentNotFoundError } from \"@helipod/errors\";\n\n/** Built-in privileged mutations the admin API invokes by id. Registered under `_system:*`. */\nexport function systemModules(): Record<string, RegisteredFunction> {\n return {\n \"_system:patchDocument\": mutation(async (ctx, args: { id: string; fields: Record<string, unknown> }) => {\n const existing = await ctx.db.get(args.id);\n if (!existing) throw new DocumentNotFoundError(`cannot edit missing document ${args.id}`);\n // Whole-document replace: the dashboard editor submits the full (user-field) document, so a\n // field removed in the editor is actually removed. _id/_creationTime are preserved by the kernel.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await ctx.db.replace(args.id, args.fields as any);\n return await ctx.db.get(args.id);\n }),\n \"_system:deleteDocument\": mutation(async (ctx, args: { id: string }) => {\n await ctx.db.delete(args.id);\n return null;\n }),\n \"_system:insertDocument\": mutation(async (ctx, args: { table: string; fields: Record<string, unknown> }) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const id = await ctx.db.insert(args.table, args.fields as any);\n return await ctx.db.get(id);\n }),\n };\n}\n","// packages/admin/src/router.ts\nimport type { JSONValue } from \"@helipod/values\";\nimport { verifyAdminKey } from \"./auth\";\nimport type { AdminApi } from \"./admin-api\";\n\nexport interface AdminRequest {\n method: string;\n path: string;\n query: Record<string, string>;\n body?: string;\n authorization?: string;\n}\nexport interface AdminResponse {\n status: number;\n body: JSONValue;\n}\n\nfunction bearer(authorization?: string): string | undefined {\n if (!authorization) return undefined;\n const m = /^Bearer (.+)$/.exec(authorization);\n return m ? m[1] : undefined;\n}\n\nexport async function handleAdminRequest(api: AdminApi, adminKey: string, req: AdminRequest): Promise<AdminResponse> {\n if (!verifyAdminKey(adminKey, bearer(req.authorization))) {\n return { status: 401, body: { error: \"unauthorized\" } };\n }\n const parts = req.path.split(\"/\").filter(Boolean); // [\"_admin\", ...]\n const seg = parts.slice(1); // drop \"_admin\"\n\n try {\n if (req.method === \"GET\" && seg.length === 1 && seg[0] === \"tables\") {\n return { status: 200, body: (await api.listTables()) as unknown as JSONValue };\n }\n if (req.method === \"GET\" && seg.length === 3 && seg[0] === \"tables\" && seg[2] === \"data\") {\n const cursor = req.query.cursor ?? null;\n const pageSize = req.query.pageSize ? Number(req.query.pageSize) : undefined;\n const data = await api.getTableData(decodeURIComponent(seg[1]!), { cursor, pageSize });\n return { status: 200, body: data as unknown as JSONValue };\n }\n if (req.method === \"GET\" && seg.length === 1 && seg[0] === \"functions\") {\n return { status: 200, body: api.listFunctions() as unknown as JSONValue };\n }\n if (req.method === \"POST\" && seg.length === 1 && seg[0] === \"run\") {\n const { path, args } = JSON.parse(req.body ?? \"{}\") as { path?: string; args?: JSONValue };\n if (!path) return { status: 400, body: { error: \"missing path\" } };\n return { status: 200, body: (await api.runFunction(path, args ?? {})) as unknown as JSONValue };\n }\n if (req.method === \"GET\" && seg.length === 1 && seg[0] === \"logs\") {\n const since = req.query.since ? Number(req.query.since) : undefined;\n return { status: 200, body: api.queryLogs({ since }) as unknown as JSONValue };\n }\n if (req.method === \"PATCH\" && seg.length === 4 && seg[0] === \"tables\" && seg[2] === \"docs\") {\n const fields = JSON.parse(req.body ?? \"{}\") as Record<string, JSONValue>;\n return { status: 200, body: await api.patchDocument(seg[3]!, fields) };\n }\n if (req.method === \"DELETE\" && seg.length === 4 && seg[0] === \"tables\" && seg[2] === \"docs\") {\n await api.deleteDocument(seg[3]!);\n return { status: 200, body: { ok: true } };\n }\n if (req.method === \"POST\" && seg.length === 3 && seg[0] === \"tables\" && seg[2] === \"docs\") {\n const fields = JSON.parse(req.body ?? \"{}\") as Record<string, JSONValue>;\n return { status: 200, body: await api.createDocument(decodeURIComponent(seg[1]!), fields) };\n }\n // Data migration (Slice 5) — export/import the app's full materialized state. Works on both the\n // container `serve` path and the Cloudflare DO host (both route `/_admin/*` through here). The DO's\n // store is writable only from inside the DO, and this runs inside it.\n if (req.method === \"GET\" && seg.length === 1 && seg[0] === \"export\") {\n return { status: 200, body: (await api.exportDump()) as unknown as JSONValue };\n }\n if (req.method === \"POST\" && seg.length === 1 && seg[0] === \"import\") {\n return { status: 200, body: (await api.importDump(req.body ?? \"\")) as unknown as JSONValue };\n }\n return { status: 404, body: { error: \"not found\" } };\n } catch (e) {\n return { status: 400, body: { error: e instanceof Error ? e.message : String(e) } };\n }\n}\n"],"mappings":"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","names":["convexToJson"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@helipod/admin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"test": "vitest run"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@helipod/docstore": "0.1.0",
|
|
20
|
+
"@helipod/errors": "0.1.0",
|
|
21
|
+
"@helipod/executor": "0.1.0",
|
|
22
|
+
"@helipod/id-codec": "0.1.0",
|
|
23
|
+
"@helipod/query-engine": "0.1.0",
|
|
24
|
+
"@helipod/runtime-embedded": "0.1.0",
|
|
25
|
+
"@helipod/values": "0.1.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@helipod/component": "0.1.0",
|
|
29
|
+
"@helipod/docstore-sqlite": "0.1.0",
|
|
30
|
+
"@helipod/query-engine": "0.1.0",
|
|
31
|
+
"@types/node": "^22.10.5",
|
|
32
|
+
"tsup": "^8.3.5",
|
|
33
|
+
"typescript": "^5.7.2",
|
|
34
|
+
"vitest": "^2.1.8"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
],
|
|
39
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/helipod-sh/helipod.git",
|
|
46
|
+
"directory": "packages/admin"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/helipod-sh/helipod"
|
|
49
|
+
}
|