@omg-dev/server 0.4.24
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.mjs +3278 -0
- package/dist/trigger-scan.mjs +95 -0
- package/package.json +40 -0
- package/src/auto-crud.ts +244 -0
- package/src/billing.ts +297 -0
- package/src/broker.ts +85 -0
- package/src/codec.ts +41 -0
- package/src/ctx.ts +33 -0
- package/src/db.ts +258 -0
- package/src/dispatcher.ts +257 -0
- package/src/http-error.ts +20 -0
- package/src/index.ts +702 -0
- package/src/migrator.ts +167 -0
- package/src/notifications.ts +628 -0
- package/src/predicate.ts +440 -0
- package/src/storage.ts +384 -0
- package/src/subscriptions.ts +654 -0
- package/src/test/auto-crud.test.ts +385 -0
- package/src/test/dispatcher.test.ts +271 -0
- package/src/test/migrator.test.ts +166 -0
- package/src/test/notifications.test.ts +96 -0
- package/src/test/predicate.test.ts +267 -0
- package/src/test/schema-swap.test.ts +252 -0
- package/src/test/security.test.ts +323 -0
- package/src/test/subscriptions.test.ts +878 -0
- package/src/test/trigger-scan.test.ts +78 -0
- package/src/trigger-scan.ts +173 -0
- package/src/triggers.ts +837 -0
- package/src/web-push.d.ts +18 -0
- package/src/workflows.test.ts +127 -0
- package/src/workflows.ts +438 -0
package/src/migrator.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
import { schemaDiff, schemaToSQL, type Schema, type CollectionConfig } from "@omg-dev/schema"
|
|
3
|
+
import { markScoped } from "./db.ts"
|
|
4
|
+
|
|
5
|
+
// ── migrate ───────────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register which tables are user-scoped so db.ts filters by _owner.
|
|
9
|
+
* MUST be called at runtime even when migrations are skipped — otherwise
|
|
10
|
+
* scoped collections silently leak data across users.
|
|
11
|
+
*/
|
|
12
|
+
export function registerScopes(schema: Schema): void {
|
|
13
|
+
for (const [tableName, col] of Object.entries(schema.collections)) {
|
|
14
|
+
if ((col as CollectionConfig).scope === "user") {
|
|
15
|
+
markScoped(tableName)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function migrate(bunDb: Database, schema: Schema): void {
|
|
21
|
+
console.log("[vibes:migrator] Running migrations...")
|
|
22
|
+
|
|
23
|
+
registerScopes(schema)
|
|
24
|
+
|
|
25
|
+
// Read existing tables from sqlite_master
|
|
26
|
+
const existingTablesResult = bunDb
|
|
27
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
|
|
28
|
+
.all() as { name: string }[]
|
|
29
|
+
|
|
30
|
+
const existingTableNames = new Set(existingTablesResult.map(r => r.name))
|
|
31
|
+
|
|
32
|
+
// Build old schema from existing DB structure
|
|
33
|
+
const oldCollections: Record<string, {
|
|
34
|
+
fields: Record<string, { type: "string" }>
|
|
35
|
+
scope: "global"
|
|
36
|
+
indexes: never[]
|
|
37
|
+
uniqueIndexes: never[]
|
|
38
|
+
}> = {}
|
|
39
|
+
for (const tableName of existingTableNames) {
|
|
40
|
+
const columns = bunDb
|
|
41
|
+
.prepare(`PRAGMA table_info(${tableName})`)
|
|
42
|
+
.all() as { name: string; type: string }[]
|
|
43
|
+
|
|
44
|
+
const userFields: Record<string, { type: "string" }> = {}
|
|
45
|
+
for (const col of columns) {
|
|
46
|
+
if (["id", "created_at", "updated_at", "_owner"].includes(col.name)) continue
|
|
47
|
+
userFields[col.name] = { type: "string" }
|
|
48
|
+
}
|
|
49
|
+
oldCollections[tableName] = {
|
|
50
|
+
fields: userFields,
|
|
51
|
+
scope: "global",
|
|
52
|
+
indexes: [],
|
|
53
|
+
uniqueIndexes: [],
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const oldSchema: Schema = { collections: oldCollections }
|
|
58
|
+
|
|
59
|
+
// If no existing tables, just run the full CREATE TABLE statements
|
|
60
|
+
if (existingTableNames.size === 0) {
|
|
61
|
+
const sqls = schemaToSQL(schema)
|
|
62
|
+
for (const sql of sqls) {
|
|
63
|
+
console.log(`[vibes:migrator] Executing: ${sql.split("\n")[0]}...`)
|
|
64
|
+
bunDb.exec(sql)
|
|
65
|
+
}
|
|
66
|
+
console.log("[vibes:migrator] Initial migration complete.")
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Ensure the implicit `_owner` column exists on any pre-existing table that
|
|
71
|
+
// is now scope:"user". `_owner` is NOT a declared field, so schemaDiff never
|
|
72
|
+
// emits an add_column for it — yet schemaToSQL adds it on fresh CREATEs. A
|
|
73
|
+
// table first created while global (no _owner) that later becomes scoped
|
|
74
|
+
// would otherwise (a) crash any `.index("_owner", …)` add_index below with
|
|
75
|
+
// "no such column: _owner", and (b) silently lose WS owner-filtering when no
|
|
76
|
+
// other diff exists (the migrations.length === 0 early-return). Add it up
|
|
77
|
+
// front so both the index pass and runtime filtering work. This is the
|
|
78
|
+
// global→user transition path the default-private flip (schema 0.3.0) made
|
|
79
|
+
// reachable for any collection scoped after its table already existed.
|
|
80
|
+
for (const [tableName, col] of Object.entries(schema.collections)) {
|
|
81
|
+
if ((col as CollectionConfig).scope !== "user") continue
|
|
82
|
+
if (!existingTableNames.has(tableName)) continue
|
|
83
|
+
const cols = bunDb
|
|
84
|
+
.prepare(`PRAGMA table_info(${tableName})`)
|
|
85
|
+
.all() as { name: string }[]
|
|
86
|
+
if (cols.some(c => c.name === "_owner")) continue
|
|
87
|
+
console.log(`[vibes:migrator] ADD COLUMN ${tableName}._owner (scope→user)`)
|
|
88
|
+
bunDb.exec(`ALTER TABLE ${tableName} ADD COLUMN _owner TEXT`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Compute diff and apply safe migrations
|
|
92
|
+
const migrations = schemaDiff(oldSchema, schema)
|
|
93
|
+
|
|
94
|
+
if (migrations.length === 0) {
|
|
95
|
+
console.log("[vibes:migrator] Schema is up to date.")
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (const migration of migrations) {
|
|
100
|
+
switch (migration.type) {
|
|
101
|
+
case "create_table": {
|
|
102
|
+
// Use schemaToSQL for this table only
|
|
103
|
+
const partial: Schema = {
|
|
104
|
+
collections: { [migration.table]: schema.collections[migration.table] },
|
|
105
|
+
}
|
|
106
|
+
const sqls = schemaToSQL(partial)
|
|
107
|
+
for (const sql of sqls) {
|
|
108
|
+
console.log(`[vibes:migrator] CREATE TABLE ${migration.table}`)
|
|
109
|
+
bunDb.exec(sql)
|
|
110
|
+
}
|
|
111
|
+
break
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
case "add_column": {
|
|
115
|
+
// SQLite supports ADD COLUMN
|
|
116
|
+
const colType = fieldTypeToSQL(migration.field.type)
|
|
117
|
+
const sql = `ALTER TABLE ${migration.table} ADD COLUMN ${migration.column} ${colType}`
|
|
118
|
+
console.log(`[vibes:migrator] ADD COLUMN ${migration.table}.${migration.column}`)
|
|
119
|
+
bunDb.exec(sql)
|
|
120
|
+
break
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
case "drop_table": {
|
|
124
|
+
// Safety: we log but skip destructive drops in automatic migrations
|
|
125
|
+
console.warn(
|
|
126
|
+
`[vibes:migrator] SKIP drop_table ${migration.table} — run manually if intended`
|
|
127
|
+
)
|
|
128
|
+
break
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
case "drop_column": {
|
|
132
|
+
// SQLite doesn't support DROP COLUMN in older versions — skip
|
|
133
|
+
console.warn(
|
|
134
|
+
`[vibes:migrator] SKIP drop_column ${migration.table}.${migration.column} — run manually if intended`
|
|
135
|
+
)
|
|
136
|
+
break
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
case "add_index": {
|
|
140
|
+
const indexName = `idx_${migration.table}_${migration.columns.join("_")}`
|
|
141
|
+
const sql = `CREATE INDEX IF NOT EXISTS ${indexName} ON ${migration.table} (${migration.columns.join(", ")})`
|
|
142
|
+
console.log(`[vibes:migrator] ADD INDEX ${indexName}`)
|
|
143
|
+
bunDb.exec(sql)
|
|
144
|
+
break
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
case "add_unique_index": {
|
|
148
|
+
const indexName = `uidx_${migration.table}_${migration.columns.join("_")}`
|
|
149
|
+
const sql = `CREATE UNIQUE INDEX IF NOT EXISTS ${indexName} ON ${migration.table} (${migration.columns.join(", ")})`
|
|
150
|
+
console.log(`[vibes:migrator] ADD UNIQUE INDEX ${indexName}`)
|
|
151
|
+
bunDb.exec(sql)
|
|
152
|
+
break
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log(`[vibes:migrator] Applied ${migrations.length} migration(s).`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function fieldTypeToSQL(type: unknown): string {
|
|
161
|
+
if (type === "string") return "TEXT"
|
|
162
|
+
if (type === "number") return "REAL"
|
|
163
|
+
if (type === "boolean") return "INTEGER"
|
|
164
|
+
if (type === "date") return "TEXT"
|
|
165
|
+
if (typeof type === "object" && type !== null) return "TEXT"
|
|
166
|
+
return "TEXT"
|
|
167
|
+
}
|