@rebasepro/server 0.12.0 → 0.12.1-canary.gdfba2a1
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/GCSStorageController-BEmDYFKc.js +216 -0
- package/dist/GCSStorageController-BEmDYFKc.js.map +1 -0
- package/dist/S3StorageController-CK7r5yZb.js +243 -0
- package/dist/S3StorageController-CK7r5yZb.js.map +1 -0
- package/dist/admin_block-BGQFSAuV.js +123 -0
- package/dist/admin_block-BGQFSAuV.js.map +1 -0
- package/dist/auth-Bb7XFCKN.js +8898 -0
- package/dist/auth-Bb7XFCKN.js.map +1 -0
- package/dist/backend-CIxN4FVm.js +15 -0
- package/dist/backend-CIxN4FVm.js.map +1 -0
- package/dist/backup-C3Bah7XZ.js +163 -0
- package/dist/backup-C3Bah7XZ.js.map +1 -0
- package/dist/boot/boot.d.ts +20 -0
- package/dist/boot/fetch-bundle.d.ts +43 -0
- package/dist/contract-routes-Dj8i5AiM.js +264 -0
- package/dist/contract-routes-Dj8i5AiM.js.map +1 -0
- package/dist/cron/cron-scheduler.d.ts +34 -0
- package/dist/cron-loader-B1S2MCSl.js +63 -0
- package/dist/cron-loader-B1S2MCSl.js.map +1 -0
- package/dist/cron-routes-Do325hDt.js +62 -0
- package/dist/cron-routes-Do325hDt.js.map +1 -0
- package/dist/cron-scheduler-B3RFt0HS.js +647 -0
- package/dist/cron-scheduler-B3RFt0HS.js.map +1 -0
- package/dist/cron-store-BywZsyfZ.js +164 -0
- package/dist/cron-store-BywZsyfZ.js.map +1 -0
- package/dist/dynamic-import-Dvh-K5fl.js.map +1 -1
- package/dist/errors-BYAQztMf.js +222 -0
- package/dist/errors-BYAQztMf.js.map +1 -0
- package/dist/function-loader-B_1fYfUY.js +86 -0
- package/dist/function-loader-B_1fYfUY.js.map +1 -0
- package/dist/function-routes-C0cLIy3N.js +28 -0
- package/dist/function-routes-C0cLIy3N.js.map +1 -0
- package/dist/index.es.js +7480 -17861
- package/dist/index.es.js.map +1 -1
- package/dist/{jwt-D-eI6TTu.js → jwt-DD6EtpGj.js} +32 -13
- package/dist/{jwt-D-eI6TTu.js.map → jwt-DD6EtpGj.js.map} +1 -1
- package/dist/logger-BYU66ENZ.js.map +1 -1
- package/dist/logs-routes-BYA72C_C.js +100 -0
- package/dist/logs-routes-BYA72C_C.js.map +1 -0
- package/dist/{openapi-generator-Bjzmb5cn.js → openapi-generator-DFDS7kVk.js} +9 -3
- package/dist/openapi-generator-DFDS7kVk.js.map +1 -0
- package/dist/{schema-editor-routes-DDxfOIid.js → schema-editor-routes-CZVW2iBr.js} +4 -3
- package/dist/{schema-editor-routes-DDxfOIid.js.map → schema-editor-routes-CZVW2iBr.js.map} +1 -1
- package/dist/{src-CoOAMnBh.js → src-DymRyxdb.js} +196 -65
- package/dist/src-DymRyxdb.js.map +1 -0
- package/dist/src-_qQ3RNCK.js +279 -0
- package/dist/src-_qQ3RNCK.js.map +1 -0
- package/dist/types-DSnOC4mF.js +38 -0
- package/dist/types-DSnOC4mF.js.map +1 -0
- package/package.json +10 -10
- package/dist/openapi-generator-Bjzmb5cn.js.map +0 -1
- package/dist/src-CoOAMnBh.js.map +0 -1
- package/dist/src-Ivjud8jD.js +0 -957
- package/dist/src-Ivjud8jD.js.map +0 -1
- /package/dist/{chunk-DSJWtz9O.js → rolldown-runtime-DSJWtz9O.js} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger-BYU66ENZ.js","names":[],"sources":["../src/utils/logger.ts"],"sourcesContent":["/**\n * Structured Logger for Rebase Backend\n *\n * Outputs JSON lines when `NODE_ENV=production`, human-readable prefixed\n * lines otherwise. Designed to work with Google Cloud Logging severity levels.\n *\n * Usage:\n * import { logger } from \"./utils/logger\";\n * logger.info(\"Server started\", { port: 3001 });\n * logger.error(\"Request failed\", { path: \"/api/test\", error: err });\n */\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\n/** Google Cloud Logging severity strings. */\nconst GCP_SEVERITY: Record<LogLevel, string> = {\n debug: \"DEBUG\",\n info: \"INFO\",\n warn: \"WARNING\",\n error: \"ERROR\"\n};\n\nconst LOG_PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3\n};\n\nexport interface LogEntry {\n severity: string;\n message: string;\n timestamp: string;\n [key: string]: unknown;\n}\n\nexport interface Logger {\n debug(message: string, data?: Record<string, unknown>): void;\n info(message: string, data?: Record<string, unknown>): void;\n warn(message: string, data?: Record<string, unknown>): void;\n error(message: string, data?: Record<string, unknown>): void;\n child(defaultFields: Record<string, unknown>): Logger;\n}\n\nfunction isProduction(): boolean {\n return process.env.NODE_ENV === \"production\";\n}\n\nfunction getMinLevel(): LogLevel {\n const env = (process.env.LOG_LEVEL || \"info\").toLowerCase();\n if (env in LOG_PRIORITY) return env as LogLevel;\n return \"info\";\n}\n\n/**\n * Serialise an Error into a plain object (stack + message).\n * Handles non-Error values gracefully.\n */\nfunction serialiseError(value: unknown): Record<string, unknown> {\n if (value instanceof Error) {\n return {\n name: value.name,\n message: value.message,\n stack: value.stack\n };\n }\n return { value: String(value) };\n}\n\nfunction formatData(data?: Record<string, unknown>): Record<string, unknown> | undefined {\n if (!data) return undefined;\n const out: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(data)) {\n if (val instanceof Error) {\n out[key] = serialiseError(val);\n } else {\n out[key] = val;\n }\n }\n return out;\n}\n\nfunction createLogger(defaultFields: Record<string, unknown> = {}): Logger {\n const minLevel = getMinLevel();\n\n function emit(level: LogLevel, message: string, data?: Record<string, unknown>): void {\n if (LOG_PRIORITY[level] < LOG_PRIORITY[minLevel]) return;\n\n const merged = { ...defaultFields,\n...formatData(data) };\n\n if (isProduction()) {\n // Structured JSON for Cloud Logging\n const entry: LogEntry = {\n severity: GCP_SEVERITY[level],\n message,\n timestamp: new Date().toISOString(),\n ...merged\n };\n const line = JSON.stringify(entry);\n\n if (level === \"error\") {\n process.stderr.write(line + \"\\n\");\n } else {\n process.stdout.write(line + \"\\n\");\n }\n } else {\n // Human-readable for development\n const prefix = level === \"error\" ? \"❌\"\n : level === \"warn\" ? \"⚠️\"\n : level === \"info\" ? \"ℹ️\"\n : \"🐛\";\n const extra = Object.keys(merged).length > 0 ? ` ${JSON.stringify(merged)}` : \"\";\n const out = `${prefix} [${level.toUpperCase()}] ${message}${extra}`;\n\n if (level === \"error\") {\n console.error(out);\n } else if (level === \"warn\") {\n console.warn(out);\n } else {\n console.log(out);\n }\n }\n }\n\n return {\n debug: (msg, data) => emit(\"debug\", msg, data),\n info: (msg, data) => emit(\"info\", msg, data),\n warn: (msg, data) => emit(\"warn\", msg, data),\n error: (msg, data) => emit(\"error\", msg, data),\n child(fields: Record<string, unknown>): Logger {\n return createLogger({ ...defaultFields,\n...fields });\n }\n };\n}\n\n/**\n * Singleton logger instance.\n * In production: emits JSON lines with `severity`, `message`, `timestamp`.\n * In development: emits human-readable prefixed lines.\n */\nexport const logger: Logger = createLogger();\n"],"mappings":";;;;;AAeA,IAAM,eAAyC;CAC3C,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;AACX;AAEA,IAAM,eAAyC;CAC3C,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;AACX;AAiBA,SAAS,eAAwB;CAC7B,OAAA,QAAA,IAAA,aAAgC;AACpC;AAEA,SAAS,cAAwB;CAC7B,MAAM,OAAO,QAAQ,IAAI,aAAa,
|
|
1
|
+
{"version":3,"file":"logger-BYU66ENZ.js","names":[],"sources":["../src/utils/logger.ts"],"sourcesContent":["/**\n * Structured Logger for Rebase Backend\n *\n * Outputs JSON lines when `NODE_ENV=production`, human-readable prefixed\n * lines otherwise. Designed to work with Google Cloud Logging severity levels.\n *\n * Usage:\n * import { logger } from \"./utils/logger\";\n * logger.info(\"Server started\", { port: 3001 });\n * logger.error(\"Request failed\", { path: \"/api/test\", error: err });\n */\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\n/** Google Cloud Logging severity strings. */\nconst GCP_SEVERITY: Record<LogLevel, string> = {\n debug: \"DEBUG\",\n info: \"INFO\",\n warn: \"WARNING\",\n error: \"ERROR\"\n};\n\nconst LOG_PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3\n};\n\nexport interface LogEntry {\n severity: string;\n message: string;\n timestamp: string;\n [key: string]: unknown;\n}\n\nexport interface Logger {\n debug(message: string, data?: Record<string, unknown>): void;\n info(message: string, data?: Record<string, unknown>): void;\n warn(message: string, data?: Record<string, unknown>): void;\n error(message: string, data?: Record<string, unknown>): void;\n child(defaultFields: Record<string, unknown>): Logger;\n}\n\nfunction isProduction(): boolean {\n return process.env.NODE_ENV === \"production\";\n}\n\nfunction getMinLevel(): LogLevel {\n const env = (process.env.LOG_LEVEL || \"info\").toLowerCase();\n if (env in LOG_PRIORITY) return env as LogLevel;\n return \"info\";\n}\n\n/**\n * Serialise an Error into a plain object (stack + message).\n * Handles non-Error values gracefully.\n */\nfunction serialiseError(value: unknown): Record<string, unknown> {\n if (value instanceof Error) {\n return {\n name: value.name,\n message: value.message,\n stack: value.stack\n };\n }\n return { value: String(value) };\n}\n\nfunction formatData(data?: Record<string, unknown>): Record<string, unknown> | undefined {\n if (!data) return undefined;\n const out: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(data)) {\n if (val instanceof Error) {\n out[key] = serialiseError(val);\n } else {\n out[key] = val;\n }\n }\n return out;\n}\n\nfunction createLogger(defaultFields: Record<string, unknown> = {}): Logger {\n const minLevel = getMinLevel();\n\n function emit(level: LogLevel, message: string, data?: Record<string, unknown>): void {\n if (LOG_PRIORITY[level] < LOG_PRIORITY[minLevel]) return;\n\n const merged = { ...defaultFields,\n...formatData(data) };\n\n if (isProduction()) {\n // Structured JSON for Cloud Logging\n const entry: LogEntry = {\n severity: GCP_SEVERITY[level],\n message,\n timestamp: new Date().toISOString(),\n ...merged\n };\n const line = JSON.stringify(entry);\n\n if (level === \"error\") {\n process.stderr.write(line + \"\\n\");\n } else {\n process.stdout.write(line + \"\\n\");\n }\n } else {\n // Human-readable for development\n const prefix = level === \"error\" ? \"❌\"\n : level === \"warn\" ? \"⚠️\"\n : level === \"info\" ? \"ℹ️\"\n : \"🐛\";\n const extra = Object.keys(merged).length > 0 ? ` ${JSON.stringify(merged)}` : \"\";\n const out = `${prefix} [${level.toUpperCase()}] ${message}${extra}`;\n\n if (level === \"error\") {\n console.error(out);\n } else if (level === \"warn\") {\n console.warn(out);\n } else {\n console.log(out);\n }\n }\n }\n\n return {\n debug: (msg, data) => emit(\"debug\", msg, data),\n info: (msg, data) => emit(\"info\", msg, data),\n warn: (msg, data) => emit(\"warn\", msg, data),\n error: (msg, data) => emit(\"error\", msg, data),\n child(fields: Record<string, unknown>): Logger {\n return createLogger({ ...defaultFields,\n...fields });\n }\n };\n}\n\n/**\n * Singleton logger instance.\n * In production: emits JSON lines with `severity`, `message`, `timestamp`.\n * In development: emits human-readable prefixed lines.\n */\nexport const logger: Logger = createLogger();\n"],"mappings":";;;;;AAeA,IAAM,eAAyC;CAC3C,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;AACX;AAEA,IAAM,eAAyC;CAC3C,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;AACX;AAiBA,SAAS,eAAwB;CAC7B,OAAA,QAAA,IAAA,aAAgC;AACpC;AAEA,SAAS,cAAwB;CAC7B,MAAM,OAAO,QAAQ,IAAI,aAAa,OAAA,CAAQ,YAAY;CAC1D,IAAI,OAAO,cAAc,OAAO;CAChC,OAAO;AACX;;;;;AAMA,SAAS,eAAe,OAAyC;CAC7D,IAAI,iBAAiB,OACjB,OAAO;EACH,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,OAAO,MAAM;CACjB;CAEJ,OAAO,EAAE,OAAO,OAAO,KAAK,EAAE;AAClC;AAEA,SAAS,WAAW,MAAqE;CACrF,IAAI,CAAC,MAAM,OAAO,KAAA;CAClB,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,IAAI,GACxC,IAAI,eAAe,OACf,IAAI,OAAO,eAAe,GAAG;MAE7B,IAAI,OAAO;CAGnB,OAAO;AACX;AAEA,SAAS,aAAa,gBAAyC,CAAC,GAAW;CACvE,MAAM,WAAW,YAAY;CAE7B,SAAS,KAAK,OAAiB,SAAiB,MAAsC;EAClF,IAAI,aAAa,SAAS,aAAa,WAAW;EAElD,MAAM,SAAS;GAAE,GAAG;GAC5B,GAAG,WAAW,IAAI;EAAE;EAEZ,IAAI,aAAa,GAAG;GAEhB,MAAM,QAAkB;IACpB,UAAU,aAAa;IACvB;IACA,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;IAClC,GAAG;GACP;GACA,MAAM,OAAO,KAAK,UAAU,KAAK;GAEjC,IAAI,UAAU,SACV,QAAQ,OAAO,MAAM,OAAO,IAAI;QAEhC,QAAQ,OAAO,MAAM,OAAO,IAAI;EAExC,OAAO;GAEH,MAAM,SAAS,UAAU,UAAU,MAC7B,UAAU,SAAS,OACnB,UAAU,SAAS,OACnB;GACN,MAAM,QAAQ,OAAO,KAAK,MAAM,CAAC,CAAC,SAAS,IAAI,IAAI,KAAK,UAAU,MAAM,MAAM;GAC9E,MAAM,MAAM,GAAG,OAAO,IAAI,MAAM,YAAY,EAAE,IAAI,UAAU;GAE5D,IAAI,UAAU,SACV,QAAQ,MAAM,GAAG;QACd,IAAI,UAAU,QACjB,QAAQ,KAAK,GAAG;QAEhB,QAAQ,IAAI,GAAG;EAEvB;CACJ;CAEA,OAAO;EACH,QAAQ,KAAK,SAAS,KAAK,SAAS,KAAK,IAAI;EAC7C,OAAO,KAAK,SAAS,KAAK,QAAQ,KAAK,IAAI;EAC3C,OAAO,KAAK,SAAS,KAAK,QAAQ,KAAK,IAAI;EAC3C,QAAQ,KAAK,SAAS,KAAK,SAAS,KAAK,IAAI;EAC7C,MAAM,QAAyC;GAC3C,OAAO,aAAa;IAAE,GAAG;IACrC,GAAG;GAAO,CAAC;EACH;CACJ;AACJ;;;;;;AAOA,IAAa,SAAiB,aAAa"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
import "process";
|
|
3
|
+
__createRequire(import.meta.url);
|
|
4
|
+
import { n as __exportAll } from "./rolldown-runtime-DSJWtz9O.js";
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
//#region src/api/logs-routes.ts
|
|
7
|
+
var logs_routes_exports = /* @__PURE__ */ __exportAll({
|
|
8
|
+
addLog: () => addLog,
|
|
9
|
+
default: () => app,
|
|
10
|
+
logBuffer: () => logBuffer,
|
|
11
|
+
logMiddleware: () => logMiddleware
|
|
12
|
+
});
|
|
13
|
+
var LogRingBuffer = class {
|
|
14
|
+
buffer = [];
|
|
15
|
+
maxSize;
|
|
16
|
+
idCounter = 0;
|
|
17
|
+
constructor(maxSize = 1e4) {
|
|
18
|
+
this.maxSize = maxSize;
|
|
19
|
+
}
|
|
20
|
+
push(entry) {
|
|
21
|
+
const id = `log_${++this.idCounter}`;
|
|
22
|
+
this.buffer.push({
|
|
23
|
+
...entry,
|
|
24
|
+
id
|
|
25
|
+
});
|
|
26
|
+
if (this.buffer.length > this.maxSize) this.buffer.shift();
|
|
27
|
+
}
|
|
28
|
+
query(options) {
|
|
29
|
+
let filtered = this.buffer;
|
|
30
|
+
if (options.level) filtered = filtered.filter((e) => e.level === options.level);
|
|
31
|
+
if (options.source) filtered = filtered.filter((e) => e.source === options.source);
|
|
32
|
+
if (options.search) {
|
|
33
|
+
const searchLower = options.search.toLowerCase();
|
|
34
|
+
filtered = filtered.filter((e) => e.message.toLowerCase().includes(searchLower));
|
|
35
|
+
}
|
|
36
|
+
if (options.since) {
|
|
37
|
+
const sinceValue = options.since;
|
|
38
|
+
filtered = filtered.filter((e) => e.timestamp >= sinceValue);
|
|
39
|
+
}
|
|
40
|
+
const sorted = [...filtered].reverse();
|
|
41
|
+
const total = sorted.length;
|
|
42
|
+
const limit = options.limit || 100;
|
|
43
|
+
const offset = options.offset || 0;
|
|
44
|
+
return {
|
|
45
|
+
entries: sorted.slice(offset, offset + limit),
|
|
46
|
+
total
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
getLatest(count = 50) {
|
|
50
|
+
return this.buffer.slice(-count).reverse();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var logBuffer = new LogRingBuffer();
|
|
54
|
+
/** Add a log entry */
|
|
55
|
+
function addLog(level, source, message, metadata) {
|
|
56
|
+
logBuffer.push({
|
|
57
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
58
|
+
level,
|
|
59
|
+
source,
|
|
60
|
+
message,
|
|
61
|
+
metadata
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** Hono middleware to log API requests */
|
|
65
|
+
function logMiddleware() {
|
|
66
|
+
return async (c, next) => {
|
|
67
|
+
const start = Date.now();
|
|
68
|
+
await next();
|
|
69
|
+
const duration = Date.now() - start;
|
|
70
|
+
const reqId = c.get("requestId");
|
|
71
|
+
addLog("info", "api", `${c.req.method} ${c.req.path} ${c.res.status} ${duration}ms`, {
|
|
72
|
+
method: c.req.method,
|
|
73
|
+
path: c.req.path,
|
|
74
|
+
status: c.res.status,
|
|
75
|
+
duration,
|
|
76
|
+
...reqId && { requestId: reqId }
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
var app = new Hono();
|
|
81
|
+
app.get("/", (c) => {
|
|
82
|
+
const query = c.req.query();
|
|
83
|
+
const result = logBuffer.query({
|
|
84
|
+
level: query.level,
|
|
85
|
+
source: query.source,
|
|
86
|
+
search: query.search,
|
|
87
|
+
limit: query.limit ? parseInt(query.limit) : void 0,
|
|
88
|
+
offset: query.offset ? parseInt(query.offset) : void 0,
|
|
89
|
+
since: query.since
|
|
90
|
+
});
|
|
91
|
+
return c.json(result);
|
|
92
|
+
});
|
|
93
|
+
app.get("/latest", (c) => {
|
|
94
|
+
const count = parseInt(c.req.query("count") || "50");
|
|
95
|
+
return c.json({ entries: logBuffer.getLatest(count) });
|
|
96
|
+
});
|
|
97
|
+
//#endregion
|
|
98
|
+
export { logs_routes_exports as n, logMiddleware as t };
|
|
99
|
+
|
|
100
|
+
//# sourceMappingURL=logs-routes-BYA72C_C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logs-routes-BYA72C_C.js","names":[],"sources":["../src/api/logs-routes.ts"],"sourcesContent":["import { Hono } from \"hono\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { HonoEnv } from \"./types\";\n\nexport interface LogEntry {\n id: string;\n timestamp: string;\n level: \"debug\" | \"info\" | \"warn\" | \"error\";\n source: \"api\" | \"auth\" | \"storage\" | \"realtime\" | \"system\";\n message: string;\n metadata?: Record<string, unknown>;\n}\n\nclass LogRingBuffer {\n private buffer: LogEntry[] = [];\n private maxSize: number;\n private idCounter = 0;\n\n constructor(maxSize = 10000) {\n this.maxSize = maxSize;\n }\n\n push(entry: Omit<LogEntry, \"id\">): void {\n const id = `log_${++this.idCounter}`;\n this.buffer.push({ ...entry,\nid });\n if (this.buffer.length > this.maxSize) {\n this.buffer.shift();\n }\n }\n\n query(options: {\n level?: string;\n source?: string;\n search?: string;\n limit?: number;\n offset?: number;\n since?: string;\n }): { entries: LogEntry[]; total: number } {\n let filtered = this.buffer;\n\n if (options.level) {\n filtered = filtered.filter(e => e.level === options.level);\n }\n if (options.source) {\n filtered = filtered.filter(e => e.source === options.source);\n }\n if (options.search) {\n const searchLower = options.search.toLowerCase();\n filtered = filtered.filter(e => e.message.toLowerCase().includes(searchLower));\n }\n if (options.since) {\n const sinceValue = options.since;\n filtered = filtered.filter(e => e.timestamp >= sinceValue);\n }\n\n // Newest first\n const sorted = [...filtered].reverse();\n const total = sorted.length;\n const limit = options.limit || 100;\n const offset = options.offset || 0;\n\n return {\n entries: sorted.slice(offset, offset + limit),\n total\n };\n }\n\n getLatest(count = 50): LogEntry[] {\n return this.buffer.slice(-count).reverse();\n }\n}\n\n// Global singleton\nexport const logBuffer = new LogRingBuffer();\n\n/** Add a log entry */\nexport function addLog(\n level: LogEntry[\"level\"],\n source: LogEntry[\"source\"],\n message: string,\n metadata?: Record<string, unknown>\n): void {\n logBuffer.push({\n timestamp: new Date().toISOString(),\n level,\n source,\n message,\n metadata\n });\n}\n\n/** Hono middleware to log API requests */\nexport function logMiddleware(): MiddlewareHandler<HonoEnv> {\n return async (c, next) => {\n const start = Date.now();\n await next();\n const duration = Date.now() - start;\n const reqId = c.get(\"requestId\");\n addLog(\"info\", \"api\", `${c.req.method} ${c.req.path} ${c.res.status} ${duration}ms`, {\n method: c.req.method,\n path: c.req.path,\n status: c.res.status,\n duration,\n ...(reqId && { requestId: reqId })\n });\n };\n}\n\nconst app = new Hono<HonoEnv>();\n\n// GET /api/logs — Query logs\napp.get(\"/\", (c) => {\n const query = c.req.query();\n const result = logBuffer.query({\n level: query.level,\n source: query.source,\n search: query.search,\n limit: query.limit ? parseInt(query.limit) : undefined,\n offset: query.offset ? parseInt(query.offset) : undefined,\n since: query.since\n });\n return c.json(result);\n});\n\n// GET /api/logs/latest — Get latest logs (for real-time)\napp.get(\"/latest\", (c) => {\n const count = parseInt(c.req.query(\"count\") || \"50\");\n return c.json({ entries: logBuffer.getLatest(count) });\n});\n\nexport default app;\n"],"mappings":";;;;;;;;;;;;AAaA,IAAM,gBAAN,MAAoB;CAChB,SAA6B,CAAC;CAC9B;CACA,YAAoB;CAEpB,YAAY,UAAU,KAAO;EACzB,KAAK,UAAU;CACnB;CAEA,KAAK,OAAmC;EACpC,MAAM,KAAK,OAAO,EAAE,KAAK;EACzB,KAAK,OAAO,KAAK;GAAE,GAAG;GAC9B;EAAG,CAAC;EACI,IAAI,KAAK,OAAO,SAAS,KAAK,SAC1B,KAAK,OAAO,MAAM;CAE1B;CAEA,MAAM,SAOqC;EACvC,IAAI,WAAW,KAAK;EAEpB,IAAI,QAAQ,OACR,WAAW,SAAS,QAAO,MAAK,EAAE,UAAU,QAAQ,KAAK;EAE7D,IAAI,QAAQ,QACR,WAAW,SAAS,QAAO,MAAK,EAAE,WAAW,QAAQ,MAAM;EAE/D,IAAI,QAAQ,QAAQ;GAChB,MAAM,cAAc,QAAQ,OAAO,YAAY;GAC/C,WAAW,SAAS,QAAO,MAAK,EAAE,QAAQ,YAAY,CAAC,CAAC,SAAS,WAAW,CAAC;EACjF;EACA,IAAI,QAAQ,OAAO;GACf,MAAM,aAAa,QAAQ;GAC3B,WAAW,SAAS,QAAO,MAAK,EAAE,aAAa,UAAU;EAC7D;EAGA,MAAM,SAAS,CAAC,GAAG,QAAQ,CAAC,CAAC,QAAQ;EACrC,MAAM,QAAQ,OAAO;EACrB,MAAM,QAAQ,QAAQ,SAAS;EAC/B,MAAM,SAAS,QAAQ,UAAU;EAEjC,OAAO;GACH,SAAS,OAAO,MAAM,QAAQ,SAAS,KAAK;GAC5C;EACJ;CACJ;CAEA,UAAU,QAAQ,IAAgB;EAC9B,OAAO,KAAK,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ;CAC7C;AACJ;AAGA,IAAa,YAAY,IAAI,cAAc;;AAG3C,SAAgB,OACZ,OACA,QACA,SACA,UACI;CACJ,UAAU,KAAK;EACX,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EAClC;EACA;EACA;EACA;CACJ,CAAC;AACL;;AAGA,SAAgB,gBAA4C;CACxD,OAAO,OAAO,GAAG,SAAS;EACtB,MAAM,QAAQ,KAAK,IAAI;EACvB,MAAM,KAAK;EACX,MAAM,WAAW,KAAK,IAAI,IAAI;EAC9B,MAAM,QAAQ,EAAE,IAAI,WAAW;EAC/B,OAAO,QAAQ,OAAO,GAAG,EAAE,IAAI,OAAO,GAAG,EAAE,IAAI,KAAK,GAAG,EAAE,IAAI,OAAO,GAAG,SAAS,KAAK;GACjF,QAAQ,EAAE,IAAI;GACd,MAAM,EAAE,IAAI;GACZ,QAAQ,EAAE,IAAI;GACd;GACA,GAAI,SAAS,EAAE,WAAW,MAAM;EACpC,CAAC;CACL;AACJ;AAEA,IAAM,MAAM,IAAI,KAAc;AAG9B,IAAI,IAAI,MAAM,MAAM;CAChB,MAAM,QAAQ,EAAE,IAAI,MAAM;CAC1B,MAAM,SAAS,UAAU,MAAM;EAC3B,OAAO,MAAM;EACb,QAAQ,MAAM;EACd,QAAQ,MAAM;EACd,OAAO,MAAM,QAAQ,SAAS,MAAM,KAAK,IAAI,KAAA;EAC7C,QAAQ,MAAM,SAAS,SAAS,MAAM,MAAM,IAAI,KAAA;EAChD,OAAO,MAAM;CACjB,CAAC;CACD,OAAO,EAAE,KAAK,MAAM;AACxB,CAAC;AAGD,IAAI,IAAI,YAAY,MAAM;CACtB,MAAM,QAAQ,SAAS,EAAE,IAAI,MAAM,OAAO,KAAK,IAAI;CACnD,OAAO,EAAE,KAAK,EAAE,SAAS,UAAU,UAAU,KAAK,EAAE,CAAC;AACzD,CAAC"}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
import "process";
|
|
3
3
|
__createRequire(import.meta.url);
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
4
|
+
import { p as resolveCollectionRelations } from "./src-DymRyxdb.js";
|
|
5
|
+
import "./src-_qQ3RNCK.js";
|
|
6
|
+
//#region ../types/src/types/relations.ts
|
|
7
|
+
/** @group Models */
|
|
8
|
+
function isToMany(relation) {
|
|
9
|
+
return relation.cardinality === "many";
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
6
12
|
//#region src/api/openapi-generator.ts
|
|
7
13
|
function generateOpenApiSpec(collections, options = {}) {
|
|
8
14
|
const basePath = options.basePath ?? "/api";
|
|
@@ -588,4 +594,4 @@ function toPascalCase(str) {
|
|
|
588
594
|
//#endregion
|
|
589
595
|
export { generateOpenApiSpec };
|
|
590
596
|
|
|
591
|
-
//# sourceMappingURL=openapi-generator-
|
|
597
|
+
//# sourceMappingURL=openapi-generator-DFDS7kVk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi-generator-DFDS7kVk.js","names":[],"sources":["../../types/src/types/relations.ts","../src/api/openapi-generator.ts"],"sourcesContent":["import type { AnyCollectionConfig } from \"./collections\";\n\n/**\n * @group Models\n */\nexport type OnAction = \"cascade\" | \"restrict\" | \"no action\" | \"set null\" | \"set default\";\n\n/**\n * What kind of link a relation is.\n *\n * The discriminant. Every other field a relation carries belongs to exactly one\n * of these, which is the point: a relation used to be a single open interface\n * where `cardinality`, `direction`, `localKey`, `foreignKeyOnTarget`, `through`\n * and `joinPath` were all optional and any combination typechecked. Which link\n * you meant then had to be *inferred* from which fields you happened to set,\n * and the inference was ~200 lines that guessed, fell back on naming\n * conventions, and swallowed its own failures.\n *\n * Most of that guessing produced bugs rather than convenience. A `many`\n * relation carrying a `localKey` — a combination the old type permitted — made\n * the write path stamp the parent's own foreign key onto the child row. Under\n * these kinds that state cannot be written down.\n *\n * @group Models\n */\nexport type RelationKind = \"belongsTo\" | \"hasOne\" | \"hasMany\" | \"manyToMany\" | \"via\";\n\n/** Fields every relation carries, whatever its kind. @group Models */\nexport interface RelationBase {\n /**\n * The name this link is addressed by: the key in `include`, the tab in the\n * admin panel, and the path segment of a nested URL.\n *\n * Defaults to the declaring property's key, or to the target's slug for an\n * entry in `relations`.\n */\n relationName?: string;\n\n /** The collection on the other end. */\n target: () => AnyCollectionConfig;\n\n onUpdate?: OnAction;\n onDelete?: OnAction;\n\n /** Presentation overrides applied when this relation is rendered as a tab. */\n overrides?: Partial<AnyCollectionConfig>;\n\n validation?: {\n required?: boolean;\n };\n}\n\n/**\n * This collection holds the foreign key. One target row per source row.\n *\n * ```ts\n * author: { kind: \"belongsTo\", target: () => authors, localKey: \"author_id\" }\n * ```\n * @group Models\n */\nexport interface BelongsToRelation extends RelationBase {\n kind: \"belongsTo\";\n /**\n * Column on **this** collection's table holding the target's key.\n * Defaults to `<relationName>_id`.\n */\n localKey?: string;\n}\n\n/**\n * The target holds the foreign key, and at most one target row points back.\n *\n * ```ts\n * profile: { kind: \"hasOne\", target: () => profiles, foreignKeyOnTarget: \"user_id\" }\n * ```\n * @group Models\n */\nexport interface HasOneRelation extends RelationBase {\n kind: \"hasOne\";\n /**\n * Column on the **target's** table holding this collection's key.\n * Defaults to `<thisCollection>_id`.\n */\n foreignKeyOnTarget?: string;\n}\n\n/**\n * The target holds the foreign key, and many target rows point back. The\n * children belong to this parent alone — deleting one deletes a row.\n *\n * ```ts\n * posts: { kind: \"hasMany\", target: () => posts, foreignKeyOnTarget: \"author_id\" }\n * ```\n * @group Models\n */\nexport interface HasManyRelation extends RelationBase {\n kind: \"hasMany\";\n /**\n * Column on the **target's** table holding this collection's key.\n * Defaults to `<thisCollection>_id`.\n */\n foreignKeyOnTarget?: string;\n}\n\n/**\n * Both sides hold many, through a junction table. The target rows are shared,\n * so this collection owns the *link* and not the row: removing one removes a\n * junction row and leaves the target alone.\n *\n * Declared the same way from either side — there is no owning and inverse\n * version. Swap `sourceColumn` and `targetColumn` to describe the other\n * direction.\n *\n * ```ts\n * tags: { kind: \"manyToMany\", target: () => tags }\n * ```\n * @group Models\n */\nexport interface ManyToManyRelation extends RelationBase {\n kind: \"manyToMany\";\n /**\n * The junction table and its two key columns. Every part defaults: the\n * table to both table names sorted and joined, the columns to\n * `<collection>_id` and `<relationName>_id`.\n */\n through?: {\n table?: string;\n /** Junction column holding **this** collection's key. */\n sourceColumn?: string;\n /** Junction column holding the **target's** key. */\n targetColumn?: string;\n };\n}\n\n/**\n * An explicit chain of joins, for links the four shapes above cannot express:\n * multi-hop paths, composite keys, or a join whose condition is not a plain\n * foreign key.\n *\n * Read-only. Rebase will not infer how to write through an arbitrary join\n * chain, and guessing is what this type exists to stop.\n *\n * ```ts\n * permissions: {\n * kind: \"via\",\n * target: () => permissions,\n * cardinality: \"many\",\n * joinPath: [\n * { table: \"user_roles\", on: { from: \"id\", to: \"user_id\" } },\n * { table: \"role_permissions\", on: { from: \"role_id\", to: \"role_id\" } },\n * { table: \"permissions\", on: { from: \"permission_id\", to: \"id\" } }\n * ]\n * }\n * ```\n * @group Models\n */\nexport interface ViaRelation extends RelationBase {\n kind: \"via\";\n /** Whether the chain yields one row or many. Cannot be derived from a join chain. */\n cardinality: \"one\" | \"many\";\n joinPath: JoinStep[];\n}\n\n/**\n * A link from one collection to another, as authored.\n *\n * A closed union: pick the kind that describes the link and the type offers\n * exactly the fields that kind needs. See {@link ResolvedRelation} for the form\n * the runtime works with, which has every default filled in.\n *\n * @group Models\n */\nexport type Relation =\n | BelongsToRelation\n | HasOneRelation\n | HasManyRelation\n | ManyToManyRelation\n | ViaRelation;\n\n/**\n * A relation with every default filled in — the form the runtime works with.\n *\n * The authored {@link Relation} and this are deliberately different types.\n * They used to be one, which meant no reader could tell which fields had been\n * supplied and which had been guessed, and so every consumer re-derived what it\n * needed with its own chain of `if (through) … else if (localKey) …` fallbacks.\n * Those chains disagreed with each other; that disagreement is what produced\n * silently wrong reads and corrupt writes.\n *\n * Here each variant carries exactly its own fields, all required. A consumer\n * switches on `kind` and gets what it needs without a fallback, and the cases\n * it forgot are a compile error rather than a wrong answer at runtime.\n *\n * @group Models\n */\nexport type ResolvedRelation =\n | ResolvedBelongsTo\n | ResolvedHasOne\n | ResolvedHasMany\n | ResolvedManyToMany\n | ResolvedVia;\n\n/** Fields present on every resolved relation. @group Models */\nexport interface ResolvedRelationBase {\n /** Always set: defaulted during resolution if the author omitted it. */\n relationName: string;\n target: () => AnyCollectionConfig;\n /** The target's slug, resolved once so consumers need not call `target()`. */\n targetSlug: string;\n onUpdate?: OnAction;\n onDelete?: OnAction;\n overrides?: Partial<AnyCollectionConfig>;\n validation?: { required?: boolean };\n /**\n * Whether one row or many come back. Derived from `kind` — kept because it\n * is what most consumers actually branch on, and because `via` is the one\n * kind where it is authored rather than implied.\n */\n cardinality: \"one\" | \"many\";\n /**\n * Whether Rebase knows how to write through this link. False only for\n * {@link ResolvedVia}, whose join chain it will not invent a write for.\n */\n writable: boolean;\n /**\n * Whether the target rows are shared with other parents. True for\n * many-to-many and for multi-hop `via`: what the parent owns is the link,\n * so removing one must not delete the row.\n */\n shared: boolean;\n}\n\n/** @group Models */\nexport interface ResolvedBelongsTo extends ResolvedRelationBase {\n kind: \"belongsTo\";\n cardinality: \"one\";\n writable: true;\n shared: false;\n /** Column on this collection's table. */\n localKey: string;\n}\n\n/** @group Models */\nexport interface ResolvedHasOne extends ResolvedRelationBase {\n kind: \"hasOne\";\n cardinality: \"one\";\n writable: true;\n shared: false;\n /** Column on the target's table. */\n foreignKeyOnTarget: string;\n}\n\n/** @group Models */\nexport interface ResolvedHasMany extends ResolvedRelationBase {\n kind: \"hasMany\";\n cardinality: \"many\";\n writable: true;\n shared: false;\n /** Column on the target's table. */\n foreignKeyOnTarget: string;\n}\n\n/** @group Models */\nexport interface ResolvedManyToMany extends ResolvedRelationBase {\n kind: \"manyToMany\";\n cardinality: \"many\";\n writable: true;\n shared: true;\n through: {\n table: string;\n sourceColumn: string;\n targetColumn: string;\n };\n}\n\n/** @group Models */\nexport interface ResolvedVia extends ResolvedRelationBase {\n kind: \"via\";\n writable: false;\n joinPath: JoinStep[];\n}\n\n// ── Narrowing helpers ────────────────────────────────────────────────\n//\n// Consumers that only care about one axis — \"does this list many rows\",\n// \"is there a column on the target\" — should ask that question rather\n// than enumerate kinds, so adding a kind later does not silently skip them.\n\n/** Relations whose target row carries this collection's key. @group Models */\nexport type ResolvedForeignKeyOnTarget = ResolvedHasOne | ResolvedHasMany;\n\n/** @group Models */\nexport function hasForeignKeyOnTarget(relation: ResolvedRelation): relation is ResolvedForeignKeyOnTarget {\n return relation.kind === \"hasOne\" || relation.kind === \"hasMany\";\n}\n\n/** @group Models */\nexport function isManyToMany(relation: ResolvedRelation): relation is ResolvedManyToMany {\n return relation.kind === \"manyToMany\";\n}\n\n/** @group Models */\nexport function isToMany(relation: ResolvedRelation): boolean {\n return relation.cardinality === \"many\";\n}\n\n/**\n * Defines a single, explicit step in a multi-join path.\n *\n * Each step represents one JOIN operation in the sequence. The `from` columns\n * refer to the previous table in the chain (or the source table for the first step),\n * and the `to` columns refer to the current table being joined.\n *\n * @example Single column join:\n * ```typescript\n * {\n * table: \"authors\",\n * on: {\n * from: \"author_id\", // Column from previous table (e.g., posts.author_id)\n * to: \"id\" // Column from current table (authors.id)\n * }\n * }\n * ```\n *\n * @example Multi-column composite key join:\n * ```typescript\n * {\n * table: \"order_items\",\n * on: {\n * from: [\"order_id\", \"store_id\"], // Multiple columns from previous table\n * to: [\"order_id\", \"store_id\"] // Corresponding columns in current table\n * }\n * }\n * ```\n */\nexport interface JoinStep {\n /**\n * The database table name to join TO in this step.\n * This is the table you're joining into, not the table you're joining from.\n *\n * @example \"authors\", \"user_roles\", \"product_categories\"\n */\n table: string;\n\n /**\n * The join condition for this step. Defines how the previous table\n * connects to the current table.\n *\n * - `from`: Column name(s) on the PREVIOUS table in the join chain\n * - `to`: Column name(s) on the CURRENT table (specified in `table`)\n *\n * For the first step, `from` refers to the source collection's table.\n * For subsequent steps, `from` refers to the table from the previous step.\n *\n * Both `from` and `to` support:\n * - Single column: `\"user_id\"`\n * - Multiple columns: `[\"company_id\", \"region_id\"]` for composite keys\n *\n * When using arrays, both `from` and `to` must have the same length,\n * and columns are matched by position (index 0 with index 0, etc.).\n */\n on: {\n from: string | string[];\n to: string | string[];\n };\n}\n","import { CollectionConfig, Property, StringProperty, NumberProperty, ArrayProperty, MapProperty, isToMany, VectorProperty } from \"@rebasepro/types\";\nimport { resolveCollectionRelations } from \"@rebasepro/common\";\n\n/**\n * OpenAPI 3.0.3 specification generator.\n *\n * Produces a spec that exactly mirrors the REST API consumed by the\n * Rebase SDK client (`@rebasepro/client`).\n *\n * Routes are mounted at `{basePath}/data/{slug}` by `initializeRebaseBackend`.\n */\n\nexport interface OpenApiGeneratorOptions {\n /** Base path for the API (e.g. \"/api\"). Defaults to \"/api\". */\n basePath?: string;\n /** Whether auth is enabled on data routes. Defaults to true. */\n requireAuth?: boolean;\n}\n\nexport function generateOpenApiSpec(\n collections: CollectionConfig[],\n options: OpenApiGeneratorOptions = {}\n): Record<string, unknown> {\n const basePath = options.basePath ?? \"/api\";\n const requireAuth = options.requireAuth ?? true;\n\n const spec: Record<string, unknown> = {\n openapi: \"3.0.3\",\n info: {\n title: \"Rebase API\",\n version: \"1.0.0\",\n description:\n \"Auto-generated REST API from Rebase collection definitions. \" +\n \"This is the same API consumed by the `@rebasepro/client` SDK.\"\n },\n servers: [\n {\n url: basePath,\n description: \"API Server\"\n }\n ],\n paths: {} as Record<string, unknown>,\n components: {\n schemas: {\n ErrorResponse: {\n type: \"object\",\n properties: {\n error: {\n type: \"object\",\n required: [\"message\", \"code\"],\n properties: {\n message: { type: \"string\" },\n code: { type: \"string\" },\n details: {}\n }\n }\n }\n },\n PaginationMeta: {\n type: \"object\",\n properties: {\n total: { type: \"integer\",\ndescription: \"Total number of matching records\" },\n limit: { type: \"integer\",\ndescription: \"Page size used for this query\" },\n offset: { type: \"integer\",\ndescription: \"Number of records skipped\" },\n hasMore: { type: \"boolean\",\ndescription: \"Whether more records exist beyond this page\" }\n }\n }\n } as Record<string, unknown>,\n securitySchemes: {} as Record<string, unknown>\n },\n tags: [] as Array<{ name: string; description?: string }>\n };\n\n // ── Security Schemes ─────────────────────────────────────────────────\n if (requireAuth) {\n (spec.components as Record<string, unknown>).securitySchemes = {\n bearerAuth: {\n type: \"http\",\n scheme: \"bearer\",\n bearerFormat: \"JWT\",\n description:\n \"JWT access token obtained from `POST /auth/login` or `POST /auth/register`. \" +\n \"Can also be a static service key for server-to-server authentication.\"\n },\n queryToken: {\n type: \"apiKey\",\n in: \"query\",\n name: \"token\",\n description: \"Alternative: pass the JWT or service key as a `token` query parameter.\"\n }\n };\n (spec as Record<string, unknown>).security = [\n { bearerAuth: [] },\n { queryToken: [] }\n ];\n }\n\n const paths = spec.paths as Record<string, unknown>;\n const schemas = (spec.components as Record<string, unknown>).schemas as Record<string, unknown>;\n const tags = spec.tags as Array<{ name: string; description?: string }>;\n\n // ── Collection routes ────────────────────────────────────────────────\n for (const collection of (collections || [])) {\n const schemaName = toPascalCase(collection.singularName || collection.name);\n const slug = collection.slug;\n\n tags.push({\n name: collection.name,\n description: collection.description || `CRUD operations for ${collection.name}`\n });\n\n // Build component schema for this collection\n schemas[schemaName] = buildCollectionSchema(collection);\n\n // Build an \"input\" schema (no read-only/auto fields like autoValue dates)\n schemas[`${schemaName}Input`] = buildCollectionInputSchema(collection);\n\n const dataPath = `/data/${slug}`;\n\n // ── GET /data/{slug} — List entities ──────────────────────────\n paths[dataPath] = {\n get: {\n tags: [collection.name],\n summary: `List ${collection.name}`,\n operationId: `list${schemaName}`,\n parameters: [\n { name: \"limit\",\nin: \"query\",\nschema: { type: \"integer\",\ndefault: 20,\nmaximum: 100 },\ndescription: \"Maximum number of records to return\" },\n { name: \"offset\",\nin: \"query\",\nschema: { type: \"integer\",\ndefault: 0 },\ndescription: \"Number of records to skip\" },\n { name: \"page\",\nin: \"query\",\nschema: { type: \"integer\",\nminimum: 1 },\ndescription: \"Page number (alternative to offset). Calculates offset as (page-1)*limit\" },\n {\n name: \"orderBy\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"Sort field and direction. Accepts `field:asc` or `field:desc`, or a JSON array `[{\\\"field\\\":\\\"name\\\",\\\"direction\\\":\\\"asc\\\"}]`\",\n example: \"created_at:desc\"\n },\n {\n name: \"where\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"JSON object filter, mapping each field to a `[operator, value]` tuple. \"\n + \"Combines with the per-field `?field=op.value` parameters below; on the same field, the per-field parameter wins.\",\n example: \"{\\\"status\\\":[\\\"==\\\",\\\"active\\\"]}\"\n },\n {\n name: \"include\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"Comma-separated list of relations to include (eager-load). Use `*` for all relations.\",\n example: \"author,tags\"\n },\n {\n name: \"fields\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"Comma-separated list of fields to return (field selection)\",\n example: \"id,name,created_at\"\n },\n {\n name: \"searchString\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"Full-text search query\"\n },\n ...buildFilterParameters(collection)\n ],\n responses: {\n 200: {\n description: \"Paginated list of entities\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: {\n type: \"array\",\n items: { $ref: `#/components/schemas/${schemaName}` }\n },\n meta: { $ref: \"#/components/schemas/PaginationMeta\" }\n }\n }\n }\n }\n },\n ...errorResponses(requireAuth)\n }\n },\n post: {\n tags: [collection.name],\n summary: `Create ${collection.singularName || collection.name}`,\n operationId: `create${schemaName}`,\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}Input` }\n }\n }\n },\n responses: {\n 201: {\n description: \"Created entity\",\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}` }\n }\n }\n },\n ...errorResponses(requireAuth)\n }\n }\n };\n\n // ── GET/PUT/DELETE /data/{slug}/{id} ──────────────────────────\n const entityPath = `/data/${slug}/{id}`;\n paths[entityPath] = {\n get: {\n tags: [collection.name],\n summary: `Get ${collection.singularName || collection.name} by ID`,\n operationId: `get${schemaName}ById`,\n parameters: [\n { name: \"id\",\nin: \"path\",\nrequired: true,\nschema: { type: \"string\" },\ndescription: \"Entity ID\" },\n {\n name: \"include\",\n in: \"query\",\n schema: { type: \"string\" },\n description: \"Comma-separated list of relations to include\",\n example: \"author,tags\"\n }\n ],\n responses: {\n 200: {\n description: \"Entity found\",\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}` }\n }\n }\n },\n 404: { description: \"Entity not found\",\ncontent: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } } },\n ...errorResponses(requireAuth)\n }\n },\n put: {\n tags: [collection.name],\n summary: `Update ${collection.singularName || collection.name}`,\n operationId: `update${schemaName}`,\n parameters: [\n { name: \"id\",\nin: \"path\",\nrequired: true,\nschema: { type: \"string\" },\ndescription: \"Entity ID\" }\n ],\n requestBody: {\n required: true,\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}Input` }\n }\n }\n },\n responses: {\n 200: {\n description: \"Updated entity\",\n content: {\n \"application/json\": {\n schema: { $ref: `#/components/schemas/${schemaName}` }\n }\n }\n },\n 404: { description: \"Entity not found\",\ncontent: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } } },\n ...errorResponses(requireAuth)\n }\n },\n delete: {\n tags: [collection.name],\n summary: `Delete ${collection.singularName || collection.name}`,\n operationId: `delete${schemaName}`,\n parameters: [\n { name: \"id\",\nin: \"path\",\nrequired: true,\nschema: { type: \"string\" },\ndescription: \"Entity ID\" }\n ],\n responses: {\n 204: { description: \"Deleted successfully\" },\n 404: { description: \"Entity not found\",\ncontent: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } } },\n ...errorResponses(requireAuth)\n }\n }\n };\n\n }\n\n // ── Subcollection routes ─────────────────────────────────────────────\n //\n // A second pass, after every collection's component schema exists. These\n // routes `$ref` the *target's* schema, and the first pass builds schemas in\n // array order — so doing this inline meant a subcollection whose target\n // appeared later in the list silently degraded to an untyped `object`.\n //\n // The names come from the *resolved* relations, not from the authored\n // `relations` array. `relationName` is optional at the authoring surface —\n // it defaults to the property key, or to the target's slug — so reading the\n // raw field skipped every relation that relied on the default, and missed\n // relations declared inline on a property entirely, since those are not in\n // the array. These are the same resolved names the nested-path router\n // matches, so the spec and the routes cannot drift apart.\n //\n // A to-one relation is left out. `posts/1/author` resolves, but it\n // addresses a single row, and documenting it as a paginated list would\n // describe a response shape the client never gets.\n for (const collection of (collections || [])) {\n const slug = collection.slug;\n const schemaName = toPascalCase(collection.singularName || collection.name);\n const relations = Object.values(resolveCollectionRelations(collection))\n .filter(isToMany);\n for (const relation of relations) {\n const relationName = relation.relationName;\n const targetCollection = relation.target();\n const targetName = targetCollection.singularName || targetCollection.name;\n const targetSchema = toPascalCase(targetName);\n\n const subPath = `/data/${slug}/{parentId}/${relationName}`;\n\n // Only add if the schema exists (target collection is also registered)\n paths[subPath] = {\n get: {\n tags: [collection.name],\n summary: `List ${relationName} for ${withIndefiniteArticle(collection.singularName || collection.name)}`,\n operationId: `list${schemaName}${toPascalCase(relationName)}`,\n parameters: [\n { name: \"parentId\",\nin: \"path\",\nrequired: true,\nschema: { type: \"string\" },\ndescription: `${collection.singularName || collection.name} ID` },\n { name: \"limit\",\nin: \"query\",\nschema: { type: \"integer\",\ndefault: 20 } },\n { name: \"offset\",\nin: \"query\",\nschema: { type: \"integer\",\ndefault: 0 } },\n { name: \"orderBy\",\nin: \"query\",\nschema: { type: \"string\" } },\n { name: \"searchString\",\nin: \"query\",\nschema: { type: \"string\" } }\n ],\n responses: {\n 200: {\n description: `List of related ${relationName}`,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n data: {\n type: \"array\",\n items: schemas[targetSchema]\n ? { $ref: `#/components/schemas/${targetSchema}` }\n : { type: \"object\" }\n },\n meta: { $ref: \"#/components/schemas/PaginationMeta\" }\n }\n }\n }\n }\n },\n ...errorResponses(requireAuth)\n }\n }\n };\n }\n }\n\n return spec;\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────\n\n/**\n * Build the component schema for a collection (output / read shape).\n * All fields are included (including relation foreign keys).\n */\nfunction buildCollectionSchema(collection: CollectionConfig): Record<string, unknown> {\n const properties: Record<string, unknown> = {\n id: { type: \"string\",\ndescription: \"Unique identifier\" }\n };\n const required: string[] = [\"id\"];\n\n for (const [key, property] of Object.entries(collection.properties)) {\n // Skip relation properties — they are virtual and not part of the REST payload\n if (property.type === \"relation\") continue;\n\n properties[key] = convertPropertyToSchema(property);\n\n if (property.validation?.required) {\n required.push(key);\n }\n }\n\n return {\n type: \"object\",\n required: required.length > 0 ? required : undefined,\n properties\n };\n}\n\n/**\n * Build an input schema (for POST/PUT) — excludes auto-generated fields.\n */\nfunction buildCollectionInputSchema(collection: CollectionConfig): Record<string, unknown> {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const [key, property] of Object.entries(collection.properties)) {\n if (property.type === \"relation\") continue;\n\n // Skip auto-value date fields from the input schema\n if (property.type === \"date\" && property.autoValue) continue;\n\n // Skip auto-generated ID fields\n if (\"isId\" in property && property.isId && property.isId !== \"manual\" && property.isId !== true) continue;\n\n properties[key] = convertPropertyToSchema(property);\n\n if (property.validation?.required) {\n required.push(key);\n }\n }\n\n // Allow explicit ID for create (optional)\n properties[\"id\"] = {\n type: \"string\",\n description: \"Optional: client-assigned ID. If omitted, the server generates one.\"\n };\n\n return {\n type: \"object\",\n required: required.length > 0 ? required : undefined,\n properties\n };\n}\n\n/**\n * Convert a Rebase Property to an OpenAPI 3.0 schema object.\n */\nfunction convertPropertyToSchema(property: Property): Record<string, unknown> {\n const base: Record<string, unknown> = {};\n\n if (property.name) {\n base.description = property.name;\n }\n\n switch (property.type) {\n case \"string\": {\n const sp = property as StringProperty;\n base.type = \"string\";\n\n if (sp.enum) {\n const enumValues = resolveEnumValues(sp.enum);\n if (enumValues.length > 0) {\n base.enum = enumValues;\n }\n }\n\n if (sp.validation) {\n if (sp.validation.min !== undefined) base.minLength = sp.validation.min;\n if (sp.validation.max !== undefined) base.maxLength = sp.validation.max;\n if (sp.validation.length !== undefined) {\n base.minLength = sp.validation.length;\n base.maxLength = sp.validation.length;\n }\n if (sp.validation.matches !== undefined) {\n base.pattern = String(sp.validation.matches);\n }\n }\n\n if (sp.email) base.format = \"email\";\n if (sp.url) base.format = \"uri\";\n if (sp.storage) base.format = \"uri\";\n\n return base;\n }\n\n case \"number\": {\n const np = property as NumberProperty;\n const isInteger = np.validation?.integer || np.columnType === \"integer\" || np.columnType === \"serial\" || np.columnType === \"bigserial\" || np.columnType === \"bigint\";\n base.type = isInteger ? \"integer\" : \"number\";\n\n if (np.enum) {\n const enumValues = resolveEnumValues(np.enum);\n if (enumValues.length > 0) {\n base.enum = enumValues;\n }\n }\n\n if (np.validation) {\n if (np.validation.min !== undefined) base.minimum = np.validation.min;\n if (np.validation.max !== undefined) base.maximum = np.validation.max;\n if (np.validation.moreThan !== undefined) {\n base.minimum = np.validation.moreThan;\n base.exclusiveMinimum = true;\n }\n if (np.validation.lessThan !== undefined) {\n base.maximum = np.validation.lessThan;\n base.exclusiveMaximum = true;\n }\n }\n\n return base;\n }\n\n case \"boolean\":\n base.type = \"boolean\";\n return base;\n\n case \"date\": {\n base.type = \"string\";\n if (property.mode === \"date\") {\n base.format = \"date\";\n } else {\n base.format = \"date-time\";\n }\n if (property.autoValue) {\n base.readOnly = true;\n base.description = (base.description || \"\") +\n (property.autoValue === \"on_create\" ? \" (Auto-set on creation)\" : \" (Auto-updated)\");\n }\n return base;\n }\n\n case \"geopoint\":\n base.type = \"object\";\n base.properties = {\n latitude: { type: \"number\" },\n longitude: { type: \"number\" }\n };\n base.required = [\"latitude\", \"longitude\"];\n return base;\n\n case \"reference\":\n base.type = \"string\";\n base.description = (base.description || \"\") + \" (Reference ID)\";\n return base;\n\n case \"array\": {\n const ap = property as ArrayProperty;\n base.type = \"array\";\n\n if (ap.oneOf) {\n // Discriminated union (e.g., content blocks)\n const typeField = ap.oneOf.typeField || \"type\";\n const valueField = ap.oneOf.valueField || \"value\";\n const variants: Record<string, unknown>[] = [];\n\n for (const [variantKey, variantProp] of Object.entries(ap.oneOf.properties)) {\n variants.push({\n type: \"object\",\n properties: {\n [typeField]: { type: \"string\",\nenum: [variantKey] },\n [valueField]: convertPropertyToSchema(variantProp)\n },\n required: [typeField, valueField]\n });\n }\n\n base.items = { oneOf: variants };\n } else if (ap.of) {\n if (Array.isArray(ap.of)) {\n base.items = { oneOf: ap.of.map(p => convertPropertyToSchema(p)) };\n } else {\n base.items = convertPropertyToSchema(ap.of);\n }\n } else {\n base.items = {};\n }\n\n if (ap.validation) {\n if (ap.validation.min !== undefined) base.minItems = ap.validation.min;\n if (ap.validation.max !== undefined) base.maxItems = ap.validation.max;\n }\n\n return base;\n }\n\n case \"map\": {\n const mp = property as MapProperty;\n base.type = \"object\";\n\n if (mp.properties) {\n const props: Record<string, unknown> = {};\n const req: string[] = [];\n\n for (const [key, subProp] of Object.entries(mp.properties)) {\n props[key] = convertPropertyToSchema(subProp);\n if (subProp.validation?.required) {\n req.push(key);\n }\n }\n\n base.properties = props;\n if (req.length > 0) base.required = req;\n } else if (mp.keyValue) {\n base.additionalProperties = true;\n }\n\n return base;\n }\n\n case \"vector\": {\n const vp = property as VectorProperty;\n base.type = \"array\";\n base.items = { type: \"number\" };\n base.description = (base.description || \"\") + ` (Vector(${vp.dimensions}))`;\n return base;\n }\n case \"binary\": {\n base.type = \"string\";\n base.description = (base.description || \"\") + \" (Binary/Base64)\";\n return base;\n }\n default:\n base.type = \"string\";\n return base;\n }\n}\n\n/**\n * Resolve EnumValues (array or record) into a flat array of enum values.\n */\nfunction resolveEnumValues(enumDef: Record<string | number, unknown> | Array<{ id: string | number }>): Array<string | number> {\n if (Array.isArray(enumDef)) {\n return enumDef.map(e => (typeof e === \"object\" && e !== null && \"id\" in e) ? e.id : e as string | number);\n }\n return Object.keys(enumDef).map(k => {\n // Preserve numeric keys as numbers\n const num = Number(k);\n return isNaN(num) ? k : num;\n });\n}\n\n/**\n * Build PostgREST-style filter parameters for a collection.\n * These are additional query parameters like `?status=eq.active&price=gte.100`.\n */\nfunction buildFilterParameters(collection: CollectionConfig): Array<Record<string, unknown>> {\n const params: Array<Record<string, unknown>> = [];\n\n for (const [key, property] of Object.entries(collection.properties)) {\n if (property.type === \"relation\" || property.type === \"map\" || property.type === \"array\" || property.type === \"geopoint\") {\n continue;\n }\n\n params.push({\n name: key,\n in: \"query\",\n required: false,\n schema: { type: \"string\" },\n description:\n `Filter by \\`${key}\\`. Supports PostgREST operators: ` +\n \"`eq.value`, `neq.value`, `gt.value`, `gte.value`, `lt.value`, `lte.value`, \" +\n \"`in.(a,b,c)`, `nin.(a,b,c)`, `cs.value` (array-contains), `csa.(a,b)` (array-contains-any). \" +\n \"Plain values imply equality.\",\n example: property.type === \"string\" ? \"eq.active\" : property.type === \"number\" ? \"gte.100\" : undefined\n });\n }\n\n return params;\n}\n\n/**\n * Standard error responses included on every endpoint.\n */\nfunction errorResponses(requireAuth: boolean): Record<string, unknown> {\n const responses: Record<string, unknown> = {\n 400: {\n description: \"Bad request\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n },\n 500: {\n description: \"Internal server error\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n }\n };\n\n if (requireAuth) {\n responses[401] = {\n description: \"Authentication required or invalid token\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n };\n responses[403] = {\n description: \"Insufficient permissions\",\n content: { \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorResponse\" } } }\n };\n }\n\n return responses;\n}\n\n/**\n * Prefix a noun with \"a\" or \"an\" based on its leading sound.\n */\nfunction withIndefiniteArticle(noun: string): string {\n return `${/^[aeiou]/i.test(noun) ? \"an\" : \"a\"} ${noun}`;\n}\n\n/**\n * Convert a string to PascalCase for schema names.\n */\nfunction toPascalCase(str: string): string {\n return str\n .replace(/[^a-zA-Z0-9]+/g, \" \")\n .split(\" \")\n .filter(Boolean)\n .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\"\");\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
import "process";
|
|
3
3
|
__createRequire(import.meta.url);
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
4
|
+
import "./src-_qQ3RNCK.js";
|
|
5
|
+
import { t as ADMIN_COLLECTION_KEYS } from "./admin_block-BGQFSAuV.js";
|
|
6
|
+
import { n as errorHandler } from "./errors-BYAQztMf.js";
|
|
6
7
|
import * as fs$1 from "fs";
|
|
7
8
|
import * as path$1 from "path";
|
|
8
9
|
import { Hono } from "hono";
|
|
@@ -244,4 +245,4 @@ function createSchemaEditorRoutes(collectionsDir) {
|
|
|
244
245
|
//#endregion
|
|
245
246
|
export { createSchemaEditorRoutes };
|
|
246
247
|
|
|
247
|
-
//# sourceMappingURL=schema-editor-routes-
|
|
248
|
+
//# sourceMappingURL=schema-editor-routes-CZVW2iBr.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-editor-routes-DDxfOIid.js","names":[],"sources":["../src/api/ast-schema-editor.ts","../src/api/schema-editor-routes.ts"],"sourcesContent":["import { Project, SyntaxKind, ObjectLiteralExpression, ObjectLiteralElementLike, PropertyAssignment, VariableDeclaration, IndentationText } from \"ts-morph\";\nimport { ADMIN_COLLECTION_KEYS } from \"@rebasepro/types\";\nimport * as path from \"path\";\nimport * as fs from \"fs\";\n\n/**\n * Move presentation keys into the `admin` block.\n *\n * `ADMIN_COLLECTION_KEYS` comes from `@rebasepro/types` rather than being spelled\n * out here, so adding a field to `AdminCollectionOptions` cannot leave this writer\n * behind. Any such key already inside `admin` wins over a top-level copy: the\n * nested one is what the file said, and a flat duplicate is the view model's\n * flattening leaking back.\n */\nexport function nestAdminKeys(collectionData: Record<string, unknown>): Record<string, unknown> {\n const adminKeys = new Set<string>(ADMIN_COLLECTION_KEYS as readonly string[]);\n const existingBlock = (collectionData.admin ?? {}) as Record<string, unknown>;\n\n const top: Record<string, unknown> = {};\n const block: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(collectionData)) {\n if (key === \"admin\") continue;\n if (adminKeys.has(key)) block[key] = value;\n else top[key] = value;\n }\n\n const merged = { ...block, ...existingBlock };\n if (Object.keys(merged).length > 0) top.admin = merged;\n return top;\n}\n\nexport class AstSchemaEditor {\n private project: Project;\n private collectionsDir: string;\n\n constructor(collectionsDir: string) {\n this.project = new Project({\n manipulationSettings: {\n indentationText: IndentationText.FourSpaces\n }\n });\n if (fs.existsSync(collectionsDir)) {\n this.project.addSourceFilesAtPaths(`${collectionsDir}/**/*.ts`);\n }\n this.collectionsDir = path.resolve(collectionsDir);\n }\n\n /**\n * Sanitize collectionId to prevent path traversal attacks.\n * Only allows alphanumeric characters, underscores, and hyphens.\n */\n private sanitizeCollectionId(collectionId: string): string {\n const sanitized = collectionId.replace(/[^a-zA-Z0-9_-]/g, \"\");\n if (!sanitized || sanitized !== collectionId) {\n throw new Error(`Invalid collection ID: \"${collectionId}\". Only alphanumeric characters, underscores, and hyphens are allowed.`);\n }\n return sanitized;\n }\n\n /**\n * Resolve a file path and ensure it falls within the collectionsDir.\n */\n private safePath(filename: string): string {\n const resolved = path.resolve(this.collectionsDir, filename);\n if (!resolved.startsWith(this.collectionsDir + path.sep) && resolved !== this.collectionsDir) {\n throw new Error(\"Path traversal detected: resolved path is outside the collections directory.\");\n }\n return resolved;\n }\n\n private getCollectionFile(collectionId: string) {\n const safeId = this.sanitizeCollectionId(collectionId);\n const filePath = this.safePath(`${safeId}.ts`);\n let file = this.project.getSourceFile(filePath);\n if (!file && fs.existsSync(filePath)) {\n this.project.addSourceFilesAtPaths(`${this.collectionsDir}/**/*.ts`);\n file = this.project.getSourceFile(filePath);\n }\n return file;\n }\n\n private getCollectionObject(collectionId: string): ObjectLiteralExpression | null {\n const file = this.getCollectionFile(collectionId);\n if (!file) return null;\n\n const defaultExport = file.getDefaultExportSymbol();\n if (defaultExport) {\n const declaration = defaultExport.getDeclarations()[0];\n if (declaration && declaration.getKind() === SyntaxKind.ExportAssignment) {\n const expr = declaration.asKind(SyntaxKind.ExportAssignment)?.getExpression();\n if (expr && expr.getKind() === SyntaxKind.Identifier) {\n const varName = expr.getText();\n const varDecl = file.getVariableDeclaration(varName);\n return varDecl?.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression) || null;\n }\n }\n }\n // Fallback: Just get the first exported VariableDeclaration with an ObjectLiteral\n const varDecls = file.getVariableDeclarations();\n for (const varDecl of varDecls) {\n const init = varDecl.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (init) return init;\n }\n return null;\n }\n\n private convertJsonToAstString(obj: unknown, indentLevel = 0, oldAstNode?: ObjectLiteralExpression): string {\n // Base TS-morph parses arrays as 2 levels deep from the property key:\n // PropertiesObject = level 1, PropertyConfig = level 2.\n // We calibrate the spacing multiples to keep the items flush with standard TS format.\n const indentStr = \" \";\n const indent = indentStr.repeat(indentLevel);\n const innerIndent = indentStr.repeat(indentLevel + 1);\n\n if (obj === null || obj === undefined) {\n return \"undefined\";\n }\n if (typeof obj === \"string\") {\n return JSON.stringify(obj);\n }\n if (typeof obj === \"number\" || typeof obj === \"boolean\") {\n return String(obj);\n }\n if (Array.isArray(obj)) {\n if (obj.length === 0) return \"[]\";\n const items = obj.map(item => this.convertJsonToAstString(item, indentLevel + 1));\n return `[\\n${innerIndent}${items.join(`,\\n${innerIndent}`)}\\n${indent}]`;\n }\n if (typeof obj === \"object\") {\n const record = obj as Record<string, unknown>;\n const keys = Object.keys(record);\n\n // Collect preserved AST properties\n const preservedProps: string[] = [];\n if (oldAstNode) {\n const oldProps = oldAstNode.getProperties();\n for (const oldProp of oldProps) {\n if (oldProp.isKind(SyntaxKind.PropertyAssignment)) {\n const nameNode = oldProp.getNameNode();\n let name = nameNode.getText();\n if (name.startsWith('\"') && name.endsWith('\"')) name = name.slice(1, -1);\n if (name.startsWith(\"'\") && name.endsWith(\"'\")) name = name.slice(1, -1);\n\n // If the JSON object doesn't have this key, check if we should preserve it\n if (!(name in record)) {\n const init = oldProp.getInitializer();\n if (init) {\n const kind = init.getKind();\n const isCode = kind === SyntaxKind.ArrowFunction ||\n kind === SyntaxKind.FunctionExpression ||\n kind === SyntaxKind.Identifier ||\n kind === SyntaxKind.CallExpression ||\n kind === SyntaxKind.JsxElement;\n\n if (isCode || name === \"target\" || name === \"callbacks\" || name === \"permissions\" || name === \"securityRules\") {\n // Preserve this property exactly as it was\n const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);\n preservedProps.push(`${keyStr}: ${init.getText()}`);\n }\n }\n }\n }\n }\n }\n\n if (keys.length === 0 && preservedProps.length === 0) return \"{}\";\n\n const props = keys.map(key => {\n const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);\n\n // If the value is an object, pass the old AST node to recurse\n let childAstNode: ObjectLiteralExpression | undefined;\n if (oldAstNode && typeof record[key] === \"object\" && record[key] !== null && !Array.isArray(record[key])) {\n const oldProp = oldAstNode.getProperty(\n (p: ObjectLiteralElementLike) => \"getName\" in p && typeof (p as PropertyAssignment).getName === \"function\" && ((p as PropertyAssignment).getName() === key || (p as PropertyAssignment).getName() === `\"${key}\"` || (p as PropertyAssignment).getName() === `'${key}'`)\n );\n if (oldProp && oldProp.isKind(SyntaxKind.PropertyAssignment)) {\n childAstNode = oldProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n }\n\n return `${keyStr}: ${this.convertJsonToAstString(record[key], indentLevel + 1, childAstNode)}`;\n });\n\n const allProps = [...props, ...preservedProps];\n return `{\\n${innerIndent}${allProps.join(`,\\n${innerIndent}`)}\\n${indent}}`;\n }\n return \"undefined\";\n }\n\n public async saveProperty(collectionId: string, propertyKey: string, propertyConfig: Record<string, unknown>) {\n const collectionObj = this.getCollectionObject(collectionId);\n if (!collectionObj) throw new Error(`Collection ${collectionId} not found in ATS workspace.`);\n\n let propertiesProp = collectionObj.getProperty(\"properties\") as PropertyAssignment;\n if (!propertiesProp) {\n propertiesProp = collectionObj.addPropertyAssignment({\n name: \"properties\",\n initializer: \"{}\"\n });\n }\n\n const propsObj = propertiesProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (propsObj) {\n const existingProp = propsObj.getProperty(\n (p: ObjectLiteralElementLike) => \"getName\" in p && typeof (p as PropertyAssignment).getName === \"function\" && ((p as PropertyAssignment).getName() === propertyKey || (p as PropertyAssignment).getName() === `\"${propertyKey}\"`)\n );\n\n let oldPropAstNode: ObjectLiteralExpression | undefined;\n if (existingProp && existingProp.isKind(SyntaxKind.PropertyAssignment)) {\n oldPropAstNode = existingProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n\n const newInitializer = this.convertJsonToAstString(propertyConfig, 2, oldPropAstNode);\n\n if (existingProp) {\n if (existingProp.isKind(SyntaxKind.PropertyAssignment)) {\n existingProp.setInitializer(newInitializer);\n }\n } else {\n propsObj.addPropertyAssignment({\n name: /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(propertyKey) ? propertyKey : JSON.stringify(propertyKey),\n initializer: newInitializer\n });\n }\n\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n }\n\n public async deleteProperty(collectionId: string, propertyKey: string) {\n const collectionObj = this.getCollectionObject(collectionId);\n if (!collectionObj) return;\n\n const propertiesProp = collectionObj.getProperty(\"properties\") as PropertyAssignment;\n if (propertiesProp) {\n const propsObj = propertiesProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (propsObj) {\n const existingProp = propsObj.getProperty(\n (p: ObjectLiteralElementLike) => \"getName\" in p && typeof (p as PropertyAssignment).getName === \"function\" && ((p as PropertyAssignment).getName() === propertyKey || (p as PropertyAssignment).getName() === `\"${propertyKey}\"`)\n );\n if (existingProp) {\n existingProp.remove();\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n }\n }\n }\n\n public async saveCollection(collectionId: string, collectionData: Record<string, unknown>) {\n let file = this.getCollectionFile(collectionId);\n const collectionObj = this.getCollectionObject(collectionId);\n\n if (!file || !collectionObj) {\n // Create a new file\n const safeId = this.sanitizeCollectionId(collectionId);\n const newFilePath = this.safePath(`${safeId}.ts`);\n file = this.project.createSourceFile(newFilePath, `import { CollectionConfig } from \"@rebasepro/types\";\\n\\nconst ${safeId}Collection: CollectionConfig = ${this.convertJsonToAstString(nestAdminKeys(collectionData))};\\n\\nexport default ${safeId}Collection;\\n`, { overwrite: true });\n } else {\n // Update root level properties gracefully\n\n // Force delete securityRules if empty or undefined to handle Formex / serialization stripping\n if (!(\"securityRules\" in collectionData) || collectionData.securityRules === undefined || (Array.isArray(collectionData.securityRules) && collectionData.securityRules.length === 0)) {\n const srProp = collectionObj.getProperty(\"securityRules\");\n if (srProp) {\n srProp.remove();\n }\n\n // If it was in collectionData as an empty array, delete it so the loop below doesn't add it back as \"[]\"\n // Actually, if it's \"[]\", omitting it entirely from the TS file achieves the same logical effect (no RLS rules)\n // and correctly triggers \"unmapped policies\" if the DB still has them.\n delete collectionData[\"securityRules\"];\n }\n\n // The panel works with a flat view model — presentation merged onto the\n // collection — so what arrives here has `icon` and `listProperties` at\n // the top level. On disk they belong inside `admin`. Writing them flat\n // would produce a file the backend loads and ignores and the panel\n // never reads back, which looks exactly like the edit not saving.\n collectionData = nestAdminKeys(collectionData);\n\n for (const key of Object.keys(collectionData)) {\n if (key === \"relations\") continue; // Kept via other AST functions or handled separately.\n\n const prop = collectionObj.getProperty(key) as PropertyAssignment;\n\n let oldAstNode: ObjectLiteralExpression | undefined;\n if (prop && prop.isKind(SyntaxKind.PropertyAssignment)) {\n oldAstNode = prop.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n\n const newInit = this.convertJsonToAstString(collectionData[key], 1, oldAstNode);\n if (prop) {\n prop.setInitializer(newInit);\n } else {\n collectionObj.addPropertyAssignment({\n name: key,\n initializer: newInit\n });\n }\n }\n }\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n\n public async deleteCollection(collectionId: string) {\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.deleteImmediatelySync();\n }\n }\n}\n","import { Hono } from \"hono\";\nimport { AstSchemaEditor } from \"./ast-schema-editor\";\nimport { errorHandler } from \"./errors\";\nimport { HonoEnv } from \"./types\";\n\nexport function createSchemaEditorRoutes(collectionsDir: string): Hono<HonoEnv> {\n const router = new Hono<HonoEnv>();\n router.onError(errorHandler);\n const editor = new AstSchemaEditor(collectionsDir);\n\n router.post(\"/property/save\", async (c) => {\n const body = await c.req.json();\n const { collectionId, propertyKey, propertyConfig } = body;\n await editor.saveProperty(collectionId, propertyKey, propertyConfig);\n return c.json({ success: true });\n });\n\n router.post(\"/property/delete\", async (c) => {\n const body = await c.req.json();\n const { collectionId, propertyKey } = body;\n await editor.deleteProperty(collectionId, propertyKey);\n return c.json({ success: true });\n });\n\n router.post(\"/collection/save\", async (c) => {\n const body = await c.req.json();\n const { collectionId, collectionData } = body;\n await editor.saveCollection(collectionId, collectionData);\n return c.json({ success: true });\n });\n\n router.post(\"/collection/delete\", async (c) => {\n const body = await c.req.json();\n const { collectionId } = body;\n await editor.deleteCollection(collectionId);\n return c.json({ success: true });\n });\n\n return router;\n}\n\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schema-editor-routes-CZVW2iBr.js","names":[],"sources":["../src/api/ast-schema-editor.ts","../src/api/schema-editor-routes.ts"],"sourcesContent":["import { Project, SyntaxKind, ObjectLiteralExpression, ObjectLiteralElementLike, PropertyAssignment, VariableDeclaration, IndentationText } from \"ts-morph\";\nimport { ADMIN_COLLECTION_KEYS } from \"@rebasepro/types\";\nimport * as path from \"path\";\nimport * as fs from \"fs\";\n\n/**\n * Move presentation keys into the `admin` block.\n *\n * `ADMIN_COLLECTION_KEYS` comes from `@rebasepro/types` rather than being spelled\n * out here, so adding a field to `AdminCollectionOptions` cannot leave this writer\n * behind. Any such key already inside `admin` wins over a top-level copy: the\n * nested one is what the file said, and a flat duplicate is the view model's\n * flattening leaking back.\n */\nexport function nestAdminKeys(collectionData: Record<string, unknown>): Record<string, unknown> {\n const adminKeys = new Set<string>(ADMIN_COLLECTION_KEYS as readonly string[]);\n const existingBlock = (collectionData.admin ?? {}) as Record<string, unknown>;\n\n const top: Record<string, unknown> = {};\n const block: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(collectionData)) {\n if (key === \"admin\") continue;\n if (adminKeys.has(key)) block[key] = value;\n else top[key] = value;\n }\n\n const merged = { ...block, ...existingBlock };\n if (Object.keys(merged).length > 0) top.admin = merged;\n return top;\n}\n\nexport class AstSchemaEditor {\n private project: Project;\n private collectionsDir: string;\n\n constructor(collectionsDir: string) {\n this.project = new Project({\n manipulationSettings: {\n indentationText: IndentationText.FourSpaces\n }\n });\n if (fs.existsSync(collectionsDir)) {\n this.project.addSourceFilesAtPaths(`${collectionsDir}/**/*.ts`);\n }\n this.collectionsDir = path.resolve(collectionsDir);\n }\n\n /**\n * Sanitize collectionId to prevent path traversal attacks.\n * Only allows alphanumeric characters, underscores, and hyphens.\n */\n private sanitizeCollectionId(collectionId: string): string {\n const sanitized = collectionId.replace(/[^a-zA-Z0-9_-]/g, \"\");\n if (!sanitized || sanitized !== collectionId) {\n throw new Error(`Invalid collection ID: \"${collectionId}\". Only alphanumeric characters, underscores, and hyphens are allowed.`);\n }\n return sanitized;\n }\n\n /**\n * Resolve a file path and ensure it falls within the collectionsDir.\n */\n private safePath(filename: string): string {\n const resolved = path.resolve(this.collectionsDir, filename);\n if (!resolved.startsWith(this.collectionsDir + path.sep) && resolved !== this.collectionsDir) {\n throw new Error(\"Path traversal detected: resolved path is outside the collections directory.\");\n }\n return resolved;\n }\n\n private getCollectionFile(collectionId: string) {\n const safeId = this.sanitizeCollectionId(collectionId);\n const filePath = this.safePath(`${safeId}.ts`);\n let file = this.project.getSourceFile(filePath);\n if (!file && fs.existsSync(filePath)) {\n this.project.addSourceFilesAtPaths(`${this.collectionsDir}/**/*.ts`);\n file = this.project.getSourceFile(filePath);\n }\n return file;\n }\n\n private getCollectionObject(collectionId: string): ObjectLiteralExpression | null {\n const file = this.getCollectionFile(collectionId);\n if (!file) return null;\n\n const defaultExport = file.getDefaultExportSymbol();\n if (defaultExport) {\n const declaration = defaultExport.getDeclarations()[0];\n if (declaration && declaration.getKind() === SyntaxKind.ExportAssignment) {\n const expr = declaration.asKind(SyntaxKind.ExportAssignment)?.getExpression();\n if (expr && expr.getKind() === SyntaxKind.Identifier) {\n const varName = expr.getText();\n const varDecl = file.getVariableDeclaration(varName);\n return varDecl?.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression) || null;\n }\n }\n }\n // Fallback: Just get the first exported VariableDeclaration with an ObjectLiteral\n const varDecls = file.getVariableDeclarations();\n for (const varDecl of varDecls) {\n const init = varDecl.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (init) return init;\n }\n return null;\n }\n\n private convertJsonToAstString(obj: unknown, indentLevel = 0, oldAstNode?: ObjectLiteralExpression): string {\n // Base TS-morph parses arrays as 2 levels deep from the property key:\n // PropertiesObject = level 1, PropertyConfig = level 2.\n // We calibrate the spacing multiples to keep the items flush with standard TS format.\n const indentStr = \" \";\n const indent = indentStr.repeat(indentLevel);\n const innerIndent = indentStr.repeat(indentLevel + 1);\n\n if (obj === null || obj === undefined) {\n return \"undefined\";\n }\n if (typeof obj === \"string\") {\n return JSON.stringify(obj);\n }\n if (typeof obj === \"number\" || typeof obj === \"boolean\") {\n return String(obj);\n }\n if (Array.isArray(obj)) {\n if (obj.length === 0) return \"[]\";\n const items = obj.map(item => this.convertJsonToAstString(item, indentLevel + 1));\n return `[\\n${innerIndent}${items.join(`,\\n${innerIndent}`)}\\n${indent}]`;\n }\n if (typeof obj === \"object\") {\n const record = obj as Record<string, unknown>;\n const keys = Object.keys(record);\n\n // Collect preserved AST properties\n const preservedProps: string[] = [];\n if (oldAstNode) {\n const oldProps = oldAstNode.getProperties();\n for (const oldProp of oldProps) {\n if (oldProp.isKind(SyntaxKind.PropertyAssignment)) {\n const nameNode = oldProp.getNameNode();\n let name = nameNode.getText();\n if (name.startsWith('\"') && name.endsWith('\"')) name = name.slice(1, -1);\n if (name.startsWith(\"'\") && name.endsWith(\"'\")) name = name.slice(1, -1);\n\n // If the JSON object doesn't have this key, check if we should preserve it\n if (!(name in record)) {\n const init = oldProp.getInitializer();\n if (init) {\n const kind = init.getKind();\n const isCode = kind === SyntaxKind.ArrowFunction ||\n kind === SyntaxKind.FunctionExpression ||\n kind === SyntaxKind.Identifier ||\n kind === SyntaxKind.CallExpression ||\n kind === SyntaxKind.JsxElement;\n\n if (isCode || name === \"target\" || name === \"callbacks\" || name === \"permissions\" || name === \"securityRules\") {\n // Preserve this property exactly as it was\n const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);\n preservedProps.push(`${keyStr}: ${init.getText()}`);\n }\n }\n }\n }\n }\n }\n\n if (keys.length === 0 && preservedProps.length === 0) return \"{}\";\n\n const props = keys.map(key => {\n const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);\n\n // If the value is an object, pass the old AST node to recurse\n let childAstNode: ObjectLiteralExpression | undefined;\n if (oldAstNode && typeof record[key] === \"object\" && record[key] !== null && !Array.isArray(record[key])) {\n const oldProp = oldAstNode.getProperty(\n (p: ObjectLiteralElementLike) => \"getName\" in p && typeof (p as PropertyAssignment).getName === \"function\" && ((p as PropertyAssignment).getName() === key || (p as PropertyAssignment).getName() === `\"${key}\"` || (p as PropertyAssignment).getName() === `'${key}'`)\n );\n if (oldProp && oldProp.isKind(SyntaxKind.PropertyAssignment)) {\n childAstNode = oldProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n }\n\n return `${keyStr}: ${this.convertJsonToAstString(record[key], indentLevel + 1, childAstNode)}`;\n });\n\n const allProps = [...props, ...preservedProps];\n return `{\\n${innerIndent}${allProps.join(`,\\n${innerIndent}`)}\\n${indent}}`;\n }\n return \"undefined\";\n }\n\n public async saveProperty(collectionId: string, propertyKey: string, propertyConfig: Record<string, unknown>) {\n const collectionObj = this.getCollectionObject(collectionId);\n if (!collectionObj) throw new Error(`Collection ${collectionId} not found in ATS workspace.`);\n\n let propertiesProp = collectionObj.getProperty(\"properties\") as PropertyAssignment;\n if (!propertiesProp) {\n propertiesProp = collectionObj.addPropertyAssignment({\n name: \"properties\",\n initializer: \"{}\"\n });\n }\n\n const propsObj = propertiesProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (propsObj) {\n const existingProp = propsObj.getProperty(\n (p: ObjectLiteralElementLike) => \"getName\" in p && typeof (p as PropertyAssignment).getName === \"function\" && ((p as PropertyAssignment).getName() === propertyKey || (p as PropertyAssignment).getName() === `\"${propertyKey}\"`)\n );\n\n let oldPropAstNode: ObjectLiteralExpression | undefined;\n if (existingProp && existingProp.isKind(SyntaxKind.PropertyAssignment)) {\n oldPropAstNode = existingProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n\n const newInitializer = this.convertJsonToAstString(propertyConfig, 2, oldPropAstNode);\n\n if (existingProp) {\n if (existingProp.isKind(SyntaxKind.PropertyAssignment)) {\n existingProp.setInitializer(newInitializer);\n }\n } else {\n propsObj.addPropertyAssignment({\n name: /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(propertyKey) ? propertyKey : JSON.stringify(propertyKey),\n initializer: newInitializer\n });\n }\n\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n }\n\n public async deleteProperty(collectionId: string, propertyKey: string) {\n const collectionObj = this.getCollectionObject(collectionId);\n if (!collectionObj) return;\n\n const propertiesProp = collectionObj.getProperty(\"properties\") as PropertyAssignment;\n if (propertiesProp) {\n const propsObj = propertiesProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (propsObj) {\n const existingProp = propsObj.getProperty(\n (p: ObjectLiteralElementLike) => \"getName\" in p && typeof (p as PropertyAssignment).getName === \"function\" && ((p as PropertyAssignment).getName() === propertyKey || (p as PropertyAssignment).getName() === `\"${propertyKey}\"`)\n );\n if (existingProp) {\n existingProp.remove();\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n }\n }\n }\n\n public async saveCollection(collectionId: string, collectionData: Record<string, unknown>) {\n let file = this.getCollectionFile(collectionId);\n const collectionObj = this.getCollectionObject(collectionId);\n\n if (!file || !collectionObj) {\n // Create a new file\n const safeId = this.sanitizeCollectionId(collectionId);\n const newFilePath = this.safePath(`${safeId}.ts`);\n file = this.project.createSourceFile(newFilePath, `import { CollectionConfig } from \"@rebasepro/types\";\\n\\nconst ${safeId}Collection: CollectionConfig = ${this.convertJsonToAstString(nestAdminKeys(collectionData))};\\n\\nexport default ${safeId}Collection;\\n`, { overwrite: true });\n } else {\n // Update root level properties gracefully\n\n // Force delete securityRules if empty or undefined to handle Formex / serialization stripping\n if (!(\"securityRules\" in collectionData) || collectionData.securityRules === undefined || (Array.isArray(collectionData.securityRules) && collectionData.securityRules.length === 0)) {\n const srProp = collectionObj.getProperty(\"securityRules\");\n if (srProp) {\n srProp.remove();\n }\n\n // If it was in collectionData as an empty array, delete it so the loop below doesn't add it back as \"[]\"\n // Actually, if it's \"[]\", omitting it entirely from the TS file achieves the same logical effect (no RLS rules)\n // and correctly triggers \"unmapped policies\" if the DB still has them.\n delete collectionData[\"securityRules\"];\n }\n\n // The panel works with a flat view model — presentation merged onto the\n // collection — so what arrives here has `icon` and `listProperties` at\n // the top level. On disk they belong inside `admin`. Writing them flat\n // would produce a file the backend loads and ignores and the panel\n // never reads back, which looks exactly like the edit not saving.\n collectionData = nestAdminKeys(collectionData);\n\n for (const key of Object.keys(collectionData)) {\n if (key === \"relations\") continue; // Kept via other AST functions or handled separately.\n\n const prop = collectionObj.getProperty(key) as PropertyAssignment;\n\n let oldAstNode: ObjectLiteralExpression | undefined;\n if (prop && prop.isKind(SyntaxKind.PropertyAssignment)) {\n oldAstNode = prop.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n\n const newInit = this.convertJsonToAstString(collectionData[key], 1, oldAstNode);\n if (prop) {\n prop.setInitializer(newInit);\n } else {\n collectionObj.addPropertyAssignment({\n name: key,\n initializer: newInit\n });\n }\n }\n }\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n\n public async deleteCollection(collectionId: string) {\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.deleteImmediatelySync();\n }\n }\n}\n","import { Hono } from \"hono\";\nimport { AstSchemaEditor } from \"./ast-schema-editor\";\nimport { errorHandler } from \"./errors\";\nimport { HonoEnv } from \"./types\";\n\nexport function createSchemaEditorRoutes(collectionsDir: string): Hono<HonoEnv> {\n const router = new Hono<HonoEnv>();\n router.onError(errorHandler);\n const editor = new AstSchemaEditor(collectionsDir);\n\n router.post(\"/property/save\", async (c) => {\n const body = await c.req.json();\n const { collectionId, propertyKey, propertyConfig } = body;\n await editor.saveProperty(collectionId, propertyKey, propertyConfig);\n return c.json({ success: true });\n });\n\n router.post(\"/property/delete\", async (c) => {\n const body = await c.req.json();\n const { collectionId, propertyKey } = body;\n await editor.deleteProperty(collectionId, propertyKey);\n return c.json({ success: true });\n });\n\n router.post(\"/collection/save\", async (c) => {\n const body = await c.req.json();\n const { collectionId, collectionData } = body;\n await editor.saveCollection(collectionId, collectionData);\n return c.json({ success: true });\n });\n\n router.post(\"/collection/delete\", async (c) => {\n const body = await c.req.json();\n const { collectionId } = body;\n await editor.deleteCollection(collectionId);\n return c.json({ success: true });\n });\n\n return router;\n}\n\n"],"mappings":"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"}
|