summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/db.ts
blob: bc0e4501776072d446f0833f5aed053e795a075b (plain)
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
import { MetaStores, Stores } from "./types/dbTypes";
import {
  openDatabase,
  Database,
  Store,
  Index,
  AnyStoreMap,
} from "./util/query";
import {
  IDBFactory,
  IDBDatabase,
  IDBObjectStore,
  IDBTransaction,
} from "@gnu-taler/idb-bridge";
import { Logger } from "./util/logging";

/**
 * Name of the Taler database.  This is effectively the major
 * version of the DB schema. Whenever it changes, custom import logic
 * for all previous versions must be written, which should be
 * avoided.
 */
const TALER_DB_NAME = "taler-wallet-main-v2";

const TALER_META_DB_NAME = "taler-wallet-meta";

const CURRENT_DB_CONFIG_KEY = "currentMainDbName";

/**
 * Current database minor version, should be incremented
 * each time we do minor schema changes on the database.
 * A change is considered minor when fields are added in a
 * backwards-compatible way or object stores and indices
 * are added.
 */
export const WALLET_DB_MINOR_VERSION = 1;

const logger = new Logger("db.ts");

function upgradeFromStoreMap(
  storeMap: AnyStoreMap,
  db: IDBDatabase,
  oldVersion: number,
  newVersion: number,
  upgradeTransaction: IDBTransaction,
): void {
  if (oldVersion === 0) {
    for (const n in storeMap) {
      if ((storeMap as any)[n] instanceof Store) {
        const si: Store<string, any> = (storeMap as any)[n];
        const s = db.createObjectStore(si.name, si.storeParams);
        for (const indexName in si as any) {
          if ((si as any)[indexName] instanceof Index) {
            const ii: Index<string, string, any, any> = (si as any)[indexName];
            s.createIndex(ii.indexName, ii.keyPath, ii.options);
          }
        }
      }
    }
    return;
  }
  if (oldVersion === newVersion) {
    return;
  }
  logger.info(`upgrading database from ${oldVersion} to ${newVersion}`);
  for (const n in Stores) {
    if ((Stores as any)[n] instanceof Store) {
      const si: Store<string, any> = (Stores as any)[n];
      let s: IDBObjectStore;
      const storeVersionAdded = si.storeParams?.versionAdded ?? 1;
      if (storeVersionAdded > oldVersion) {
        s = db.createObjectStore(si.name, si.storeParams);
      } else {
        s = upgradeTransaction.objectStore(si.name);
      }
      for (const indexName in si as any) {
        if ((si as any)[indexName] instanceof Index) {
          const ii: Index<string, string, any, any> = (si as any)[indexName];
          const indexVersionAdded = ii.options?.versionAdded ?? 0;
          if (
            indexVersionAdded > oldVersion ||
            storeVersionAdded > oldVersion
          ) {
            s.createIndex(ii.indexName, ii.keyPath, ii.options);
          }
        }
      }
    }
  }
}

function onTalerDbUpgradeNeeded(
  db: IDBDatabase,
  oldVersion: number,
  newVersion: number,
  upgradeTransaction: IDBTransaction,
) {
  upgradeFromStoreMap(Stores, db, oldVersion, newVersion, upgradeTransaction);
}

function onMetaDbUpgradeNeeded(
  db: IDBDatabase,
  oldVersion: number,
  newVersion: number,
  upgradeTransaction: IDBTransaction,
) {
  upgradeFromStoreMap(
    MetaStores,
    db,
    oldVersion,
    newVersion,
    upgradeTransaction,
  );
}

/**
 * Return a promise that resolves
 * to the taler wallet db.
 */
export async function openTalerDatabase(
  idbFactory: IDBFactory,
  onVersionChange: () => void,
): Promise<Database<typeof Stores>> {
  const metaDbHandle = await openDatabase(
    idbFactory,
    TALER_META_DB_NAME,
    1,
    () => {},
    onMetaDbUpgradeNeeded,
  );

  const metaDb = new Database(metaDbHandle, MetaStores);
  let currentMainVersion: string | undefined;
  await metaDb.runWithWriteTransaction([MetaStores.metaConfig], async (tx) => {
    const dbVersionRecord = await tx.get(
      MetaStores.metaConfig,
      CURRENT_DB_CONFIG_KEY,
    );
    if (!dbVersionRecord) {
      currentMainVersion = TALER_DB_NAME;
      await tx.put(MetaStores.metaConfig, {
        key: CURRENT_DB_CONFIG_KEY,
        value: TALER_DB_NAME,
      });
    } else {
      currentMainVersion = dbVersionRecord.value;
    }
  });

  if (currentMainVersion !== TALER_DB_NAME) {
    // In the future, the migration logic will be implemented here.
    throw Error(`migration from database ${currentMainVersion} not supported`);
  }

  const mainDbHandle = await openDatabase(
    idbFactory,
    TALER_DB_NAME,
    WALLET_DB_MINOR_VERSION,
    onVersionChange,
    onTalerDbUpgradeNeeded,
  );

  return new Database(mainDbHandle, Stores);
}

export function deleteTalerDatabase(idbFactory: IDBFactory): void {
  Database.deleteDatabase(idbFactory, TALER_DB_NAME);
}