@sixb/sqlite 0.1.16 → 0.1.18
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/dist/ai-limit-storage.d.ts.map +1 -1
- package/dist/ai-usage-storage.d.ts.map +1 -1
- package/dist/execution-storage.d.ts +28 -0
- package/dist/execution-storage.d.ts.map +1 -1
- package/dist/file-download-grant-storage.d.ts +11 -0
- package/dist/file-download-grant-storage.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1362 -579
- package/dist/index.js.map +27 -25
- package/dist/migrations.d.ts.map +1 -1
- package/dist/objects/query-compiler.d.ts.map +1 -1
- package/dist/objects/reader.d.ts.map +1 -1
- package/dist/objects/storage.d.ts.map +1 -1
- package/dist/ontology-storage/index.d.ts +2 -0
- package/dist/ontology-storage/index.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-session.d.ts +17 -37
- package/dist/ontology-storage/materialization-session.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-state.d.ts +3 -26
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-writer.d.ts +1 -1
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts +3 -6
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/outbox.d.ts.map +1 -1
- package/dist/ontology-storage/replacement-plans.d.ts +88 -0
- package/dist/ontology-storage/replacement-plans.d.ts.map +1 -0
- package/dist/ontology-storage/shared.d.ts +8 -5
- package/dist/ontology-storage/shared.d.ts.map +1 -1
- package/dist/ontology-storage/source-cleanup.d.ts.map +1 -1
- package/dist/ontology-storage/source-roots.d.ts +15 -2
- package/dist/ontology-storage/source-roots.d.ts.map +1 -1
- package/dist/ontology-storage/sources.d.ts +1 -2
- package/dist/ontology-storage/sources.d.ts.map +1 -1
- package/dist/projection-run-storage.d.ts.map +1 -1
- package/dist/rules-storage.d.ts.map +1 -1
- package/dist/testing.d.ts +4 -1
- package/dist/testing.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/ai-cost-storage.ts +10 -0
- package/src/ai-limit-storage.ts +13 -2
- package/src/ai-usage-storage.ts +9 -1
- package/src/execution-storage.ts +7 -2
- package/src/file-download-grant-storage.ts +76 -0
- package/src/index.ts +10 -3
- package/src/migrations/053-replacement-plans.sql +73 -0
- package/src/migrations/054-slim-ontology-outbox.sql +21 -0
- package/src/migrations/055-commit-touches.sql +31 -0
- package/src/migrations/056-audio-transcription.sql +10 -0
- package/src/migrations/057-reranking-model-kind.sql +16 -0
- package/src/migrations/058-file-download-grants.sql +21 -0
- package/src/migrations/059-drained-source-versions.sql +11 -0
- package/src/migrations.ts +115 -0
- package/src/objects/query-compiler.ts +5 -0
- package/src/objects/reader.ts +1 -0
- package/src/objects/storage.ts +5 -11
- package/src/ontology-storage/index.ts +6 -1
- package/src/ontology-storage/materialization-session.ts +120 -211
- package/src/ontology-storage/materialization-state.ts +5 -261
- package/src/ontology-storage/materialization-writer.ts +11 -13
- package/src/ontology-storage/materializations.ts +133 -210
- package/src/ontology-storage/outbox.ts +50 -16
- package/src/ontology-storage/replacement-plans.ts +1064 -0
- package/src/ontology-storage/shared.ts +17 -12
- package/src/ontology-storage/source-cleanup.ts +24 -9
- package/src/ontology-storage/source-roots.ts +38 -14
- package/src/ontology-storage/sources.ts +0 -26
- package/src/projection-run-storage.ts +7 -1
- package/src/rules-storage.ts +9 -14
- package/src/testing.ts +6 -4
|
@@ -2,6 +2,7 @@ import type { Database } from "bun:sqlite"
|
|
|
2
2
|
import { parseSixbFailure } from "@sixb/core/internal/errors"
|
|
3
3
|
import { MaterializationConflictError } from "@sixb/core/internal/materialization"
|
|
4
4
|
import {
|
|
5
|
+
type ClaimedOutboxDraft,
|
|
5
6
|
sourceAssertionFromColumns,
|
|
6
7
|
sourceEntityFromKey,
|
|
7
8
|
} from "@sixb/core/internal/ontology-storage-provider"
|
|
@@ -9,7 +10,6 @@ import type {
|
|
|
9
10
|
AssertSourceMaterializationExecutionInput,
|
|
10
11
|
OntologyCommitRecord,
|
|
11
12
|
OntologyCommitWrite,
|
|
12
|
-
OntologyOutboxRecord,
|
|
13
13
|
OntologySourceRecord,
|
|
14
14
|
StageSourceAssertion,
|
|
15
15
|
} from "@sixb/core/storage"
|
|
@@ -40,8 +40,6 @@ export interface SqliteOntologyCommitRow {
|
|
|
40
40
|
readonly origin_run_id: string | null
|
|
41
41
|
readonly origin_batch_ordinal: number | null
|
|
42
42
|
readonly origin: string
|
|
43
|
-
readonly requested_by: string | null
|
|
44
|
-
readonly executor: string
|
|
45
43
|
readonly ontology_revision: string
|
|
46
44
|
readonly projection_revision: string | null
|
|
47
45
|
readonly ownership_hash: string | null
|
|
@@ -102,7 +100,10 @@ export interface SqliteStoredOverrideRow {
|
|
|
102
100
|
}
|
|
103
101
|
|
|
104
102
|
export interface SqliteOntologyOutboxRow {
|
|
105
|
-
readonly
|
|
103
|
+
readonly id: string
|
|
104
|
+
readonly commit_id: string
|
|
105
|
+
readonly commit_ordinal: number
|
|
106
|
+
readonly event: string
|
|
106
107
|
readonly available_at: string
|
|
107
108
|
readonly attempts: number
|
|
108
109
|
readonly lease_id: string | null
|
|
@@ -172,12 +173,6 @@ export function commitRecord(row: SqliteOntologyCommitRow): OntologyCommitRecord
|
|
|
172
173
|
requestHash: row.request_hash,
|
|
173
174
|
executionId: row.execution_id,
|
|
174
175
|
origin: parseJson<OntologyCommitWrite["origin"]>(row.origin),
|
|
175
|
-
...(row.requested_by === null
|
|
176
|
-
? {}
|
|
177
|
-
: {
|
|
178
|
-
requestedBy: parseJson<NonNullable<OntologyCommitWrite["requestedBy"]>>(row.requested_by),
|
|
179
|
-
}),
|
|
180
|
-
executor: parseJson<OntologyCommitWrite["executor"]>(row.executor),
|
|
181
176
|
ontologyRevision: row.ontology_revision,
|
|
182
177
|
...(row.projection_revision === null ? {} : { projectionRevision: row.projection_revision }),
|
|
183
178
|
...(row.ownership_hash === null ? {} : { ownershipHash: row.ownership_hash }),
|
|
@@ -188,9 +183,19 @@ export function commitRecord(row: SqliteOntologyCommitRow): OntologyCommitRecord
|
|
|
188
183
|
return { ...base, intent, result } as OntologyCommitRecord
|
|
189
184
|
}
|
|
190
185
|
|
|
191
|
-
|
|
186
|
+
/** A claimed row as stored: its draft, to rebuild from its commit. */
|
|
187
|
+
export function claimedOutboxDraft(row: SqliteOntologyOutboxRow): ClaimedOutboxDraft {
|
|
188
|
+
if (row.lease_id === null || row.lease_expires_at === null) {
|
|
189
|
+
throw new MaterializationConflictError(
|
|
190
|
+
"outbox-lease",
|
|
191
|
+
"Ontology outbox claim returned an unpaired lease."
|
|
192
|
+
)
|
|
193
|
+
}
|
|
192
194
|
return {
|
|
193
|
-
|
|
195
|
+
id: row.id,
|
|
196
|
+
commitId: row.commit_id,
|
|
197
|
+
commitOrdinal: row.commit_ordinal,
|
|
198
|
+
event: parseJson<ClaimedOutboxDraft["event"]>(row.event),
|
|
194
199
|
availableAt: row.available_at,
|
|
195
200
|
attempts: row.attempts,
|
|
196
201
|
leaseId: row.lease_id,
|
|
@@ -5,6 +5,14 @@ import type {
|
|
|
5
5
|
PurgeAbandonedSourceMaterializationsInput,
|
|
6
6
|
} from "@sixb/core/storage"
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* A version outlives its replacement plan, which references it: the next version may take a
|
|
10
|
+
* deleted one's id, and would then resume the plan left under it.
|
|
11
|
+
*/
|
|
12
|
+
const PLAN_PURGED = `NOT EXISTS (
|
|
13
|
+
SELECT 1 FROM ontology_replacement_plans AS plans WHERE plans.version_id = versions.version_id
|
|
14
|
+
)`
|
|
15
|
+
|
|
8
16
|
/**
|
|
9
17
|
* Superseded versions can still hold live roots (a delta keeps unchanged roots where they were),
|
|
10
18
|
* so they are cleaned root by root once retired. An abandoned candidate was never published:
|
|
@@ -18,8 +26,8 @@ export function purgeAbandonedSourceVersions(
|
|
|
18
26
|
let rowsDeleted = 0
|
|
19
27
|
let materializationsDeleted = 0
|
|
20
28
|
const nextCandidate = db.query(`
|
|
21
|
-
SELECT version_id FROM ontology_sources
|
|
22
|
-
WHERE project_id = ? AND status = 'abandoned'
|
|
29
|
+
SELECT version_id FROM ontology_sources AS versions
|
|
30
|
+
WHERE project_id = ? AND status = 'abandoned' AND ${PLAN_PURGED}
|
|
23
31
|
ORDER BY terminal_at, source_id, materialization_id LIMIT 1
|
|
24
32
|
`)
|
|
25
33
|
const deleteRows = db.query(`DELETE FROM ontology_source_rows WHERE rowid IN (
|
|
@@ -75,10 +83,18 @@ export function cleanupSourceVersions(
|
|
|
75
83
|
WHERE ${retired}
|
|
76
84
|
AND NOT EXISTS (SELECT 1 FROM ontology_source_rows AS rows WHERE rows.root_id = roots.id)
|
|
77
85
|
ORDER BY roots.retired_at, roots.id LIMIT ?
|
|
78
|
-
)`)
|
|
79
|
-
.
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
) RETURNING version_id`)
|
|
87
|
+
.all(input.projectId, input.terminalBefore, remaining) as { readonly version_id: number }[]
|
|
88
|
+
// Each version's root_count follows its roots down, so a drained version is found by index.
|
|
89
|
+
const counts = new Map<number, number>()
|
|
90
|
+
for (const root of removed) counts.set(root.version_id, (counts.get(root.version_id) ?? 0) + 1)
|
|
91
|
+
const drain = db.query(
|
|
92
|
+
"UPDATE ontology_sources SET root_count = root_count - ? WHERE version_id = ?"
|
|
93
|
+
)
|
|
94
|
+
// These commit with the delete: every ontology storage call runs in the caller's BEGIN IMMEDIATE.
|
|
95
|
+
for (const [versionId, count] of counts) drain.run(count, versionId)
|
|
96
|
+
rowsDeleted += removed.length
|
|
97
|
+
remaining -= removed.length
|
|
82
98
|
}
|
|
83
99
|
const materializationsDeleted =
|
|
84
100
|
remaining === 0
|
|
@@ -86,9 +102,8 @@ export function cleanupSourceVersions(
|
|
|
86
102
|
: db
|
|
87
103
|
.query(`DELETE FROM ontology_sources WHERE version_id IN (
|
|
88
104
|
SELECT versions.version_id FROM ontology_sources AS versions
|
|
89
|
-
WHERE project_id = ? AND status = 'superseded' AND terminal_at < ?
|
|
90
|
-
AND
|
|
91
|
-
WHERE roots.version_id = versions.version_id)
|
|
105
|
+
WHERE project_id = ? AND status = 'superseded' AND root_count = 0 AND terminal_at < ?
|
|
106
|
+
AND ${PLAN_PURGED}
|
|
92
107
|
ORDER BY terminal_at, source_id, materialization_id LIMIT ?
|
|
93
108
|
)`)
|
|
94
109
|
.run(input.projectId, input.terminalBefore, remaining).changes
|
|
@@ -22,7 +22,7 @@ import { canonicalJson, type SqliteOntologySourceRow } from "./shared"
|
|
|
22
22
|
* version; the candidate's own roots are never rewritten. One live root per source and key holds
|
|
23
23
|
* because activation, under the source fence, retires every root the candidate replaces.
|
|
24
24
|
*/
|
|
25
|
-
const PUBLISHED = "('active', 'superseded')"
|
|
25
|
+
export const PUBLISHED = "('active', 'superseded')"
|
|
26
26
|
|
|
27
27
|
/** The columns `sourceAssertion` reads, over `versions`, `roots` and `rows`. */
|
|
28
28
|
export const SOURCE_ASSERTION_COLUMNS = `versions.source_id, versions.materialization_id,
|
|
@@ -162,10 +162,14 @@ export function stageSourceRoots(
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
/**
|
|
165
|
+
/**
|
|
166
|
+
* Retires what the candidate replaces; its own roots go live when its version turns active. Every
|
|
167
|
+
* link those roots assert changes its live source, which commit `commitId` records as touched.
|
|
168
|
+
*/
|
|
166
169
|
export function activateSourceRoots(
|
|
167
170
|
db: Database,
|
|
168
171
|
projectId: string,
|
|
172
|
+
commitId: string,
|
|
169
173
|
candidate: SqliteOntologySourceRow,
|
|
170
174
|
activation: SourceActivationWrite
|
|
171
175
|
): void {
|
|
@@ -180,20 +184,40 @@ export function activateSourceRoots(
|
|
|
180
184
|
)
|
|
181
185
|
if (candidate.base_materialization_id !== null && candidate.root_count === 0) return
|
|
182
186
|
const delta = candidate.base_materialization_id !== null
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
AND
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
activation.updatedAt,
|
|
187
|
+
const replaced = `retired_at IS NULL AND deleted = 0
|
|
188
|
+
AND version_id IN (SELECT version_id FROM ontology_sources
|
|
189
|
+
WHERE project_id = ? AND source_id = ? AND status IN ${PUBLISHED})
|
|
190
|
+
${
|
|
191
|
+
delta
|
|
192
|
+
? `AND project_id = ? AND root_key IN (
|
|
193
|
+
SELECT root_key FROM ontology_source_roots WHERE version_id = ?)`
|
|
194
|
+
: ""
|
|
195
|
+
}`
|
|
196
|
+
const replacedParams = [
|
|
194
197
|
projectId,
|
|
195
198
|
activation.source.projectionId,
|
|
196
|
-
...(delta ? [projectId, candidate.version_id] : [])
|
|
199
|
+
...(delta ? [projectId, candidate.version_id] : []),
|
|
200
|
+
]
|
|
201
|
+
db.query(`INSERT INTO ontology_commit_touches (project_id, commit_id, entity_kind, identity_key)
|
|
202
|
+
SELECT DISTINCT ?, ?, 'link', json_array(
|
|
203
|
+
rows.source_type_id, rows.source_primary_id, rows.link_id, rows.target_type_id,
|
|
204
|
+
rows.target_primary_id
|
|
205
|
+
)
|
|
206
|
+
FROM (
|
|
207
|
+
SELECT id FROM ontology_source_roots WHERE ${replaced}
|
|
208
|
+
UNION ALL
|
|
209
|
+
SELECT id FROM ontology_source_roots WHERE version_id = ?
|
|
210
|
+
) AS changed
|
|
211
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = changed.id
|
|
212
|
+
WHERE rows.entity_kind = 'link'`).run(
|
|
213
|
+
projectId,
|
|
214
|
+
commitId,
|
|
215
|
+
...replacedParams,
|
|
216
|
+
candidate.version_id
|
|
217
|
+
)
|
|
218
|
+
db.query(`UPDATE ontology_source_roots SET retired_at = ? WHERE ${replaced}`).run(
|
|
219
|
+
activation.updatedAt,
|
|
220
|
+
...replacedParams
|
|
197
221
|
)
|
|
198
222
|
// Deletions are never live; retiring them lets cleanup remove them with their version.
|
|
199
223
|
db.query(`UPDATE ontology_source_roots SET retired_at = ?
|
|
@@ -33,8 +33,6 @@ import type {
|
|
|
33
33
|
PurgeAbandonedSourceMaterializationsInput,
|
|
34
34
|
StageSourceRowsInput,
|
|
35
35
|
StageSourceRowsResult,
|
|
36
|
-
SummarizeTerminalSourceMaterializationsInput,
|
|
37
|
-
TerminalSourceMaterializationSummary,
|
|
38
36
|
} from "@sixb/core/storage"
|
|
39
37
|
import { yieldSqliteEventLoop } from "../transactions"
|
|
40
38
|
import {
|
|
@@ -317,30 +315,6 @@ export class SqliteOntologySourceStorage implements OntologySourceStorage {
|
|
|
317
315
|
})
|
|
318
316
|
}
|
|
319
317
|
|
|
320
|
-
async summarizeTerminal(
|
|
321
|
-
input: SummarizeTerminalSourceMaterializationsInput
|
|
322
|
-
): Promise<TerminalSourceMaterializationSummary> {
|
|
323
|
-
return this.runRootOperation(() => {
|
|
324
|
-
assertNonblank(input.projectId, "Terminal source summary project id")
|
|
325
|
-
const summary = this.db
|
|
326
|
-
.query(
|
|
327
|
-
`
|
|
328
|
-
SELECT COUNT(*) AS terminal_count, MIN(terminal_at) AS oldest_terminal_at
|
|
329
|
-
FROM ontology_sources
|
|
330
|
-
WHERE project_id = ? AND status IN ('superseded', 'abandoned')
|
|
331
|
-
`
|
|
332
|
-
)
|
|
333
|
-
.get(input.projectId) as {
|
|
334
|
-
readonly terminal_count: number
|
|
335
|
-
readonly oldest_terminal_at: string | null
|
|
336
|
-
}
|
|
337
|
-
return {
|
|
338
|
-
count: summary.terminal_count,
|
|
339
|
-
oldestTerminalAt: summary.oldest_terminal_at,
|
|
340
|
-
}
|
|
341
|
-
})
|
|
342
|
-
}
|
|
343
|
-
|
|
344
318
|
private abandonCandidate(
|
|
345
319
|
input: AbandonSourceMaterializationCandidateInput
|
|
346
320
|
): OntologySourceRecord {
|
|
@@ -396,7 +396,7 @@ export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
|
396
396
|
async list(input: ListProjectionRunsInput): Promise<ListProjectionRunsResult> {
|
|
397
397
|
assertProjectionRunNonEmpty(input.projectId, "projectId")
|
|
398
398
|
|
|
399
|
-
if (input.statuses
|
|
399
|
+
if (input.statuses?.length === 0 || input.errorCodes?.length === 0) {
|
|
400
400
|
return { runs: [], hasMore: false, total: 0 }
|
|
401
401
|
}
|
|
402
402
|
|
|
@@ -435,6 +435,12 @@ export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
|
435
435
|
whereClauses.push(`status IN (${input.statuses.map(() => "?").join(", ")})`)
|
|
436
436
|
args.push(...input.statuses)
|
|
437
437
|
}
|
|
438
|
+
if (input.errorCodes) {
|
|
439
|
+
whereClauses.push(
|
|
440
|
+
`json_extract(error, '$.code') IN (${input.errorCodes.map(() => "?").join(", ")})`
|
|
441
|
+
)
|
|
442
|
+
args.push(...input.errorCodes)
|
|
443
|
+
}
|
|
438
444
|
if (input.startedAfter) {
|
|
439
445
|
whereClauses.push("COALESCE(started_at, queued_at) >= ?")
|
|
440
446
|
args.push(input.startedAfter.toISOString())
|
package/src/rules-storage.ts
CHANGED
|
@@ -175,28 +175,23 @@ export class SqliteRulesStorage implements RulesStorage {
|
|
|
175
175
|
input: ListRuleStatesReconciliationPageInput
|
|
176
176
|
): Promise<ListRuleStatesReconciliationPageResult> {
|
|
177
177
|
assertPositiveLimit(input.limit)
|
|
178
|
+
const { after } = input
|
|
179
|
+
const cursor = after ? [after.ruleId, after.objectTypeId, after.primaryId] : []
|
|
180
|
+
// The cursor and order follow the primary key column for column so each page seeks to its
|
|
181
|
+
// start. `subject_kind` is always 'object' (CHECK); filtering on it steers SQLite onto
|
|
182
|
+
// idx_rule_states_project_rule and a full sort per page.
|
|
178
183
|
const rows = this.db
|
|
179
184
|
.query(
|
|
180
185
|
`
|
|
181
186
|
SELECT project_id, rule_id, subject_kind, object_type_id, primary_id, triggered_at
|
|
182
187
|
FROM rule_states
|
|
183
|
-
WHERE project_id = ?
|
|
184
|
-
AND (
|
|
185
|
-
|
|
186
|
-
(rule_id, object_type_id, primary_id) > (?, ?, ?)
|
|
187
|
-
)
|
|
188
|
-
ORDER BY rule_id ASC, object_type_id ASC, primary_id ASC
|
|
188
|
+
WHERE project_id = ?
|
|
189
|
+
${cursor.length > 0 ? "AND (rule_id, subject_kind, object_type_id, primary_id) > (?, 'object', ?, ?)" : ""}
|
|
190
|
+
ORDER BY rule_id ASC, subject_kind ASC, object_type_id ASC, primary_id ASC
|
|
189
191
|
LIMIT ?
|
|
190
192
|
`
|
|
191
193
|
)
|
|
192
|
-
.all(
|
|
193
|
-
input.projectId,
|
|
194
|
-
input.after?.ruleId ?? null,
|
|
195
|
-
input.after?.ruleId ?? null,
|
|
196
|
-
input.after?.objectTypeId ?? null,
|
|
197
|
-
input.after?.primaryId ?? null,
|
|
198
|
-
input.limit + 1
|
|
199
|
-
) as SqliteRuleStateRow[]
|
|
194
|
+
.all(input.projectId, ...cursor, input.limit + 1) as SqliteRuleStateRow[]
|
|
200
195
|
return reconciliationPage(rows, input.limit)
|
|
201
196
|
}
|
|
202
197
|
|
package/src/testing.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
|
|
1
3
|
export interface SqliteStorageTestingAdapter {
|
|
2
4
|
advanceConnectorConnectionTime(durationMs: number): void
|
|
5
|
+
/** The store's write connection, for tests that inspect tables no public read exposes. */
|
|
6
|
+
readonly db: Database
|
|
3
7
|
}
|
|
4
8
|
|
|
5
9
|
const adapters = new WeakMap<object, SqliteStorageTestingAdapter>()
|
|
6
10
|
|
|
7
11
|
export function registerSqliteStorageTestingAdapter(
|
|
8
12
|
storage: object,
|
|
9
|
-
|
|
13
|
+
adapter: SqliteStorageTestingAdapter
|
|
10
14
|
): void {
|
|
11
|
-
adapters.set(storage,
|
|
12
|
-
advanceConnectorConnectionTime,
|
|
13
|
-
})
|
|
15
|
+
adapters.set(storage, adapter)
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export function getSqliteStorageTestingAdapter(storage: object): SqliteStorageTestingAdapter {
|