rastack 0.0.16 → 0.0.19
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/CHANGELOG.md +43874 -43013
- package/dist/.prettierrc +7 -7
- package/dist/compile/analyze.js +19 -11
- package/dist/compile/index.js +26 -17
- package/dist/compile/manifest.js +2 -3
- package/dist/compile/openapi.js +5 -8
- package/dist/compile/program.js +19 -10
- package/dist/csv-schema.js +5 -6
- package/dist/define/index.js +2 -2
- package/dist/entity-generation/delete-method.js +8 -10
- package/dist/entity-generation/form.js +80 -82
- package/dist/entity-generation/get-method.js +35 -36
- package/dist/entity-generation/imports.js +24 -20
- package/dist/entity-generation/sync-method.d.ts +1 -1
- package/dist/entity-generation/sync-method.js +45 -46
- package/dist/entity-generation/update-method.js +9 -10
- package/dist/rad-wasm-build.d.ts +1 -1
- package/dist/rad-wasm-build.js +15 -6
- package/dist/rad.js +9 -2
- package/dist/schema/camel-to-pastel.js +1 -2
- package/dist/schema/capitalise-first-letter.js +1 -2
- package/dist/schema/extract-response.js +3 -4
- package/dist/schema/fetch-schema.js +4 -16
- package/dist/schema/remove-non-model-paths.js +1 -2
- package/dist/schema/to-camel-case.js +1 -2
- package/dist/schema/to-pastel-case.js +1 -2
- package/dist/schema-convert.js +22 -33
- package/dist/schema-entities.js +177 -189
- package/dist/schema-fetch.js +46 -63
- package/dist/schema-full.js +23 -36
- package/dist/schema-index.js +16 -27
- package/dist/schema-params.js +11 -22
- package/dist/seed.js +95 -97
- package/hooks/form/form.ts +207 -207
- package/hooks/form/index.ts +8 -8
- package/hooks/form/interfaces.ts +217 -217
- package/hooks/form/structure.ts +39 -39
- package/hooks/form/validate-schema.ts +49 -49
- package/hooks/index.ts +3 -3
- package/hooks/query/api.ts +42 -42
- package/hooks/query/delete.ts +45 -45
- package/hooks/query/fetch.ts +48 -48
- package/hooks/query/index.ts +21 -21
- package/hooks/query/interfaces.ts +111 -111
- package/hooks/query/list.ts +286 -286
- package/hooks/query/update.ts +88 -88
- package/hooks/query/url.ts +31 -31
- package/hooks/real-time/index.ts +1 -1
- package/hooks/real-time/pusher.ts +43 -43
- package/jest.config.cjs +6 -6
- package/package.json +57 -57
- package/provider/index.ts +13 -13
- package/provider/provider.tsx +187 -187
- package/provider/types.ts +114 -114
- package/provider/warehouse.ts +167 -167
- package/provider/wasm.ts +124 -124
- package/runtime.ts +3 -3
- package/src/.prettierrc +7 -7
- package/src/compile/analyze.ts +224 -224
- package/src/compile/index.ts +86 -86
- package/src/compile/manifest.ts +10 -10
- package/src/compile/model.ts +69 -69
- package/src/compile/openapi.ts +266 -266
- package/src/compile/program.ts +40 -40
- package/src/csv-schema.ts +187 -187
- package/src/define/index.ts +162 -162
- package/src/entity-generation/delete-method.ts +37 -37
- package/src/entity-generation/form.ts +169 -169
- package/src/entity-generation/get-method.ts +80 -80
- package/src/entity-generation/imports.ts +110 -110
- package/src/entity-generation/sync-method.ts +222 -222
- package/src/entity-generation/update-method.ts +38 -38
- package/src/index.ts +77 -77
- package/src/rad-compile.ts +88 -88
- package/src/rad-wasm-build.ts +102 -102
- package/src/rad.ts +101 -101
- package/src/scan.ts +89 -89
- package/src/schema/camel-to-pastel.ts +3 -3
- package/src/schema/capitalise-first-letter.ts +3 -3
- package/src/schema/extract-response.ts +38 -38
- package/src/schema/fetch-schema.ts +12 -12
- package/src/schema/remove-non-model-paths.ts +13 -13
- package/src/schema/to-camel-case.ts +7 -7
- package/src/schema/to-pastel-case.ts +6 -6
- package/src/schema-convert.ts +61 -61
- package/src/schema-entities.ts +401 -401
- package/src/schema-fetch.ts +82 -82
- package/src/schema-full.ts +38 -38
- package/src/schema-index.ts +29 -29
- package/src/schema-params.ts +116 -116
- package/src/seed.ts +297 -297
- package/sync/engine.ts +392 -392
- package/sync/hooks.ts +450 -450
- package/sync/index.ts +8 -8
- package/sync/persistence.ts +237 -237
- package/sync/provider.tsx +91 -91
- package/sync/registry.ts +36 -36
- package/sync/store.ts +126 -126
- package/sync/transactions.ts +300 -300
- package/sync/types.ts +94 -94
- package/test/compile.spec.ts +192 -192
- package/test/csv-schema.spec.ts +143 -143
- package/test/schema-entities.spec.ts +688 -688
- package/tsconfig.json +24 -24
- package/types.ts +18 -18
package/sync/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export * from "./types";
|
|
2
|
-
export * from "./registry";
|
|
3
|
-
export * from "./store";
|
|
4
|
-
export * from "./persistence";
|
|
5
|
-
export * from "./transactions";
|
|
6
|
-
export * from "./engine";
|
|
7
|
-
export * from "./provider";
|
|
8
|
-
export * from "./hooks";
|
|
1
|
+
export * from "./types";
|
|
2
|
+
export * from "./registry";
|
|
3
|
+
export * from "./store";
|
|
4
|
+
export * from "./persistence";
|
|
5
|
+
export * from "./transactions";
|
|
6
|
+
export * from "./engine";
|
|
7
|
+
export * from "./provider";
|
|
8
|
+
export * from "./hooks";
|
package/sync/persistence.ts
CHANGED
|
@@ -1,237 +1,237 @@
|
|
|
1
|
-
import { EntityKey, SyncTransaction } from "./types";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Durable storage behind the sync engine: entity rows, the transaction
|
|
5
|
-
* queue, and cursor metadata. IndexedDB in the browser; an in-memory
|
|
6
|
-
* fallback everywhere else (SSR, tests, unsupported browsers) so the
|
|
7
|
-
* engine still runs — it just re-bootstraps on reload.
|
|
8
|
-
*/
|
|
9
|
-
export interface SyncPersistence {
|
|
10
|
-
getMeta(key: string): Promise<any | undefined>;
|
|
11
|
-
setMeta(key: string, value: any): Promise<void>;
|
|
12
|
-
getRecords(entity: EntityKey): Promise<Record<string, any>[]>;
|
|
13
|
-
putRecords(
|
|
14
|
-
entity: EntityKey,
|
|
15
|
-
rows: { id: string; data: Record<string, any> }[],
|
|
16
|
-
): Promise<void>;
|
|
17
|
-
deleteRecord(entity: EntityKey, id: string): Promise<void>;
|
|
18
|
-
replaceRecords(
|
|
19
|
-
entity: EntityKey,
|
|
20
|
-
rows: { id: string; data: Record<string, any> }[],
|
|
21
|
-
): Promise<void>;
|
|
22
|
-
getTransactions(): Promise<SyncTransaction[]>;
|
|
23
|
-
putTransaction(txn: SyncTransaction): Promise<void>;
|
|
24
|
-
deleteTransaction(txnId: string): Promise<void>;
|
|
25
|
-
clear(): Promise<void>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const RECORDS = "records";
|
|
29
|
-
const TRANSACTIONS = "transactions";
|
|
30
|
-
const META = "meta";
|
|
31
|
-
|
|
32
|
-
class IndexedDbPersistence implements SyncPersistence {
|
|
33
|
-
private db: Promise<IDBDatabase>;
|
|
34
|
-
|
|
35
|
-
constructor(databaseName: string) {
|
|
36
|
-
this.db = new Promise((resolve, reject) => {
|
|
37
|
-
const request = indexedDB.open(databaseName, 1);
|
|
38
|
-
request.onupgradeneeded = () => {
|
|
39
|
-
const db = request.result;
|
|
40
|
-
if (!db.objectStoreNames.contains(RECORDS)) {
|
|
41
|
-
const records = db.createObjectStore(RECORDS, {
|
|
42
|
-
keyPath: ["entity", "id"],
|
|
43
|
-
});
|
|
44
|
-
records.createIndex("entity", "entity", { unique: false });
|
|
45
|
-
}
|
|
46
|
-
if (!db.objectStoreNames.contains(TRANSACTIONS)) {
|
|
47
|
-
db.createObjectStore(TRANSACTIONS, { keyPath: "txnId" });
|
|
48
|
-
}
|
|
49
|
-
if (!db.objectStoreNames.contains(META)) {
|
|
50
|
-
db.createObjectStore(META, { keyPath: "key" });
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
request.onsuccess = () => resolve(request.result);
|
|
54
|
-
request.onerror = () => reject(request.error);
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private async transaction(
|
|
59
|
-
stores: string[],
|
|
60
|
-
mode: IDBTransactionMode,
|
|
61
|
-
): Promise<IDBTransaction> {
|
|
62
|
-
const db = await this.db;
|
|
63
|
-
return db.transaction(stores, mode);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
private request<T>(request: IDBRequest<T>): Promise<T> {
|
|
67
|
-
return new Promise((resolve, reject) => {
|
|
68
|
-
request.onsuccess = () => resolve(request.result);
|
|
69
|
-
request.onerror = () => reject(request.error);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private done(txn: IDBTransaction): Promise<void> {
|
|
74
|
-
return new Promise((resolve, reject) => {
|
|
75
|
-
txn.oncomplete = () => resolve();
|
|
76
|
-
txn.onerror = () => reject(txn.error);
|
|
77
|
-
txn.onabort = () => reject(txn.error);
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async getMeta(key: string): Promise<any | undefined> {
|
|
82
|
-
const txn = await this.transaction([META], "readonly");
|
|
83
|
-
const row = await this.request(txn.objectStore(META).get(key));
|
|
84
|
-
return row ? (row as any).value : undefined;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async setMeta(key: string, value: any): Promise<void> {
|
|
88
|
-
const txn = await this.transaction([META], "readwrite");
|
|
89
|
-
txn.objectStore(META).put({ key, value });
|
|
90
|
-
await this.done(txn);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async getRecords(entity: EntityKey): Promise<Record<string, any>[]> {
|
|
94
|
-
const txn = await this.transaction([RECORDS], "readonly");
|
|
95
|
-
const index = txn.objectStore(RECORDS).index("entity");
|
|
96
|
-
const rows = await this.request(index.getAll(entity));
|
|
97
|
-
return (rows as any[]).map((row) => row.data);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async putRecords(
|
|
101
|
-
entity: EntityKey,
|
|
102
|
-
rows: { id: string; data: Record<string, any> }[],
|
|
103
|
-
): Promise<void> {
|
|
104
|
-
const txn = await this.transaction([RECORDS], "readwrite");
|
|
105
|
-
const store = txn.objectStore(RECORDS);
|
|
106
|
-
rows.forEach(({ id, data }) => store.put({ entity, id, data }));
|
|
107
|
-
await this.done(txn);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async deleteRecord(entity: EntityKey, id: string): Promise<void> {
|
|
111
|
-
const txn = await this.transaction([RECORDS], "readwrite");
|
|
112
|
-
txn.objectStore(RECORDS).delete([entity, id]);
|
|
113
|
-
await this.done(txn);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async replaceRecords(
|
|
117
|
-
entity: EntityKey,
|
|
118
|
-
rows: { id: string; data: Record<string, any> }[],
|
|
119
|
-
): Promise<void> {
|
|
120
|
-
const txn = await this.transaction([RECORDS], "readwrite");
|
|
121
|
-
const store = txn.objectStore(RECORDS);
|
|
122
|
-
const index = store.index("entity");
|
|
123
|
-
const keys = await this.request(index.getAllKeys(entity));
|
|
124
|
-
(keys as IDBValidKey[]).forEach((key) => store.delete(key));
|
|
125
|
-
rows.forEach(({ id, data }) => store.put({ entity, id, data }));
|
|
126
|
-
await this.done(txn);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async getTransactions(): Promise<SyncTransaction[]> {
|
|
130
|
-
const txn = await this.transaction([TRANSACTIONS], "readonly");
|
|
131
|
-
const rows = await this.request(txn.objectStore(TRANSACTIONS).getAll());
|
|
132
|
-
return (rows as SyncTransaction[]).sort(
|
|
133
|
-
(a, b) => a.createdAt - b.createdAt,
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async putTransaction(transaction: SyncTransaction): Promise<void> {
|
|
138
|
-
const txn = await this.transaction([TRANSACTIONS], "readwrite");
|
|
139
|
-
txn.objectStore(TRANSACTIONS).put(transaction);
|
|
140
|
-
await this.done(txn);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async deleteTransaction(txnId: string): Promise<void> {
|
|
144
|
-
const txn = await this.transaction([TRANSACTIONS], "readwrite");
|
|
145
|
-
txn.objectStore(TRANSACTIONS).delete(txnId);
|
|
146
|
-
await this.done(txn);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async clear(): Promise<void> {
|
|
150
|
-
const txn = await this.transaction(
|
|
151
|
-
[RECORDS, TRANSACTIONS, META],
|
|
152
|
-
"readwrite",
|
|
153
|
-
);
|
|
154
|
-
txn.objectStore(RECORDS).clear();
|
|
155
|
-
txn.objectStore(TRANSACTIONS).clear();
|
|
156
|
-
txn.objectStore(META).clear();
|
|
157
|
-
await this.done(txn);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export class MemoryPersistence implements SyncPersistence {
|
|
162
|
-
private meta = new Map<string, any>();
|
|
163
|
-
private records = new Map<EntityKey, Map<string, Record<string, any>>>();
|
|
164
|
-
private transactions = new Map<string, SyncTransaction>();
|
|
165
|
-
|
|
166
|
-
private entityRecords(entity: EntityKey): Map<string, Record<string, any>> {
|
|
167
|
-
let rows = this.records.get(entity);
|
|
168
|
-
if (!rows) {
|
|
169
|
-
rows = new Map();
|
|
170
|
-
this.records.set(entity, rows);
|
|
171
|
-
}
|
|
172
|
-
return rows;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async getMeta(key: string): Promise<any | undefined> {
|
|
176
|
-
return this.meta.get(key);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async setMeta(key: string, value: any): Promise<void> {
|
|
180
|
-
this.meta.set(key, value);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async getRecords(entity: EntityKey): Promise<Record<string, any>[]> {
|
|
184
|
-
return Array.from(this.entityRecords(entity).values());
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async putRecords(
|
|
188
|
-
entity: EntityKey,
|
|
189
|
-
rows: { id: string; data: Record<string, any> }[],
|
|
190
|
-
): Promise<void> {
|
|
191
|
-
const store = this.entityRecords(entity);
|
|
192
|
-
rows.forEach(({ id, data }) => store.set(id, data));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async deleteRecord(entity: EntityKey, id: string): Promise<void> {
|
|
196
|
-
this.entityRecords(entity).delete(id);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
async replaceRecords(
|
|
200
|
-
entity: EntityKey,
|
|
201
|
-
rows: { id: string; data: Record<string, any> }[],
|
|
202
|
-
): Promise<void> {
|
|
203
|
-
const store = this.entityRecords(entity);
|
|
204
|
-
store.clear();
|
|
205
|
-
rows.forEach(({ id, data }) => store.set(id, data));
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async getTransactions(): Promise<SyncTransaction[]> {
|
|
209
|
-
return Array.from(this.transactions.values()).sort(
|
|
210
|
-
(a, b) => a.createdAt - b.createdAt,
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
async putTransaction(txn: SyncTransaction): Promise<void> {
|
|
215
|
-
this.transactions.set(txn.txnId, txn);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
async deleteTransaction(txnId: string): Promise<void> {
|
|
219
|
-
this.transactions.delete(txnId);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
async clear(): Promise<void> {
|
|
223
|
-
this.meta.clear();
|
|
224
|
-
this.records.clear();
|
|
225
|
-
this.transactions.clear();
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export function createPersistence(
|
|
230
|
-
persist: boolean,
|
|
231
|
-
databaseName: string,
|
|
232
|
-
): SyncPersistence {
|
|
233
|
-
if (persist && typeof indexedDB !== "undefined") {
|
|
234
|
-
return new IndexedDbPersistence(databaseName);
|
|
235
|
-
}
|
|
236
|
-
return new MemoryPersistence();
|
|
237
|
-
}
|
|
1
|
+
import { EntityKey, SyncTransaction } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Durable storage behind the sync engine: entity rows, the transaction
|
|
5
|
+
* queue, and cursor metadata. IndexedDB in the browser; an in-memory
|
|
6
|
+
* fallback everywhere else (SSR, tests, unsupported browsers) so the
|
|
7
|
+
* engine still runs — it just re-bootstraps on reload.
|
|
8
|
+
*/
|
|
9
|
+
export interface SyncPersistence {
|
|
10
|
+
getMeta(key: string): Promise<any | undefined>;
|
|
11
|
+
setMeta(key: string, value: any): Promise<void>;
|
|
12
|
+
getRecords(entity: EntityKey): Promise<Record<string, any>[]>;
|
|
13
|
+
putRecords(
|
|
14
|
+
entity: EntityKey,
|
|
15
|
+
rows: { id: string; data: Record<string, any> }[],
|
|
16
|
+
): Promise<void>;
|
|
17
|
+
deleteRecord(entity: EntityKey, id: string): Promise<void>;
|
|
18
|
+
replaceRecords(
|
|
19
|
+
entity: EntityKey,
|
|
20
|
+
rows: { id: string; data: Record<string, any> }[],
|
|
21
|
+
): Promise<void>;
|
|
22
|
+
getTransactions(): Promise<SyncTransaction[]>;
|
|
23
|
+
putTransaction(txn: SyncTransaction): Promise<void>;
|
|
24
|
+
deleteTransaction(txnId: string): Promise<void>;
|
|
25
|
+
clear(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const RECORDS = "records";
|
|
29
|
+
const TRANSACTIONS = "transactions";
|
|
30
|
+
const META = "meta";
|
|
31
|
+
|
|
32
|
+
class IndexedDbPersistence implements SyncPersistence {
|
|
33
|
+
private db: Promise<IDBDatabase>;
|
|
34
|
+
|
|
35
|
+
constructor(databaseName: string) {
|
|
36
|
+
this.db = new Promise((resolve, reject) => {
|
|
37
|
+
const request = indexedDB.open(databaseName, 1);
|
|
38
|
+
request.onupgradeneeded = () => {
|
|
39
|
+
const db = request.result;
|
|
40
|
+
if (!db.objectStoreNames.contains(RECORDS)) {
|
|
41
|
+
const records = db.createObjectStore(RECORDS, {
|
|
42
|
+
keyPath: ["entity", "id"],
|
|
43
|
+
});
|
|
44
|
+
records.createIndex("entity", "entity", { unique: false });
|
|
45
|
+
}
|
|
46
|
+
if (!db.objectStoreNames.contains(TRANSACTIONS)) {
|
|
47
|
+
db.createObjectStore(TRANSACTIONS, { keyPath: "txnId" });
|
|
48
|
+
}
|
|
49
|
+
if (!db.objectStoreNames.contains(META)) {
|
|
50
|
+
db.createObjectStore(META, { keyPath: "key" });
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
request.onsuccess = () => resolve(request.result);
|
|
54
|
+
request.onerror = () => reject(request.error);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private async transaction(
|
|
59
|
+
stores: string[],
|
|
60
|
+
mode: IDBTransactionMode,
|
|
61
|
+
): Promise<IDBTransaction> {
|
|
62
|
+
const db = await this.db;
|
|
63
|
+
return db.transaction(stores, mode);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private request<T>(request: IDBRequest<T>): Promise<T> {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
request.onsuccess = () => resolve(request.result);
|
|
69
|
+
request.onerror = () => reject(request.error);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private done(txn: IDBTransaction): Promise<void> {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
txn.oncomplete = () => resolve();
|
|
76
|
+
txn.onerror = () => reject(txn.error);
|
|
77
|
+
txn.onabort = () => reject(txn.error);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async getMeta(key: string): Promise<any | undefined> {
|
|
82
|
+
const txn = await this.transaction([META], "readonly");
|
|
83
|
+
const row = await this.request(txn.objectStore(META).get(key));
|
|
84
|
+
return row ? (row as any).value : undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async setMeta(key: string, value: any): Promise<void> {
|
|
88
|
+
const txn = await this.transaction([META], "readwrite");
|
|
89
|
+
txn.objectStore(META).put({ key, value });
|
|
90
|
+
await this.done(txn);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async getRecords(entity: EntityKey): Promise<Record<string, any>[]> {
|
|
94
|
+
const txn = await this.transaction([RECORDS], "readonly");
|
|
95
|
+
const index = txn.objectStore(RECORDS).index("entity");
|
|
96
|
+
const rows = await this.request(index.getAll(entity));
|
|
97
|
+
return (rows as any[]).map((row) => row.data);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async putRecords(
|
|
101
|
+
entity: EntityKey,
|
|
102
|
+
rows: { id: string; data: Record<string, any> }[],
|
|
103
|
+
): Promise<void> {
|
|
104
|
+
const txn = await this.transaction([RECORDS], "readwrite");
|
|
105
|
+
const store = txn.objectStore(RECORDS);
|
|
106
|
+
rows.forEach(({ id, data }) => store.put({ entity, id, data }));
|
|
107
|
+
await this.done(txn);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async deleteRecord(entity: EntityKey, id: string): Promise<void> {
|
|
111
|
+
const txn = await this.transaction([RECORDS], "readwrite");
|
|
112
|
+
txn.objectStore(RECORDS).delete([entity, id]);
|
|
113
|
+
await this.done(txn);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async replaceRecords(
|
|
117
|
+
entity: EntityKey,
|
|
118
|
+
rows: { id: string; data: Record<string, any> }[],
|
|
119
|
+
): Promise<void> {
|
|
120
|
+
const txn = await this.transaction([RECORDS], "readwrite");
|
|
121
|
+
const store = txn.objectStore(RECORDS);
|
|
122
|
+
const index = store.index("entity");
|
|
123
|
+
const keys = await this.request(index.getAllKeys(entity));
|
|
124
|
+
(keys as IDBValidKey[]).forEach((key) => store.delete(key));
|
|
125
|
+
rows.forEach(({ id, data }) => store.put({ entity, id, data }));
|
|
126
|
+
await this.done(txn);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async getTransactions(): Promise<SyncTransaction[]> {
|
|
130
|
+
const txn = await this.transaction([TRANSACTIONS], "readonly");
|
|
131
|
+
const rows = await this.request(txn.objectStore(TRANSACTIONS).getAll());
|
|
132
|
+
return (rows as SyncTransaction[]).sort(
|
|
133
|
+
(a, b) => a.createdAt - b.createdAt,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async putTransaction(transaction: SyncTransaction): Promise<void> {
|
|
138
|
+
const txn = await this.transaction([TRANSACTIONS], "readwrite");
|
|
139
|
+
txn.objectStore(TRANSACTIONS).put(transaction);
|
|
140
|
+
await this.done(txn);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async deleteTransaction(txnId: string): Promise<void> {
|
|
144
|
+
const txn = await this.transaction([TRANSACTIONS], "readwrite");
|
|
145
|
+
txn.objectStore(TRANSACTIONS).delete(txnId);
|
|
146
|
+
await this.done(txn);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async clear(): Promise<void> {
|
|
150
|
+
const txn = await this.transaction(
|
|
151
|
+
[RECORDS, TRANSACTIONS, META],
|
|
152
|
+
"readwrite",
|
|
153
|
+
);
|
|
154
|
+
txn.objectStore(RECORDS).clear();
|
|
155
|
+
txn.objectStore(TRANSACTIONS).clear();
|
|
156
|
+
txn.objectStore(META).clear();
|
|
157
|
+
await this.done(txn);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export class MemoryPersistence implements SyncPersistence {
|
|
162
|
+
private meta = new Map<string, any>();
|
|
163
|
+
private records = new Map<EntityKey, Map<string, Record<string, any>>>();
|
|
164
|
+
private transactions = new Map<string, SyncTransaction>();
|
|
165
|
+
|
|
166
|
+
private entityRecords(entity: EntityKey): Map<string, Record<string, any>> {
|
|
167
|
+
let rows = this.records.get(entity);
|
|
168
|
+
if (!rows) {
|
|
169
|
+
rows = new Map();
|
|
170
|
+
this.records.set(entity, rows);
|
|
171
|
+
}
|
|
172
|
+
return rows;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async getMeta(key: string): Promise<any | undefined> {
|
|
176
|
+
return this.meta.get(key);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async setMeta(key: string, value: any): Promise<void> {
|
|
180
|
+
this.meta.set(key, value);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async getRecords(entity: EntityKey): Promise<Record<string, any>[]> {
|
|
184
|
+
return Array.from(this.entityRecords(entity).values());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async putRecords(
|
|
188
|
+
entity: EntityKey,
|
|
189
|
+
rows: { id: string; data: Record<string, any> }[],
|
|
190
|
+
): Promise<void> {
|
|
191
|
+
const store = this.entityRecords(entity);
|
|
192
|
+
rows.forEach(({ id, data }) => store.set(id, data));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async deleteRecord(entity: EntityKey, id: string): Promise<void> {
|
|
196
|
+
this.entityRecords(entity).delete(id);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async replaceRecords(
|
|
200
|
+
entity: EntityKey,
|
|
201
|
+
rows: { id: string; data: Record<string, any> }[],
|
|
202
|
+
): Promise<void> {
|
|
203
|
+
const store = this.entityRecords(entity);
|
|
204
|
+
store.clear();
|
|
205
|
+
rows.forEach(({ id, data }) => store.set(id, data));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async getTransactions(): Promise<SyncTransaction[]> {
|
|
209
|
+
return Array.from(this.transactions.values()).sort(
|
|
210
|
+
(a, b) => a.createdAt - b.createdAt,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async putTransaction(txn: SyncTransaction): Promise<void> {
|
|
215
|
+
this.transactions.set(txn.txnId, txn);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async deleteTransaction(txnId: string): Promise<void> {
|
|
219
|
+
this.transactions.delete(txnId);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async clear(): Promise<void> {
|
|
223
|
+
this.meta.clear();
|
|
224
|
+
this.records.clear();
|
|
225
|
+
this.transactions.clear();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function createPersistence(
|
|
230
|
+
persist: boolean,
|
|
231
|
+
databaseName: string,
|
|
232
|
+
): SyncPersistence {
|
|
233
|
+
if (persist && typeof indexedDB !== "undefined") {
|
|
234
|
+
return new IndexedDbPersistence(databaseName);
|
|
235
|
+
}
|
|
236
|
+
return new MemoryPersistence();
|
|
237
|
+
}
|
package/sync/provider.tsx
CHANGED
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
createContext,
|
|
3
|
-
ReactNode,
|
|
4
|
-
useContext,
|
|
5
|
-
useEffect,
|
|
6
|
-
useMemo,
|
|
7
|
-
} from "react";
|
|
8
|
-
import { AxiosInstance } from "axios";
|
|
9
|
-
import { API } from "../hooks/query/api";
|
|
10
|
-
import { SyncEngine } from "./engine";
|
|
11
|
-
import { SyncEngineConfig } from "./types";
|
|
12
|
-
|
|
13
|
-
interface RadSyncContextValue {
|
|
14
|
-
engine: SyncEngine;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const RadSyncContext = createContext<RadSyncContextValue | null>(null);
|
|
18
|
-
|
|
19
|
-
export interface RadSyncProviderProps extends SyncEngineConfig {
|
|
20
|
-
children: ReactNode;
|
|
21
|
-
/** Axios instance to sync through. Defaults to the shared rastack API client. */
|
|
22
|
-
api?: AxiosInstance;
|
|
23
|
-
/** When provided, a Bearer token is attached to every request on the instance. */
|
|
24
|
-
getToken?: () => string | Promise<string>;
|
|
25
|
-
/** Start the engine on mount (default true). */
|
|
26
|
-
autoStart?: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Mounts the sync engine for the app. This is the only client-side
|
|
31
|
-
* configuration a consumer does — every generated hook finds the engine
|
|
32
|
-
* through this context.
|
|
33
|
-
*/
|
|
34
|
-
export function RadSyncProvider({
|
|
35
|
-
children,
|
|
36
|
-
api,
|
|
37
|
-
getToken,
|
|
38
|
-
autoStart = true,
|
|
39
|
-
...config
|
|
40
|
-
}: RadSyncProviderProps) {
|
|
41
|
-
const instance = api || API;
|
|
42
|
-
const engine = useMemo(
|
|
43
|
-
() => new SyncEngine(instance, config),
|
|
44
|
-
// The engine is created once for the provider's lifetime.
|
|
45
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
46
|
-
[instance],
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
let interceptor: number | undefined;
|
|
51
|
-
if (getToken) {
|
|
52
|
-
interceptor = instance.interceptors.request.use(async (request) => {
|
|
53
|
-
const token = await getToken();
|
|
54
|
-
request.headers = request.headers || ({} as any);
|
|
55
|
-
(request.headers as any).Authorization = `Bearer ${token}`;
|
|
56
|
-
return request;
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
if (autoStart) {
|
|
60
|
-
void engine.start().catch(() => {
|
|
61
|
-
// Reported through config.onError and engine status.
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
return () => {
|
|
65
|
-
engine.stop();
|
|
66
|
-
if (interceptor !== undefined) {
|
|
67
|
-
instance.interceptors.request.eject(interceptor);
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
71
|
-
}, [engine]);
|
|
72
|
-
|
|
73
|
-
const value = useMemo(() => ({ engine }), [engine]);
|
|
74
|
-
return (
|
|
75
|
-
<RadSyncContext.Provider value={value}>{children}</RadSyncContext.Provider>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function useSyncEngine(): SyncEngine {
|
|
80
|
-
const context = useContext(RadSyncContext);
|
|
81
|
-
if (!context) {
|
|
82
|
-
throw new Error(
|
|
83
|
-
"No RadSyncProvider found. Wrap your app in <RadSyncProvider> to use sync-mode entity hooks.",
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
return context.engine;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function useSyncEngineOptional(): SyncEngine | null {
|
|
90
|
-
return useContext(RadSyncContext)?.engine ?? null;
|
|
91
|
-
}
|
|
1
|
+
import React, {
|
|
2
|
+
createContext,
|
|
3
|
+
ReactNode,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
} from "react";
|
|
8
|
+
import { AxiosInstance } from "axios";
|
|
9
|
+
import { API } from "../hooks/query/api";
|
|
10
|
+
import { SyncEngine } from "./engine";
|
|
11
|
+
import { SyncEngineConfig } from "./types";
|
|
12
|
+
|
|
13
|
+
interface RadSyncContextValue {
|
|
14
|
+
engine: SyncEngine;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const RadSyncContext = createContext<RadSyncContextValue | null>(null);
|
|
18
|
+
|
|
19
|
+
export interface RadSyncProviderProps extends SyncEngineConfig {
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
/** Axios instance to sync through. Defaults to the shared rastack API client. */
|
|
22
|
+
api?: AxiosInstance;
|
|
23
|
+
/** When provided, a Bearer token is attached to every request on the instance. */
|
|
24
|
+
getToken?: () => string | Promise<string>;
|
|
25
|
+
/** Start the engine on mount (default true). */
|
|
26
|
+
autoStart?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Mounts the sync engine for the app. This is the only client-side
|
|
31
|
+
* configuration a consumer does — every generated hook finds the engine
|
|
32
|
+
* through this context.
|
|
33
|
+
*/
|
|
34
|
+
export function RadSyncProvider({
|
|
35
|
+
children,
|
|
36
|
+
api,
|
|
37
|
+
getToken,
|
|
38
|
+
autoStart = true,
|
|
39
|
+
...config
|
|
40
|
+
}: RadSyncProviderProps) {
|
|
41
|
+
const instance = api || API;
|
|
42
|
+
const engine = useMemo(
|
|
43
|
+
() => new SyncEngine(instance, config),
|
|
44
|
+
// The engine is created once for the provider's lifetime.
|
|
45
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
46
|
+
[instance],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
let interceptor: number | undefined;
|
|
51
|
+
if (getToken) {
|
|
52
|
+
interceptor = instance.interceptors.request.use(async (request) => {
|
|
53
|
+
const token = await getToken();
|
|
54
|
+
request.headers = request.headers || ({} as any);
|
|
55
|
+
(request.headers as any).Authorization = `Bearer ${token}`;
|
|
56
|
+
return request;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (autoStart) {
|
|
60
|
+
void engine.start().catch(() => {
|
|
61
|
+
// Reported through config.onError and engine status.
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return () => {
|
|
65
|
+
engine.stop();
|
|
66
|
+
if (interceptor !== undefined) {
|
|
67
|
+
instance.interceptors.request.eject(interceptor);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
71
|
+
}, [engine]);
|
|
72
|
+
|
|
73
|
+
const value = useMemo(() => ({ engine }), [engine]);
|
|
74
|
+
return (
|
|
75
|
+
<RadSyncContext.Provider value={value}>{children}</RadSyncContext.Provider>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function useSyncEngine(): SyncEngine {
|
|
80
|
+
const context = useContext(RadSyncContext);
|
|
81
|
+
if (!context) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
"No RadSyncProvider found. Wrap your app in <RadSyncProvider> to use sync-mode entity hooks.",
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return context.engine;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function useSyncEngineOptional(): SyncEngine | null {
|
|
90
|
+
return useContext(RadSyncContext)?.engine ?? null;
|
|
91
|
+
}
|