@livestore/livestore 0.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/README.md +108 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/LiveRiffleStore.d.ts +42 -0
- package/dist/LiveRiffleStore.d.ts.map +1 -0
- package/dist/LiveRiffleStore.js +36 -0
- package/dist/LiveRiffleStore.js.map +1 -0
- package/dist/QueryCache.d.ts +20 -0
- package/dist/QueryCache.d.ts.map +1 -0
- package/dist/QueryCache.js +71 -0
- package/dist/QueryCache.js.map +1 -0
- package/dist/__tests__/react/fixture.d.ts +141 -0
- package/dist/__tests__/react/fixture.d.ts.map +1 -0
- package/dist/__tests__/react/fixture.js +72 -0
- package/dist/__tests__/react/fixture.js.map +1 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.d.ts +2 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.d.ts.map +1 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.js +78 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.js.map +1 -0
- package/dist/__tests__/react/useRiffleComponent.test.d.ts +2 -0
- package/dist/__tests__/react/useRiffleComponent.test.d.ts.map +1 -0
- package/dist/__tests__/react/useRiffleComponent.test.js +78 -0
- package/dist/__tests__/react/useRiffleComponent.test.js.map +1 -0
- package/dist/__tests__/reactive.test.d.ts +2 -0
- package/dist/__tests__/reactive.test.d.ts.map +1 -0
- package/dist/__tests__/reactive.test.js +167 -0
- package/dist/__tests__/reactive.test.js.map +1 -0
- package/dist/backends/base.d.ts +13 -0
- package/dist/backends/base.d.ts.map +1 -0
- package/dist/backends/base.js +53 -0
- package/dist/backends/base.js.map +1 -0
- package/dist/backends/index.d.ts +41 -0
- package/dist/backends/index.d.ts.map +1 -0
- package/dist/backends/index.js +38 -0
- package/dist/backends/index.js.map +1 -0
- package/dist/backends/noop.d.ts +18 -0
- package/dist/backends/noop.d.ts.map +1 -0
- package/dist/backends/noop.js +21 -0
- package/dist/backends/noop.js.map +1 -0
- package/dist/backends/tauri.d.ts +24 -0
- package/dist/backends/tauri.d.ts.map +1 -0
- package/dist/backends/tauri.js +48 -0
- package/dist/backends/tauri.js.map +1 -0
- package/dist/backends/utils/idb.d.ts +10 -0
- package/dist/backends/utils/idb.d.ts.map +1 -0
- package/dist/backends/utils/idb.js +58 -0
- package/dist/backends/utils/idb.js.map +1 -0
- package/dist/backends/web-in-memory.d.ts +24 -0
- package/dist/backends/web-in-memory.d.ts.map +1 -0
- package/dist/backends/web-in-memory.js +46 -0
- package/dist/backends/web-in-memory.js.map +1 -0
- package/dist/backends/web-worker.d.ts +17 -0
- package/dist/backends/web-worker.d.ts.map +1 -0
- package/dist/backends/web-worker.js +139 -0
- package/dist/backends/web-worker.js.map +1 -0
- package/dist/backends/web.d.ts +28 -0
- package/dist/backends/web.d.ts.map +1 -0
- package/dist/backends/web.js +64 -0
- package/dist/backends/web.js.map +1 -0
- package/dist/bounded-collections.d.ts +34 -0
- package/dist/bounded-collections.d.ts.map +1 -0
- package/dist/bounded-collections.js +103 -0
- package/dist/bounded-collections.js.map +1 -0
- package/dist/componentKey.d.ts +20 -0
- package/dist/componentKey.d.ts.map +1 -0
- package/dist/componentKey.js +3 -0
- package/dist/componentKey.js.map +1 -0
- package/dist/effect/LiveStore.d.ts +42 -0
- package/dist/effect/LiveStore.d.ts.map +1 -0
- package/dist/effect/LiveStore.js +36 -0
- package/dist/effect/LiveStore.js.map +1 -0
- package/dist/effect/index.d.ts +2 -0
- package/dist/effect/index.d.ts.map +1 -0
- package/dist/effect/index.js +2 -0
- package/dist/effect/index.js.map +1 -0
- package/dist/events.d.ts +7 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +2 -0
- package/dist/events.js.map +1 -0
- package/dist/inMemoryDatabase.d.ts +65 -0
- package/dist/inMemoryDatabase.d.ts.map +1 -0
- package/dist/inMemoryDatabase.js +241 -0
- package/dist/inMemoryDatabase.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/otel.d.ts +5 -0
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +17 -0
- package/dist/otel.js.map +1 -0
- package/dist/react/LiveStoreContext.d.ts +11 -0
- package/dist/react/LiveStoreContext.d.ts.map +1 -0
- package/dist/react/LiveStoreContext.js +10 -0
- package/dist/react/LiveStoreContext.js.map +1 -0
- package/dist/react/LiveStoreProvider.d.ts +21 -0
- package/dist/react/LiveStoreProvider.d.ts.map +1 -0
- package/dist/react/LiveStoreProvider.js +48 -0
- package/dist/react/LiveStoreProvider.js.map +1 -0
- package/dist/react/RiffleProvider.d.ts +21 -0
- package/dist/react/RiffleProvider.d.ts.map +1 -0
- package/dist/react/RiffleProvider.js +48 -0
- package/dist/react/RiffleProvider.js.map +1 -0
- package/dist/react/StoreContext.d.ts +11 -0
- package/dist/react/StoreContext.d.ts.map +1 -0
- package/dist/react/StoreContext.js +10 -0
- package/dist/react/StoreContext.js.map +1 -0
- package/dist/react/index.d.ts +7 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +6 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/useGlobalQuery.d.ts +3 -0
- package/dist/react/useGlobalQuery.d.ts.map +1 -0
- package/dist/react/useGlobalQuery.js +25 -0
- package/dist/react/useGlobalQuery.js.map +1 -0
- package/dist/react/useGraphQL.d.ts +11 -0
- package/dist/react/useGraphQL.d.ts.map +1 -0
- package/dist/react/useGraphQL.js +68 -0
- package/dist/react/useGraphQL.js.map +1 -0
- package/dist/react/useLiveStoreComponent.d.ts +70 -0
- package/dist/react/useLiveStoreComponent.d.ts.map +1 -0
- package/dist/react/useLiveStoreComponent.js +261 -0
- package/dist/react/useLiveStoreComponent.js.map +1 -0
- package/dist/react/useRiffleComponent.d.ts +70 -0
- package/dist/react/useRiffleComponent.d.ts.map +1 -0
- package/dist/react/useRiffleComponent.js +261 -0
- package/dist/react/useRiffleComponent.js.map +1 -0
- package/dist/react/useRiffleJsonHook.d.ts +4 -0
- package/dist/react/useRiffleJsonHook.d.ts.map +1 -0
- package/dist/react/useRiffleJsonHook.js +21 -0
- package/dist/react/useRiffleJsonHook.js.map +1 -0
- package/dist/react/utils/useStateRefWithReactiveInput.d.ts +13 -0
- package/dist/react/utils/useStateRefWithReactiveInput.d.ts.map +1 -0
- package/dist/react/utils/useStateRefWithReactiveInput.js +38 -0
- package/dist/react/utils/useStateRefWithReactiveInput.js.map +1 -0
- package/dist/reactive.d.ts +140 -0
- package/dist/reactive.d.ts.map +1 -0
- package/dist/reactive.js +301 -0
- package/dist/reactive.js.map +1 -0
- package/dist/reactiveQueries/base-class.d.ts +24 -0
- package/dist/reactiveQueries/base-class.d.ts.map +1 -0
- package/dist/reactiveQueries/base-class.js +22 -0
- package/dist/reactiveQueries/base-class.js.map +1 -0
- package/dist/reactiveQueries/graphql.d.ts +25 -0
- package/dist/reactiveQueries/graphql.d.ts.map +1 -0
- package/dist/reactiveQueries/graphql.js +14 -0
- package/dist/reactiveQueries/graphql.js.map +1 -0
- package/dist/reactiveQueries/js.d.ts +19 -0
- package/dist/reactiveQueries/js.d.ts.map +1 -0
- package/dist/reactiveQueries/js.js +13 -0
- package/dist/reactiveQueries/js.js.map +1 -0
- package/dist/reactiveQueries/sql.d.ts +31 -0
- package/dist/reactiveQueries/sql.d.ts.map +1 -0
- package/dist/reactiveQueries/sql.js +28 -0
- package/dist/reactiveQueries/sql.js.map +1 -0
- package/dist/schema.d.ts +163 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +92 -0
- package/dist/schema.js.map +1 -0
- package/dist/store.d.ts +175 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +546 -0
- package/dist/store.js.map +1 -0
- package/dist/util.d.ts +24 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +51 -0
- package/dist/util.js.map +1 -0
- package/package.json +52 -0
- package/src/QueryCache.ts +81 -0
- package/src/__tests__/react/fixture.tsx +106 -0
- package/src/__tests__/react/useLiveStoreComponent.test.tsx +111 -0
- package/src/__tests__/reactive.test.ts +227 -0
- package/src/ambient.d.ts +7 -0
- package/src/backends/base.ts +67 -0
- package/src/backends/index.ts +94 -0
- package/src/backends/noop.ts +32 -0
- package/src/backends/tauri.ts +74 -0
- package/src/backends/utils/idb.ts +71 -0
- package/src/backends/web-in-memory.ts +65 -0
- package/src/backends/web-worker.ts +176 -0
- package/src/backends/web.ts +96 -0
- package/src/bounded-collections.ts +112 -0
- package/src/componentKey.ts +9 -0
- package/src/effect/LiveStore.ts +123 -0
- package/src/effect/index.ts +7 -0
- package/src/events.ts +8 -0
- package/src/inMemoryDatabase.ts +347 -0
- package/src/index.ts +47 -0
- package/src/otel.ts +20 -0
- package/src/react/LiveStoreContext.ts +23 -0
- package/src/react/LiveStoreProvider.tsx +93 -0
- package/src/react/index.ts +11 -0
- package/src/react/useGlobalQuery.ts +40 -0
- package/src/react/useGraphQL.ts +113 -0
- package/src/react/useLiveStoreComponent.ts +493 -0
- package/src/react/utils/useStateRefWithReactiveInput.ts +51 -0
- package/src/reactive.ts +538 -0
- package/src/reactiveQueries/base-class.ts +49 -0
- package/src/reactiveQueries/graphql.ts +52 -0
- package/src/reactiveQueries/js.ts +38 -0
- package/src/reactiveQueries/sql.ts +65 -0
- package/src/schema.ts +219 -0
- package/src/store.ts +889 -0
- package/src/util.ts +59 -0
- package/tsconfig.json +15 -0
- package/vitest.config.js +13 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type * as otel from '@opentelemetry/api'
|
|
2
|
+
|
|
3
|
+
import type { ComponentKey } from '../componentKey.js'
|
|
4
|
+
import type { GetAtom, Thunk } from '../reactive.js'
|
|
5
|
+
import type { Store } from '../store.js'
|
|
6
|
+
import { LiveStoreQueryBase } from './base-class.js'
|
|
7
|
+
import type { LiveStoreJSQuery } from './js.js'
|
|
8
|
+
|
|
9
|
+
/* An object encapsulating a reactive SQL query */
|
|
10
|
+
export class LiveStoreSQLQuery<Row> extends LiveStoreQueryBase {
|
|
11
|
+
_tag: 'sql' = 'sql'
|
|
12
|
+
/** A reactive thunk representing the query text */
|
|
13
|
+
queryString$: Thunk<string>
|
|
14
|
+
/** A reactive thunk representing the query results */
|
|
15
|
+
results$: Thunk<Row[]>
|
|
16
|
+
|
|
17
|
+
constructor({
|
|
18
|
+
queryString$,
|
|
19
|
+
results$,
|
|
20
|
+
...baseProps
|
|
21
|
+
}: {
|
|
22
|
+
queryString$: Thunk<string>
|
|
23
|
+
results$: Thunk<Row[]>
|
|
24
|
+
componentKey: ComponentKey
|
|
25
|
+
label: string
|
|
26
|
+
store: Store<any>
|
|
27
|
+
otelContext: otel.Context
|
|
28
|
+
}) {
|
|
29
|
+
super(baseProps)
|
|
30
|
+
|
|
31
|
+
this.queryString$ = queryString$
|
|
32
|
+
this.results$ = results$
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns a new reactive query that contains the result of
|
|
37
|
+
* running an arbitrary JS computation on the results of this SQL query.
|
|
38
|
+
*/
|
|
39
|
+
pipe = <U>(f: (result: Row[], get: GetAtom) => U): LiveStoreJSQuery<U> =>
|
|
40
|
+
this.store.queryJS(
|
|
41
|
+
(get) => {
|
|
42
|
+
const results = get(this.results$)
|
|
43
|
+
return f(results, get)
|
|
44
|
+
},
|
|
45
|
+
this.componentKey,
|
|
46
|
+
`${this.label}:js`,
|
|
47
|
+
this.otelContext,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
/** Returns a reactive query */
|
|
51
|
+
getFirstRow = (args?: { defaultValue?: Row }) =>
|
|
52
|
+
this.store.queryJS(
|
|
53
|
+
(get) => {
|
|
54
|
+
const results = get(this.results$)
|
|
55
|
+
if (results.length === 0 && args?.defaultValue === undefined) {
|
|
56
|
+
const queryLabel = this._tag === 'sql' ? this.queryString$.result : this.label
|
|
57
|
+
throw new Error(`Expected query ${queryLabel} to return at least one result`)
|
|
58
|
+
}
|
|
59
|
+
return (results[0] ?? args?.defaultValue) as Row
|
|
60
|
+
},
|
|
61
|
+
this.componentKey,
|
|
62
|
+
`${this.label}:first`,
|
|
63
|
+
this.otelContext,
|
|
64
|
+
)
|
|
65
|
+
}
|
package/src/schema.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { Backend } from './backends/index.js'
|
|
2
|
+
import { EVENTS_TABLE_NAME } from './events.js'
|
|
3
|
+
import type { InMemoryDatabase } from './inMemoryDatabase.js'
|
|
4
|
+
import { sql } from './util.js'
|
|
5
|
+
|
|
6
|
+
export type ColumnDefinition = {
|
|
7
|
+
nullable?: boolean
|
|
8
|
+
primaryKey?: boolean
|
|
9
|
+
} & (
|
|
10
|
+
| { type: 'text'; default?: string }
|
|
11
|
+
| { type: 'json'; default?: string }
|
|
12
|
+
| { type: 'integer'; default?: number }
|
|
13
|
+
| { type: 'boolean'; default?: boolean }
|
|
14
|
+
| { type: 'real'; default?: number }
|
|
15
|
+
| { type: 'blob'; default?: any }
|
|
16
|
+
) // sqlite uses numbers for booleans but we fake it
|
|
17
|
+
|
|
18
|
+
// TODO: defaults should be nullable for nullable columns
|
|
19
|
+
type ColumnDefinitionWithDefault = {
|
|
20
|
+
primaryKey?: boolean
|
|
21
|
+
} & (
|
|
22
|
+
| { type: 'text'; nullable?: true; default: string }
|
|
23
|
+
| { type: 'json'; nullable?: true; default: string }
|
|
24
|
+
| { type: 'integer'; nullable?: true; default: number }
|
|
25
|
+
| { type: 'boolean'; nullable?: true; default: boolean }
|
|
26
|
+
| { type: 'real'; nullable: true; default: number | null }
|
|
27
|
+
| { type: 'blob'; nullable: true; default: any | null }
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
export type TableDefinition = {
|
|
31
|
+
columns: {
|
|
32
|
+
[key: string]: ColumnDefinition
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Can be used for various purposes e.g. to provide a foreign key constraint like below:
|
|
36
|
+
* ```ts
|
|
37
|
+
* columnsRaw: (columnsStr) => `${columnsStr}, foreign key (userId) references users(id)`
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
columnsRaw?: (columnsStr: string) => string
|
|
41
|
+
indexes?: Index[]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type Index = {
|
|
45
|
+
name: string
|
|
46
|
+
columns: string[]
|
|
47
|
+
/** @default false */
|
|
48
|
+
isUnique?: boolean
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type ComponentStateSchema<T> = {
|
|
52
|
+
componentType: string
|
|
53
|
+
columns: {
|
|
54
|
+
[k in keyof T]: ColumnDefinitionWithDefault
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// A global variable representing component state tables we should create in the database
|
|
59
|
+
export const componentStateTables: { [key: string]: TableDefinition } = {}
|
|
60
|
+
|
|
61
|
+
export const defineComponentStateSchema = <T>(
|
|
62
|
+
schema: ComponentStateSchema<T>,
|
|
63
|
+
): ComponentStateSchema<T & { id: string }> => {
|
|
64
|
+
const tablePath = `components__${schema.componentType}`
|
|
65
|
+
if (Object.keys(componentStateTables).includes(tablePath)) {
|
|
66
|
+
// throw new Error(`Can't register duplicate component: ${name}`)
|
|
67
|
+
console.error(`Can't register duplicate component: ${tablePath}`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const schemaWithId = schema as ComponentStateSchema<T & { id: string }>
|
|
71
|
+
|
|
72
|
+
schemaWithId.columns.id = { type: 'text', primaryKey: true } as any
|
|
73
|
+
|
|
74
|
+
componentStateTables[tablePath] = schemaWithId as any
|
|
75
|
+
|
|
76
|
+
return schemaWithId
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type SQLWriteStatement = {
|
|
80
|
+
sql: string
|
|
81
|
+
|
|
82
|
+
/** Tables written by the statement */
|
|
83
|
+
writeTables: string[]
|
|
84
|
+
// TODO refactor this
|
|
85
|
+
argsAlreadyBound?: boolean
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type ActionDefinition<TArgs = any> = {
|
|
89
|
+
statement: SQLWriteStatement | ((args: TArgs) => SQLWriteStatement)
|
|
90
|
+
prepareBindValues?: (args: TArgs) => any
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type Schema = {
|
|
94
|
+
tables: TableDefinitions
|
|
95
|
+
materializedViews: MaterializedViewDefinitions
|
|
96
|
+
actions: ActionDefinitions<any>
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type TableDefinitions = { [key: string]: TableDefinition }
|
|
100
|
+
export type MaterializedViewDefinitions = { [key: string]: {} }
|
|
101
|
+
export type ActionDefinitions<TArgsMap extends Record<string, any>> = {
|
|
102
|
+
[key in keyof TArgsMap]: ActionDefinition<TArgsMap[key]>
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const EVENT_CURSOR_TABLE = 'livestore__event_cursor'
|
|
106
|
+
|
|
107
|
+
const systemTables = {
|
|
108
|
+
[EVENTS_TABLE_NAME]: {
|
|
109
|
+
columns: {
|
|
110
|
+
id: { type: 'text', primaryKey: true },
|
|
111
|
+
type: { type: 'text', nullable: false },
|
|
112
|
+
args: { type: 'text', nullable: false },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
[EVENT_CURSOR_TABLE]: {
|
|
116
|
+
columns: {
|
|
117
|
+
id: { type: 'text', primaryKey: true },
|
|
118
|
+
cursor: { type: 'text', nullable: false },
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
} as const
|
|
122
|
+
|
|
123
|
+
export const defineSchema = <S extends Schema>(schema: S) => mergeSystemSchema(schema)
|
|
124
|
+
|
|
125
|
+
export const defineTables = <T extends TableDefinitions>(tables: T) => tables
|
|
126
|
+
|
|
127
|
+
export const defineMaterializedViews = <M extends MaterializedViewDefinitions>(materializedViews: M) =>
|
|
128
|
+
materializedViews
|
|
129
|
+
|
|
130
|
+
export const defineActions = <A extends ActionDefinitions<any>>(actions: A) => actions
|
|
131
|
+
export const defineAction = <TArgs extends Record<string, any>>(
|
|
132
|
+
action: ActionDefinition<TArgs>,
|
|
133
|
+
): ActionDefinition<TArgs> => action
|
|
134
|
+
|
|
135
|
+
export type GetApplyEventArgs<TActionDefinitionsMap> = RecordValues<{
|
|
136
|
+
[eventType in keyof TActionDefinitionsMap]: {
|
|
137
|
+
eventType: eventType
|
|
138
|
+
args: GetActionArgs<TActionDefinitionsMap[eventType]>
|
|
139
|
+
}
|
|
140
|
+
}>
|
|
141
|
+
|
|
142
|
+
type RecordValues<T> = T extends Record<string, infer V> ? V : never
|
|
143
|
+
|
|
144
|
+
export type GetActionArgs<A> = A extends ActionDefinition<infer TArgs> ? TArgs : never
|
|
145
|
+
|
|
146
|
+
declare global {
|
|
147
|
+
// NOTE Can be extended
|
|
148
|
+
interface LiveStoreActionDefinitionsTypes {
|
|
149
|
+
[key: string]: ActionDefinition
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const mergeSystemSchema = <S extends Schema>(schema: S) => {
|
|
154
|
+
return {
|
|
155
|
+
...schema,
|
|
156
|
+
tables: {
|
|
157
|
+
...schema.tables,
|
|
158
|
+
...systemTables,
|
|
159
|
+
},
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Destructively load a schema into a database,
|
|
165
|
+
* dropping any existing tables and creating new ones.
|
|
166
|
+
*/
|
|
167
|
+
export const loadSchema = async (backend: InMemoryDatabase | Backend, schema: Schema) => {
|
|
168
|
+
const fullSchemaWithComponents = { ...schema, tables: { ...schema.tables, ...componentStateTables } }
|
|
169
|
+
|
|
170
|
+
// Loop through all the tables and create them in the SQLite database
|
|
171
|
+
for (const [tableName, tableDefinition] of Object.entries(fullSchemaWithComponents.tables)) {
|
|
172
|
+
const primaryKeys = Object.entries(tableDefinition.columns)
|
|
173
|
+
.filter(([_, columnDef]) => columnDef.primaryKey)
|
|
174
|
+
.map(([columnName, _]) => columnName)
|
|
175
|
+
const columnDefStrs = Object.entries(tableDefinition.columns).map(([columnName, column]) =>
|
|
176
|
+
toSqliteColumnSpec(columnName, column),
|
|
177
|
+
)
|
|
178
|
+
if (primaryKeys.length > 0) {
|
|
179
|
+
columnDefStrs.push(`PRIMARY KEY (${primaryKeys.join(', ')})`)
|
|
180
|
+
}
|
|
181
|
+
const mapColumns = tableDefinition.columnsRaw ?? ((_) => _)
|
|
182
|
+
const columnSpec = mapColumns(columnDefStrs.join(', '))
|
|
183
|
+
|
|
184
|
+
backend.execute(sql`drop table if exists ${tableName}`)
|
|
185
|
+
|
|
186
|
+
backend.execute(sql`create table if not exists ${tableName} (${columnSpec});`)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
await createIndexes(backend, schema)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const toSqliteColumnSpec = (columnName: string, column: ColumnDefinition) => {
|
|
193
|
+
const columnType = column.type === 'boolean' ? 'integer' : column.type
|
|
194
|
+
// const primaryKey = column.primaryKey ? 'primary key' : ''
|
|
195
|
+
const nullable = column.nullable === false ? 'not null' : ''
|
|
196
|
+
const defaultValue =
|
|
197
|
+
column.default === undefined
|
|
198
|
+
? ''
|
|
199
|
+
: column.type === 'text'
|
|
200
|
+
? `default '${column.default}'`
|
|
201
|
+
: `default ${column.default}`
|
|
202
|
+
|
|
203
|
+
return `${columnName} ${columnType} ${nullable} ${defaultValue}`
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const createIndexFromDefinition = (tableName: string, index: Index) => {
|
|
207
|
+
const uniqueStr = index.isUnique ? 'UNIQUE' : ''
|
|
208
|
+
return sql`create ${uniqueStr} index ${index.name} on ${tableName} (${index.columns.join(', ')})`
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const createIndexes = async (db: Backend | InMemoryDatabase, schema: Schema) => {
|
|
212
|
+
for (const [tableName, tableDefinition] of Object.entries(schema.tables)) {
|
|
213
|
+
if (tableDefinition.indexes !== undefined) {
|
|
214
|
+
for (const index of tableDefinition.indexes) {
|
|
215
|
+
db.execute(createIndexFromDefinition(tableName, index))
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|