@swoff/cli 0.3.5 → 0.3.6
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/README.md +327 -25
- package/dist/__tests__/assemble-sw.test.js +15 -1
- package/dist/__tests__/assemble-sw.test.js.map +1 -1
- package/dist/__tests__/cli-integration.test.js +9 -8
- package/dist/__tests__/cli-integration.test.js.map +1 -1
- package/dist/__tests__/config-types.test.js +6 -6
- package/dist/__tests__/config-types.test.js.map +1 -1
- package/dist/__tests__/file-generators.test.js +67 -59
- package/dist/__tests__/file-generators.test.js.map +1 -1
- package/dist/__tests__/loader.test.js +12 -13
- package/dist/__tests__/loader.test.js.map +1 -1
- package/dist/__tests__/validator.test.js +41 -21
- package/dist/__tests__/validator.test.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/cli/help.js +3 -3
- package/dist/lib/cli/help.js.map +1 -1
- package/dist/lib/commands/add.js +7 -1
- package/dist/lib/commands/add.js.map +1 -1
- package/dist/lib/commands/clean.js +5 -0
- package/dist/lib/commands/clean.js.map +1 -1
- package/dist/lib/commands/generate-guide.js +12 -9
- package/dist/lib/commands/generate-guide.js.map +1 -1
- package/dist/lib/commands/generate.js +7 -3
- package/dist/lib/commands/generate.js.map +1 -1
- package/dist/lib/commands/info.js +135 -32
- package/dist/lib/commands/info.js.map +1 -1
- package/dist/lib/commands/validate.js +3 -1
- package/dist/lib/commands/validate.js.map +1 -1
- package/dist/lib/config/validator.js +25 -16
- package/dist/lib/config/validator.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-fetch.js +29 -34
- package/dist/lib/generators/file-generators/auth-fetch.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-state.js +8 -5
- package/dist/lib/generators/file-generators/auth-state.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-store.js +71 -40
- package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-user.js +27 -31
- package/dist/lib/generators/file-generators/auth-user.js.map +1 -1
- package/dist/lib/generators/file-generators/background-sync.js +9 -5
- package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
- package/dist/lib/generators/file-generators/cache.js +7 -2
- package/dist/lib/generators/file-generators/cache.js.map +1 -1
- package/dist/lib/generators/file-generators/client-injector.js +113 -0
- package/dist/lib/generators/file-generators/client-injector.js.map +1 -0
- package/dist/lib/generators/file-generators/context.js +7 -6
- package/dist/lib/generators/file-generators/context.js.map +1 -1
- package/dist/lib/generators/file-generators/fetch-wrapper.js +170 -40
- package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/generate-hooks.js +61 -3
- package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
- package/dist/lib/generators/file-generators/guide-generator.js +333 -0
- package/dist/lib/generators/file-generators/guide-generator.js.map +1 -0
- package/dist/lib/generators/file-generators/invalidation-tags.js +12 -10
- package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-queue.js +34 -84
- package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-reconcile.js +57 -0
- package/dist/lib/generators/file-generators/mutation-reconcile.js.map +1 -0
- package/dist/lib/generators/file-generators/pwa-install.js +41 -9
- package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
- package/dist/lib/generators/file-generators/quick-readme.js +78 -0
- package/dist/lib/generators/file-generators/quick-readme.js.map +1 -0
- package/dist/lib/generators/file-generators/reconcile.js +7 -12
- package/dist/lib/generators/file-generators/reconcile.js.map +1 -1
- package/dist/lib/generators/file-generators/store.js +30 -20
- package/dist/lib/generators/file-generators/store.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-generator-build.js +37 -16
- package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-injector.js +81 -110
- package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-template.js +1 -1
- package/dist/lib/generators/file-generators/type-definitions.js +33 -10
- package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
- package/dist/lib/generators/sw-generator.js +19 -10
- package/dist/lib/generators/sw-generator.js.map +1 -1
- package/dist/lib/generators/sw-sections/assemble-sw.js +32 -12
- package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
- package/dist/lib/generators/sw-sections/config-header.js +1 -1
- package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
- package/dist/lib/generators/sw-sections/fetch-handler.js +25 -10
- package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/tag-management.js +25 -5
- package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
- package/dist/lib/generators/swoff-files-generator.js +7 -5
- package/dist/lib/generators/swoff-files-generator.js.map +1 -1
- package/dist/lib/shared/config-types.js +21 -6
- package/dist/lib/shared/config-types.js.map +1 -1
- package/dist/lib/utils/build-script.js +35 -0
- package/dist/lib/utils/build-script.js.map +1 -0
- package/package.json +1 -1
|
@@ -4,14 +4,22 @@
|
|
|
4
4
|
import { writeFile } from "./context.js";
|
|
5
5
|
export function generateMutationQueue(ctx) {
|
|
6
6
|
const ext = ctx.ext;
|
|
7
|
+
const ts = ext === "ts";
|
|
8
|
+
const T = (type) => (ts ? `: ${type}` : "");
|
|
9
|
+
const R = (type) => (ts ? `: ${type} ` : " ");
|
|
10
|
+
const PT = (type) => (ts ? `<${type}>` : "");
|
|
11
|
+
const AS = (type) => (ts ? ` as ${type}` : "");
|
|
7
12
|
const authEnabled = ctx.config.features.auth.enabled;
|
|
8
13
|
const importLines = authEnabled
|
|
9
|
-
? `import { getAuth } from "./auth
|
|
14
|
+
? `import { getAuth } from "./auth/store.${ext}";
|
|
10
15
|
`
|
|
11
16
|
: "";
|
|
17
|
+
const additionalImports = `import { invalidateByTags } from "./cache.${ext}";
|
|
18
|
+
${ts ? `import type { MutationQueueItem } from "./swoff.d.ts";
|
|
19
|
+
` : ""}`;
|
|
12
20
|
const authReplayBlock = authEnabled
|
|
13
21
|
? ` const auth = await getAuth();
|
|
14
|
-
const authHeader = auth?.token ? { Authorization: \`Bearer \${auth.token}\` } : {};
|
|
22
|
+
const authHeader${T("Record<string, string>")} = auth?.token ? { Authorization: \`Bearer \${auth.token}\` } : {};
|
|
15
23
|
`
|
|
16
24
|
: "";
|
|
17
25
|
const authHeadersSpread = authEnabled
|
|
@@ -30,8 +38,6 @@ export function generateMutationQueue(ctx) {
|
|
|
30
38
|
* url: "/api/todos",
|
|
31
39
|
* body: { title: "Grocery" },
|
|
32
40
|
* tags: ["todos"],
|
|
33
|
-
* storeName: "todos",
|
|
34
|
-
* tempId: "temp_abc123",
|
|
35
41
|
* });
|
|
36
42
|
*
|
|
37
43
|
* // Flush queued mutations (e.g., after login)
|
|
@@ -40,28 +46,29 @@ export function generateMutationQueue(ctx) {
|
|
|
40
46
|
* // Auto-processes on online event
|
|
41
47
|
*/
|
|
42
48
|
|
|
43
|
-
${importLines}const DB_NAME = "swoff-queue";
|
|
49
|
+
${importLines}${additionalImports}const DB_NAME = "swoff-queue";
|
|
44
50
|
const STORE_NAME = "mutations";
|
|
45
51
|
const MAX_RETRIES = 5;
|
|
46
52
|
|
|
47
|
-
function openQueueDB()
|
|
48
|
-
return new Promise((resolve, reject) => {
|
|
53
|
+
function openQueueDB()${R("Promise<IDBDatabase>")}{
|
|
54
|
+
return new Promise${PT("IDBDatabase")}((resolve, reject) => {
|
|
49
55
|
const request = indexedDB.open(DB_NAME, 1);
|
|
50
56
|
request.onupgradeneeded = (e) => {
|
|
51
|
-
const db = e.target.result;
|
|
57
|
+
const db = (e.target${AS("IDBOpenDBRequest")}).result;
|
|
52
58
|
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
53
59
|
const store = db.createObjectStore(STORE_NAME, { keyPath: "id" });
|
|
54
60
|
store.createIndex("by-timestamp", "timestamp");
|
|
55
61
|
}
|
|
56
62
|
};
|
|
57
|
-
request.onsuccess = (e) => resolve(e.target.result);
|
|
58
|
-
request.onerror = (e) => reject(e.target.error);
|
|
63
|
+
request.onsuccess = (e) => resolve((e.target${AS("IDBOpenDBRequest")}).result);
|
|
64
|
+
request.onerror = (e) => reject((e.target${AS("IDBRequest")}).error);
|
|
59
65
|
});
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
let isSyncing = false;
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
/** Store a write operation in IndexedDB for later sync. Works offline — use it for POST/PUT/PATCH/DELETE when the user might be offline. */
|
|
71
|
+
export async function queueMutation(mutation${T("Partial<MutationQueueItem>")})${R("Promise<void>")}{
|
|
65
72
|
const db = await openQueueDB();
|
|
66
73
|
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
67
74
|
const store = tx.objectStore(STORE_NAME);
|
|
@@ -72,15 +79,12 @@ export async function queueMutation(mutation) {
|
|
|
72
79
|
url: mutation.url,
|
|
73
80
|
body: mutation.body,
|
|
74
81
|
headers: mutation.headers || {},
|
|
75
|
-
previousData: mutation.previousData || null,
|
|
76
82
|
timestamp: Date.now(),
|
|
77
83
|
retryCount: 0,
|
|
78
84
|
tags: mutation.tags || [],
|
|
79
|
-
storeName: mutation.storeName || null,
|
|
80
|
-
tempId: mutation.tempId || null,
|
|
81
85
|
});
|
|
82
86
|
|
|
83
|
-
await new Promise((resolve, reject) => {
|
|
87
|
+
await new Promise<void>((resolve, reject) => {
|
|
84
88
|
tx.oncomplete = () => resolve();
|
|
85
89
|
tx.onerror = () => reject(tx.error);
|
|
86
90
|
});
|
|
@@ -88,7 +92,8 @@ export async function queueMutation(mutation) {
|
|
|
88
92
|
window.dispatchEvent(new CustomEvent("mutation-queue-changed"));
|
|
89
93
|
}
|
|
90
94
|
|
|
91
|
-
|
|
95
|
+
/** Process all queued mutations in order. Sends them to the server. Runs automatically on the online event. */
|
|
96
|
+
export async function processMutationQueue()${R("Promise<void>")}{
|
|
92
97
|
if (!navigator.onLine || isSyncing) return;
|
|
93
98
|
isSyncing = true;
|
|
94
99
|
|
|
@@ -97,7 +102,7 @@ ${authReplayBlock} const db = await openQueueDB();
|
|
|
97
102
|
const tx = db.transaction(STORE_NAME, "readonly");
|
|
98
103
|
const store = tx.objectStore(STORE_NAME);
|
|
99
104
|
const index = store.index("by-timestamp");
|
|
100
|
-
const queue = await new Promise((resolve, reject) => {
|
|
105
|
+
const queue${T("MutationQueueItem[]")} = await new Promise${PT("MutationQueueItem[]")}((resolve, reject) => {
|
|
101
106
|
const request = index.getAll();
|
|
102
107
|
request.onsuccess = () => resolve(request.result);
|
|
103
108
|
request.onerror = () => reject(request.error);
|
|
@@ -108,7 +113,6 @@ ${authReplayBlock} const db = await openQueueDB();
|
|
|
108
113
|
|
|
109
114
|
for (const item of queue) {
|
|
110
115
|
if (item.retryCount >= MAX_RETRIES) {
|
|
111
|
-
await rollbackMutation(item);
|
|
112
116
|
await removeFromQueue(item.id);
|
|
113
117
|
failed++;
|
|
114
118
|
continue;
|
|
@@ -126,14 +130,7 @@ ${authHeadersSpread} ...item.headers,
|
|
|
126
130
|
|
|
127
131
|
if (!response.ok) throw new Error(\`HTTP \${response.status}\`);
|
|
128
132
|
|
|
129
|
-
const serverData = await response.json();
|
|
130
|
-
|
|
131
|
-
if (item.tempId && item.storeName) {
|
|
132
|
-
await reconcileRecord(item.storeName, item.tempId, serverData);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
133
|
if (item.tags && item.tags.length > 0) {
|
|
136
|
-
const { invalidateByTags } = await import("./cache.${ext}");
|
|
137
134
|
await invalidateByTags(item.tags);
|
|
138
135
|
}
|
|
139
136
|
|
|
@@ -157,88 +154,41 @@ ${authHeadersSpread} ...item.headers,
|
|
|
157
154
|
}
|
|
158
155
|
}
|
|
159
156
|
|
|
160
|
-
|
|
157
|
+
/** Immediately process all queued mutations. Call this after re-login to flush mutations that failed due to auth expiry. */
|
|
158
|
+
export async function flushMutations()${R("Promise<void>")}{
|
|
161
159
|
await processMutationQueue();
|
|
162
160
|
}
|
|
163
161
|
`;
|
|
164
|
-
// Helper functions
|
|
162
|
+
// Helper functions appended outside the template string
|
|
165
163
|
const helpers = `
|
|
166
|
-
async function removeFromQueue(id)
|
|
164
|
+
async function removeFromQueue(id${T("string")})${R("Promise<void>")}{
|
|
167
165
|
const db = await openQueueDB();
|
|
168
166
|
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
169
167
|
tx.objectStore(STORE_NAME).delete(id);
|
|
170
|
-
await new Promise((resolve, reject) => {
|
|
168
|
+
await new Promise${PT("void")}((resolve, reject) => {
|
|
171
169
|
tx.oncomplete = () => resolve();
|
|
172
170
|
tx.onerror = () => reject(tx.error);
|
|
173
171
|
});
|
|
174
172
|
}
|
|
175
173
|
|
|
176
|
-
async function updateInQueue(item)
|
|
174
|
+
async function updateInQueue(item${T("MutationQueueItem")})${R("Promise<void>")}{
|
|
177
175
|
const db = await openQueueDB();
|
|
178
176
|
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
179
177
|
tx.objectStore(STORE_NAME).put(item);
|
|
180
|
-
await new Promise((resolve, reject) => {
|
|
178
|
+
await new Promise${PT("void")}((resolve, reject) => {
|
|
181
179
|
tx.oncomplete = () => resolve();
|
|
182
180
|
tx.onerror = () => reject(tx.error);
|
|
183
181
|
});
|
|
184
182
|
}
|
|
185
183
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (item.method === "POST" && item.tempId) {
|
|
190
|
-
const { deleteRecord } = await import("./store.${ext}");
|
|
191
|
-
await deleteRecord(item.storeName, item.tempId);
|
|
192
|
-
} else if (
|
|
193
|
-
(item.method === "PUT" || item.method === "PATCH") &&
|
|
194
|
-
item.previousData
|
|
195
|
-
) {
|
|
196
|
-
const { putRecord } = await import("./store.${ext}");
|
|
197
|
-
await putRecord(item.storeName, { ...item.previousData, $synced: true });
|
|
198
|
-
} else if (item.method === "DELETE" && item.tempId && item.previousData) {
|
|
199
|
-
const { putRecord } = await import("./store.${ext}");
|
|
200
|
-
await putRecord(item.storeName, { ...item.previousData, $synced: true });
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
window.dispatchEvent(
|
|
204
|
-
new CustomEvent("mutation-rollback", {
|
|
205
|
-
detail: {
|
|
206
|
-
method: item.method,
|
|
207
|
-
url: item.url,
|
|
208
|
-
tempId: item.tempId,
|
|
209
|
-
previousData: item.previousData,
|
|
210
|
-
},
|
|
211
|
-
})
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async function reconcileRecord(storeName, tempId, serverData) {
|
|
216
|
-
const { getRecord, putRecord, deleteRecord } = await import("./store.${ext}");
|
|
217
|
-
const existing = await getRecord(storeName, tempId);
|
|
218
|
-
if (!existing) return;
|
|
219
|
-
|
|
220
|
-
const reconciled = {
|
|
221
|
-
...existing,
|
|
222
|
-
...serverData,
|
|
223
|
-
id: serverData.id,
|
|
224
|
-
$synced: true,
|
|
225
|
-
$syncedAt: Date.now(),
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
await putRecord(storeName, reconciled);
|
|
229
|
-
|
|
230
|
-
if (String(tempId) !== String(serverData.id)) {
|
|
231
|
-
await deleteRecord(storeName, tempId);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export async function getPendingCount() {
|
|
184
|
+
/** Get the number of queued mutations waiting to be synced. Useful for showing a sync badge. */
|
|
185
|
+
export async function getPendingCount()${R("Promise<number>")}{
|
|
236
186
|
const db = await openQueueDB();
|
|
237
|
-
return new Promise((resolve, reject) => {
|
|
187
|
+
return new Promise${PT("number")}((resolve, reject) => {
|
|
238
188
|
const tx = db.transaction(STORE_NAME, "readonly");
|
|
239
189
|
const request = tx.objectStore(STORE_NAME).count();
|
|
240
|
-
request.onsuccess = () => resolve(request.result);
|
|
241
|
-
request.onerror = () => reject(request.error);
|
|
190
|
+
request.onsuccess = () => resolve((request${AS("IDBRequest<number>")}).result);
|
|
191
|
+
request.onerror = () => reject((request${AS("IDBRequest")}).error);
|
|
242
192
|
});
|
|
243
193
|
}
|
|
244
194
|
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mutation-queue.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/mutation-queue.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,qBAAqB,CAAC,GAAqB;IACzD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC;IAErD,MAAM,WAAW,GAAG,WAAW;QAC7B,CAAC,CAAC,yCAAyC,GAAG;CACjD;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,eAAe,GAAG,WAAW;QACjC,CAAC,CAAC
|
|
1
|
+
{"version":3,"file":"mutation-queue.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/mutation-queue.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,qBAAqB,CAAC,GAAqB;IACzD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC;IACxB,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtD,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACrD,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACvD,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC;IAErD,MAAM,WAAW,GAAG,WAAW;QAC7B,CAAC,CAAC,yCAAyC,GAAG;CACjD;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,iBAAiB,GAAG,6CAA6C,GAAG;EAC1E,EAAE,CAAC,CAAC,CAAC;CACN,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAEP,MAAM,eAAe,GAAG,WAAW;QACjC,CAAC,CAAC;oBACc,CAAC,CAAC,wBAAwB,CAAC;CAC9C;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,iBAAiB,GAAG,WAAW;QACnC,CAAC,CAAC,4BAA4B;QAC9B,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,IAAI,GAAG;;;;;oHAKqG,GAAG;;;;;;;;;;;;;;;;EAgBrH,WAAW,GAAG,iBAAiB;;;;wBAIT,CAAC,CAAC,sBAAsB,CAAC;sBAC3B,EAAE,CAAC,aAAa,CAAC;;;4BAGX,EAAE,CAAC,kBAAkB,CAAC;;;;;;kDAMA,EAAE,CAAC,kBAAkB,CAAC;+CACzB,EAAE,CAAC,YAAY,CAAC;;;;;;;8CAOjB,CAAC,CAAC,4BAA4B,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;8CAyBrD,CAAC,CAAC,eAAe,CAAC;;;;;EAK9D,eAAe;;;;iBAIA,CAAC,CAAC,qBAAqB,CAAC,uBAAuB,EAAE,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;EAqBvF,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wCAgCqB,CAAC,CAAC,eAAe,CAAC;;;CAGzD,CAAC;IAEA,wDAAwD;IACxD,MAAM,OAAO,GAAG;mCACiB,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;;qBAI/C,EAAE,CAAC,MAAM,CAAC;;;;;;mCAMI,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;;qBAI1D,EAAE,CAAC,MAAM,CAAC;;;;;;;yCAOU,CAAC,CAAC,iBAAiB,CAAC;;sBAEvC,EAAE,CAAC,QAAQ,CAAC;;;gDAGc,EAAE,CAAC,oBAAoB,CAAC;6CAC3B,EAAE,CAAC,YAAY,CAAC;;;CAG5D,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,kBAAkB,GAAG,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates mutation-reconcile.js - ID reconciliation after mutation sync.
|
|
3
|
+
*/
|
|
4
|
+
import { writeFile } from "./context.js";
|
|
5
|
+
export function generateMutationReconcile(ctx) {
|
|
6
|
+
const ext = ctx.ext;
|
|
7
|
+
const ts = ext === "ts";
|
|
8
|
+
const T = (type) => (ts ? `: ${type}` : "");
|
|
9
|
+
const R = (type) => (ts ? `: ${type} ` : " ");
|
|
10
|
+
const AS = (type) => (ts ? ` as ${type}` : "");
|
|
11
|
+
const code = `/**
|
|
12
|
+
* Swoff Mutation Reconciliation
|
|
13
|
+
* Reconciles local optimistic records with server response after a queued
|
|
14
|
+
* mutation is processed by the service worker.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* import { reconcileRecord } from './swoff/mutation-reconcile.${ext}';
|
|
18
|
+
*
|
|
19
|
+
* // Called automatically by mutation-queue after a successful sync
|
|
20
|
+
* await reconcileRecord('todos', 'temp_abc123', serverData);
|
|
21
|
+
*
|
|
22
|
+
* // Only override this if your data has foreign key references:
|
|
23
|
+
* // export async function reconcileReferences(...) { ... }
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { getRecord, putRecord, deleteRecord } from './store.${ext}';
|
|
27
|
+
|
|
28
|
+
/** Replace a temporary optimistic ID with the server-assigned ID after mutation sync. Called automatically by mutation-queue. */
|
|
29
|
+
export async function reconcileRecord(storeName${T("string")}, tempId${T("string")}, serverData${T("Record<string, unknown>")})${R("Promise<void>")}{
|
|
30
|
+
const existing = await getRecord(storeName, tempId);
|
|
31
|
+
if (!existing) return;
|
|
32
|
+
|
|
33
|
+
const reconciled = {
|
|
34
|
+
...existing,
|
|
35
|
+
...serverData,
|
|
36
|
+
id: serverData.id,
|
|
37
|
+
$synced: true,
|
|
38
|
+
$syncedAt: Date.now(),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
await putRecord(storeName, reconciled);
|
|
42
|
+
|
|
43
|
+
if (String(tempId) !== String(serverData.id)) {
|
|
44
|
+
await deleteRecord(storeName, tempId);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await reconcileReferences(storeName, tempId, serverData.id${AS("string | number")});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Update foreign key references in other records that pointed to the old temp ID. Override this for your app's schema relationships. */
|
|
51
|
+
export async function reconcileReferences(_storeName${T("string")}, _oldId${T("string")}, _newId${T("string | number")})${R("Promise<void>")}{
|
|
52
|
+
// Override this for your app's schema.
|
|
53
|
+
}
|
|
54
|
+
`;
|
|
55
|
+
writeFile(ctx, `mutation-reconcile.${ext}`, code);
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=mutation-reconcile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutation-reconcile.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/mutation-reconcile.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,yBAAyB,CAAC,GAAqB;IAC7D,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC;IACxB,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtD,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAEvD,MAAM,IAAI,GAAG;;;;;;mEAMoD,GAAG;;;;;;;;;8DASR,GAAG;;;iDAGhB,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,yBAAyB,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;;;;;;;;;;;;;;;;8DAkBrF,EAAE,CAAC,iBAAiB,CAAC;;;;sDAI7B,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;CAG3I,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,sBAAsB,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -4,28 +4,60 @@
|
|
|
4
4
|
import { writeFile } from "./context.js";
|
|
5
5
|
export function generatePwaInstall(ctx) {
|
|
6
6
|
const ext = ctx.ext;
|
|
7
|
+
const ts = ext === "ts";
|
|
8
|
+
const R = (type) => (ts ? `: ${type} ` : " ");
|
|
9
|
+
const preventDefaultInstall = ctx.config.features.pwa.preventDefaultInstall;
|
|
10
|
+
const preventLine = preventDefaultInstall ? " e.preventDefault();\n" : "";
|
|
7
11
|
const code = `/**
|
|
8
|
-
* Swoff PWA Install
|
|
9
|
-
* Manual install trigger and
|
|
10
|
-
* Event listeners are registered in sw-injector at app entry.
|
|
12
|
+
* Swoff PWA Install Support
|
|
13
|
+
* Manual install trigger, installability check, and event listener setup.
|
|
11
14
|
*
|
|
12
15
|
* Usage:
|
|
13
|
-
* import { isInstallable, promptInstall } from './swoff/pwa
|
|
16
|
+
* import { setupPwaInstall, isInstallable, promptInstall } from './swoff/pwa/install.${ext}';
|
|
17
|
+
*
|
|
18
|
+
* setupPwaInstall(); // sets up beforeinstallprompt and appinstalled listeners
|
|
14
19
|
*
|
|
15
20
|
* if (isInstallable()) {
|
|
16
21
|
* const result = await promptInstall();
|
|
17
|
-
* console.log('User choice:', result.outcome);
|
|
18
22
|
* }
|
|
19
23
|
*
|
|
20
|
-
* Window properties
|
|
24
|
+
* Window properties:
|
|
21
25
|
* window.deferredInstallPrompt - The captured BeforeInstallPromptEvent
|
|
22
26
|
*/
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
/** Set up beforeinstallprompt and appinstalled event listeners. Call once at app startup. */
|
|
29
|
+
export function setupPwaInstall()${R("void")}{
|
|
30
|
+
window.addEventListener("beforeinstallprompt", (e) => {
|
|
31
|
+
window.deferredInstallPrompt = e as BeforeInstallPromptEvent;
|
|
32
|
+
window.pwaInstallable = true;
|
|
33
|
+
|
|
34
|
+
${preventLine}
|
|
35
|
+
window.dispatchEvent(
|
|
36
|
+
new CustomEvent("pwa-installable", {
|
|
37
|
+
detail: { isInstallable: true },
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
window.addEventListener("appinstalled", () => {
|
|
43
|
+
window.deferredInstallPrompt = null;
|
|
44
|
+
window.pwaInstallable = false;
|
|
45
|
+
|
|
46
|
+
window.dispatchEvent(
|
|
47
|
+
new CustomEvent("pwa-installed", {
|
|
48
|
+
detail: { outcome: "accepted" },
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Check if the PWA install prompt is available (user has met install criteria). */
|
|
55
|
+
export function isInstallable()${R("boolean")}{
|
|
25
56
|
return !!window.deferredInstallPrompt;
|
|
26
57
|
}
|
|
27
58
|
|
|
28
|
-
|
|
59
|
+
/** Show the browser's native install prompt. Throws if prompt is not available. */
|
|
60
|
+
export async function promptInstall()${R("Promise<{ outcome: string }>")}{
|
|
29
61
|
if (!window.deferredInstallPrompt) {
|
|
30
62
|
throw new Error("Install prompt not available");
|
|
31
63
|
}
|
|
@@ -53,6 +85,6 @@ export async function promptInstall() {
|
|
|
53
85
|
return choice;
|
|
54
86
|
}
|
|
55
87
|
`;
|
|
56
|
-
writeFile(ctx, `pwa
|
|
88
|
+
writeFile(ctx, `pwa/install.${ext}`, code);
|
|
57
89
|
}
|
|
58
90
|
//# sourceMappingURL=pwa-install.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pwa-install.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/pwa-install.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,kBAAkB,CAAC,GAAqB;IACtD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;
|
|
1
|
+
{"version":3,"file":"pwa-install.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/pwa-install.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,kBAAkB,CAAC,GAAqB;IACtD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC;IACxB,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtD,MAAM,qBAAqB,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAC5E,MAAM,WAAW,GAAG,qBAAqB,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC,EAAE,CAAC;IAE7E,MAAM,IAAI,GAAG;;;;;0FAK2E,GAAG;;;;;;;;;;;;;mCAa1D,CAAC,CAAC,MAAM,CAAC;;;;;EAK1C,WAAW;;;;;;;;;;;;;;;;;;;;;iCAqBoB,CAAC,CAAC,SAAS,CAAC;;;;;uCAKN,CAAC,CAAC,8BAA8B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BvE,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,eAAe,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates README.md — quick reference for integrating enabled features.
|
|
3
|
+
*/
|
|
4
|
+
import { writeFile } from "./context.js";
|
|
5
|
+
export function generateReadme(ctx) {
|
|
6
|
+
const { config } = ctx;
|
|
7
|
+
const ext = ctx.ext;
|
|
8
|
+
const lines = [];
|
|
9
|
+
const w = (s) => lines.push(s);
|
|
10
|
+
w("# Swoff — Quick Reference");
|
|
11
|
+
w("");
|
|
12
|
+
w("For the full guide with explanations, read **[GUIDE.md](./GUIDE.md)**.");
|
|
13
|
+
w("");
|
|
14
|
+
const hasClientInjector = config.features.clientRegistration || config.features.pwa.enabled || config.features.crossTabSync;
|
|
15
|
+
if (hasClientInjector) {
|
|
16
|
+
w("## Entry point");
|
|
17
|
+
w("```ts");
|
|
18
|
+
w(`import { initServiceWorker } from "./swoff/client-injector.${ext}";`);
|
|
19
|
+
w("initServiceWorker();");
|
|
20
|
+
w("```");
|
|
21
|
+
w("");
|
|
22
|
+
}
|
|
23
|
+
w("## API calls (use for all fetch requests)");
|
|
24
|
+
w("```ts");
|
|
25
|
+
w(`import { fetchWithCache } from "./swoff/fetch-wrapper.${ext}";`);
|
|
26
|
+
w('const data = await fetchWithCache("/api/data").then(r => r.json());');
|
|
27
|
+
w("```");
|
|
28
|
+
w("");
|
|
29
|
+
if (config.features.auth.enabled) {
|
|
30
|
+
w("## Authenticated API calls");
|
|
31
|
+
w("```ts");
|
|
32
|
+
w(`import { authenticatedFetch } from "./swoff/auth/fetch.${ext}";`);
|
|
33
|
+
w('const data = await authenticatedFetch("/api/me").then(r => r.json());');
|
|
34
|
+
w("```");
|
|
35
|
+
w("");
|
|
36
|
+
}
|
|
37
|
+
if (config.features.mutationQueue) {
|
|
38
|
+
w("## Offline mutations (queue writes when offline)");
|
|
39
|
+
w("```ts");
|
|
40
|
+
w(`import { queueMutation, getPendingCount } from "./swoff/mutation-queue.${ext}";`);
|
|
41
|
+
w("await queueMutation({ method: \"POST\", url: \"/api/todos\", body: {...} });");
|
|
42
|
+
w("```");
|
|
43
|
+
w("");
|
|
44
|
+
if (config.features.auth.enabled) {
|
|
45
|
+
w("## After re-login (flush queued mutations)");
|
|
46
|
+
w("```ts");
|
|
47
|
+
w(`import { flushMutations } from "./swoff/mutation-queue.${ext}";`);
|
|
48
|
+
w("await flushMutations();");
|
|
49
|
+
w("```");
|
|
50
|
+
w("");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (config.features.tagInvalidation) {
|
|
54
|
+
w("## Cache invalidation");
|
|
55
|
+
w("```ts");
|
|
56
|
+
w(`import { generateTags, invalidateUrl } from "./swoff/invalidation-tags.${ext}";`);
|
|
57
|
+
w('const data = await fetchWithCache("/api/todos", { tags: generateTags("/api/todos") });');
|
|
58
|
+
w('await invalidateUrl("/api/todos/42"); // after mutation');
|
|
59
|
+
w("```");
|
|
60
|
+
w("");
|
|
61
|
+
}
|
|
62
|
+
if (ctx.frameworkName === "react") {
|
|
63
|
+
w("## React hook — auto-refetch on cache invalidation");
|
|
64
|
+
w("```tsx");
|
|
65
|
+
w(`import { useCachedFetch } from "./swoff/hooks/useCachedFetch.${ext}x";`);
|
|
66
|
+
w('const { data, error, loading, refetch } = useCachedFetch("/api/todos");');
|
|
67
|
+
w("```");
|
|
68
|
+
w("");
|
|
69
|
+
}
|
|
70
|
+
w("## Build script");
|
|
71
|
+
w("The SW generator learns your build output to precache assets.");
|
|
72
|
+
w("Swoff has already added this to your `package.json`:");
|
|
73
|
+
w("```json");
|
|
74
|
+
w('"build": "<your-build> && node swoff/sw/generator.js"');
|
|
75
|
+
w("```");
|
|
76
|
+
writeFile(ctx, "README.md", lines.join("\n"));
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=quick-readme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quick-readme.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/quick-readme.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
import { writeFile } from "./context.js";
|
|
5
5
|
export function generateReconcile(ctx) {
|
|
6
6
|
const ext = ctx.ext;
|
|
7
|
+
const ts = ext === "ts";
|
|
8
|
+
const T = (type) => (ts ? `: ${type}` : "");
|
|
9
|
+
const R = (type) => (ts ? `: ${type} ` : " ");
|
|
10
|
+
const AS = (type) => (ts ? ` as ${type}` : "");
|
|
7
11
|
const code = `/**
|
|
8
12
|
* Swoff ID Reconciliation
|
|
9
13
|
* Update local records with server data after mutation sync.
|
|
@@ -16,7 +20,7 @@ export function generateReconcile(ctx) {
|
|
|
16
20
|
|
|
17
21
|
import { getRecord, putRecord, deleteRecord } from './store.${ext}';
|
|
18
22
|
|
|
19
|
-
export async function reconcileRecord(storeName, tempId, serverData)
|
|
23
|
+
export async function reconcileRecord(storeName${T("string")}, tempId${T("string")}, serverData${T("Record<string, unknown>")})${R("Promise<void>")}{
|
|
20
24
|
const existing = await getRecord(storeName, tempId);
|
|
21
25
|
if (!existing) return;
|
|
22
26
|
|
|
@@ -34,20 +38,11 @@ export async function reconcileRecord(storeName, tempId, serverData) {
|
|
|
34
38
|
await deleteRecord(storeName, tempId);
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
await reconcileReferences(storeName, tempId, serverData.id);
|
|
41
|
+
await reconcileReferences(storeName, tempId, serverData.id${AS("string | number")});
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
export async function reconcileReferences(storeName, oldId, newId)
|
|
44
|
+
export async function reconcileReferences(storeName${T("string")}, oldId${T("string")}, newId${T("string | number")})${R("Promise<void>")}{
|
|
41
45
|
// Override this for your app's schema.
|
|
42
|
-
// Example: update foreign-key references in related stores.
|
|
43
|
-
//
|
|
44
|
-
// const txns = await getAllRecords('transactions');
|
|
45
|
-
// for (const txn of txns) {
|
|
46
|
-
// if (txn.todoId === oldId) {
|
|
47
|
-
// txn.todoId = newId;
|
|
48
|
-
// await putRecord('transactions', txn);
|
|
49
|
-
// }
|
|
50
|
-
// }
|
|
51
46
|
}
|
|
52
47
|
`;
|
|
53
48
|
writeFile(ctx, `reconcile.${ext}`, code);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/reconcile.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,iBAAiB,CAAC,GAAqB;IACrD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,IAAI,GAAG;;;;;0DAK2C,GAAG;;;;;8DAKC,GAAG
|
|
1
|
+
{"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/reconcile.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,iBAAiB,CAAC,GAAqB;IACrD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC;IACxB,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtD,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAEvD,MAAM,IAAI,GAAG;;;;;0DAK2C,GAAG;;;;;8DAKC,GAAG;;iDAEhB,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,yBAAyB,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;;;;;;;;;;;;;;;;8DAkBrF,EAAE,CAAC,iBAAiB,CAAC;;;qDAG9B,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;CAGxI,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,aAAa,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC3C,CAAC"}
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
import { writeFile } from "./context.js";
|
|
5
5
|
export function generateStore(ctx) {
|
|
6
6
|
const ext = ctx.ext;
|
|
7
|
+
const ts = ext === "ts";
|
|
8
|
+
const T = (type) => (ts ? `: ${type}` : "");
|
|
9
|
+
const R = (type) => (ts ? `: ${type} ` : " ");
|
|
10
|
+
const PT = (type) => (ts ? `<${type}>` : "");
|
|
11
|
+
const AS = (type) => (ts ? ` as ${type}` : "");
|
|
7
12
|
const dbName = "app-db";
|
|
8
13
|
const code = `/**
|
|
9
14
|
* Swoff IndexedDB Store
|
|
@@ -19,55 +24,60 @@ export function generateStore(ctx) {
|
|
|
19
24
|
|
|
20
25
|
const DB_NAME = "${dbName}";
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
/** Open the app's IndexedDB database. Creates it if it doesn't exist. */
|
|
28
|
+
export function openAppDB()${R("Promise<IDBDatabase>")}{
|
|
29
|
+
return new Promise${PT("IDBDatabase")}((resolve, reject) => {
|
|
24
30
|
const request = indexedDB.open(DB_NAME);
|
|
25
|
-
request.onsuccess = (e) => resolve(e.target.result);
|
|
26
|
-
request.onerror = (e) => reject(e.target.error);
|
|
31
|
+
request.onsuccess = (e) => resolve((e.target${AS("IDBOpenDBRequest")}).result);
|
|
32
|
+
request.onerror = (e) => reject((e.target${AS("IDBRequest")}).error);
|
|
27
33
|
});
|
|
28
34
|
}
|
|
29
35
|
|
|
30
|
-
|
|
36
|
+
/** Get a record by ID from an IndexedDB store. Returns undefined if not found. */
|
|
37
|
+
export async function getRecord(storeName${T("string")}, id${T("IDBValidKey")})${R("Promise<Record<string, unknown> | undefined>")}{
|
|
31
38
|
const db = await openAppDB();
|
|
32
|
-
return new Promise((resolve, reject) => {
|
|
39
|
+
return new Promise${PT("Record<string, unknown> | undefined")}((resolve, reject) => {
|
|
33
40
|
const tx = db.transaction(storeName, "readonly");
|
|
34
41
|
const store = tx.objectStore(storeName);
|
|
35
42
|
const request = store.get(id);
|
|
36
|
-
request.onsuccess = () => resolve(request.result);
|
|
37
|
-
request.onerror = () => reject(request.error);
|
|
43
|
+
request.onsuccess = () => resolve((request${AS("IDBRequest<Record<string, unknown> | undefined>")}).result);
|
|
44
|
+
request.onerror = () => reject((request${AS("IDBRequest")}).error);
|
|
38
45
|
});
|
|
39
46
|
}
|
|
40
47
|
|
|
41
|
-
|
|
48
|
+
/** Store a record in an IndexedDB store (insert or update). Returns the record's key. */
|
|
49
|
+
export async function putRecord(storeName${T("string")}, record${T("Record<string, unknown>")})${R("Promise<IDBValidKey>")}{
|
|
42
50
|
const db = await openAppDB();
|
|
43
|
-
return new Promise((resolve, reject) => {
|
|
51
|
+
return new Promise${PT("IDBValidKey")}((resolve, reject) => {
|
|
44
52
|
const tx = db.transaction(storeName, "readwrite");
|
|
45
53
|
const store = tx.objectStore(storeName);
|
|
46
54
|
const request = store.put(record);
|
|
47
|
-
tx.oncomplete = () => resolve(request.result);
|
|
48
|
-
tx.onerror = () => reject(tx.error);
|
|
55
|
+
tx.oncomplete = () => resolve((request${AS("IDBRequest<IDBValidKey>")}).result);
|
|
56
|
+
tx.onerror = () => reject((tx${AS("IDBTransaction")}).error);
|
|
49
57
|
});
|
|
50
58
|
}
|
|
51
59
|
|
|
52
|
-
|
|
60
|
+
/** Delete a record by ID from an IndexedDB store. */
|
|
61
|
+
export async function deleteRecord(storeName${T("string")}, id${T("IDBValidKey")})${R("Promise<void>")}{
|
|
53
62
|
const db = await openAppDB();
|
|
54
|
-
return new Promise((resolve, reject) => {
|
|
63
|
+
return new Promise${PT("void")}((resolve, reject) => {
|
|
55
64
|
const tx = db.transaction(storeName, "readwrite");
|
|
56
65
|
const store = tx.objectStore(storeName);
|
|
57
|
-
|
|
66
|
+
store.delete(id);
|
|
58
67
|
tx.oncomplete = () => resolve();
|
|
59
|
-
tx.onerror = () => reject(tx.error);
|
|
68
|
+
tx.onerror = () => reject((tx${AS("IDBTransaction")}).error);
|
|
60
69
|
});
|
|
61
70
|
}
|
|
62
71
|
|
|
63
|
-
|
|
72
|
+
/** Get all records from an IndexedDB store. */
|
|
73
|
+
export async function getAllRecords(storeName${T("string")})${R("Promise<Record<string, unknown>[]>")}{
|
|
64
74
|
const db = await openAppDB();
|
|
65
|
-
return new Promise((resolve, reject) => {
|
|
75
|
+
return new Promise${PT("Record<string, unknown>[]")}((resolve, reject) => {
|
|
66
76
|
const tx = db.transaction(storeName, "readonly");
|
|
67
77
|
const store = tx.objectStore(storeName);
|
|
68
78
|
const request = store.getAll();
|
|
69
|
-
request.onsuccess = () => resolve(request.result);
|
|
70
|
-
request.onerror = () => reject(request.error);
|
|
79
|
+
request.onsuccess = () => resolve((request${AS("IDBRequest<Record<string, unknown>[]>")}).result);
|
|
80
|
+
request.onerror = () => reject((request${AS("IDBRequest")}).error);
|
|
71
81
|
});
|
|
72
82
|
}
|
|
73
83
|
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,aAAa,CAAC,GAAqB;IACjD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,MAAM,GAAG,QAAQ,CAAC;IAExB,MAAM,IAAI,GAAG;;;;;oFAKqE,GAAG;;;;;;;mBAOpE,MAAM
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,aAAa,CAAC,GAAqB;IACjD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC;IACxB,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtD,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACrD,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,QAAQ,CAAC;IAExB,MAAM,IAAI,GAAG;;;;;oFAKqE,GAAG;;;;;;;mBAOpE,MAAM;;;6BAGI,CAAC,CAAC,sBAAsB,CAAC;sBAChC,EAAE,CAAC,aAAa,CAAC;;kDAEW,EAAE,CAAC,kBAAkB,CAAC;+CACzB,EAAE,CAAC,YAAY,CAAC;;;;;2CAKpB,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,8CAA8C,CAAC;;sBAE5G,EAAE,CAAC,qCAAqC,CAAC;;;;gDAIf,EAAE,CAAC,iDAAiD,CAAC;6CACxD,EAAE,CAAC,YAAY,CAAC;;;;;2CAKlB,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,yBAAyB,CAAC,IAAI,CAAC,CAAC,sBAAsB,CAAC;;sBAEpG,EAAE,CAAC,aAAa,CAAC;;;;4CAIK,EAAE,CAAC,yBAAyB,CAAC;mCACtC,EAAE,CAAC,gBAAgB,CAAC;;;;;8CAKT,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;sBAEhF,EAAE,CAAC,MAAM,CAAC;;;;;mCAKG,EAAE,CAAC,gBAAgB,CAAC;;;;;+CAKR,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,oCAAoC,CAAC;;sBAE/E,EAAE,CAAC,2BAA2B,CAAC;;;;gDAIL,EAAE,CAAC,uCAAuC,CAAC;6CAC9C,EAAE,CAAC,YAAY,CAAC;;;CAG5D,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,SAAS,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC"}
|