@gonvex/expo-sqlite 0.5.2-staging.10 → 0.5.2-staging.11

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/index.d.ts CHANGED
@@ -18,6 +18,8 @@ export declare class ExpoSQLiteLocalReplicaStorage implements LocalReplicaStorag
18
18
  private initialize;
19
19
  private migrateSchema;
20
20
  load(scope?: ReplicaScope): Promise<ReplicaSnapshot | undefined>;
21
+ loadSession(scope: string): Promise<import("@gonvex/client").LocalReplicaSession | undefined>;
22
+ saveSession(scope: string, session: import("@gonvex/client").LocalReplicaSession | undefined): Promise<void>;
21
23
  applyTransaction(transaction: ReplicaTransaction, _snapshot: ReplicaSnapshot, scope?: ReplicaScope): Promise<void>;
22
24
  advanceWatermark(windows: readonly ReplicaWindow[], cursor: ReplicaSnapshot["cursor"], scope?: ReplicaScope): Promise<void>;
23
25
  replaceSnapshot(snapshot: ReplicaSnapshot, scope?: ReplicaScope): Promise<void>;
package/dist/index.js CHANGED
@@ -93,6 +93,18 @@ export class ExpoSQLiteLocalReplicaStorage {
93
93
  return undefined;
94
94
  return { cursor: cursor ? JSON.parse(cursor.value) : undefined, entities, liveQueries };
95
95
  }
