@eltjs/elt-sqlite 0.0.0-stage → 0.1.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 +202 -0
- package/README.md +248 -2
- package/dist/computed-dataset.d.ts +22 -0
- package/dist/computed-dataset.js +230 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/open-destination.d.ts +3 -0
- package/dist/open-destination.js +14 -0
- package/dist/reserved.d.ts +7 -0
- package/dist/reserved.js +9 -0
- package/dist/sqlite-append-writer.d.ts +8 -0
- package/dist/sqlite-append-writer.js +10 -0
- package/dist/sqlite-catalog.d.ts +3 -0
- package/dist/sqlite-catalog.js +33 -0
- package/dist/sqlite-checkpoint-store.d.ts +9 -0
- package/dist/sqlite-checkpoint-store.js +84 -0
- package/dist/sqlite-column.d.ts +53 -0
- package/dist/sqlite-column.js +306 -0
- package/dist/sqlite-columns.d.ts +14 -0
- package/dist/sqlite-columns.js +118 -0
- package/dist/sqlite-deduplicating-writer.d.ts +21 -0
- package/dist/sqlite-deduplicating-writer.js +124 -0
- package/dist/sqlite-descriptions.d.ts +8 -0
- package/dist/sqlite-descriptions.js +37 -0
- package/dist/sqlite-destination.d.ts +19 -0
- package/dist/sqlite-destination.js +67 -0
- package/dist/sqlite-file-store.d.ts +17 -0
- package/dist/sqlite-file-store.js +82 -0
- package/dist/sqlite-identifiers.d.ts +8 -0
- package/dist/sqlite-identifiers.js +11 -0
- package/dist/sqlite-overwrite-writer.d.ts +9 -0
- package/dist/sqlite-overwrite-writer.js +13 -0
- package/dist/sqlite-passes.d.ts +39 -0
- package/dist/sqlite-passes.js +174 -0
- package/dist/sqlite-sync-history-schema.d.ts +8 -0
- package/dist/sqlite-sync-history-schema.js +94 -0
- package/dist/sqlite-sync-history.d.ts +12 -0
- package/dist/sqlite-sync-history.js +119 -0
- package/dist/sqlite-table.d.ts +15 -0
- package/dist/sqlite-table.js +83 -0
- package/dist/sqlite-views.d.ts +16 -0
- package/dist/sqlite-views.js +62 -0
- package/dist/sqlite-writer.d.ts +38 -0
- package/dist/sqlite-writer.js +483 -0
- package/dist/view.d.ts +20 -0
- package/dist/view.js +59 -0
- package/package.json +52 -4
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { transaction } from '@zukhruf/sqlite-transaction';
|
|
2
|
+
import { SyncHistory, copyStatus, errorMessage, passError, passFailureType, passStatus, recordedFailure, } from '@eltjs/elt';
|
|
3
|
+
import { openDestination } from './open-destination.js';
|
|
4
|
+
import { installSQLiteCatalog } from './sqlite-catalog.js';
|
|
5
|
+
import { SQLiteDestination } from './sqlite-destination.js';
|
|
6
|
+
import { attempts, checks, coverage, syncHistoryTables, syncHistoryViews, } from './sqlite-sync-history-schema.js';
|
|
7
|
+
import { publishSQLiteViews } from './sqlite-views.js';
|
|
8
|
+
// Records every pass in the file its connection loads, beside the data, and
|
|
9
|
+
// shows readers that history, and the catalog, through views there.
|
|
10
|
+
export class SQLiteSyncHistory extends SyncHistory {
|
|
11
|
+
#soleWriter;
|
|
12
|
+
// soleWriter: the caller knows that no other process runs a pass of its
|
|
13
|
+
// connections while this history is in use, such as the leader of a
|
|
14
|
+
// connection's flight.
|
|
15
|
+
constructor(options) {
|
|
16
|
+
super();
|
|
17
|
+
this.#soleWriter = options?.soleWriter === true;
|
|
18
|
+
Object.freeze(this);
|
|
19
|
+
}
|
|
20
|
+
validate(connection) {
|
|
21
|
+
this.#path(connection);
|
|
22
|
+
}
|
|
23
|
+
async recordChecks(connection, streamChecks) {
|
|
24
|
+
write(this.#path(connection), (database) => {
|
|
25
|
+
const check = database.prepare(`INSERT INTO ${checks} ("connector", "stream", "checked_at") VALUES (?, ?, ?) ON CONFLICT DO UPDATE SET "checked_at" = excluded."checked_at"`);
|
|
26
|
+
for (const { stream, at } of streamChecks)
|
|
27
|
+
check.run(connection.name, stream, at.toISOString());
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
// Every selected stream is declared before reading, even if it produces no
|
|
31
|
+
// rows or the process dies: an unfinished attempt stays running, never success.
|
|
32
|
+
async begin(connection, copies, at) {
|
|
33
|
+
const path = this.#path(connection);
|
|
34
|
+
const id = write(path, (database) => {
|
|
35
|
+
const attempt = database
|
|
36
|
+
.prepare(`INSERT INTO ${attempts} ("connector", "source", "started_at") VALUES (?, ?, ?) RETURNING "id"`)
|
|
37
|
+
.get(connection.name, connection.source.identity, at.toISOString());
|
|
38
|
+
if (attempt === undefined)
|
|
39
|
+
throw new Error('Sync attempt was not recorded');
|
|
40
|
+
const id = Number(attempt.id);
|
|
41
|
+
const declare = database.prepare(`INSERT INTO ${coverage} ("attempt_id", "stream", "target_schema", "target_table", "sync_mode", "destination_sync_mode", "description", "selection") VALUES (?, ?, 'main', ?, ?, ?, ?, ?)`);
|
|
42
|
+
for (const { copy, coverage } of copies)
|
|
43
|
+
declare.run(id, copy.from.name, copy.to.name, copy.configuration.syncMode, copy.configuration.destinationSyncMode, coverage.description, JSON.stringify(coverage.selection));
|
|
44
|
+
return id;
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
finish: async (outcomes, upstream, ended) => this.#finish(path, id, outcomes, upstream, ended),
|
|
48
|
+
fail: async (error, failureType, ended) => write(path, (database) => fail(database, id, error, failureType, ended)),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// Brings the history's tables to their declaration and publishes its views
|
|
52
|
+
// and the catalog in each connection's file. Tables another version
|
|
53
|
+
// declared otherwise are replaced, and their history with them. A sole
|
|
54
|
+
// writer then records every pass of its connections that began and never
|
|
55
|
+
// ended as failed: the process running it ended first, so no outcome will
|
|
56
|
+
// come, as Airbyte fails a connection's unfinished jobs before it starts the
|
|
57
|
+
// next. Any other caller leaves them running, since another process may
|
|
58
|
+
// still be running them.
|
|
59
|
+
async install(connections, at) {
|
|
60
|
+
for (const path of new Set(connections.map((each) => this.#path(each)))) {
|
|
61
|
+
write(path, (database) => {
|
|
62
|
+
syncHistoryTables.apply(database);
|
|
63
|
+
publishSQLiteViews(database, { views: syncHistoryViews });
|
|
64
|
+
});
|
|
65
|
+
installSQLiteCatalog({ path });
|
|
66
|
+
}
|
|
67
|
+
if (this.#soleWriter)
|
|
68
|
+
for (const connection of connections)
|
|
69
|
+
this.#failUnfinished(connection, at);
|
|
70
|
+
}
|
|
71
|
+
#failUnfinished(connection, at) {
|
|
72
|
+
const error = new Error('The process running this pass ended before the pass did');
|
|
73
|
+
write(this.#path(connection), (database) => {
|
|
74
|
+
const unfinished = database
|
|
75
|
+
.prepare(`SELECT "id" FROM ${attempts} WHERE "connector" = ? AND "status" = 'running'`)
|
|
76
|
+
.all(connection.name);
|
|
77
|
+
for (const { id } of unfinished)
|
|
78
|
+
fail(database, Number(id), error, 'system', at);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
#path({ name, destination }) {
|
|
82
|
+
if (!(destination instanceof SQLiteDestination))
|
|
83
|
+
throw new TypeError(`Connection ${name}: SQLite sync history records SQLite destinations only`);
|
|
84
|
+
return persistent(destination);
|
|
85
|
+
}
|
|
86
|
+
#finish(path, id, outcomes, upstream, ended) {
|
|
87
|
+
write(path, (database) => {
|
|
88
|
+
const record = database.prepare(`UPDATE ${coverage} SET "status" = ?, "written_count" = ?, "deleted_count" = ?, "failures" = ? WHERE "attempt_id" = ? AND "stream" = ?`);
|
|
89
|
+
for (const outcome of outcomes)
|
|
90
|
+
record.run(copyStatus(outcome), outcome.count, outcome.deleted, JSON.stringify(outcome.failures.map(recordedFailure)), id, outcome.copy.from.name);
|
|
91
|
+
const bound = database.prepare(`UPDATE ${coverage} SET "upstream_current_as_of" = ? WHERE "attempt_id" = ? AND "stream" = ?`);
|
|
92
|
+
for (const [stream, at] of upstream)
|
|
93
|
+
bound.run(at.toISOString(), id, stream);
|
|
94
|
+
database
|
|
95
|
+
.prepare(`UPDATE ${attempts} SET "completed_at" = ?, "status" = ?, "error" = ?, "failure_type" = ? WHERE "id" = ?`)
|
|
96
|
+
.run(ended.toISOString(), passStatus(outcomes), passError(outcomes), passFailureType(outcomes), id);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// A pass that produced no outcomes, so what it committed is unknown.
|
|
101
|
+
function fail(database, id, error, failureType, at) {
|
|
102
|
+
database
|
|
103
|
+
.prepare(`UPDATE ${coverage} SET "status" = 'failed', "failures" = ? WHERE "attempt_id" = ?`)
|
|
104
|
+
.run(JSON.stringify([
|
|
105
|
+
recordedFailure({ partition: null, error, failureType }),
|
|
106
|
+
]), id);
|
|
107
|
+
database
|
|
108
|
+
.prepare(`UPDATE ${attempts} SET "status" = 'failed', "completed_at" = ?, "error" = ?, "failure_type" = ? WHERE "id" = ?`)
|
|
109
|
+
.run(at.toISOString(), errorMessage(error), failureType, id);
|
|
110
|
+
}
|
|
111
|
+
function persistent({ path }) {
|
|
112
|
+
if (path === ':memory:')
|
|
113
|
+
throw new TypeError('SQLite sync history requires a destination file');
|
|
114
|
+
return path;
|
|
115
|
+
}
|
|
116
|
+
function write(path, work) {
|
|
117
|
+
using database = openDestination(path);
|
|
118
|
+
return transaction(database, () => work(database), { mode: 'immediate' });
|
|
119
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Stream } from '@eltjs/elt';
|
|
2
|
+
import { Target } from '@eltjs/elt';
|
|
3
|
+
import { SQLiteColumn } from './sqlite-column.ts';
|
|
4
|
+
export declare class SQLiteTable extends Target {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly columns: readonly SQLiteColumn[];
|
|
7
|
+
readonly readerView: string | undefined;
|
|
8
|
+
constructor(name: string, columns?: readonly SQLiteColumn[], readerView?: string);
|
|
9
|
+
withReaderView(name: string): SQLiteTable;
|
|
10
|
+
resolve(stream: Stream): SQLiteTable;
|
|
11
|
+
get location(): string;
|
|
12
|
+
get quotedName(): string;
|
|
13
|
+
get createTableSQL(): string;
|
|
14
|
+
definition(name: string): string;
|
|
15
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Target } from '@eltjs/elt';
|
|
2
|
+
import { SQLiteColumn, canonical } from './sqlite-column.js';
|
|
3
|
+
import { SQLiteColumns } from './sqlite-columns.js';
|
|
4
|
+
import { identifiers } from './sqlite-identifiers.js';
|
|
5
|
+
// A reusable target definition. Empty columns mean infer from the Copy's
|
|
6
|
+
// stream. Its own name, its view's and its columns' follow the destination's
|
|
7
|
+
// naming rule, so a field SQLite would take for another still loads.
|
|
8
|
+
export class SQLiteTable extends Target {
|
|
9
|
+
name;
|
|
10
|
+
columns;
|
|
11
|
+
// The name readers query: a documented view of exactly this table.
|
|
12
|
+
readerView;
|
|
13
|
+
constructor(name, columns, readerView) {
|
|
14
|
+
if (typeof name !== 'string' ||
|
|
15
|
+
(readerView !== undefined && typeof readerView !== 'string'))
|
|
16
|
+
throw new TypeError('A table and its view are named by strings');
|
|
17
|
+
const table = identifiers.relation(name);
|
|
18
|
+
const view = readerView === undefined ? undefined : identifiers.relation(readerView);
|
|
19
|
+
if (view !== undefined && identifiers.key(view) === identifiers.key(table))
|
|
20
|
+
throw new TypeError('A reader view needs a name of its own');
|
|
21
|
+
if (columns !== undefined &&
|
|
22
|
+
(!Array.isArray(columns) ||
|
|
23
|
+
columns.length === 0 ||
|
|
24
|
+
!columns.every((column) => column instanceof SQLiteColumn)))
|
|
25
|
+
throw new TypeError('A table requires at least one SQLite column');
|
|
26
|
+
const fields = (columns ?? []).map((column) => column.field);
|
|
27
|
+
if (new Set(fields).size !== fields.length)
|
|
28
|
+
throw new TypeError('Two columns hold one field');
|
|
29
|
+
if ((columns ?? []).filter((column) => column.isPrimaryKey).length > 1)
|
|
30
|
+
throw new TypeError('Only one primary-key column is supported');
|
|
31
|
+
const fileReads = (columns ?? []).flatMap((column) => column.fileRead === undefined ? [] : [column.fileRead]);
|
|
32
|
+
for (const column of columns ?? []) {
|
|
33
|
+
if (column.fileRead === undefined)
|
|
34
|
+
continue;
|
|
35
|
+
if (column.fileRead.outputType === 'bytes'
|
|
36
|
+
? column.kind !== 'blob'
|
|
37
|
+
: column.kind !== 'text')
|
|
38
|
+
throw new TypeError('Original files require a BLOB column; parsed text and stored references require a TEXT column');
|
|
39
|
+
}
|
|
40
|
+
super(fileReads);
|
|
41
|
+
this.name = table;
|
|
42
|
+
this.columns = Object.freeze(identifiers
|
|
43
|
+
.columns(columns ?? [])
|
|
44
|
+
.map(([column, stored]) => column.named(stored)));
|
|
45
|
+
this.readerView = view;
|
|
46
|
+
Object.freeze(this);
|
|
47
|
+
}
|
|
48
|
+
// Loads also keep a view of this table under another name, created with the
|
|
49
|
+
// table and described by the same descriptions, which every column then needs.
|
|
50
|
+
withReaderView(name) {
|
|
51
|
+
return new SQLiteTable(this.name, this.columns.length === 0 ? undefined : this.columns, name);
|
|
52
|
+
}
|
|
53
|
+
resolve(stream) {
|
|
54
|
+
if (this.columns.length === 0)
|
|
55
|
+
return new SQLiteTable(this.name, SQLiteColumns.fromSchema(stream.jsonSchema), this.readerView);
|
|
56
|
+
const { properties } = stream.jsonSchema;
|
|
57
|
+
// Explicit columns of a stream that declares no properties stand alone.
|
|
58
|
+
if (properties === undefined)
|
|
59
|
+
return this;
|
|
60
|
+
for (const column of this.columns)
|
|
61
|
+
if (column.fileRead === undefined &&
|
|
62
|
+
!Object.hasOwn(properties, column.field))
|
|
63
|
+
throw new TypeError(`Stream ${stream.name} does not describe field ${column.field}`);
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
// SQLite takes names that differ only in ASCII case for one, so one table
|
|
67
|
+
// has one location.
|
|
68
|
+
get location() {
|
|
69
|
+
return identifiers.key(this.name);
|
|
70
|
+
}
|
|
71
|
+
get quotedName() {
|
|
72
|
+
return `"${this.name.replaceAll('"', '""')}"`;
|
|
73
|
+
}
|
|
74
|
+
get createTableSQL() {
|
|
75
|
+
return `CREATE TABLE IF NOT EXISTS ${this.definition(this.quotedName)}`;
|
|
76
|
+
}
|
|
77
|
+
// A table named name with these columns, as CREATE TABLE spells it.
|
|
78
|
+
definition(name) {
|
|
79
|
+
if (this.columns.length === 0)
|
|
80
|
+
throw new TypeError('Resolve inferred columns before creating a table');
|
|
81
|
+
return `${name} (${this.columns.map((column) => column.definition).join(', ')}, "loaded_at" TEXT NOT NULL${canonical('timestamp', '"loaded_at"')}) STRICT`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { ReaderRelation } from '@eltjs/elt';
|
|
3
|
+
export type SQLiteView = ReaderRelation & {
|
|
4
|
+
readonly query: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function viewDefinition(view: SQLiteView): string;
|
|
7
|
+
/**
|
|
8
|
+
* Publish ordinary views and their descriptions on a caller-owned connection,
|
|
9
|
+
* inside its transaction when one is open. Supply trusted SQL and list
|
|
10
|
+
* dependencies before dependents. Only a named view whose stored definition
|
|
11
|
+
* differs is replaced, so a publication that changes nothing leaves the schema
|
|
12
|
+
* as it was.
|
|
13
|
+
*/
|
|
14
|
+
export declare function publishSQLiteViews(database: DatabaseSync, { views }: {
|
|
15
|
+
views: readonly SQLiteView[];
|
|
16
|
+
}): void;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Declaration } from '@zukhruf/sqlite-drift';
|
|
2
|
+
import { transaction } from '@zukhruf/sqlite-transaction';
|
|
3
|
+
import { isReserved } from './reserved.js';
|
|
4
|
+
import { describe } from './sqlite-descriptions.js';
|
|
5
|
+
const quote = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
6
|
+
export function viewDefinition(view) {
|
|
7
|
+
return `CREATE VIEW ${quote(view.name)} AS ${view.query}`;
|
|
8
|
+
}
|
|
9
|
+
function text(value, what) {
|
|
10
|
+
if (typeof value !== 'string' ||
|
|
11
|
+
!value.trim() ||
|
|
12
|
+
value.includes('\0') ||
|
|
13
|
+
!value.isWellFormed())
|
|
14
|
+
throw new TypeError(`Invalid ${what}`);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Publish ordinary views and their descriptions on a caller-owned connection,
|
|
18
|
+
* inside its transaction when one is open. Supply trusted SQL and list
|
|
19
|
+
* dependencies before dependents. Only a named view whose stored definition
|
|
20
|
+
* differs is replaced, so a publication that changes nothing leaves the schema
|
|
21
|
+
* as it was.
|
|
22
|
+
*/
|
|
23
|
+
export function publishSQLiteViews(database, { views }) {
|
|
24
|
+
const names = new Set();
|
|
25
|
+
for (const view of views) {
|
|
26
|
+
text(view.name, 'view name');
|
|
27
|
+
if (isReserved(view.name))
|
|
28
|
+
throw new TypeError('View names starting with _elt_ are reserved');
|
|
29
|
+
if (names.has(view.name.toLowerCase()))
|
|
30
|
+
throw new TypeError('Duplicate view names');
|
|
31
|
+
names.add(view.name.toLowerCase());
|
|
32
|
+
text(view.query, 'view query');
|
|
33
|
+
text(view.description, 'view description');
|
|
34
|
+
for (const [column, description] of Object.entries(view.columns)) {
|
|
35
|
+
text(column, 'column name');
|
|
36
|
+
text(description, 'column description');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (views.length === 0)
|
|
40
|
+
return;
|
|
41
|
+
// A failed definition restores every replaced view, even if the caller
|
|
42
|
+
// catches the error and continues its transaction. With no transaction of
|
|
43
|
+
// the caller, it begins deferred.
|
|
44
|
+
transaction(database, () => {
|
|
45
|
+
// A declaration refuses an element of more than one statement, so a
|
|
46
|
+
// query cannot append DDL.
|
|
47
|
+
new Declaration(views.map(viewDefinition), { rows: 'replace' }).apply(database);
|
|
48
|
+
for (const view of views) {
|
|
49
|
+
const columns = database
|
|
50
|
+
.prepare('SELECT "name" FROM pragma_table_info(?)')
|
|
51
|
+
.all(view.name)
|
|
52
|
+
.map(({ name }) => String(name));
|
|
53
|
+
if (columns.length !== Object.keys(view.columns).length ||
|
|
54
|
+
columns.some((name) => !Object.hasOwn(view.columns, name)))
|
|
55
|
+
throw new TypeError(`View ${quote(view.name)} must describe exactly its output columns: ${columns.join(', ')}`);
|
|
56
|
+
describe(database, view.name, view.description, Object.fromEntries(Object.entries(view.columns).map(([column, description]) => [
|
|
57
|
+
column,
|
|
58
|
+
{ description },
|
|
59
|
+
])));
|
|
60
|
+
}
|
|
61
|
+
}, { mode: 'deferred' });
|
|
62
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { DatabaseSync, SQLInputValue } from 'node:sqlite';
|
|
2
|
+
import type { CopyConfiguration, FieldValues, KeyValue, Stage } from '@eltjs/elt';
|
|
3
|
+
import { Writer } from '@eltjs/elt';
|
|
4
|
+
import type { SQLiteColumn } from './sqlite-column.ts';
|
|
5
|
+
import type { SQLiteTable } from './sqlite-table.ts';
|
|
6
|
+
export declare const seq = "\"_elt_seq\"";
|
|
7
|
+
export declare const op = "\"_elt_op\"";
|
|
8
|
+
export declare abstract class SQLiteWriter extends Writer {
|
|
9
|
+
#private;
|
|
10
|
+
readonly configuration: CopyConfiguration;
|
|
11
|
+
readonly path: string;
|
|
12
|
+
readonly table: SQLiteTable;
|
|
13
|
+
constructor(configuration: CopyConfiguration, path: string, table: SQLiteTable);
|
|
14
|
+
protected initialize(database: DatabaseSync, _replacing: boolean): void;
|
|
15
|
+
protected dropDedupIndexes(database: DatabaseSync, kept: string | null): void;
|
|
16
|
+
protected inspect(_database: DatabaseSync): void;
|
|
17
|
+
protected abstract merge(database: DatabaseSync, stage: string, loadedAt: string, into: string): void;
|
|
18
|
+
protected build(_database: DatabaseSync, _into: string): void;
|
|
19
|
+
protected adopt(_database: DatabaseSync): void;
|
|
20
|
+
protected append(database: DatabaseSync, stage: string, loadedAt: string, into: string): void;
|
|
21
|
+
protected get replaces(): boolean;
|
|
22
|
+
protected replace(database: DatabaseSync): void;
|
|
23
|
+
private get hash();
|
|
24
|
+
protected get fields(): string[];
|
|
25
|
+
protected encode(record: unknown): SQLInputValue[];
|
|
26
|
+
protected deletionKeys(_key: Readonly<Record<string, KeyValue>>): readonly [readonly SQLiteColumn[], SQLInputValue[]];
|
|
27
|
+
private writers;
|
|
28
|
+
private own;
|
|
29
|
+
private exists;
|
|
30
|
+
private values;
|
|
31
|
+
clear(writer: string, committed?: (values: FieldValues) => Promise<void>): Promise<void>;
|
|
32
|
+
private describe;
|
|
33
|
+
private installReaderView;
|
|
34
|
+
prepare(database: DatabaseSync, { writer, reloading }: {
|
|
35
|
+
writer: string;
|
|
36
|
+
reloading: boolean;
|
|
37
|
+
}, loadedAt: string): Stage;
|
|
38
|
+
}
|