this repo has no description
at main 134 lines 4.0 kB view raw
1// builders 2 3import {StoreBuilder, StoreUpdater} from './builder.store' 4import type { 5 DatabaseDef, 6 ExtantKey, 7 Increment, 8 IndexesConstraint, 9 MigrationDef, 10 MigrationFn, 11 RowConstraint, 12 SchemaConstraint, 13 Simplify, 14 StoreDef, 15 UnusedKey, 16} from './schema' 17 18// Helper: merge row type changes into schema, preserving indexes for existing stores 19type MergeRowChanges< 20 Current extends SchemaConstraint, 21 Changes extends Record<string, RowConstraint> 22> = { 23 [K in keyof Current | keyof Changes]: 24 K extends keyof Changes 25 ? K extends keyof Current 26 // Existing store: update row, preserve indexes 27 ? StoreDef<K & string, Changes[K], Changes[K], Current[K]['__indexes']> 28 // New store: no indexes 29 : StoreDef<K & string, Changes[K], Changes[K], IndexesConstraint> 30 // Unchanged store 31 : K extends keyof Current ? Current[K] : never 32} 33 34export class DatabaseBuilder< 35 Name extends string, 36 Version extends number = 0, 37 Schema extends SchemaConstraint = {}, 38> { 39 #name: Name 40 #stores: Record<string, StoreDef<string, any, any, any>> 41 #migrations: MigrationDef[] 42 43 constructor(name: Name) { 44 this.#name = name 45 this.#stores = {} 46 this.#migrations = [] 47 } 48 49 create<SName extends string, Builder extends StoreBuilder<any, any, any>>( 50 name: UnusedKey<SName, Schema>, 51 callback: (builder: StoreBuilder<{}>) => Builder 52 ) { 53 if (this.#stores[name]) throw new Error('cannot add store ${name} - already exists') 54 55 const builder = callback(new StoreBuilder()) 56 const store = builder.build(name) 57 58 this.#stores[name] = store 59 this.#migrations.push(['create', name, store]) 60 61 type FinalStore = Builder extends StoreBuilder<infer R, infer D, infer I> 62 ? StoreDef<SName, R, D, I> 63 : never 64 return this as unknown as DatabaseBuilder< 65 Name, 66 Increment<Version>, 67 Simplify<Schema & {[K in SName]: FinalStore}> 68 > 69 } 70 71 modify<SName extends string, Builder extends StoreUpdater<any, any, any, any>>( 72 name: ExtantKey<SName, Schema>, 73 callback: (b: StoreUpdater< 74 SName, 75 Schema[SName]['__row'], 76 Schema[SName]['__doc'], 77 Schema[SName]['__indexes'] 78 >) => Builder 79 ) { 80 if (this.#stores[name] === undefined) throw new Error('cannot update store ${name} - does not exist') 81 82 const extant = this.#stores[name satisfies string] 83 type InputUpdater = StoreUpdater< 84 SName, 85 Schema[SName]['__row'], 86 Schema[SName]['__doc'], 87 Schema[SName]['__indexes'] 88 > 89 const builder = new StoreUpdater(extant as any) as InputUpdater 90 const store = callback(builder).build() 91 92 this.#stores[name] = store 93 this.#migrations.push(['update', name, store]) 94 95 type FinalStore = Builder extends StoreUpdater<any, infer R, infer D, infer I> 96 ? StoreDef<SName, R, D, I> 97 : never 98 99 return this as unknown as DatabaseBuilder< 100 Name, 101 Increment<Version>, 102 Simplify<Omit<Schema, SName> & {[K in SName]: FinalStore}> 103 > 104 } 105 106 drop<SName extends string>(name: ExtantKey<SName, Schema>) { 107 if (this.#stores[name] === undefined) throw new Error('cannot remove store ${name} - does not exist') 108 109 this.#migrations.push(['drop', name, this.#stores[name]]) 110 delete this.#stores[name] 111 112 return this as unknown as DatabaseBuilder<Name, Increment<Version>, Simplify<Omit<Schema, SName>>> 113 } 114 115 migrate< 116 Write extends Record<string, RowConstraint>, 117 NextSchema extends SchemaConstraint = Simplify<MergeRowChanges<Schema, Write>>, 118 >(fn: MigrationFn<Schema, NextSchema>) { 119 this.#migrations.push(fn) 120 121 return this as unknown as DatabaseBuilder<Name, Increment<Version>, NextSchema> 122 } 123 124 build(): DatabaseDef<Name, Version, Schema> { 125 return { 126 name: this.#name, 127 version: this.#migrations.length as Version, 128 migrations: this.#migrations, 129 stores: this.#stores, 130 131 __schema: undefined as unknown as Schema, 132 } 133 } 134}