96
+ async loadSession(scope) {
97
+ await this.initialize();
98
+ const row = await this.database.getFirstAsync(`SELECT scope, key, value FROM _gonvex_replica_meta WHERE scope = ? AND key = 'session'`, scope);
99
+ return row ? JSON.parse(row.value) : undefined;
100
+ }
101
+ async saveSession(scope, session) {
102
+ await this.initialize();
103
+ if (session)
104
+ await this.database.runAsync(`INSERT OR REPLACE INTO _gonvex_replica_meta (scope,key,value) VALUES (?, 'session', ?)`, scope, JSON.stringify(session));
105
+ else
106
+ await this.database.runAsync(`DELETE FROM _gonvex_replica_meta WHERE scope = ? AND key = 'session'`, scope);
107
+ }
96
108
  async applyTransaction(transaction, _snapshot, scope = defaultReplicaScope) {
97
109
  await this.initialize();
98
110
  await this.database.withTransactionAsync(async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gonvex/expo-sqlite",
3
- "version": "0.5.2-staging.10",
3
+ "version": "0.5.2-staging.11",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -18,7 +18,7 @@
18
18
  "README.md"
19
19
  ],
20
20
  "dependencies": {
21
- "@gonvex/client": "0.5.2-staging.10"
21
+ "@gonvex/client": "0.5.2-staging.11"
22
22
  },
23
23
  "devDependencies": {
24
24
  "typescript": "^6.0.3",
@@ -1,24 +0,0 @@
1
- import type { LocalReplicaStorage, ReplicaScope, ReplicaSnapshot, ReplicaTransaction } from "@gonvex/client";
2
- export interface ExpoSQLiteDatabase {
3
- execAsync(sql: string): Promise<void>;
4
- runAsync(sql: string, ...params: unknown[]): Promise<unknown>;
5
- getFirstAsync<T>(sql: string, ...params: unknown[]): Promise<T | null>;
6
- getAllAsync<T>(sql: string, ...params: unknown[]): Promise<T[]>;
7
- withTransactionAsync(task: () => Promise<void>): Promise<void>;
8
- }
9
- /**
10
- * Transactional, normalized SQLite persistence for Expo. Every server
11
- * transaction updates entities, Live Query memberships, and the replica
12
- * cursor inside one SQLite transaction.
13
- */
14
- export declare class ExpoSQLiteLocalReplicaStorage implements LocalReplicaStorage {
15
- private readonly database;
16
- private initialized?;
17
- constructor(database: ExpoSQLiteDatabase);
18
- private initialize;
19
- private migrateSchema;
20
- load(scope?: ReplicaScope): Promise<ReplicaSnapshot | undefined>;
21
- applyTransaction(transaction: ReplicaTransaction, _snapshot: ReplicaSnapshot, scope?: ReplicaScope): Promise<void>;
22
- replaceSnapshot(snapshot: ReplicaSnapshot, scope?: ReplicaScope): Promise<void>;
23
- }
24
- export declare function expoSQLite(database: ExpoSQLiteDatabase): LocalReplicaStorage;
package/dist/src/index.js DELETED
@@ -1,135 +0,0 @@
1
- const defaultReplicaScope = "default";
2
- const replicaSchemaVersion = 2;
3
- /**
4
- * Transactional, normalized SQLite persistence for Expo. Every server
5
- * transaction updates entities, Live Query memberships, and the replica
6
- * cursor inside one SQLite transaction.
7
- */
8
- export class ExpoSQLiteLocalReplicaStorage {
9
- database;
10
- initialized;
11
- constructor(database) {
12
- this.database = database;
13
- }
14
- initialize() {
15
- return this.initialized ??= this.migrateSchema();
16
- }
17
- async migrateSchema() {
18
- const [entities, queries, meta] = await Promise.all([
19
- this.database.getAllAsync(`PRAGMA table_info(_gonvex_replica_entities)`),
20
- this.database.getAllAsync(`PRAGMA table_info(_gonvex_replica_queries)`),
21
- this.database.getAllAsync(`PRAGMA table_info(_gonvex_replica_meta)`),
22
- ]);
23
- const legacyEntities = entities.length > 0 && !entities.some((column) => column.name === "scope");
24
- const legacyQueries = queries.length > 0 && !queries.some((column) => column.name === "scope");
25
- const legacyMeta = meta.length > 0 && !meta.some((column) => column.name === "scope");
26
- const createTables = `
27
- CREATE TABLE IF NOT EXISTS _gonvex_replica_entities (
28
- scope TEXT NOT NULL, entity TEXT NOT NULL, id TEXT NOT NULL, value TEXT NOT NULL,
29
- PRIMARY KEY (scope, entity, id)
30
- );
31
- CREATE TABLE IF NOT EXISTS _gonvex_replica_queries (
32
- scope TEXT NOT NULL, signature TEXT NOT NULL, value TEXT NOT NULL,
33
- PRIMARY KEY (scope, signature)
34
- );
35
- CREATE TABLE IF NOT EXISTS _gonvex_replica_meta (
36
- scope TEXT NOT NULL, key TEXT NOT NULL, value TEXT NOT NULL,
37
- PRIMARY KEY (scope, key)
38
- );
39
- `;
40
- if (!legacyEntities && !legacyQueries && !legacyMeta) {
41
- await this.database.execAsync(`PRAGMA user_version = ${replicaSchemaVersion};${createTables}`);
42
- return;
43
- }
44
- // The v1 tables had no namespace at all. Keep their contents available to
45
- // direct/default LocalReplica users, but never leave those rows in the
46
- // canonical tables where an authenticated scope could read them.
47
- await this.database.withTransactionAsync(async () => {
48
- if (legacyEntities)
49
- await this.database.execAsync(`ALTER TABLE _gonvex_replica_entities RENAME TO _gonvex_replica_entities_legacy_v1`);
50
- if (legacyQueries)
51
- await this.database.execAsync(`ALTER TABLE _gonvex_replica_queries RENAME TO _gonvex_replica_queries_legacy_v1`);
52
- if (legacyMeta)
53
- await this.database.execAsync(`ALTER TABLE _gonvex_replica_meta RENAME TO _gonvex_replica_meta_legacy_v1`);
54
- await this.database.execAsync(createTables);
55
- if (legacyEntities) {
56
- await this.database.execAsync(`INSERT INTO _gonvex_replica_entities (scope, entity, id, value)
57
- SELECT '${defaultReplicaScope}', entity, id, value FROM _gonvex_replica_entities_legacy_v1`);
58
- }
59
- if (legacyQueries) {
60
- await this.database.execAsync(`INSERT INTO _gonvex_replica_queries (scope, signature, value)
61
- SELECT '${defaultReplicaScope}', signature, value FROM _gonvex_replica_queries_legacy_v1`);
62
- }
63
- if (legacyMeta) {
64
- await this.database.execAsync(`INSERT INTO _gonvex_replica_meta (scope, key, value)
65
- SELECT '${defaultReplicaScope}', key, value FROM _gonvex_replica_meta_legacy_v1`);
66
- }
67
- await this.database.execAsync(`PRAGMA user_version = ${replicaSchemaVersion}`);
68
- });
69
- }
70
- async load(scope = defaultReplicaScope) {
71
- await this.initialize();
72
- const cursor = await this.database.getFirstAsync(`SELECT scope, key, value FROM _gonvex_replica_meta WHERE scope = ? AND key = 'cursor'`, scope);
73
- const entities = {};
74
- const entityRecords = await this.database.getAllAsync(`SELECT scope, entity, id, value FROM _gonvex_replica_entities WHERE scope = ? ORDER BY entity, id`, scope);
75
- for (const row of entityRecords) {
76
- (entities[row.entity] ??= {})[row.id] = JSON.parse(row.value);
77
- }
78
- const liveQueries = {};
79
- const queryRecords = await this.database.getAllAsync(`SELECT scope, signature, value FROM _gonvex_replica_queries WHERE scope = ? ORDER BY signature`, scope);
80
- for (const row of queryRecords) {
81
- liveQueries[row.signature] = JSON.parse(row.value);
82
- }
83
- if (!cursor && entityRecords.length === 0 && queryRecords.length === 0)
84
- return undefined;
85
- return { cursor: cursor ? JSON.parse(cursor.value) : undefined, entities, liveQueries };
86
- }
87
- async applyTransaction(transaction, _snapshot, scope = defaultReplicaScope) {
88
- await this.initialize();
89
- await this.database.withTransactionAsync(async () => {
90
- const previous = await this.database.getFirstAsync(`SELECT scope, key, value FROM _gonvex_replica_meta WHERE scope = ? AND key = 'cursor'`, scope);
91
- if (previous && JSON.parse(previous.value).epoch !== transaction.cursor.epoch) {
92
- await this.database.runAsync(`DELETE FROM _gonvex_replica_entities WHERE scope = ?`, scope);
93
- await this.database.runAsync(`DELETE FROM _gonvex_replica_queries WHERE scope = ?`, scope);
94
- }
95
- for (const change of transaction.changes) {
96
- if (change.operation === "delete") {
97
- await this.database.runAsync(`DELETE FROM _gonvex_replica_entities WHERE scope = ? AND entity = ? AND id = ?`, scope, change.entity, change.id);
98
- }
99
- else {
100
- await this.database.runAsync(`INSERT INTO _gonvex_replica_entities (scope, entity, id, value) VALUES (?, ?, ?, ?)
101
- ON CONFLICT(scope, entity, id) DO UPDATE SET value = excluded.value`, scope, change.entity, change.id, JSON.stringify(change.newValue));
102
- }
103
- }
104
- for (const membership of transaction.memberships ?? []) {
105
- await this.database.runAsync(`INSERT INTO _gonvex_replica_queries (scope, signature, value) VALUES (?, ?, ?)
106
- ON CONFLICT(scope, signature) DO UPDATE SET value = excluded.value`, scope, membership.signature, JSON.stringify(membership));
107
- }
108
- await this.database.runAsync(`INSERT INTO _gonvex_replica_meta (scope, key, value) VALUES (?, 'cursor', ?)
109
- ON CONFLICT(scope, key) DO UPDATE SET value = excluded.value`, scope, JSON.stringify(transaction.cursor));
110
- });
111
- }
112
- async replaceSnapshot(snapshot, scope = defaultReplicaScope) {
113
- await this.initialize();
114
- await this.database.withTransactionAsync(async () => {
115
- await this.database.runAsync(`DELETE FROM _gonvex_replica_entities WHERE scope = ?`, scope);
116
- await this.database.runAsync(`DELETE FROM _gonvex_replica_queries WHERE scope = ?`, scope);
117
- await this.database.runAsync(`DELETE FROM _gonvex_replica_meta WHERE scope = ? AND key = 'cursor'`, scope);
118
- for (const [entity, rows] of Object.entries(snapshot.entities)) {
119
- for (const [id, value] of Object.entries(rows)) {
120
- await this.database.runAsync(`INSERT INTO _gonvex_replica_entities (scope, entity, id, value) VALUES (?, ?, ?, ?)`, scope, entity, id, JSON.stringify(value));
121
- }
122
- }
123
- for (const [signature, membership] of Object.entries(snapshot.liveQueries)) {
124
- await this.database.runAsync(`INSERT INTO _gonvex_replica_queries (scope, signature, value) VALUES (?, ?, ?)`, scope, signature, JSON.stringify(membership));
125
- }
126
- if (snapshot.cursor) {
127
- await this.database.runAsync(`INSERT INTO _gonvex_replica_meta (scope, key, value) VALUES (?, 'cursor', ?)
128
- ON CONFLICT(scope, key) DO UPDATE SET value = excluded.value`, scope, JSON.stringify(snapshot.cursor));
129
- }
130
- });
131
- }
132
- }
133
- export function expoSQLite(database) {
134
- return new ExpoSQLiteLocalReplicaStorage(database);
135
- }