@palbase/backend 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/LICENSE +21 -0
- package/dist/db/index.cjs +362 -0
- package/dist/db/index.cjs.map +1 -0
- package/dist/db/index.d.cts +87 -0
- package/dist/db/index.d.ts +87 -0
- package/dist/db/index.js +325 -0
- package/dist/db/index.js.map +1 -0
- package/dist/endpoint-tZi55HU8.d.cts +115 -0
- package/dist/endpoint-tZi55HU8.d.ts +115 -0
- package/dist/index.cjs +342 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +381 -0
- package/dist/index.d.ts +381 -0
- package/dist/index.js +306 -0
- package/dist/index.js.map +1 -0
- package/dist/test/index.cjs +163 -0
- package/dist/test/index.cjs.map +1 -0
- package/dist/test/index.d.cts +45 -0
- package/dist/test/index.d.ts +45 -0
- package/dist/test/index.js +135 -0
- package/dist/test/index.js.map +1 -0
- package/package.json +65 -0
package/dist/db/index.js
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// src/db/schema.ts
|
|
2
|
+
function table(name, columns) {
|
|
3
|
+
return { name, columns };
|
|
4
|
+
}
|
|
5
|
+
function defineSchema(tables) {
|
|
6
|
+
return { tables };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// src/db/columns.ts
|
|
10
|
+
var ColumnBuilder = class {
|
|
11
|
+
_def;
|
|
12
|
+
constructor(type) {
|
|
13
|
+
this._def = {
|
|
14
|
+
type,
|
|
15
|
+
nullable: false,
|
|
16
|
+
primaryKey: false
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Mark this column as the primary key. */
|
|
20
|
+
primaryKey() {
|
|
21
|
+
this._def.primaryKey = true;
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
/** Mark this column as NOT NULL (default). */
|
|
25
|
+
notNull() {
|
|
26
|
+
this._def.nullable = false;
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
/** Allow NULL values. */
|
|
30
|
+
nullable() {
|
|
31
|
+
this._def.nullable = true;
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
/** Set a default value. */
|
|
35
|
+
default(value) {
|
|
36
|
+
this._def.defaultValue = value;
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
/** UUID: generate a random default (gen_random_uuid()). */
|
|
40
|
+
defaultRandom() {
|
|
41
|
+
this._def.defaultRandom = true;
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
/** Timestamp: default to now(). */
|
|
45
|
+
defaultNow() {
|
|
46
|
+
this._def.defaultNow = true;
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
/** Add a foreign key reference. */
|
|
50
|
+
references(table2, column) {
|
|
51
|
+
this._def.references = { table: table2, column };
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
/** Set the ON DELETE action for a foreign key reference. */
|
|
55
|
+
onDelete(action) {
|
|
56
|
+
this._def.onDeleteAction = action;
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
function uuid() {
|
|
61
|
+
return new ColumnBuilder("uuid");
|
|
62
|
+
}
|
|
63
|
+
function text() {
|
|
64
|
+
return new ColumnBuilder("text");
|
|
65
|
+
}
|
|
66
|
+
function integer() {
|
|
67
|
+
return new ColumnBuilder("integer");
|
|
68
|
+
}
|
|
69
|
+
function boolean() {
|
|
70
|
+
return new ColumnBuilder("boolean");
|
|
71
|
+
}
|
|
72
|
+
function timestamp() {
|
|
73
|
+
return new ColumnBuilder("timestamp");
|
|
74
|
+
}
|
|
75
|
+
function jsonb() {
|
|
76
|
+
return new ColumnBuilder("jsonb");
|
|
77
|
+
}
|
|
78
|
+
function enumType(name, values) {
|
|
79
|
+
const builder = new ColumnBuilder("enum");
|
|
80
|
+
builder._def.enumName = name;
|
|
81
|
+
builder._def.enumValues = values;
|
|
82
|
+
return builder;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/db/generator.ts
|
|
86
|
+
function columnTypeToSQL(col) {
|
|
87
|
+
switch (col.type) {
|
|
88
|
+
case "uuid":
|
|
89
|
+
return "UUID";
|
|
90
|
+
case "text":
|
|
91
|
+
return "TEXT";
|
|
92
|
+
case "integer":
|
|
93
|
+
return "INTEGER";
|
|
94
|
+
case "boolean":
|
|
95
|
+
return "BOOLEAN";
|
|
96
|
+
case "timestamp":
|
|
97
|
+
return "TIMESTAMPTZ";
|
|
98
|
+
case "jsonb":
|
|
99
|
+
return "JSONB";
|
|
100
|
+
case "enum":
|
|
101
|
+
return col.enumName ?? "TEXT";
|
|
102
|
+
default:
|
|
103
|
+
return "TEXT";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function defaultClause(col) {
|
|
107
|
+
if (col.defaultRandom) {
|
|
108
|
+
return " DEFAULT gen_random_uuid()";
|
|
109
|
+
}
|
|
110
|
+
if (col.defaultNow) {
|
|
111
|
+
return " DEFAULT now()";
|
|
112
|
+
}
|
|
113
|
+
if (col.defaultValue !== void 0) {
|
|
114
|
+
if (typeof col.defaultValue === "string") {
|
|
115
|
+
const escaped = col.defaultValue.replace(/'/g, "''");
|
|
116
|
+
return ` DEFAULT '${escaped}'`;
|
|
117
|
+
}
|
|
118
|
+
if (typeof col.defaultValue === "boolean") {
|
|
119
|
+
return ` DEFAULT ${col.defaultValue ? "true" : "false"}`;
|
|
120
|
+
}
|
|
121
|
+
if (typeof col.defaultValue === "object" && col.defaultValue !== null) {
|
|
122
|
+
const escaped = JSON.stringify(col.defaultValue).replace(/'/g, "''");
|
|
123
|
+
return ` DEFAULT '${escaped}'::jsonb`;
|
|
124
|
+
}
|
|
125
|
+
return ` DEFAULT ${col.defaultValue}`;
|
|
126
|
+
}
|
|
127
|
+
return "";
|
|
128
|
+
}
|
|
129
|
+
function onDeleteSQL(action) {
|
|
130
|
+
switch (action) {
|
|
131
|
+
case "cascade":
|
|
132
|
+
return "CASCADE";
|
|
133
|
+
case "set null":
|
|
134
|
+
return "SET NULL";
|
|
135
|
+
case "restrict":
|
|
136
|
+
return "RESTRICT";
|
|
137
|
+
case "no action":
|
|
138
|
+
return "NO ACTION";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function generateEnumTypes(tables) {
|
|
142
|
+
const enums = /* @__PURE__ */ new Map();
|
|
143
|
+
for (const tableDef of Object.values(tables)) {
|
|
144
|
+
for (const col of Object.values(tableDef.columns)) {
|
|
145
|
+
if (col._def.type === "enum" && col._def.enumName && col._def.enumValues) {
|
|
146
|
+
enums.set(col._def.enumName, col._def.enumValues);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return Array.from(enums.entries()).map(([name, values]) => {
|
|
151
|
+
const vals = values.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ");
|
|
152
|
+
return `CREATE TYPE ${name} AS ENUM (${vals});`;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function generateCreateTable(tableDef) {
|
|
156
|
+
const lines = [];
|
|
157
|
+
for (const [colName, colBuilder] of Object.entries(tableDef.columns)) {
|
|
158
|
+
const col = colBuilder._def;
|
|
159
|
+
let line = ` ${colName} ${columnTypeToSQL(col)}`;
|
|
160
|
+
if (col.primaryKey) {
|
|
161
|
+
line += " PRIMARY KEY";
|
|
162
|
+
}
|
|
163
|
+
if (!col.nullable && !col.primaryKey) {
|
|
164
|
+
line += " NOT NULL";
|
|
165
|
+
}
|
|
166
|
+
line += defaultClause(col);
|
|
167
|
+
if (col.references) {
|
|
168
|
+
line += ` REFERENCES ${col.references.table}(${col.references.column})`;
|
|
169
|
+
if (col.onDeleteAction) {
|
|
170
|
+
line += ` ON DELETE ${onDeleteSQL(col.onDeleteAction)}`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
lines.push(line);
|
|
174
|
+
}
|
|
175
|
+
return `CREATE TABLE ${tableDef.name} (
|
|
176
|
+
${lines.join(",\n")}
|
|
177
|
+
);`;
|
|
178
|
+
}
|
|
179
|
+
function generateDropTable(tableDef) {
|
|
180
|
+
return `DROP TABLE IF EXISTS ${tableDef.name};`;
|
|
181
|
+
}
|
|
182
|
+
function generateDropEnumTypes(tables) {
|
|
183
|
+
const enums = /* @__PURE__ */ new Set();
|
|
184
|
+
for (const tableDef of Object.values(tables)) {
|
|
185
|
+
for (const col of Object.values(tableDef.columns)) {
|
|
186
|
+
if (col._def.type === "enum" && col._def.enumName) {
|
|
187
|
+
enums.add(col._def.enumName);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return Array.from(enums).map((name) => `DROP TYPE IF EXISTS ${name};`);
|
|
192
|
+
}
|
|
193
|
+
function generateMigration(schema) {
|
|
194
|
+
const upParts = [];
|
|
195
|
+
const downParts = [];
|
|
196
|
+
const enumTypes = generateEnumTypes(schema.tables);
|
|
197
|
+
upParts.push(...enumTypes);
|
|
198
|
+
for (const tableDef of Object.values(schema.tables)) {
|
|
199
|
+
upParts.push(generateCreateTable(tableDef));
|
|
200
|
+
}
|
|
201
|
+
const tableNames = Object.values(schema.tables);
|
|
202
|
+
for (let i = tableNames.length - 1; i >= 0; i--) {
|
|
203
|
+
downParts.push(generateDropTable(tableNames[i]));
|
|
204
|
+
}
|
|
205
|
+
downParts.push(...generateDropEnumTypes(schema.tables));
|
|
206
|
+
return {
|
|
207
|
+
up: upParts.join("\n\n"),
|
|
208
|
+
down: downParts.join("\n\n")
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function diffSchemas(oldSchema, newSchema) {
|
|
212
|
+
const addedTables = [];
|
|
213
|
+
const droppedTables = [];
|
|
214
|
+
const columnDiffs = [];
|
|
215
|
+
const oldTableNames = new Set(Object.keys(oldSchema.tables));
|
|
216
|
+
const newTableNames = new Set(Object.keys(newSchema.tables));
|
|
217
|
+
for (const name of newTableNames) {
|
|
218
|
+
if (!oldTableNames.has(name)) {
|
|
219
|
+
addedTables.push(newSchema.tables[name]);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
for (const name of oldTableNames) {
|
|
223
|
+
if (!newTableNames.has(name)) {
|
|
224
|
+
droppedTables.push(oldSchema.tables[name]);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
for (const name of newTableNames) {
|
|
228
|
+
if (!oldTableNames.has(name)) continue;
|
|
229
|
+
const oldTable = oldSchema.tables[name];
|
|
230
|
+
const newTable = newSchema.tables[name];
|
|
231
|
+
const oldCols = new Set(Object.keys(oldTable.columns));
|
|
232
|
+
const newCols = new Set(Object.keys(newTable.columns));
|
|
233
|
+
for (const col of newCols) {
|
|
234
|
+
if (!oldCols.has(col)) {
|
|
235
|
+
columnDiffs.push({
|
|
236
|
+
type: "add",
|
|
237
|
+
table: newTable.name,
|
|
238
|
+
column: col,
|
|
239
|
+
def: newTable.columns[col]._def
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
for (const col of oldCols) {
|
|
244
|
+
if (!newCols.has(col)) {
|
|
245
|
+
columnDiffs.push({
|
|
246
|
+
type: "drop",
|
|
247
|
+
table: oldTable.name,
|
|
248
|
+
column: col,
|
|
249
|
+
oldDef: oldTable.columns[col]._def
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return { addedTables, droppedTables, columnDiffs };
|
|
255
|
+
}
|
|
256
|
+
function generateAddColumn(table2, column, col) {
|
|
257
|
+
let sql = `ALTER TABLE ${table2} ADD COLUMN ${column} ${columnTypeToSQL(col)}`;
|
|
258
|
+
if (!col.nullable) {
|
|
259
|
+
sql += " NOT NULL";
|
|
260
|
+
}
|
|
261
|
+
sql += defaultClause(col);
|
|
262
|
+
if (col.references) {
|
|
263
|
+
sql += ` REFERENCES ${col.references.table}(${col.references.column})`;
|
|
264
|
+
if (col.onDeleteAction) {
|
|
265
|
+
sql += ` ON DELETE ${onDeleteSQL(col.onDeleteAction)}`;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return `${sql};`;
|
|
269
|
+
}
|
|
270
|
+
function generateDropColumn(table2, column) {
|
|
271
|
+
return `ALTER TABLE ${table2} DROP COLUMN ${column};`;
|
|
272
|
+
}
|
|
273
|
+
function generateDiffMigration(oldSchema, newSchema) {
|
|
274
|
+
const { addedTables, droppedTables, columnDiffs } = diffSchemas(oldSchema, newSchema);
|
|
275
|
+
if (addedTables.length === 0 && droppedTables.length === 0 && columnDiffs.length === 0) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
const upParts = [];
|
|
279
|
+
const downParts = [];
|
|
280
|
+
const newEnums = generateEnumTypes(
|
|
281
|
+
Object.fromEntries(addedTables.map((t) => [t.name, t]))
|
|
282
|
+
);
|
|
283
|
+
upParts.push(...newEnums);
|
|
284
|
+
for (const table2 of addedTables) {
|
|
285
|
+
upParts.push(generateCreateTable(table2));
|
|
286
|
+
downParts.push(generateDropTable(table2));
|
|
287
|
+
}
|
|
288
|
+
for (const table2 of droppedTables) {
|
|
289
|
+
upParts.push(generateDropTable(table2));
|
|
290
|
+
downParts.push(generateCreateTable(table2));
|
|
291
|
+
}
|
|
292
|
+
for (const diff of columnDiffs) {
|
|
293
|
+
if (diff.type === "add" && diff.def) {
|
|
294
|
+
upParts.push(generateAddColumn(diff.table, diff.column, diff.def));
|
|
295
|
+
downParts.push(generateDropColumn(diff.table, diff.column));
|
|
296
|
+
} else if (diff.type === "drop") {
|
|
297
|
+
upParts.push(generateDropColumn(diff.table, diff.column));
|
|
298
|
+
if (diff.oldDef) {
|
|
299
|
+
downParts.push(generateAddColumn(diff.table, diff.column, diff.oldDef));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const droppedEnums = generateDropEnumTypes(
|
|
304
|
+
Object.fromEntries(droppedTables.map((t) => [t.name, t]))
|
|
305
|
+
);
|
|
306
|
+
downParts.push(...droppedEnums);
|
|
307
|
+
return {
|
|
308
|
+
up: upParts.join("\n\n"),
|
|
309
|
+
down: downParts.join("\n\n")
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
export {
|
|
313
|
+
boolean,
|
|
314
|
+
defineSchema,
|
|
315
|
+
enumType,
|
|
316
|
+
generateDiffMigration,
|
|
317
|
+
generateMigration,
|
|
318
|
+
integer,
|
|
319
|
+
jsonb,
|
|
320
|
+
table,
|
|
321
|
+
text,
|
|
322
|
+
timestamp,
|
|
323
|
+
uuid
|
|
324
|
+
};
|
|
325
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/db/schema.ts","../../src/db/columns.ts","../../src/db/generator.ts"],"sourcesContent":["import type { ColumnBuilder } from \"./columns.js\";\n\n/** A table definition with its columns. */\nexport interface TableDef {\n name: string;\n columns: Record<string, ColumnBuilder>;\n}\n\n/** A schema definition containing multiple tables. */\nexport interface SchemaDef<T extends Record<string, TableDef> = Record<string, TableDef>> {\n tables: T;\n}\n\n/** Define a table with a name and columns. */\nexport function table(name: string, columns: Record<string, ColumnBuilder>): TableDef {\n return { name, columns };\n}\n\n/** Define a schema containing multiple tables. */\nexport function defineSchema<T extends Record<string, TableDef>>(tables: T): SchemaDef<T> {\n return { tables };\n}\n","/** On delete action for foreign key references. */\nexport type OnDeleteAction = \"cascade\" | \"set null\" | \"restrict\" | \"no action\";\n\n/** Column type identifiers. */\nexport type ColumnType = \"uuid\" | \"text\" | \"integer\" | \"boolean\" | \"timestamp\" | \"jsonb\" | \"enum\";\n\n/** Base column definition shared by all column types. */\nexport interface ColumnDef {\n type: ColumnType;\n nullable: boolean;\n primaryKey: boolean;\n defaultValue?: unknown;\n defaultRandom?: boolean;\n defaultNow?: boolean;\n references?: { table: string; column: string };\n onDeleteAction?: OnDeleteAction;\n enumName?: string;\n enumValues?: string[];\n}\n\n/** Fluent column builder. Methods return `this` for chaining. */\nclass ColumnBuilder {\n readonly _def: ColumnDef;\n\n constructor(type: ColumnType) {\n this._def = {\n type,\n nullable: false,\n primaryKey: false,\n };\n }\n\n /** Mark this column as the primary key. */\n primaryKey(): this {\n this._def.primaryKey = true;\n return this;\n }\n\n /** Mark this column as NOT NULL (default). */\n notNull(): this {\n this._def.nullable = false;\n return this;\n }\n\n /** Allow NULL values. */\n nullable(): this {\n this._def.nullable = true;\n return this;\n }\n\n /** Set a default value. */\n default(value: unknown): this {\n this._def.defaultValue = value;\n return this;\n }\n\n /** UUID: generate a random default (gen_random_uuid()). */\n defaultRandom(): this {\n this._def.defaultRandom = true;\n return this;\n }\n\n /** Timestamp: default to now(). */\n defaultNow(): this {\n this._def.defaultNow = true;\n return this;\n }\n\n /** Add a foreign key reference. */\n references(table: string, column: string): this {\n this._def.references = { table, column };\n return this;\n }\n\n /** Set the ON DELETE action for a foreign key reference. */\n onDelete(action: OnDeleteAction): this {\n this._def.onDeleteAction = action;\n return this;\n }\n}\n\n/** Create a UUID column. */\nexport function uuid(): ColumnBuilder {\n return new ColumnBuilder(\"uuid\");\n}\n\n/** Create a TEXT column. */\nexport function text(): ColumnBuilder {\n return new ColumnBuilder(\"text\");\n}\n\n/** Create an INTEGER column. */\nexport function integer(): ColumnBuilder {\n return new ColumnBuilder(\"integer\");\n}\n\n/** Create a BOOLEAN column. */\nexport function boolean(): ColumnBuilder {\n return new ColumnBuilder(\"boolean\");\n}\n\n/** Create a TIMESTAMP column. */\nexport function timestamp(): ColumnBuilder {\n return new ColumnBuilder(\"timestamp\");\n}\n\n/** Create a JSONB column. */\nexport function jsonb(): ColumnBuilder {\n return new ColumnBuilder(\"jsonb\");\n}\n\n/** Create an ENUM column. */\nexport function enumType(name: string, values: string[]): ColumnBuilder {\n const builder = new ColumnBuilder(\"enum\");\n builder._def.enumName = name;\n builder._def.enumValues = values;\n return builder;\n}\n\nexport { ColumnBuilder };\n","import type { ColumnDef, OnDeleteAction } from \"./columns.js\";\nimport type { SchemaDef, TableDef } from \"./schema.js\";\n\n/** Maps column types to their PostgreSQL SQL equivalents. */\nfunction columnTypeToSQL(col: ColumnDef): string {\n switch (col.type) {\n case \"uuid\":\n return \"UUID\";\n case \"text\":\n return \"TEXT\";\n case \"integer\":\n return \"INTEGER\";\n case \"boolean\":\n return \"BOOLEAN\";\n case \"timestamp\":\n return \"TIMESTAMPTZ\";\n case \"jsonb\":\n return \"JSONB\";\n case \"enum\":\n return col.enumName ?? \"TEXT\";\n default:\n return \"TEXT\";\n }\n}\n\n/** Generates the DEFAULT clause for a column. */\nfunction defaultClause(col: ColumnDef): string {\n if (col.defaultRandom) {\n return \" DEFAULT gen_random_uuid()\";\n }\n if (col.defaultNow) {\n return \" DEFAULT now()\";\n }\n if (col.defaultValue !== undefined) {\n if (typeof col.defaultValue === \"string\") {\n // Escape single quotes in string defaults\n const escaped = col.defaultValue.replace(/'/g, \"''\");\n return ` DEFAULT '${escaped}'`;\n }\n if (typeof col.defaultValue === \"boolean\") {\n return ` DEFAULT ${col.defaultValue ? \"true\" : \"false\"}`;\n }\n if (typeof col.defaultValue === \"object\" && col.defaultValue !== null) {\n const escaped = JSON.stringify(col.defaultValue).replace(/'/g, \"''\");\n return ` DEFAULT '${escaped}'::jsonb`;\n }\n return ` DEFAULT ${col.defaultValue}`;\n }\n return \"\";\n}\n\n/** Maps OnDeleteAction to SQL. */\nfunction onDeleteSQL(action: OnDeleteAction): string {\n switch (action) {\n case \"cascade\":\n return \"CASCADE\";\n case \"set null\":\n return \"SET NULL\";\n case \"restrict\":\n return \"RESTRICT\";\n case \"no action\":\n return \"NO ACTION\";\n }\n}\n\n/** Generates CREATE TYPE statements for enum columns. */\nfunction generateEnumTypes(tables: Record<string, TableDef>): string[] {\n const enums = new Map<string, string[]>();\n for (const tableDef of Object.values(tables)) {\n for (const col of Object.values(tableDef.columns)) {\n if (col._def.type === \"enum\" && col._def.enumName && col._def.enumValues) {\n enums.set(col._def.enumName, col._def.enumValues);\n }\n }\n }\n\n return Array.from(enums.entries()).map(([name, values]) => {\n const vals = values.map((v) => `'${v.replace(/'/g, \"''\")}'`).join(\", \");\n return `CREATE TYPE ${name} AS ENUM (${vals});`;\n });\n}\n\n/** Generates a CREATE TABLE statement for a single table. */\nfunction generateCreateTable(tableDef: TableDef): string {\n const lines: string[] = [];\n\n for (const [colName, colBuilder] of Object.entries(tableDef.columns)) {\n const col = colBuilder._def;\n let line = ` ${colName} ${columnTypeToSQL(col)}`;\n\n if (col.primaryKey) {\n line += \" PRIMARY KEY\";\n }\n\n if (!col.nullable && !col.primaryKey) {\n line += \" NOT NULL\";\n }\n\n line += defaultClause(col);\n\n if (col.references) {\n line += ` REFERENCES ${col.references.table}(${col.references.column})`;\n if (col.onDeleteAction) {\n line += ` ON DELETE ${onDeleteSQL(col.onDeleteAction)}`;\n }\n }\n\n lines.push(line);\n }\n\n return `CREATE TABLE ${tableDef.name} (\\n${lines.join(\",\\n\")}\\n);`;\n}\n\n/** Generates a DROP TABLE statement (for down migration). */\nfunction generateDropTable(tableDef: TableDef): string {\n return `DROP TABLE IF EXISTS ${tableDef.name};`;\n}\n\n/** Generates DROP TYPE statements for enum columns (for down migration). */\nfunction generateDropEnumTypes(tables: Record<string, TableDef>): string[] {\n const enums = new Set<string>();\n for (const tableDef of Object.values(tables)) {\n for (const col of Object.values(tableDef.columns)) {\n if (col._def.type === \"enum\" && col._def.enumName) {\n enums.add(col._def.enumName);\n }\n }\n }\n return Array.from(enums).map((name) => `DROP TYPE IF EXISTS ${name};`);\n}\n\n/** Result of migration generation. */\nexport interface MigrationResult {\n up: string;\n down: string;\n}\n\n/**\n * Generates a full migration (up + down) from a schema definition.\n * Used for initial schema creation.\n */\nexport function generateMigration(schema: SchemaDef): MigrationResult {\n const upParts: string[] = [];\n const downParts: string[] = [];\n\n // Enum types first (up) / last (down)\n const enumTypes = generateEnumTypes(schema.tables);\n upParts.push(...enumTypes);\n\n // Tables\n for (const tableDef of Object.values(schema.tables)) {\n upParts.push(generateCreateTable(tableDef));\n }\n\n // Down: reverse order — drop tables first, then types\n const tableNames = Object.values(schema.tables);\n for (let i = tableNames.length - 1; i >= 0; i--) {\n downParts.push(generateDropTable(tableNames[i]!));\n }\n downParts.push(...generateDropEnumTypes(schema.tables));\n\n return {\n up: upParts.join(\"\\n\\n\"),\n down: downParts.join(\"\\n\\n\"),\n };\n}\n\n/** Column diff operation. */\ninterface ColumnDiff {\n type: \"add\" | \"drop\" | \"alter\";\n table: string;\n column: string;\n def?: ColumnDef;\n oldDef?: ColumnDef;\n}\n\n/** Computes column-level diffs between two schemas. */\nfunction diffSchemas(\n oldSchema: SchemaDef,\n newSchema: SchemaDef,\n): { addedTables: TableDef[]; droppedTables: TableDef[]; columnDiffs: ColumnDiff[] } {\n const addedTables: TableDef[] = [];\n const droppedTables: TableDef[] = [];\n const columnDiffs: ColumnDiff[] = [];\n\n const oldTableNames = new Set(Object.keys(oldSchema.tables));\n const newTableNames = new Set(Object.keys(newSchema.tables));\n\n // New tables\n for (const name of newTableNames) {\n if (!oldTableNames.has(name)) {\n addedTables.push(newSchema.tables[name]!);\n }\n }\n\n // Dropped tables\n for (const name of oldTableNames) {\n if (!newTableNames.has(name)) {\n droppedTables.push(oldSchema.tables[name]!);\n }\n }\n\n // Column-level diffs for existing tables\n for (const name of newTableNames) {\n if (!oldTableNames.has(name)) continue;\n\n const oldTable = oldSchema.tables[name]!;\n const newTable = newSchema.tables[name]!;\n const oldCols = new Set(Object.keys(oldTable.columns));\n const newCols = new Set(Object.keys(newTable.columns));\n\n for (const col of newCols) {\n if (!oldCols.has(col)) {\n columnDiffs.push({\n type: \"add\",\n table: newTable.name,\n column: col,\n def: newTable.columns[col]!._def,\n });\n }\n }\n\n for (const col of oldCols) {\n if (!newCols.has(col)) {\n columnDiffs.push({\n type: \"drop\",\n table: oldTable.name,\n column: col,\n oldDef: oldTable.columns[col]!._def,\n });\n }\n }\n }\n\n return { addedTables, droppedTables, columnDiffs };\n}\n\n/** Generates an ALTER TABLE ADD COLUMN statement. */\nfunction generateAddColumn(table: string, column: string, col: ColumnDef): string {\n let sql = `ALTER TABLE ${table} ADD COLUMN ${column} ${columnTypeToSQL(col)}`;\n if (!col.nullable) {\n sql += \" NOT NULL\";\n }\n sql += defaultClause(col);\n if (col.references) {\n sql += ` REFERENCES ${col.references.table}(${col.references.column})`;\n if (col.onDeleteAction) {\n sql += ` ON DELETE ${onDeleteSQL(col.onDeleteAction)}`;\n }\n }\n return `${sql};`;\n}\n\n/** Generates an ALTER TABLE DROP COLUMN statement. */\nfunction generateDropColumn(table: string, column: string): string {\n return `ALTER TABLE ${table} DROP COLUMN ${column};`;\n}\n\n/**\n * Generates a diff migration between two schemas.\n * Returns null if there are no changes.\n */\nexport function generateDiffMigration(\n oldSchema: SchemaDef,\n newSchema: SchemaDef,\n): MigrationResult | null {\n const { addedTables, droppedTables, columnDiffs } = diffSchemas(oldSchema, newSchema);\n\n if (addedTables.length === 0 && droppedTables.length === 0 && columnDiffs.length === 0) {\n return null;\n }\n\n const upParts: string[] = [];\n const downParts: string[] = [];\n\n // New enum types from added tables\n const newEnums = generateEnumTypes(\n Object.fromEntries(addedTables.map((t) => [t.name, t])),\n );\n upParts.push(...newEnums);\n\n // New tables\n for (const table of addedTables) {\n upParts.push(generateCreateTable(table));\n downParts.push(generateDropTable(table));\n }\n\n // Dropped tables\n for (const table of droppedTables) {\n upParts.push(generateDropTable(table));\n downParts.push(generateCreateTable(table));\n }\n\n // Column diffs\n for (const diff of columnDiffs) {\n if (diff.type === \"add\" && diff.def) {\n upParts.push(generateAddColumn(diff.table, diff.column, diff.def));\n downParts.push(generateDropColumn(diff.table, diff.column));\n } else if (diff.type === \"drop\") {\n upParts.push(generateDropColumn(diff.table, diff.column));\n if (diff.oldDef) {\n downParts.push(generateAddColumn(diff.table, diff.column, diff.oldDef));\n }\n }\n }\n\n // Drop enum types from dropped tables (down)\n const droppedEnums = generateDropEnumTypes(\n Object.fromEntries(droppedTables.map((t) => [t.name, t])),\n );\n downParts.push(...droppedEnums);\n\n return {\n up: upParts.join(\"\\n\\n\"),\n down: downParts.join(\"\\n\\n\"),\n };\n}\n"],"mappings":"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","names":["table","table"]}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { ZodSchema, z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/** Supported HTTP methods for endpoints. */
|
|
4
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
5
|
+
/** Authenticated user attached to the request context. */
|
|
6
|
+
interface User {
|
|
7
|
+
id: string;
|
|
8
|
+
email: string;
|
|
9
|
+
role: string;
|
|
10
|
+
metadata: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
/** Authentication configuration for an endpoint. */
|
|
13
|
+
interface AuthConfig {
|
|
14
|
+
/** Whether authentication is required. Defaults to true. */
|
|
15
|
+
required: boolean;
|
|
16
|
+
/** Required role for access. If undefined, any authenticated user is allowed. */
|
|
17
|
+
role?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Middleware context — subset of EndpointContext without input (not yet validated). */
|
|
21
|
+
interface MiddlewareContext {
|
|
22
|
+
params: Record<string, string>;
|
|
23
|
+
query: Record<string, string>;
|
|
24
|
+
headers: Record<string, string>;
|
|
25
|
+
user: User | null;
|
|
26
|
+
db: DBClient;
|
|
27
|
+
env: Record<string, string>;
|
|
28
|
+
log: Logger;
|
|
29
|
+
cache: CacheClient;
|
|
30
|
+
palbase: PalbaseBindings;
|
|
31
|
+
requestId: string;
|
|
32
|
+
projectId: string;
|
|
33
|
+
environmentId: string;
|
|
34
|
+
}
|
|
35
|
+
/** Middleware function signature — receives context and next function. */
|
|
36
|
+
type MiddlewareHandler = (ctx: MiddlewareContext, next: () => Promise<void>) => Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Define a middleware function for use in the middleware/ directory or
|
|
39
|
+
* as endpoint-specific middleware.
|
|
40
|
+
*
|
|
41
|
+
* Middleware runs before the handler. Call `next()` to pass control
|
|
42
|
+
* to the next middleware or handler. If `next()` is not called, the
|
|
43
|
+
* handler will not execute.
|
|
44
|
+
*
|
|
45
|
+
* Errors thrown in middleware are caught by the pipeline and returned
|
|
46
|
+
* as error responses.
|
|
47
|
+
*/
|
|
48
|
+
declare function defineMiddleware(fn: MiddlewareHandler): MiddlewareHandler;
|
|
49
|
+
|
|
50
|
+
/** Rate limit configuration for an endpoint. */
|
|
51
|
+
interface RateLimitConfig {
|
|
52
|
+
/** Maximum number of requests in the window. */
|
|
53
|
+
max: number;
|
|
54
|
+
/** Window duration in seconds. */
|
|
55
|
+
window: number;
|
|
56
|
+
}
|
|
57
|
+
/** Database client interface injected into endpoint context. */
|
|
58
|
+
interface DBClient {
|
|
59
|
+
insert(table: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
60
|
+
update(table: string, id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
61
|
+
delete(table: string, id: string): Promise<void>;
|
|
62
|
+
findById(table: string, id: string): Promise<Record<string, unknown> | null>;
|
|
63
|
+
findMany(table: string, query?: Record<string, unknown>): Promise<Record<string, unknown>[]>;
|
|
64
|
+
}
|
|
65
|
+
/** Logger interface injected into endpoint context. */
|
|
66
|
+
interface Logger {
|
|
67
|
+
info(message: string, ...args: unknown[]): void;
|
|
68
|
+
warn(message: string, ...args: unknown[]): void;
|
|
69
|
+
error(message: string, ...args: unknown[]): void;
|
|
70
|
+
debug(message: string, ...args: unknown[]): void;
|
|
71
|
+
}
|
|
72
|
+
/** Cache client interface injected into endpoint context. */
|
|
73
|
+
interface CacheClient {
|
|
74
|
+
get(key: string): Promise<string | null>;
|
|
75
|
+
set(key: string, value: string, ttl?: number): Promise<void>;
|
|
76
|
+
del(key: string): Promise<void>;
|
|
77
|
+
incr(key: string): Promise<number>;
|
|
78
|
+
}
|
|
79
|
+
/** Palbase service bindings for accessing other modules. */
|
|
80
|
+
interface PalbaseBindings {
|
|
81
|
+
auth: Record<string, unknown>;
|
|
82
|
+
storage: Record<string, unknown>;
|
|
83
|
+
}
|
|
84
|
+
/** Endpoint context — injected into every handler. */
|
|
85
|
+
interface EndpointContext<TInput = unknown> {
|
|
86
|
+
input: TInput;
|
|
87
|
+
params: Record<string, string>;
|
|
88
|
+
query: Record<string, string>;
|
|
89
|
+
headers: Record<string, string>;
|
|
90
|
+
user: User | null;
|
|
91
|
+
db: DBClient;
|
|
92
|
+
env: Record<string, string>;
|
|
93
|
+
log: Logger;
|
|
94
|
+
cache: CacheClient;
|
|
95
|
+
palbase: PalbaseBindings;
|
|
96
|
+
requestId: string;
|
|
97
|
+
projectId: string;
|
|
98
|
+
environmentId: string;
|
|
99
|
+
}
|
|
100
|
+
/** Middleware function signature — uses MiddlewareContext (no input, not yet validated). */
|
|
101
|
+
type Middleware = (ctx: MiddlewareContext, next: () => Promise<void>) => Promise<void>;
|
|
102
|
+
/** Configuration for defining an endpoint. */
|
|
103
|
+
interface EndpointConfig<TInputSchema extends ZodSchema = ZodSchema, TOutputSchema extends ZodSchema = ZodSchema> {
|
|
104
|
+
method: HttpMethod;
|
|
105
|
+
auth?: Partial<AuthConfig>;
|
|
106
|
+
rateLimit?: RateLimitConfig;
|
|
107
|
+
input?: TInputSchema;
|
|
108
|
+
output?: TOutputSchema;
|
|
109
|
+
middleware?: Middleware[];
|
|
110
|
+
handler: (ctx: EndpointContext<z.infer<TInputSchema>>) => Promise<z.infer<TOutputSchema>>;
|
|
111
|
+
}
|
|
112
|
+
/** Define a type-safe endpoint. Input schema infers the ctx.input type. */
|
|
113
|
+
declare function defineEndpoint<TInputSchema extends ZodSchema, TOutputSchema extends ZodSchema>(config: EndpointConfig<TInputSchema, TOutputSchema>): EndpointConfig<TInputSchema, TOutputSchema>;
|
|
114
|
+
|
|
115
|
+
export { type AuthConfig as A, type CacheClient as C, type DBClient as D, type EndpointContext as E, type HttpMethod as H, type Logger as L, type Middleware as M, type PalbaseBindings as P, type RateLimitConfig as R, type User as U, type EndpointConfig as a, type MiddlewareContext as b, type MiddlewareHandler as c, defineEndpoint as d, defineMiddleware as e };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { ZodSchema, z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/** Supported HTTP methods for endpoints. */
|
|
4
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
5
|
+
/** Authenticated user attached to the request context. */
|
|
6
|
+
interface User {
|
|
7
|
+
id: string;
|
|
8
|
+
email: string;
|
|
9
|
+
role: string;
|
|
10
|
+
metadata: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
/** Authentication configuration for an endpoint. */
|
|
13
|
+
interface AuthConfig {
|
|
14
|
+
/** Whether authentication is required. Defaults to true. */
|
|
15
|
+
required: boolean;
|
|
16
|
+
/** Required role for access. If undefined, any authenticated user is allowed. */
|
|
17
|
+
role?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Middleware context — subset of EndpointContext without input (not yet validated). */
|
|
21
|
+
interface MiddlewareContext {
|
|
22
|
+
params: Record<string, string>;
|
|
23
|
+
query: Record<string, string>;
|
|
24
|
+
headers: Record<string, string>;
|
|
25
|
+
user: User | null;
|
|
26
|
+
db: DBClient;
|
|
27
|
+
env: Record<string, string>;
|
|
28
|
+
log: Logger;
|
|
29
|
+
cache: CacheClient;
|
|
30
|
+
palbase: PalbaseBindings;
|
|
31
|
+
requestId: string;
|
|
32
|
+
projectId: string;
|
|
33
|
+
environmentId: string;
|
|
34
|
+
}
|
|
35
|
+
/** Middleware function signature — receives context and next function. */
|
|
36
|
+
type MiddlewareHandler = (ctx: MiddlewareContext, next: () => Promise<void>) => Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Define a middleware function for use in the middleware/ directory or
|
|
39
|
+
* as endpoint-specific middleware.
|
|
40
|
+
*
|
|
41
|
+
* Middleware runs before the handler. Call `next()` to pass control
|
|
42
|
+
* to the next middleware or handler. If `next()` is not called, the
|
|
43
|
+
* handler will not execute.
|
|
44
|
+
*
|
|
45
|
+
* Errors thrown in middleware are caught by the pipeline and returned
|
|
46
|
+
* as error responses.
|
|
47
|
+
*/
|
|
48
|
+
declare function defineMiddleware(fn: MiddlewareHandler): MiddlewareHandler;
|
|
49
|
+
|
|
50
|
+
/** Rate limit configuration for an endpoint. */
|
|
51
|
+
interface RateLimitConfig {
|
|
52
|
+
/** Maximum number of requests in the window. */
|
|
53
|
+
max: number;
|
|
54
|
+
/** Window duration in seconds. */
|
|
55
|
+
window: number;
|
|
56
|
+
}
|
|
57
|
+
/** Database client interface injected into endpoint context. */
|
|
58
|
+
interface DBClient {
|
|
59
|
+
insert(table: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
60
|
+
update(table: string, id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
61
|
+
delete(table: string, id: string): Promise<void>;
|
|
62
|
+
findById(table: string, id: string): Promise<Record<string, unknown> | null>;
|
|
63
|
+
findMany(table: string, query?: Record<string, unknown>): Promise<Record<string, unknown>[]>;
|
|
64
|
+
}
|
|
65
|
+
/** Logger interface injected into endpoint context. */
|
|
66
|
+
interface Logger {
|
|
67
|
+
info(message: string, ...args: unknown[]): void;
|
|
68
|
+
warn(message: string, ...args: unknown[]): void;
|
|
69
|
+
error(message: string, ...args: unknown[]): void;
|
|
70
|
+
debug(message: string, ...args: unknown[]): void;
|
|
71
|
+
}
|
|
72
|
+
/** Cache client interface injected into endpoint context. */
|
|
73
|
+
interface CacheClient {
|
|
74
|
+
get(key: string): Promise<string | null>;
|
|
75
|
+
set(key: string, value: string, ttl?: number): Promise<void>;
|
|
76
|
+
del(key: string): Promise<void>;
|
|
77
|
+
incr(key: string): Promise<number>;
|
|
78
|
+
}
|
|
79
|
+
/** Palbase service bindings for accessing other modules. */
|
|
80
|
+
interface PalbaseBindings {
|
|
81
|
+
auth: Record<string, unknown>;
|
|
82
|
+
storage: Record<string, unknown>;
|
|
83
|
+
}
|
|
84
|
+
/** Endpoint context — injected into every handler. */
|
|
85
|
+
interface EndpointContext<TInput = unknown> {
|
|
86
|
+
input: TInput;
|
|
87
|
+
params: Record<string, string>;
|
|
88
|
+
query: Record<string, string>;
|
|
89
|
+
headers: Record<string, string>;
|
|
90
|
+
user: User | null;
|
|
91
|
+
db: DBClient;
|
|
92
|
+
env: Record<string, string>;
|
|
93
|
+
log: Logger;
|
|
94
|
+
cache: CacheClient;
|
|
95
|
+
palbase: PalbaseBindings;
|
|
96
|
+
requestId: string;
|
|
97
|
+
projectId: string;
|
|
98
|
+
environmentId: string;
|
|
99
|
+
}
|
|
100
|
+
/** Middleware function signature — uses MiddlewareContext (no input, not yet validated). */
|
|
101
|
+
type Middleware = (ctx: MiddlewareContext, next: () => Promise<void>) => Promise<void>;
|
|
102
|
+
/** Configuration for defining an endpoint. */
|
|
103
|
+
interface EndpointConfig<TInputSchema extends ZodSchema = ZodSchema, TOutputSchema extends ZodSchema = ZodSchema> {
|
|
104
|
+
method: HttpMethod;
|
|
105
|
+
auth?: Partial<AuthConfig>;
|
|
106
|
+
rateLimit?: RateLimitConfig;
|
|
107
|
+
input?: TInputSchema;
|
|
108
|
+
output?: TOutputSchema;
|
|
109
|
+
middleware?: Middleware[];
|
|
110
|
+
handler: (ctx: EndpointContext<z.infer<TInputSchema>>) => Promise<z.infer<TOutputSchema>>;
|
|
111
|
+
}
|
|
112
|
+
/** Define a type-safe endpoint. Input schema infers the ctx.input type. */
|
|
113
|
+
declare function defineEndpoint<TInputSchema extends ZodSchema, TOutputSchema extends ZodSchema>(config: EndpointConfig<TInputSchema, TOutputSchema>): EndpointConfig<TInputSchema, TOutputSchema>;
|
|
114
|
+
|
|
115
|
+
export { type AuthConfig as A, type CacheClient as C, type DBClient as D, type EndpointContext as E, type HttpMethod as H, type Logger as L, type Middleware as M, type PalbaseBindings as P, type RateLimitConfig as R, type User as U, type EndpointConfig as a, type MiddlewareContext as b, type MiddlewareHandler as c, defineEndpoint as d, defineMiddleware as e };
|