-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathtypes.d.ts
992 lines (950 loc) · 30.3 KB
/
types.d.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
declare module 'strato-db'
type DBCallback = (db: DB) => Promise<unknown> | unknown
/** The types that SQLite can handle as parameter values */
type SQLiteValue = string | number | null
type SQLiteParam = SQLiteValue | boolean
type SQLiteMeta = {lastID: number; changes: number}
type SQLiteRow = Record<string, null | string | number>
type SQLiteColumnType =
| 'TEXT'
| 'NUMERIC'
| 'INTEGER'
| 'REAL'
| 'BLOB'
| 'JSON'
type DBEachCallback = (row: SQLiteRow) => Promise<unknown> | unknown
type SqlTag = (
tpl: TemplateStringsArray,
...interpolations: SQLiteParam[]
) => [string, string[]]
interface Statement {
isStatement: true
sql: string
/** Closes the statement, removing it from the SQLite instance */
finalize(): Promise<void>
/** Run the statement and return the metadata. */
run(vars: SQLiteParam[]): Promise<SQLiteMeta>
/** Return the first row for the statement result. */
get(vars: SQLiteParam[]): Promise<SQLiteRow | null>
/** Return all result rows for the statement. */
all(vars: SQLiteParam[]): Promise<SQLiteRow[]>
/** Run the callback on each row of the result */
each(vars: SQLiteParam[], onRow: DBEachCallback): Promise<void>
}
type SQLiteOptions = {
/** Path to db file. */
file?: string
/** Open read-only. */
readOnly?: boolean
/** Verbose errors. */
verbose?: boolean
/** Called before opening. */
onWillOpen?: () => Promise<unknown> | unknown
/** Called after opened. */
onDidOpen?: DBCallback
/** Name for debugging. */
name?: string
/** Run incremental vacuum. */
autoVacuum?: boolean
/** Seconds between incremental vacuums. */
vacuumInterval?: number
/** Number of pages to clean per vacuum. */
vacuumPageCount?: number
}
/**
* SQLite is a wrapper around a single SQLite connection (via node-sqlite3). It
* provides a Promise API, lazy opening, auto-cleaning prepared statements and
* safe `db.run`select * from foo where bar=${bar}` ` templating. emits these
* events, all without parameters:
*
* - 'begin': transaction begins
* - 'rollback': transaction finished with failure
* - 'end': transaction finished successfully
* - 'finally': transaction finished
* - 'call': call to SQLite completed, includes data and duration
*/
interface SQLite extends EventEmitter {
new (options?: SQLiteOptions)
/**
* Template Tag for SQL statements.
*
* @example
* `` db.all`select * from ${'foo'}ID where ${'t'}LIT = ${bar} AND json =
* ${obj}JSON` ``
*
* is converted to `db.all('select * from "foo" where t = ? and json = ?', [bar,
* JSON.stringify(obj)])`
*/
sql(): {quoteId: (id: SQLiteParam) => string} & SqlTag
/** `true` if an sqlite connection was set up. Mostly useful for tests. */
isOpen: boolean
/**
* Force opening the database instead of doing it lazily on first access.
*
* @returns - A promise for the DB being ready to use.
*/
open(): Promise<void>
/**
* Close the database connection, including the prepared statements.
*
* @returns - A promise for the DB being closed.
*/
close(): Promise<void>
/**
* Runs the passed function once, either immediately if the connection is
* already open, or when the database will be opened next. Note that if the
* function runs immediately, its return value is returned. If this is a
* Promise, it is the caller's responsibility to handle errors. Otherwise, the
* function will be run once after onDidOpen, and errors will cause the open
* to fail.
*
* @returns Either the function return value or undefined.
*/
runOnceOnOpen(fn: (db: SQLite) => void): void
/**
* Return all rows for the given query.
*
* @param sql - The SQL statement to be executed.
* @param [vars] - The variables to be bound to the statement.
*/
all(sql: string, vars?: SQLiteParam[]): Promise<SQLiteRow[]>
all(sql: TemplateStringsArray, ...vars: SQLiteParam[]): Promise<SQLiteRow[]>
/**
* Return the first row for the given query.
*
* @param sql - The SQL statement to be executed.
* @param [vars] - The variables to be bound to the statement.
*/
get(sql: string, vars?: SQLiteParam[]): Promise<SQLiteRow | null>
get(
sql: TemplateStringsArray,
...vars: SQLiteParam[]
): Promise<SQLiteRow | null>
/**
* Run the given query and return the metadata.
*
* @param sql - The SQL statement to be executed.
* @param [vars] - The variables to be bound to the statement.
*/
run(sql: string, vars?: SQLiteParam[]): Promise<SQLiteMeta>
run(sql: TemplateStringsArray, ...vars: SQLiteParam[]): Promise<SQLiteMeta>
/**
* Run the given query and return nothing. Slightly more efficient than
* {@link run}.
*
* @param sql - The SQL statement to be executed.
* @param [vars] - The variables to be bound to the statement.
* @returns - A promise for execution completion.
*/
exec(sql: string, vars?: SQLiteParam[]): Promise<void>
exec(sql: TemplateStringsArray, ...vars: SQLiteParam[]): Promise<void>
/**
* Register an SQL statement for repeated running. This will store the SQL and
* will prepare the statement with SQLite whenever needed, as well as finalize
* it when closing the connection.
*
* @param sql - The SQL statement to be executed.
* @param [name] - A short name to use in debug logs.
*/
prepare(sql: string, name?: string): Statement
/**
* Run the given query and call the function on each item. Note that
* node-sqlite3 seems to just fetch all data in one go.
*
* @param sql - The SQL statement to be executed.
* @param [vars] - The variables to be bound to the statement.
* @param cb - The function to call on each row.
* @returns - A promise for execution completion.
*/
each(sql: string, cb: (row: SQLiteRow) => any): Promise<void>
each(sql: string, vars: SQLiteParam[], cb: DBEachCallback): Promise<void>
/**
* Returns the data_version, which increases when other connections write to
* the database.
*/
dataVersion(): Promise<number>
/**
* Returns or sets the user_version, an arbitrary integer connected to the
* database.
*
* @param [newV] - If given, sets the user version.
* @returns - The user version or nothing when setting.
*/
userVersion(newV?: number): Promise<number | void>
/**
* Run a function in an immediate transaction. Within a connection, the
* invocations are serialized, and between connections it uses busy retry
* waiting. During a transaction, the database can still be read.
*
* @param fn - The function to call. It doesn't get any parameters.
* @returns - A promise for transaction completion.
*/
withTransaction(fn: () => Promise<void>): Promise<void>
}
type DBMigration = {up: DBCallback} | DBCallback
/** Migrations are marked completed by their name in the `{sdb} migrations` table */
type DBMigrations = Record<string, DBMigration>
interface DBModel<Options extends {db: DB} = {db: DB}> {
new (options: Options): any
}
type DBOptions = {
/** Open the DB read-only */
readOnly?: boolean
migrations?: DBMigrations
/** Called before migrations run. Not called for read-only */
onBeforeMigrations?: (...params: any[]) => any
/**
* Called after migrations ran. If readOnly is set, it runs after opening DB.
* The DB is open after this function resolves.
*/
onDidOpen?: (...params: any[]) => any
} & SQLiteOptions
/**
* DB adds model management and migrations to Wrapper. The migration state is
* kept in the table ""{sdb} migrations"".
*/
interface DBI extends SQLite {
new (options: DBOptions): DB
/** The models. */
store: Record<string, InstanceType<DBModel>>
/**
* Add a model to the DB, which will manage one or more tables in the SQLite
* database. The model should use the given `db` instance at creation time.
*
* @param Model - A class to be instatiated with the DB.
* @param options - Options passed during Model creation as `{...options,
* db}`.
* @returns - The created Model instance.
*/
addModel(Model: DBModel, options?: Record<string, any>): InstanceType<DBModel>
/**
* Register an object with migrations. Migrations are marked completed by the
* given name + their name in the `{sdb} migrations` table.
*
* @param groupName - The name under which to register these migrations.
* @param migrations - The migrations object.
*/
registerMigrations(groupName: string, migrations: DBMigrations): void
/**
* Runs the migrations in a transaction and waits for completion.
*
* @param db - An opened SQLite instance.
* @returns - Promise for completed migrations.
*/
runMigrations(db: SQLite): Promise<void>
}
declare class DB implements DBI {}
/** A callback receiving an item */
type ItemCallback<Item> = (obj: Item) => Promise<void>
/** The value of the stored objects' id */
type IDValue = string | number
/**
* Search for simple values. Keys are column names, values are what they should
* equal
*/
type JMSearchAttrs<Columns> = {
[attr in keyof Columns]?: any
}
/** The key for the column definition */
type JMColName = string
type Loader<T, U> = import('dataloader')<U, T | null>
/** A lookup cache, managed by DataLoader */
type JMCache<Item extends Record<string, any>, IDCol extends string> = {
[name: string]: Loader<Item, Item[IDCol]>
}
/** A real or virtual column definition in the created sqlite table */
type JMColumnDef = {
/** The column key, used for the column name if it's a real column. */
name?: JMColName
/** Is this a real table column. */
real?: boolean
/** Sql column type as accepted by DB. */
type?: SQLiteColumnType
/** Path to the value in the object. */
path?: string
/** INTEGER id column only: apply AUTOINCREMENT on the column. */
autoIncrement?: boolean
/** The alias to use in SELECT statements. */
alias?: string
/** Should the column be included in search results. */
get?: boolean
/** Process the value after getting from DB. */
parse?: (val: SQLiteValue) => any
/** Process the value before putting into DB. */
stringify?: (any) => SQLiteParam
/**
* The value is an object and must always be there. If this is a real column,
* a NULL column value will be replaced by `{}` and vice versa.
*/
alwaysObject?: boolean
/**
* Function getting object and returning the value for the column; this
* creates a real column. Right now the column value is not regenerated for
* existing rows.
*/
value?: (object: Record<string, any>) => any
/** Same as value, but the result is used to generate a unique slug. */
slugValue?: (object: Record<string, any>) => any
/** Any sql expression to use in SELECT statements. */
sql?: string
/** If the value is nullish, this will be stored instead. */
default?: any
/** Throw when trying to store a NULL. */
required?: boolean
/**
* Store/retrieve this boolean value as either `true` or absent from the
* object.
*/
falsyBool?: boolean
/** Should it be indexed? If `unique` is false, NULLs are never indexed. */
index?: boolean
/** Are null values ignored in the index?. */
ignoreNull?: boolean
/** Should the index enforce uniqueness?. */
unique?: boolean
/**
* A function receiving `origVals` and returning the `vals` given to `where`.
* It should return falsy or an array of values.
*/
whereVal?: (vals: any[]) => any
/**
* The where clause for querying, or a function returning one given `(vals,
* origVals)`.
*/
where?: string | ((vals: any[], origVals: any[]) => any)
/** This column contains an array of values. */
isArray?: boolean
/** To query, this column value must match one of the given array items. */
in?: boolean
/**
* [isArray only] to query, this column value must match all of the given
* array items.
*/
inAll?: boolean
/** Perform searches as substring search with LIKE. */
textSearch?: boolean
/** Alias for isArray+inAll. */
isAnyOfArray?: boolean
}
type JMColumnDefOrFn = (({name: string}) => JMColumnDef) | JMColumnDef
/** A function that performs a migration before the DB is opened */
type JMMigration<T extends {[x: string]: any}, IDCol extends string> = (
args: Record<string, any> & {db: DB; model: JsonModel<T, IDCol>}
) => Promise<void>
type JMColums<IDCol extends string = 'id'> = {
[colName: string]: JMColumnDefOrFn | undefined
} & {
[id in IDCol]?: JMColumnDef
}
type JMOptions<
T extends {[x: string]: any},
IDCol extends string = 'id',
Columns extends JMColums<IDCol> = {[id in IDCol]?: {type: 'TEXT'}},
> = {
/** A DB instance, normally passed by DB */
db: DB
/** The table name */
name: string
/** An object with migration functions. They are run in alphabetical order */
migrations?: {[tag: string]: JMMigration<T, IDCol>}
/** Free-form data passed to the migration functions */
migrationOptions?: Record<string, any>
/** The column definitions */
columns?: Columns
/**
* An object class to use for results, must be able to handle
* `Object.assign(item, result)`
*/
ItemClass?: object
/** The key of the IDCol column */
idCol?: IDCol
/** Preserve row id after vacuum */
keepRowId?: boolean
}
/**
* Keys: literal WHERE clauses that are AND-ed together.
*
* They are applied if the value is an array, and the number of items in the
* array must match the number of `?` in the clause.
*/
type JMWhereClauses = {
[key: string]: (string | number | boolean)[] | undefined | null | false
}
type JMSearchOptions<Columns> = {
/** Literal value search, for convenience. */
attrs?: JMSearchAttrs<Columns>
/** Sql expressions as keys with arrays of applicable parameters as values. */
where?: JMWhereClauses
/** Arbitrary join clause. Not processed at all. */
join?: string
/** Values needed by the join clause. */
joinVals?: any[]
/**
* Object with sql expressions as keys and +/- for direction and precedence.
* Lower number sort the column first.
*/
sort?: Record<string, number>
/** Max number of rows to return. */
limit?: number
/** Number of rows to skip. */
offset?: number
/** Override the columns to select. */
cols?: string[]
/** Opaque value telling from where to continue. */
cursor?: string
/** Do not calculate cursor. */
noCursor?: boolean
/** Do not calculate totals. */
noTotal?: boolean
}
/**
* Stores Item objects in a SQLite table. Pass the type of the item it stores
* and the config so it can determine the columns
*/
interface JsonModel<
RealItem extends {[x: string]: any} = {id: string},
// Allow the id column name as well for backwards compatibility
ConfigOrID = 'id',
IDCol extends string = ConfigOrID extends {idCol: string}
? ConfigOrID['idCol']
: ConfigOrID extends string
? ConfigOrID
: 'id',
Item extends {[x: string]: any} = RealItem extends {[id in IDCol]?: unknown}
? RealItem
: RealItem & {[id in IDCol]: IDValue},
Config = ConfigOrID extends string ? object : ConfigOrID,
Columns extends JMColums<IDCol> = Config extends {columns: object}
? Config['columns']
: // If we didn't get a config, assume all keys are columns
{[colName in keyof Item]: object},
SearchAttrs = JMSearchAttrs<Columns>,
SearchOptions = JMSearchOptions<Columns>,
> {
// TODO have it infer the columns from the call to super
new (options: JMOptions<Item, IDCol, Columns>): this
/** The DB instance storing this model */
db: DB
/** The table name */
name: string
/** The SQL-quoted table name */
quoted: string
/** The name of the id column */
idCol: IDCol
/** The SQL-quoted name of the id column */
idColQ: string
/** The prototype of returned Items */
Item: object
/** The column definitions */
columnArr: JMColumnDef[]
/** The column definitions keyed by name */
columns: Columns
/** Parses a row as returned by sqlite */
parseRow: (row: SQLiteRow, options?: SearchOptions) => Item
/**
* Parses query options into query parts. Override this function to implement
* search behaviors.
*/
makeSelect(
/** The query options. */
options: SearchOptions
): [string, SQLiteParam[], string[], string, SQLiteParam[]]
/**
* Search the first matching object.
*
* @returns The result or undefined if no match.
*/
searchOne(
/** Simple value attributes. */
attrs: SearchAttrs,
options?: SearchOptions
): Promise<Item | undefined>
/**
* Search the all matching objects.
*
* @returns - `{items[], cursor}`. If no cursor, you got all the results. If
* `options.itemsOnly`, returns only the items array.
*/
search(
/** Simple value attributes. */
attrs?: SearchAttrs,
options?: SearchOptions & {
itemsOnly?: false
}
): Promise<{items: Item[]; cursor: string}>
search(
/** Simple value attributes. */
attrs: SearchAttrs | null | undefined,
options: SearchOptions & {
/** Return only the items array. */
itemsOnly: true
}
): Promise<Item[]>
/**
* A shortcut for setting `itemsOnly: true` on {@link search}.
*
* @param attrs - Simple value attributes.
* @param [options] - Search options.
* @returns - The search results.
*/
searchAll(attrs: SearchAttrs, options?: SearchOptions): Promise<Item[]>
/**
* Check for existence of objects. Returns `true` if the search would yield
* results.
*
* @returns The search results exist.
*/
exists(id: Item[IDCol]): Promise<boolean>
exists(attrs: SearchAttrs, options?: SearchOptions): Promise<boolean>
/**
* Count of search results.
*
* @returns - The count.
*/
count(attrs?: SearchAttrs, options?: SearchOptions): Promise<number>
/** Numeric Aggregate Operation. */
numAggOp(
/** The SQL function, e.g. MAX. */
op: string,
/** The column to aggregate. */
colName: JMColName,
attrs?: SearchAttrs,
options?: SearchOptions
): Promise<number>
/** Maximum value. */
max(
colName: JMColName,
attrs?: SearchAttrs,
options?: SearchOptions
): Promise<number>
/** Minimum value. */
min(
colName: JMColName,
attrs?: SearchAttrs,
options?: SearchOptions
): Promise<number>
/** Sum values. */
sum(
colName: JMColName,
attrs?: SearchAttrs,
options?: SearchOptions
): Promise<number>
/** Average value. */
avg(
colName: JMColName,
attrs?: SearchAttrs,
options?: SearchOptions
): Promise<number>
/**
* Get all objects. This can result in out-of-memory errors.
*
* @returns - The table contents.
*/
all(): Promise<Item[]>
/**
* Get an object by a unique value, like its ID.
*
* @returns - The object if it exists.
*/
get(
/** The value for the column */
id: SQLiteParam,
/** The column name, defaults to the ID column */
colName?: JMColName
): Promise<Item | undefined>
/**
* Get several objects by their unique value, like their ID.
*
* @returns - The objects, or undefined where they don't exist, in order of
* their requested ID.
*/
getAll(
/** The values for the column */
ids: SQLiteParam[],
/** The column name, defaults to the ID column */
colName?: JMColName
): Promise<(Item | undefined)[]>
/**
* Get an object by a unique value, like its ID, using a cache. This also
* coalesces multiple calls in the same tick into a single query, courtesy of
* DataLoader.
*
* @returns - The object if it exists. It will be cached.
*/
getCached(
/** The lookup cache. It is managed with DataLoader. */
// We add the {} to allow easy initialization
cache: JMCache<Item, IDCol> | object,
/** The value for the column */
id: SQLiteParam,
/** The column name, defaults to the ID column */
colName?: JMColName
): Promise<Item | undefined>
/**
* Lets you clear all the cache or just a key. Useful for when you change only
* some items.
*
* @returns - The column cache, you can call `.prime(key, value)` on it to
* insert a value.
*/
clearCache(
/** The lookup cache. It is managed with DataLoader. */
cache: JMCache<Item, IDCol>,
id?: Item[IDCol],
colName?: string
): Loader<Item, Item[IDCol]>
/**
* Iterate through search results. Calls `fn` on every result. The iteration
* uses a cursored search, so changes to the model during the iteration can
* influence the iteration. If you pass `concurrent` it will limit the
* concurrently called functions `batchSize` sets the paging size.
*
* @returns Table iteration completed.
*/
each(cb: ItemCallback<Item>): Promise<void>
each(attrs: SearchAttrs, cb: ItemCallback<Item>): Promise<void>
each(
attrs: SearchAttrs,
opts: SearchOptions & {concurrent?: number; batchSize?: number},
cb: ItemCallback<Item>
): Promise<void>
/**
* Insert or replace the given object into the database.
*
* @param obj - The object to store. If there is no `id` value (or whatever
* the `id` column is named), one is assigned automatically.
* @param [insertOnly] - Don't allow replacing existing objects.
* @param [noReturn] - Do not return the stored object; an optimization.
* @returns - If `noReturn` is false, the stored object is fetched from the
* DB.
*/
set(
obj: Partial<Item>,
insertOnly?: boolean,
noReturn?: boolean
): Promise<Item>
/**
* Update or upsert an object. This uses a transaction if one is not active.
*
* @param obj - The changes to store, including the id field.
* @param [upsert] - Insert the object if it doesn't exist.
* @param [noReturn] - Do not return the stored object, preventing a query.
* @returns A copy of the stored object.
*/
update(
obj: Partial<Item>,
upsert?: boolean,
noReturn?: boolean
): Promise<Item>
/**
* Update or upsert an object. This does not use a transaction so is open to
* race conditions if you don't run it in a transaction.
*
* @param obj - The changes to store, including the id field.
* @param [upsert] - Insert the object if it doesn't exist.
* @param [noReturn] - Do not return the stored object, preventing a query.
* @returns A copy of the stored object.
*/
updateNoTrans(
obj: Partial<Item>,
upsert?: false,
noReturn?: boolean
): Promise<Item>
updateNoTrans(obj: Item, upsert: true, noReturn?: boolean): Promise<Item>
/**
* Remove an object. If the object doesn't exist, this doesn't do anything.
*
* @param idOrObj - The id or the object itself.
* @returns A promise for the deletion.
*/
remove(idOrObj: Item[IDCol] | Item): Promise<void>
/**
* "Rename" an object.
*
* @param oldId - The current ID. If it doesn't exist this will throw.
* @param newId - The new ID. If this ID is already in use this will throw.
* @returns A promise for the rename.
*/
changeId(oldId: Item[IDCol], newId: Item[IDCol]): Promise<void>
}
type ESEvent = {
/** The version */
v: number
/** Event type */
type: string
/** Ms since epoch of event */
ts: number
/** Event data */
data?: any
/** Event processing result */
result?: Record<string, ReduceResult>
}
type EQOptions<T extends {[x: string]: any}> = JMOptions<T, 'v'> & {
/** The table name, defaults to `"history"` */
name?: string
/** Should getNext poll forever? */
forever?: boolean
/** Add views to the database to assist with inspecting the data */
withViews?: boolean
}
/** Creates a new EventQueue model, called by DB. */
interface EventQueue<
T extends ESEvent = ESEvent,
Config extends Partial<EQOptions<T>> = object,
> extends JsonModel<
T,
{idCol: 'v'; columns: import('./dist/EventQueue').Columns} & Config
> {
new (options: EQOptions<T>): this
/**
* Get the highest version stored in the queue.
*
* @returns - The version.
*/
getMaxV(): Promise<number>
/**
* Atomically add an event to the queue.
*
* @param type - Event type.
* @param [data] - Event data.
* @param [ts=Date.now()] - Event timestamp, ms since epoch. Default is
* `Date.now()`
* @returns - Promise for the added event.
*/
add(type: string, data?: any, ts?: number): Promise<T>
/**
* Get the next event after v (gaps are ok). The wait can be cancelled by
* `.cancelNext()`.
*
* @param [v=0] - The version. Default is `0`
* @param [noWait=false] - Do not wait for the next event. Default is `false`
* @returns The event if found.
*/
getNext(v?: number, noWait?: boolean): Promise<T>
/** Cancel any pending `.getNext()` calls */
cancelNext(): void
/**
* Set the latest known version. New events will have higher versions.
*
* @param v - The last known version.
*/
setKnownV(v: number): Promise<void>
}
type ReduceResult = {[applyType: string]: any}
type ReduxArgs<M extends ESDBModel> = {
cache: object
model: InstanceType<M>
event: ESEvent
store: EventSourcingDB['store']
addEvent: AddEventFn
isMainEvent: boolean
}
type PreprocessorFn<M extends ESDBModel = ESModel> = (
args: ReduxArgs<M>
) => Promise<ESEvent | undefined> | ESEvent | undefined
type ReducerFn<M extends ESDBModel = ESModel> = (
args: ReduxArgs<M>
) =>
| Promise<ReduceResult | undefined | false>
| ReduceResult
| undefined
| false
type ApplyResultFn = (result: ReduceResult) => Promise<void>
type DeriverFn<M extends ESDBModel = ESModel> = (
args: ReduxArgs<M> & {result: ReduceResult}
) => Promise<void>
type TransactFn<M extends ESDBModel = ESModel> = (
args: Omit<ReduxArgs<M>, 'addEvent'> & {dispatch: DispatchFn}
) => Promise<void>
type DispatchFn = (
...args:
| [type: string, data?: any, ts?: number]
| [arg: {type: string; data?: any; ts?: number}]
) => Promise<ESEvent>
type AddEventFn = (
...args: [type: string, data?: any] | [arg: {type: string; data?: any}]
) => void
// TODO get from models config
type ESDBModelArgs = {
name: string
db: DB
dispatch: DispatchFn
migrationOptions: Record<string, any> & {queue: EventQueue}
emitter: EventEmitter
}
interface ESDBModel {
new (args: ESDBModelArgs): this
name: string
preprocessor?: PreprocessorFn<this>
reducer?: ReducerFn<this>
applyResult?: ApplyResultFn
deriver?: DeriverFn<this>
transact?: TransactFn<this>
}
type ESDBOptions = DBOptions & {
models: {[name: string]: ESDBModel}
queue?: InstanceType<EventQueue>
queueFile?: string
withViews?: boolean
onWillOpen?: DBCallback
onBeforeMigrations?: DBCallback
onDidOpen?: DBCallback
}
interface EventSourcingDB extends EventEmitter {
new (options: ESDBOptions): this
/** The read-only models. Use these freely, they don't "see" transactions */
store: Record<string, InstanceType<ESDBModel>>
/** The writable models. Do not use. */
rwStore: Record<string, InstanceType<ESDBModel>>
/** DB instance for the read-only models */
db: DB
/** DB instance for the writable models */
rwDb: DB
/** Queue instance holding the events */
queue: EventQueue
/** Open the DBs */
open(): Promise<void>
/** Close the DBs */
close(): Promise<void>
checkForEvents(): Promise<void>
waitForQueue(): Promise<void>
startPolling(wantVersion?: number): Promise<void>
stopPolling(): Promise<void>
dispatch: DispatchFn
getVersion(): Promise<number>
handledVersion(v: number): Promise<void>
}
type EMOptions<T extends {[x: string]: any}, IDCol extends string> = JMOptions<
T,
IDCol
> & {
/** The ESDB dispatch function */
dispatch: DispatchFn
/**
* Emit an event with type `es/INIT:${modelname}` at table creation time, to
* be used by custom reducers.
*/
init?: boolean
}
/**
* ESModel is a drop-in wrapper around JsonModel to turn changes into events.
*
* Use it to convert your database to be event sourcing.
*
* Event data is encoded as an array: `[subtype, id, data, meta]` Subtype is one
* of `ESModel.(REMOVE|SET|INSERT|UPDATE|SAVE)`. `id` is filled in by the
* preprocessor at the time of the event. `meta` is free-form data about the
* event. It is just stored in the history table.
*
* For example: `model.set({foo: true})` would result in the event `[1, 1, {foo:
* true}]` Pass the type of the item it stores and the config so it can
* determine the columns
*/
// TODO fix Item vs Item type incompatibility
interface ESModel<
RealItem extends {[x: string]: any} = {id: string},
// Allow the id column name as well for backwards compatibility
ConfigOrID = 'id',
IDCol extends string = ConfigOrID extends {idCol: string}
? ConfigOrID['idCol']
: ConfigOrID extends string
? ConfigOrID
: 'id',
Item extends {[x: string]: any} = RealItem extends {[id in IDCol]: unknown}
? RealItem
: RealItem & {[id in IDCol]: IDValue},
> extends JsonModel<RealItem, ConfigOrID, IDCol, Item>,
ESDBModel {
new (options: EMOptions<Item, IDCol>): this
REMOVE: 0
SET: 1
INSERT: 2
UPDATE: 3
SAVE: 4
TYPE: string
INIT: string
/**
* Assigns the object id to the event at the start of the cycle. When
* subclassing ESModel, be sure to call this too
* (`ESModel.preprocessor(arg)`)
*/
preprocessor?: PreprocessorFn<this>
/**
* Calculates the desired change. ESModel will only emit `rm`, `ins`, `upd`
* and `esFail`.
*/
reducer?: ReducerFn<this>
/**
* Applies the result from the reducer.
*
* @param result - Free-form change descriptor.
* @returns - Promise for completion.
*/
applyResult?: ApplyResultFn
/**
* Calculates the desired change. ESModel will only emit `rm`, `ins`, `upd`
* and `esFail`.
*/
deriver?: DeriverFn<this>
dispatch: DispatchFn
/**
* Slight hack: use the writable state to fall back to JsonModel behavior.
* This makes deriver and migrations work without changes. Note: while
* writable, no events are created. Be careful.
*
* @param state - Writeable or not.
*/
setWritable(state: boolean): void
/**
* Insert or replace the given object into the database.
*
* @param obj - The object to store. If there is no `id` value (or whatever
* the `id` column is named), one is assigned automatically.
* @param [insertOnly] - Don't allow replacing existing objects.
* @param [noReturn] - Do not return the stored object; an optimization.
* @param [meta] - Extra metadata to store in the event but not in the object.
* @returns - If `noReturn` is false, the stored object is fetched from the
* DB.
*/
set(
obj: Partial<Item>,
insertOnly?: boolean,
noReturn?: boolean,
meta?: any
): Promise<Item>
/**
* Update an existing object. Returns the current object.
*
* @param o - The data to store.
* @param [upsert] - If `true`, allow inserting if the object doesn't exist.
* @param [noReturn] - Do not return the stored object; an optimization.
* @param [meta] - Extra metadata to store in the event at `data[3]` but not
* in the object.
* @returns - If `noReturn` is false, the stored object is fetched from the
* DB.
*/
update(
o: Partial<Item>,
upsert?: boolean,
noReturn?: boolean,
meta?: any
): Promise<Item>
/**
* Remove an object.
*
* @param idOrObj - The id or the object itself.
* @param meta - Metadata, attached to the event only, at `data[3]`.
*/
remove(idOrObj: Item | Item[IDCol], meta?: any): Promise<void>
/** ChangeId: not implemented yet, had no need so far */
changeId(): Promise<void>
/**
* Returns the next available integer ID for the model. Calling this multiple
* times during a redux cycle will give increasing numbers even though the
* database table doesn't change. Use this from the redux functions to assign
* unique ids to new objects. **Only works if the ID type is number.**
*
* @returns - The next usable ID.
*/
getNextId(): Promise<number>
}