@bakery-framework/orm 1.0.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 +19 -0
- package/README.md +88 -0
- package/package.json +59 -0
- package/src/adapters/base.ts +1128 -0
- package/src/adapters/mysql.ts +619 -0
- package/src/adapters/observe.ts +261 -0
- package/src/adapters/pgsql.ts +611 -0
- package/src/adapters/registry.ts +204 -0
- package/src/adapters/sqlite.ts +588 -0
- package/src/adapters.ts +72 -0
- package/src/backup.ts +37 -0
- package/src/connection.ts +69 -0
- package/src/define.ts +380 -0
- package/src/field.ts +595 -0
- package/src/globals.d.ts +22 -0
- package/src/index.ts +63 -0
- package/src/orm/index.ts +24 -0
- package/src/orm/mutation.ts +692 -0
- package/src/orm/query.ts +1680 -0
- package/src/pool.ts +83 -0
- package/src/schema-registry.ts +75 -0
- package/src/schema-util.ts +467 -0
- package/src/sync/builder.ts +618 -0
- package/src/sync/engine.ts +399 -0
- package/src/sync/helpers.ts +1119 -0
- package/src/sync/history.ts +169 -0
- package/src/sync/index.ts +113 -0
- package/src/sync/ledger.ts +335 -0
- package/src/sync/load.ts +368 -0
- package/src/sync/rollback.ts +200 -0
- package/src/sync/types.ts +101 -0
- package/src/sync/view-sql.ts +160 -0
- package/templates/schema.example.ts +94 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
|
+
import { Try } from '@bakery-framework/core/utils'
|
|
3
|
+
import { createDbAdapter, type SQLAdapter } from './adapters'
|
|
4
|
+
|
|
5
|
+
let dbCache: any = null
|
|
6
|
+
let dbPromise: Promise<any> | null = null
|
|
7
|
+
let testDb: any = null
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Test seam. Unit tests that only need a stub adapter should call this instead
|
|
11
|
+
* of `mock.module('./connection', …)` — Bun's module mocks are process-global
|
|
12
|
+
* and are never restored, so one mocking test file silently breaks every later
|
|
13
|
+
* test that needs a real connection.
|
|
14
|
+
*/
|
|
15
|
+
export function __setTestDb(db: unknown): void {
|
|
16
|
+
testDb = db
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function __resetTestDb(): void {
|
|
20
|
+
testDb = null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function initDB(): Promise<SQLAdapter> {
|
|
24
|
+
if (dbPromise) return dbPromise
|
|
25
|
+
|
|
26
|
+
dbPromise = (async () => {
|
|
27
|
+
dbCache = await createDbAdapter()
|
|
28
|
+
return dbCache
|
|
29
|
+
})().catch(error => {
|
|
30
|
+
// Don't cache the rejection: a transient failure at boot would otherwise
|
|
31
|
+
// make every later initDB() return the same error for the process lifetime,
|
|
32
|
+
// with no way to reconnect short of a restart.
|
|
33
|
+
dbPromise = null
|
|
34
|
+
throw error
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return dbPromise
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function closeDB(): Promise<void> {
|
|
41
|
+
if (dbCache) {
|
|
42
|
+
await Try.catch(() => dbCache.close())
|
|
43
|
+
dbCache = null
|
|
44
|
+
dbPromise = null
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getConn() {
|
|
49
|
+
if (testDb) return testDb
|
|
50
|
+
if (!dbCache) {
|
|
51
|
+
throw new Error("DB not initialized. Call 'await initDB()' first.")
|
|
52
|
+
}
|
|
53
|
+
return dbCache
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const connection: SQLAdapter = new Proxy({} as SQLAdapter, {
|
|
57
|
+
// Resolve once per access: this is the hottest path in the ORM, and calling
|
|
58
|
+
// getConn() for both the target and the receiver doubled the work.
|
|
59
|
+
get(_, prop) {
|
|
60
|
+
const conn = getConn()
|
|
61
|
+
return Reflect.get(conn, prop, conn)
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
export const txStorage = new AsyncLocalStorage<any>()
|
|
66
|
+
|
|
67
|
+
export function getActiveDb(): SQLAdapter {
|
|
68
|
+
return txStorage.getStore() ?? connection
|
|
69
|
+
}
|
package/src/define.ts
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtractOptionals,
|
|
3
|
+
ExtractTableTypes,
|
|
4
|
+
ExtractViews,
|
|
5
|
+
} from './schema-util'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Prototype: tables as values.
|
|
9
|
+
*
|
|
10
|
+
* Today a schema is one `constraints` object plus a hand-written `DBSchema`
|
|
11
|
+
* type, and everything that references a table — indexes, foreign keys, query
|
|
12
|
+
* columns — does so by string. Typos surface at sync time, or not at all.
|
|
13
|
+
*
|
|
14
|
+
* Here a table is a value carrying its own name and columns, so `indexes.ts`
|
|
15
|
+
* and `foreign.ts` can import it, rename-symbol works across files, and an
|
|
16
|
+
* identifier reaching SQL is one the framework constructed rather than one a
|
|
17
|
+
* caller typed.
|
|
18
|
+
*
|
|
19
|
+
* The column descriptors are unchanged — the `Field` builders and the
|
|
20
|
+
* `ExtractTableTypes` mapping are reused as-is, so this is a restructuring of
|
|
21
|
+
* how tables are *declared*, not of how their types are computed. That is what
|
|
22
|
+
* keeps `DB.table('users')` autocompleting exactly as it does now: the derived
|
|
23
|
+
* `DBSchema` has the same shape the hand-written one had.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** Columns as declared: `{ id: Field.Primary(), name: Field.Text(true) }`. */
|
|
27
|
+
export type ColumnMap = Record<string, unknown>
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A declared table: the name columns qualify against, the table actually read
|
|
31
|
+
* from, and the columns themselves.
|
|
32
|
+
*
|
|
33
|
+
* **Was `TableDef`, and the rename is the point.** `schema-util.ts` exports a
|
|
34
|
+
* `TableDef<T, N, O>` describing a *column* — and that is the one every
|
|
35
|
+
* internal call site, every test and every schema file means. Both were public,
|
|
36
|
+
* and the root barrel re-exported *this* one, so `import type { TableDef } from
|
|
37
|
+
* '@bakery-framework/orm'` handed you a table where you asked for a column. Nothing
|
|
38
|
+
* errored at the import, and nothing errored until you tried to use it.
|
|
39
|
+
*
|
|
40
|
+
* `TableRef` also says what it is, and pairs with `TableColumn` below.
|
|
41
|
+
*/
|
|
42
|
+
export interface TableRef<
|
|
43
|
+
N extends string = string,
|
|
44
|
+
C extends ColumnMap = ColumnMap,
|
|
45
|
+
> {
|
|
46
|
+
/** The name columns qualify against — the alias, when aliased. */
|
|
47
|
+
readonly __table: N
|
|
48
|
+
/** The real table to read FROM. Differs from `__table` only for an alias. */
|
|
49
|
+
readonly __source: string
|
|
50
|
+
readonly __columns: C
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A reference to one column, carrying its table. This is the value that
|
|
55
|
+
* replaces the `'users.id'` string — `qId` receives structured data rather
|
|
56
|
+
* than text that has to be validated before it can be trusted.
|
|
57
|
+
*/
|
|
58
|
+
export interface TableColumn<
|
|
59
|
+
N extends string = string,
|
|
60
|
+
K extends string = string,
|
|
61
|
+
> {
|
|
62
|
+
readonly __table: N
|
|
63
|
+
readonly __column: K
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Declare a table. The name is explicit rather than inferred from the export
|
|
68
|
+
* binding: deriving it would need a build step or a Proxy, and neither is
|
|
69
|
+
* worth the indirection for one string.
|
|
70
|
+
*/
|
|
71
|
+
export function table<N extends string, C extends ColumnMap>(
|
|
72
|
+
name: N,
|
|
73
|
+
columns: C,
|
|
74
|
+
): TableRef<N, C> & { readonly [K in keyof C]: TableColumn<N, K & string> } {
|
|
75
|
+
const refs = Object.fromEntries(
|
|
76
|
+
Object.keys(columns).map(key => [key, { __table: name, __column: key }]),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return Object.assign(Object.create(null), refs, {
|
|
80
|
+
__table: name,
|
|
81
|
+
__source: name,
|
|
82
|
+
__columns: columns,
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Declare a view — a stored `SELECT` the database treats as a table.
|
|
88
|
+
*
|
|
89
|
+
* export const activeUsers = view(
|
|
90
|
+
* 'active_users',
|
|
91
|
+
* 'SELECT id, name FROM users WHERE active = 1',
|
|
92
|
+
* { id: Field.Primary(), name: Field.Varchar(64) },
|
|
93
|
+
* )
|
|
94
|
+
*
|
|
95
|
+
* The columns are the shape the `SELECT` returns. They are declared rather than
|
|
96
|
+
* inferred because nothing here parses SQL, and they are what gives the view a
|
|
97
|
+
* row type — reading from it is typed exactly like reading a table.
|
|
98
|
+
*
|
|
99
|
+
* **Writes are rejected at compile time.** `InferViews` collects these names and
|
|
100
|
+
* `Mutation.Tables` excludes them, so `DB.Insert.into('active_users')` does not
|
|
101
|
+
* typecheck. A view is a `SELECT`; the database would refuse the write anyway,
|
|
102
|
+
* and refusing it earlier is strictly better.
|
|
103
|
+
*
|
|
104
|
+
* `db:sync` emits `CREATE VIEW`, diffs the body as normalised text, and drops
|
|
105
|
+
* and recreates the view when it changes — views hold no data, so recreating is
|
|
106
|
+
* free and there is no migration to plan.
|
|
107
|
+
*
|
|
108
|
+
* Previously declarable only in the older `DBInfo` layout, or here by writing
|
|
109
|
+
* `_view` into a `table()` call and casting. The key is the same; this just
|
|
110
|
+
* types it.
|
|
111
|
+
*/
|
|
112
|
+
export function view<N extends string, C extends ColumnMap>(
|
|
113
|
+
name: N,
|
|
114
|
+
body: string,
|
|
115
|
+
columns: C,
|
|
116
|
+
): TableRef<N, C & { _view: string }> & {
|
|
117
|
+
readonly [K in keyof C]: TableColumn<N, K & string>
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* A view over one table, borrowing its columns.
|
|
121
|
+
*
|
|
122
|
+
* export const activeUsers = view('active_users', users, 'SELECT * FROM users WHERE active = 1')
|
|
123
|
+
*
|
|
124
|
+
* The filtered-view case, which is the common one: the shape is the source
|
|
125
|
+
* table's, so restating it is duplication that nothing checks — declare a
|
|
126
|
+
* column the `SELECT` does not return and you find out at query time.
|
|
127
|
+
*
|
|
128
|
+
* The source is a **value**, not a type argument, and that is forced rather
|
|
129
|
+
* than chosen. `view<typeof users>(name, body)` cannot work: TypeScript stops
|
|
130
|
+
* inferring the *remaining* type parameters as soon as one is supplied
|
|
131
|
+
* explicitly, so `N` would fall back to `string` and the view's name would stop
|
|
132
|
+
* being a literal. `__table` is what `InferConstraints` keys the schema map on,
|
|
133
|
+
* so that name degrading takes `InferViews` with it — and since
|
|
134
|
+
* `Mutation.Tables` now excludes views, `Exclude<…, string>` is `never` and
|
|
135
|
+
* *every* mutation stops compiling. Passing the table keeps both inferred.
|
|
136
|
+
*
|
|
137
|
+
* Projecting a subset? Use the three-argument form and name the columns.
|
|
138
|
+
*/
|
|
139
|
+
export function view<N extends string, C extends ColumnMap>(
|
|
140
|
+
name: N,
|
|
141
|
+
source: TableRef<string, C>,
|
|
142
|
+
body: string,
|
|
143
|
+
): TableRef<N, C & { _view: string }> & {
|
|
144
|
+
readonly [K in keyof C]: TableColumn<N, K & string>
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* A view described by an interface rather than by column builders.
|
|
148
|
+
*
|
|
149
|
+
* export interface ActiveUsersView {
|
|
150
|
+
* id: number
|
|
151
|
+
* name: string
|
|
152
|
+
* }
|
|
153
|
+
*
|
|
154
|
+
* export const activeUsers = view<'active_users', ActiveUsersView>(
|
|
155
|
+
* 'active_users',
|
|
156
|
+
* 'SELECT id, name FROM users WHERE active = 1',
|
|
157
|
+
* )
|
|
158
|
+
*
|
|
159
|
+
* This is what `db:sync --choose=db` writes into `orm/views.ts`, and it is the
|
|
160
|
+
* honest shape for a view: **a view has no column DDL.** `CREATE VIEW x AS
|
|
161
|
+
* SELECT …` declares no types, and the sync engine only ever reads the body —
|
|
162
|
+
* `createView(name, sql)` takes nothing else, and the diff compares the two
|
|
163
|
+
* bodies as text. So a view's columns exist purely to give it a row type, and
|
|
164
|
+
* writing `Field.Varchar(64)` there would imply a width the database neither
|
|
165
|
+
* stores nor enforces.
|
|
166
|
+
*
|
|
167
|
+
* **Both type arguments are given, and that is forced.** TypeScript stops
|
|
168
|
+
* inferring the remaining type parameters as soon as one is supplied, so
|
|
169
|
+
* `view<ActiveUsersView>(name, body)` would leave `N` as `string` — and `N` is
|
|
170
|
+
* what `TablesOf` re-keys the schema map on, so the whole map collapses to an
|
|
171
|
+
* index signature and every mutation stops compiling. Naming both keeps it a
|
|
172
|
+
* literal. In generated code the repetition costs nothing.
|
|
173
|
+
*
|
|
174
|
+
* Column references still work — `activeUsers.id` — even though the keys are
|
|
175
|
+
* known only to the type. See the implementation.
|
|
176
|
+
*/
|
|
177
|
+
export function view<N extends string, T>(
|
|
178
|
+
name: N,
|
|
179
|
+
body: string,
|
|
180
|
+
): TableRef<N, ViewColumns<T>> & {
|
|
181
|
+
readonly [K in keyof T]: TableColumn<N, K & string>
|
|
182
|
+
}
|
|
183
|
+
export function view(
|
|
184
|
+
name: string,
|
|
185
|
+
bodyOrSource: string | TableRef,
|
|
186
|
+
columnsOrBody?: ColumnMap | string,
|
|
187
|
+
): unknown {
|
|
188
|
+
// Two arguments means the interface form: the columns are type-only, so the
|
|
189
|
+
// runtime object carries the body and nothing else.
|
|
190
|
+
if (columnsOrBody === undefined && typeof bodyOrSource === 'string') {
|
|
191
|
+
return viewFromType(name, bodyOrSource)
|
|
192
|
+
}
|
|
193
|
+
// The second argument tells the two forms apart: a `SELECT` string, or the
|
|
194
|
+
// table to borrow columns from.
|
|
195
|
+
const derived = typeof bodyOrSource !== 'string'
|
|
196
|
+
const body = derived ? (columnsOrBody as string) : bodyOrSource
|
|
197
|
+
const columns = derived
|
|
198
|
+
? (bodyOrSource as TableRef).__columns
|
|
199
|
+
: (columnsOrBody as ColumnMap)
|
|
200
|
+
return viewImpl(name, body, columns)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* A row interface, as the descriptor map `ExtractTableTypes` reads.
|
|
205
|
+
*
|
|
206
|
+
* One `{ type: T[K] }` per property — which is all a descriptor needs now that
|
|
207
|
+
* `type` carries the row type — plus the `_view` marker that makes
|
|
208
|
+
* `ExtractViews` classify it as a view.
|
|
209
|
+
*/
|
|
210
|
+
type ViewColumns<T> = { [K in keyof T]-?: { type: T[K] } } & { _view: string }
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The interface form's runtime value.
|
|
214
|
+
*
|
|
215
|
+
* The column keys live only in the type, so the refs cannot be enumerated the
|
|
216
|
+
* way `table()` enumerates them. A `Proxy` answers for any property instead,
|
|
217
|
+
* which is exactly as correct here: a ref is `{ __table, __column }` computed
|
|
218
|
+
* from the key, and the key is whatever was asked for. The type is what
|
|
219
|
+
* restricts *which* keys are askable.
|
|
220
|
+
*
|
|
221
|
+
* `__table`, `__source` and `__columns` are answered from the real object so
|
|
222
|
+
* `collectConstraints` and the sync engine see what they expect.
|
|
223
|
+
*/
|
|
224
|
+
function viewFromType(name: string, body: string): unknown {
|
|
225
|
+
const base: Record<string, unknown> = {
|
|
226
|
+
__table: name,
|
|
227
|
+
__source: name,
|
|
228
|
+
__columns: { _view: body },
|
|
229
|
+
}
|
|
230
|
+
return new Proxy(base, {
|
|
231
|
+
get(target, prop) {
|
|
232
|
+
if (typeof prop !== 'string' || prop in target) {
|
|
233
|
+
return Reflect.get(target, prop)
|
|
234
|
+
}
|
|
235
|
+
return { __table: name, __column: prop }
|
|
236
|
+
},
|
|
237
|
+
// Without this the sync engine's `Object.keys`/spread would see the three
|
|
238
|
+
// internals as ordinary columns.
|
|
239
|
+
ownKeys: target => Reflect.ownKeys(target),
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function viewImpl<N extends string, C extends ColumnMap>(
|
|
244
|
+
name: N,
|
|
245
|
+
body: string,
|
|
246
|
+
columns: C,
|
|
247
|
+
): TableRef<N, C & { _view: string }> & {
|
|
248
|
+
readonly [K in keyof C]: TableColumn<N, K & string>
|
|
249
|
+
} {
|
|
250
|
+
// `_view` rides inside `__columns` because that is the object the sync engine
|
|
251
|
+
// receives, and it is where `ExtractViews` and the adapters already look for
|
|
252
|
+
// it. Adding a sibling field would mean teaching `collectConstraints`, the
|
|
253
|
+
// diff and three adapters about a second place to check.
|
|
254
|
+
//
|
|
255
|
+
// It has to be in `__columns`'s *declared type* too, not only at runtime:
|
|
256
|
+
// `ExtractViews` is what `InferViews` reads and what `Mutation.Tables`
|
|
257
|
+
// excludes, so erasing `_view` from the type left writes to a view
|
|
258
|
+
// compiling — the exact thing declaring one is supposed to prevent.
|
|
259
|
+
// `ExtractTableTypes` filters the key out of the row type separately.
|
|
260
|
+
return Object.assign(table(name, columns), {
|
|
261
|
+
__columns: { _view: body, ...columns },
|
|
262
|
+
}) as any
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Alias a table for a join, so the same table can appear twice in one query.
|
|
267
|
+
*
|
|
268
|
+
* This is the object-form answer to `join('users.id', ..., 'author')` followed
|
|
269
|
+
* by `'author.username'`. It is not a downgrade: with strings the alias is
|
|
270
|
+
* declared in one place and referenced as text everywhere else, and nothing
|
|
271
|
+
* checks that they agree. Here the alias *is* the value you use, so a typo is
|
|
272
|
+
* a compile error and rename-symbol reaches every usage.
|
|
273
|
+
*
|
|
274
|
+
* Columns re-qualify against the alias while `__source` remembers the real
|
|
275
|
+
* table, which is what lets the builder emit `FROM users AS author`.
|
|
276
|
+
*/
|
|
277
|
+
export function alias<N extends string, C extends ColumnMap, A extends string>(
|
|
278
|
+
base: TableRef<N, C>,
|
|
279
|
+
name: A,
|
|
280
|
+
): TableRef<A, C> & { readonly [K in keyof C]: TableColumn<A, K & string> } {
|
|
281
|
+
const refs = Object.fromEntries(
|
|
282
|
+
Object.keys(base.__columns).map(key => [
|
|
283
|
+
key,
|
|
284
|
+
{ __table: name, __column: key },
|
|
285
|
+
]),
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
return Object.assign(Object.create(null), refs, {
|
|
289
|
+
__table: name,
|
|
290
|
+
__source: base.__source,
|
|
291
|
+
__columns: base.__columns,
|
|
292
|
+
})
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Every `TableRef` exported by a module, keyed by its declared table name. */
|
|
296
|
+
type TablesOf<M> = {
|
|
297
|
+
[K in keyof M as M[K] extends TableRef<infer N, any> ? N : never]: M[K]
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* The `constraints` shape the sync engine already consumes, rebuilt from
|
|
302
|
+
* table values. Keeping this identical is what lets the diff engine, the
|
|
303
|
+
* generator and the query builder stay untouched.
|
|
304
|
+
*/
|
|
305
|
+
export type InferConstraints<M> = {
|
|
306
|
+
[N in keyof TablesOf<M>]: TablesOf<M>[N] extends TableRef<any, infer C>
|
|
307
|
+
? C
|
|
308
|
+
: never
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Row types — the derived replacement for a hand-written `DBSchema`. */
|
|
312
|
+
export type InferSchema<M> = {
|
|
313
|
+
[N in keyof InferConstraints<M>]: ExtractTableTypes<InferConstraints<M>, N>
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Columns optional on insert, derived the same way. */
|
|
317
|
+
export type InferOptionals<M> = {
|
|
318
|
+
[N in keyof InferConstraints<M>]: ExtractOptionals<InferConstraints<M>, N>
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** View names, for exclusion from mutation targets. */
|
|
322
|
+
export type InferViews<M> = ExtractViews<InferConstraints<M>>
|
|
323
|
+
|
|
324
|
+
/** Collect the runtime constraints object the sync engine loads. */
|
|
325
|
+
export function collectConstraints(module: Record<string, unknown>) {
|
|
326
|
+
const constraints: Record<string, unknown> = {}
|
|
327
|
+
|
|
328
|
+
for (const exported of Object.values(module)) {
|
|
329
|
+
if (!exported || typeof exported !== 'object') continue
|
|
330
|
+
const def = exported as TableRef
|
|
331
|
+
if (typeof def.__table !== 'string' || !def.__columns) continue
|
|
332
|
+
|
|
333
|
+
// Skip aliases. An alias is a query-time view of an existing table, and
|
|
334
|
+
// collecting one would tell the sync engine to CREATE a table named after
|
|
335
|
+
// it — so `alias(users, 'author')` would try to build an `author` table.
|
|
336
|
+
if (def.__source !== def.__table) continue
|
|
337
|
+
|
|
338
|
+
constraints[def.__table] = def.__columns
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return constraints
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* The row type of a `table()` or `view()`, for naming.
|
|
346
|
+
*
|
|
347
|
+
* export type ActiveUsersView = RowOf<typeof activeUsers>
|
|
348
|
+
* // ^ { id: number; name: string }
|
|
349
|
+
*
|
|
350
|
+
* TypeScript cannot mint a *named* interface from a value — a name has to be
|
|
351
|
+
* written somewhere — so this is the one line that does it, and it stays
|
|
352
|
+
* correct when the declaration changes because it is derived rather than
|
|
353
|
+
* copied. A hand-written `interface ActiveUsersView` would be a second source
|
|
354
|
+
* of truth that nothing checks against the first.
|
|
355
|
+
*
|
|
356
|
+
* `_view` is filtered out by `ExtractTableTypes`, so a view's row type is its
|
|
357
|
+
* columns and nothing else.
|
|
358
|
+
*/
|
|
359
|
+
export type RowOf<T extends TableRef> = ExtractTableTypes<
|
|
360
|
+
{ t: T['__columns'] },
|
|
361
|
+
't'
|
|
362
|
+
>
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* What an `INSERT` into it accepts: {@link RowOf} with the optional columns
|
|
366
|
+
* made optional.
|
|
367
|
+
*
|
|
368
|
+
* export type NewUser = InsertOf<typeof users>
|
|
369
|
+
* // ^ { name: string; id?: number; createdAt?: number }
|
|
370
|
+
*/
|
|
371
|
+
export type InsertOf<T extends TableRef> = Omit<
|
|
372
|
+
RowOf<T>,
|
|
373
|
+
ExtractOptionals<{ t: T['__columns'] }, 't'> & keyof RowOf<T>
|
|
374
|
+
> &
|
|
375
|
+
Partial<
|
|
376
|
+
Pick<
|
|
377
|
+
RowOf<T>,
|
|
378
|
+
ExtractOptionals<{ t: T['__columns'] }, 't'> & keyof RowOf<T>
|
|
379
|
+
>
|
|
380
|
+
>
|