@pramen/server 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/runtime/ddl.js +9 -3
- package/dist/runtime/migrate.js +20 -8
- package/dist/sdk/infer.d.ts +2 -0
- package/dist/sdk/schema.d.ts +24 -1
- package/dist/sdk/schema.js +17 -2
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/runtime/ddl.ts +8 -3
- package/src/runtime/migrate.ts +19 -7
- package/src/sdk/infer.ts +5 -2
- package/src/sdk/schema.ts +26 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, defaultTo, primaryKey, generated } from "./sdk/schema";
|
|
1
|
+
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, defaultTo, primaryKey, generated, expr, ExprDefault } from "./sdk/schema";
|
|
2
2
|
export { isValidUuid } from "./sdk/uuid";
|
|
3
3
|
export type { DefaultValue, FieldType, FieldDef, EntityFields, EntityDef, SchemaDef, RelationDef, RelationDefs, BelongsToDef, HasManyDef, } from "./sdk/schema";
|
|
4
4
|
export { createApp } from "./sdk/app";
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// which only exists in the Workers runtime; keeping it separate lets the CLI, tests,
|
|
8
8
|
// and codegen load an app.ts for its schema without dragging in the DO runtime.
|
|
9
9
|
// --- schema authoring ---
|
|
10
|
-
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, defaultTo, primaryKey, generated } from "./sdk/schema";
|
|
10
|
+
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, defaultTo, primaryKey, generated, expr, ExprDefault } from "./sdk/schema";
|
|
11
11
|
export { isValidUuid } from "./sdk/uuid";
|
|
12
12
|
// --- app + handlers ---
|
|
13
13
|
export { createApp } from "./sdk/app";
|
package/dist/runtime/ddl.js
CHANGED
|
@@ -19,10 +19,16 @@ function defaultLiteral(v) {
|
|
|
19
19
|
return String(v);
|
|
20
20
|
return `'${v.replace(/'/g, "''")}'`;
|
|
21
21
|
}
|
|
22
|
-
/** The ` DEFAULT x` fragment for a column, or "" when it has no default.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
/** The ` DEFAULT x` fragment for a column, or "" when it has no default. A raw-SQL
|
|
23
|
+
* `defaultExpr` (e.g. `datetime('now')`) is emitted UNQUOTED; a literal `default` is
|
|
24
|
+
* quote-escaped. UNIQUE/index are NOT inline — they're emitted as separate index
|
|
25
|
+
* statements so the same code path serves both CREATE TABLE and ALTER TABLE ADD COLUMN. */
|
|
25
26
|
function defaultSql(f) {
|
|
27
|
+
// A raw-SQL default is parenthesized: SQLite's column-DEFAULT grammar only takes a
|
|
28
|
+
// bare literal/keyword, so a function call (e.g. datetime('now')) must be wrapped —
|
|
29
|
+
// `DEFAULT (datetime('now'))`. Parens are harmless around a keyword too.
|
|
30
|
+
if (f.defaultExpr !== undefined)
|
|
31
|
+
return ` DEFAULT (${f.defaultExpr})`;
|
|
26
32
|
return f.default !== undefined ? ` DEFAULT ${defaultLiteral(f.default)}` : "";
|
|
27
33
|
}
|
|
28
34
|
function columnSql(name, f) {
|
package/dist/runtime/migrate.js
CHANGED
|
@@ -114,9 +114,18 @@ export async function migrate(driver, schema, opts = {}) {
|
|
|
114
114
|
continue;
|
|
115
115
|
}
|
|
116
116
|
// Pass 1 — additive: add any column the schema declares but the table lacks.
|
|
117
|
+
// SQLite forbids ALTER ADD COLUMN with a non-constant DEFAULT (e.g. expr.now()),
|
|
118
|
+
// so such a column is added via a table rebuild instead — which is still additive
|
|
119
|
+
// (no data loss): the rebuild's INSERT omits the new column, so SQLite applies its
|
|
120
|
+
// CREATE TABLE default, backfilling existing rows. Flagged here, done in Pass 2.
|
|
121
|
+
let needsAdditiveRebuild = false;
|
|
117
122
|
for (const [name, field] of Object.entries(def.fields)) {
|
|
118
123
|
if (existing.has(name))
|
|
119
124
|
continue;
|
|
125
|
+
if (field.defaultExpr !== undefined) {
|
|
126
|
+
needsAdditiveRebuild = true;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
120
129
|
await driver.exec(`ALTER TABLE ${ident(table)} ADD COLUMN ${addColumnSql(name, field)}`, []);
|
|
121
130
|
added.push(`${table}.${name}`);
|
|
122
131
|
}
|
|
@@ -132,14 +141,17 @@ export async function migrate(driver, schema, opts = {}) {
|
|
|
132
141
|
}
|
|
133
142
|
const needsDrop = [...live.keys()].some((c) => !desired.has(c) && !renamedSources.has(c));
|
|
134
143
|
const needsTypeChange = Object.entries(def.fields).some(([n, f]) => live.has(n) && live.get(n) !== sqlType(f));
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
const destructive = needsDrop || needsTypeChange || renamedSources.size > 0;
|
|
145
|
+
if (destructive && !allowDestructive) {
|
|
146
|
+
// The destructive part is gated off — skip the whole rebuild (any pending
|
|
147
|
+
// expr-default column waits until destructive migrations are allowed).
|
|
148
|
+
skipped.push(`rebuild ${table} (drop/type-change/rename)`);
|
|
149
|
+
}
|
|
150
|
+
else if (destructive || needsAdditiveRebuild) {
|
|
151
|
+
// An additive-only rebuild (just an expr-default column) needs no permission —
|
|
152
|
+
// it loses no data.
|
|
153
|
+
await rebuildTable(driver, table, def, live);
|
|
154
|
+
rebuilt.push(table);
|
|
143
155
|
}
|
|
144
156
|
}
|
|
145
157
|
// Ensure unique/index declarations (idempotent, via IF NOT EXISTS). Indexes can be
|
package/dist/sdk/infer.d.ts
CHANGED
|
@@ -65,6 +65,8 @@ type RequiredInsertKeys<F extends EntityFields> = {
|
|
|
65
65
|
generated: true;
|
|
66
66
|
} ? never : F[K] extends {
|
|
67
67
|
default: DefaultValue;
|
|
68
|
+
} ? never : F[K] extends {
|
|
69
|
+
defaultExpr: string;
|
|
68
70
|
} ? never : K : never;
|
|
69
71
|
}[keyof F];
|
|
70
72
|
type OptionalInsertKeys<F extends EntityFields> = Exclude<keyof F, RequiredInsertKeys<F>>;
|
package/dist/sdk/schema.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ export interface FieldDef {
|
|
|
16
16
|
readonly generated?: boolean;
|
|
17
17
|
/** A column DEFAULT (a literal). Makes the column optional on insert. */
|
|
18
18
|
readonly default?: DefaultValue;
|
|
19
|
+
/** A column DEFAULT that is raw SQL, emitted UNQUOTED (e.g. `datetime('now')`) —
|
|
20
|
+
* set by `defaultTo(field, expr.now())`/`expr.raw(...)`. Distinct from `default`
|
|
21
|
+
* (a quoted literal). Makes the column optional on insert. */
|
|
22
|
+
readonly defaultExpr?: string;
|
|
19
23
|
/** Migration hint: this column was previously named X. On boot the migrator
|
|
20
24
|
* rebuilds the table, copying data from the old column. A diff cannot tell a
|
|
21
25
|
* rename from a drop+add, so the rename must be declared explicitly. */
|
|
@@ -120,7 +124,26 @@ export declare function unique<F extends FieldDef>(field: F): F & {
|
|
|
120
124
|
export declare function indexed<F extends FieldDef>(field: F): F & {
|
|
121
125
|
readonly index: true;
|
|
122
126
|
};
|
|
123
|
-
/**
|
|
127
|
+
/** A raw-SQL column DEFAULT (emitted unquoted in the DDL), produced by the `expr`
|
|
128
|
+
* helpers below. Distinct from a literal default so `defaultTo` can render
|
|
129
|
+
* `DEFAULT datetime('now')` rather than the quoted string `DEFAULT 'datetime(...)'`. */
|
|
130
|
+
export declare class ExprDefault {
|
|
131
|
+
readonly sql: string;
|
|
132
|
+
constructor(sql: string);
|
|
133
|
+
}
|
|
134
|
+
/** SQL-expression defaults for `defaultTo(field, expr.now())`. `now()` is the current
|
|
135
|
+
* UTC timestamp as TEXT (`'YYYY-MM-DD HH:MM:SS'`, like `CURRENT_TIMESTAMP`) — pair it
|
|
136
|
+
* with `t.text()`. `raw(sql)` is an escape hatch for any other SQLite default expression. */
|
|
137
|
+
export declare const expr: {
|
|
138
|
+
now: () => ExprDefault;
|
|
139
|
+
raw: (sql: string) => ExprDefault;
|
|
140
|
+
};
|
|
141
|
+
/** Give the column a DEFAULT — also makes it optional on insert. Pass a literal
|
|
142
|
+
* (rendered as a quoted SQL literal) or an `expr.*()` value (raw SQL, unquoted),
|
|
143
|
+
* e.g. `defaultTo(t.text(), "pending")` or `defaultTo(t.text(), expr.now())`. */
|
|
144
|
+
export declare function defaultTo<F extends FieldDef>(field: F, value: ExprDefault): F & {
|
|
145
|
+
readonly defaultExpr: string;
|
|
146
|
+
};
|
|
124
147
|
export declare function defaultTo<F extends FieldDef, D extends DefaultValue>(field: F, value: D): F & {
|
|
125
148
|
readonly default: D;
|
|
126
149
|
};
|
package/dist/sdk/schema.js
CHANGED
|
@@ -57,9 +57,24 @@ export function unique(field) {
|
|
|
57
57
|
export function indexed(field) {
|
|
58
58
|
return { ...field, index: true };
|
|
59
59
|
}
|
|
60
|
-
/**
|
|
60
|
+
/** A raw-SQL column DEFAULT (emitted unquoted in the DDL), produced by the `expr`
|
|
61
|
+
* helpers below. Distinct from a literal default so `defaultTo` can render
|
|
62
|
+
* `DEFAULT datetime('now')` rather than the quoted string `DEFAULT 'datetime(...)'`. */
|
|
63
|
+
export class ExprDefault {
|
|
64
|
+
sql;
|
|
65
|
+
constructor(sql) {
|
|
66
|
+
this.sql = sql;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** SQL-expression defaults for `defaultTo(field, expr.now())`. `now()` is the current
|
|
70
|
+
* UTC timestamp as TEXT (`'YYYY-MM-DD HH:MM:SS'`, like `CURRENT_TIMESTAMP`) — pair it
|
|
71
|
+
* with `t.text()`. `raw(sql)` is an escape hatch for any other SQLite default expression. */
|
|
72
|
+
export const expr = {
|
|
73
|
+
now: () => new ExprDefault("datetime('now')"),
|
|
74
|
+
raw: (sql) => new ExprDefault(sql),
|
|
75
|
+
};
|
|
61
76
|
export function defaultTo(field, value) {
|
|
62
|
-
return { ...field, default: value };
|
|
77
|
+
return value instanceof ExprDefault ? { ...field, defaultExpr: value.sql } : { ...field, default: value };
|
|
63
78
|
}
|
|
64
79
|
/** Mark a column as the PRIMARY KEY (implies NOT NULL). Composes with any builder,
|
|
65
80
|
* e.g. `id: primaryKey(generated(t.uuid()))` or `code: primaryKey(t.text())`. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pramen/server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "pramen server runtime — schema, ACL, ORM, live queries, files, and the createPramen(app) factory for Cloudflare Workers + Durable Objects.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// and codegen load an app.ts for its schema without dragging in the DO runtime.
|
|
9
9
|
|
|
10
10
|
// --- schema authoring ---
|
|
11
|
-
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, defaultTo, primaryKey, generated } from "./sdk/schema";
|
|
11
|
+
export { Entity, defineSchema, renamedFrom, notNull, unique, indexed, defaultTo, primaryKey, generated, expr, ExprDefault } from "./sdk/schema";
|
|
12
12
|
export { isValidUuid } from "./sdk/uuid";
|
|
13
13
|
export type {
|
|
14
14
|
DefaultValue,
|
package/src/runtime/ddl.ts
CHANGED
|
@@ -22,10 +22,15 @@ function defaultLiteral(v: DefaultValue): string {
|
|
|
22
22
|
return `'${v.replace(/'/g, "''")}'`;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/** The ` DEFAULT x` fragment for a column, or "" when it has no default.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
/** The ` DEFAULT x` fragment for a column, or "" when it has no default. A raw-SQL
|
|
26
|
+
* `defaultExpr` (e.g. `datetime('now')`) is emitted UNQUOTED; a literal `default` is
|
|
27
|
+
* quote-escaped. UNIQUE/index are NOT inline — they're emitted as separate index
|
|
28
|
+
* statements so the same code path serves both CREATE TABLE and ALTER TABLE ADD COLUMN. */
|
|
28
29
|
function defaultSql(f: FieldDef): string {
|
|
30
|
+
// A raw-SQL default is parenthesized: SQLite's column-DEFAULT grammar only takes a
|
|
31
|
+
// bare literal/keyword, so a function call (e.g. datetime('now')) must be wrapped —
|
|
32
|
+
// `DEFAULT (datetime('now'))`. Parens are harmless around a keyword too.
|
|
33
|
+
if (f.defaultExpr !== undefined) return ` DEFAULT (${f.defaultExpr})`;
|
|
29
34
|
return f.default !== undefined ? ` DEFAULT ${defaultLiteral(f.default)}` : "";
|
|
30
35
|
}
|
|
31
36
|
|
package/src/runtime/migrate.ts
CHANGED
|
@@ -157,8 +157,17 @@ export async function migrate(driver: Driver, schema: SchemaDef, opts: MigrateOp
|
|
|
157
157
|
continue;
|
|
158
158
|
}
|
|
159
159
|
// Pass 1 — additive: add any column the schema declares but the table lacks.
|
|
160
|
+
// SQLite forbids ALTER ADD COLUMN with a non-constant DEFAULT (e.g. expr.now()),
|
|
161
|
+
// so such a column is added via a table rebuild instead — which is still additive
|
|
162
|
+
// (no data loss): the rebuild's INSERT omits the new column, so SQLite applies its
|
|
163
|
+
// CREATE TABLE default, backfilling existing rows. Flagged here, done in Pass 2.
|
|
164
|
+
let needsAdditiveRebuild = false;
|
|
160
165
|
for (const [name, field] of Object.entries(def.fields)) {
|
|
161
166
|
if (existing.has(name)) continue;
|
|
167
|
+
if ((field as FieldDef).defaultExpr !== undefined) {
|
|
168
|
+
needsAdditiveRebuild = true;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
162
171
|
await driver.exec(`ALTER TABLE ${ident(table)} ADD COLUMN ${addColumnSql(name, field as FieldDef)}`, []);
|
|
163
172
|
added.push(`${table}.${name}`);
|
|
164
173
|
}
|
|
@@ -176,13 +185,16 @@ export async function migrate(driver: Driver, schema: SchemaDef, opts: MigrateOp
|
|
|
176
185
|
const needsTypeChange = Object.entries(def.fields).some(
|
|
177
186
|
([n, f]) => live.has(n) && live.get(n) !== sqlType(f as FieldDef),
|
|
178
187
|
);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
188
|
+
const destructive = needsDrop || needsTypeChange || renamedSources.size > 0;
|
|
189
|
+
if (destructive && !allowDestructive) {
|
|
190
|
+
// The destructive part is gated off — skip the whole rebuild (any pending
|
|
191
|
+
// expr-default column waits until destructive migrations are allowed).
|
|
192
|
+
skipped.push(`rebuild ${table} (drop/type-change/rename)`);
|
|
193
|
+
} else if (destructive || needsAdditiveRebuild) {
|
|
194
|
+
// An additive-only rebuild (just an expr-default column) needs no permission —
|
|
195
|
+
// it loses no data.
|
|
196
|
+
await rebuildTable(driver, table, def, live);
|
|
197
|
+
rebuilt.push(table);
|
|
186
198
|
}
|
|
187
199
|
}
|
|
188
200
|
|
package/src/sdk/infer.ts
CHANGED
|
@@ -113,7 +113,8 @@ export type WhereClause<S extends SchemaDef, T extends keyof S, D extends number
|
|
|
113
113
|
export type InferUpdate<F extends EntityFields> = Partial<{ [K in keyof F]: FieldTsType<F[K]> | null }>;
|
|
114
114
|
|
|
115
115
|
// Insert: a NOT NULL column is required unless it's auto-generated (autoIncrement,
|
|
116
|
-
// or a `generated()` uuid the runtime mints) or has a DEFAULT
|
|
116
|
+
// or a `generated()` uuid the runtime mints) or has a DEFAULT — a literal (`default`)
|
|
117
|
+
// or a SQL expression (`defaultExpr`, e.g. expr.now()), both filled by the DB;
|
|
117
118
|
// everything else is optional.
|
|
118
119
|
type RequiredInsertKeys<F extends EntityFields> = {
|
|
119
120
|
[K in keyof F]: IsNotNull<F[K]> extends true
|
|
@@ -123,7 +124,9 @@ type RequiredInsertKeys<F extends EntityFields> = {
|
|
|
123
124
|
? never
|
|
124
125
|
: F[K] extends { default: DefaultValue }
|
|
125
126
|
? never
|
|
126
|
-
: K
|
|
127
|
+
: F[K] extends { defaultExpr: string }
|
|
128
|
+
? never
|
|
129
|
+
: K
|
|
127
130
|
: never;
|
|
128
131
|
}[keyof F];
|
|
129
132
|
type OptionalInsertKeys<F extends EntityFields> = Exclude<keyof F, RequiredInsertKeys<F>>;
|
package/src/sdk/schema.ts
CHANGED
|
@@ -31,6 +31,10 @@ export interface FieldDef {
|
|
|
31
31
|
readonly generated?: boolean;
|
|
32
32
|
/** A column DEFAULT (a literal). Makes the column optional on insert. */
|
|
33
33
|
readonly default?: DefaultValue;
|
|
34
|
+
/** A column DEFAULT that is raw SQL, emitted UNQUOTED (e.g. `datetime('now')`) —
|
|
35
|
+
* set by `defaultTo(field, expr.now())`/`expr.raw(...)`. Distinct from `default`
|
|
36
|
+
* (a quoted literal). Makes the column optional on insert. */
|
|
37
|
+
readonly defaultExpr?: string;
|
|
34
38
|
/** Migration hint: this column was previously named X. On boot the migrator
|
|
35
39
|
* rebuilds the table, copying data from the old column. A diff cannot tell a
|
|
36
40
|
* rename from a drop+add, so the rename must be declared explicitly. */
|
|
@@ -130,9 +134,28 @@ export function indexed<F extends FieldDef>(field: F): F & { readonly index: tru
|
|
|
130
134
|
return { ...field, index: true };
|
|
131
135
|
}
|
|
132
136
|
|
|
133
|
-
/**
|
|
134
|
-
|
|
135
|
-
|
|
137
|
+
/** A raw-SQL column DEFAULT (emitted unquoted in the DDL), produced by the `expr`
|
|
138
|
+
* helpers below. Distinct from a literal default so `defaultTo` can render
|
|
139
|
+
* `DEFAULT datetime('now')` rather than the quoted string `DEFAULT 'datetime(...)'`. */
|
|
140
|
+
export class ExprDefault {
|
|
141
|
+
constructor(readonly sql: string) {}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** SQL-expression defaults for `defaultTo(field, expr.now())`. `now()` is the current
|
|
145
|
+
* UTC timestamp as TEXT (`'YYYY-MM-DD HH:MM:SS'`, like `CURRENT_TIMESTAMP`) — pair it
|
|
146
|
+
* with `t.text()`. `raw(sql)` is an escape hatch for any other SQLite default expression. */
|
|
147
|
+
export const expr = {
|
|
148
|
+
now: (): ExprDefault => new ExprDefault("datetime('now')"),
|
|
149
|
+
raw: (sql: string): ExprDefault => new ExprDefault(sql),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/** Give the column a DEFAULT — also makes it optional on insert. Pass a literal
|
|
153
|
+
* (rendered as a quoted SQL literal) or an `expr.*()` value (raw SQL, unquoted),
|
|
154
|
+
* e.g. `defaultTo(t.text(), "pending")` or `defaultTo(t.text(), expr.now())`. */
|
|
155
|
+
export function defaultTo<F extends FieldDef>(field: F, value: ExprDefault): F & { readonly defaultExpr: string };
|
|
156
|
+
export function defaultTo<F extends FieldDef, D extends DefaultValue>(field: F, value: D): F & { readonly default: D };
|
|
157
|
+
export function defaultTo<F extends FieldDef>(field: F, value: DefaultValue | ExprDefault): FieldDef {
|
|
158
|
+
return value instanceof ExprDefault ? { ...field, defaultExpr: value.sql } : { ...field, default: value };
|
|
136
159
|
}
|
|
137
160
|
|
|
138
161
|
/** Mark a column as the PRIMARY KEY (implies NOT NULL). Composes with any builder,
|