@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.
Files changed (91) hide show
  1. package/README.md +327 -25
  2. package/dist/__tests__/assemble-sw.test.js +15 -1
  3. package/dist/__tests__/assemble-sw.test.js.map +1 -1
  4. package/dist/__tests__/cli-integration.test.js +9 -8
  5. package/dist/__tests__/cli-integration.test.js.map +1 -1
  6. package/dist/__tests__/config-types.test.js +6 -6
  7. package/dist/__tests__/config-types.test.js.map +1 -1
  8. package/dist/__tests__/file-generators.test.js +67 -59
  9. package/dist/__tests__/file-generators.test.js.map +1 -1
  10. package/dist/__tests__/loader.test.js +12 -13
  11. package/dist/__tests__/loader.test.js.map +1 -1
  12. package/dist/__tests__/validator.test.js +41 -21
  13. package/dist/__tests__/validator.test.js.map +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/lib/cli/help.js +3 -3
  17. package/dist/lib/cli/help.js.map +1 -1
  18. package/dist/lib/commands/add.js +7 -1
  19. package/dist/lib/commands/add.js.map +1 -1
  20. package/dist/lib/commands/clean.js +5 -0
  21. package/dist/lib/commands/clean.js.map +1 -1
  22. package/dist/lib/commands/generate-guide.js +12 -9
  23. package/dist/lib/commands/generate-guide.js.map +1 -1
  24. package/dist/lib/commands/generate.js +7 -3
  25. package/dist/lib/commands/generate.js.map +1 -1
  26. package/dist/lib/commands/info.js +135 -32
  27. package/dist/lib/commands/info.js.map +1 -1
  28. package/dist/lib/commands/validate.js +3 -1
  29. package/dist/lib/commands/validate.js.map +1 -1
  30. package/dist/lib/config/validator.js +25 -16
  31. package/dist/lib/config/validator.js.map +1 -1
  32. package/dist/lib/generators/file-generators/auth-fetch.js +29 -34
  33. package/dist/lib/generators/file-generators/auth-fetch.js.map +1 -1
  34. package/dist/lib/generators/file-generators/auth-state.js +8 -5
  35. package/dist/lib/generators/file-generators/auth-state.js.map +1 -1
  36. package/dist/lib/generators/file-generators/auth-store.js +71 -40
  37. package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
  38. package/dist/lib/generators/file-generators/auth-user.js +27 -31
  39. package/dist/lib/generators/file-generators/auth-user.js.map +1 -1
  40. package/dist/lib/generators/file-generators/background-sync.js +9 -5
  41. package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
  42. package/dist/lib/generators/file-generators/cache.js +7 -2
  43. package/dist/lib/generators/file-generators/cache.js.map +1 -1
  44. package/dist/lib/generators/file-generators/client-injector.js +113 -0
  45. package/dist/lib/generators/file-generators/client-injector.js.map +1 -0
  46. package/dist/lib/generators/file-generators/context.js +7 -6
  47. package/dist/lib/generators/file-generators/context.js.map +1 -1
  48. package/dist/lib/generators/file-generators/fetch-wrapper.js +170 -40
  49. package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
  50. package/dist/lib/generators/file-generators/generate-hooks.js +61 -3
  51. package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
  52. package/dist/lib/generators/file-generators/guide-generator.js +333 -0
  53. package/dist/lib/generators/file-generators/guide-generator.js.map +1 -0
  54. package/dist/lib/generators/file-generators/invalidation-tags.js +12 -10
  55. package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
  56. package/dist/lib/generators/file-generators/mutation-queue.js +34 -84
  57. package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
  58. package/dist/lib/generators/file-generators/mutation-reconcile.js +57 -0
  59. package/dist/lib/generators/file-generators/mutation-reconcile.js.map +1 -0
  60. package/dist/lib/generators/file-generators/pwa-install.js +41 -9
  61. package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
  62. package/dist/lib/generators/file-generators/quick-readme.js +78 -0
  63. package/dist/lib/generators/file-generators/quick-readme.js.map +1 -0
  64. package/dist/lib/generators/file-generators/reconcile.js +7 -12
  65. package/dist/lib/generators/file-generators/reconcile.js.map +1 -1
  66. package/dist/lib/generators/file-generators/store.js +30 -20
  67. package/dist/lib/generators/file-generators/store.js.map +1 -1
  68. package/dist/lib/generators/file-generators/sw-generator-build.js +37 -16
  69. package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
  70. package/dist/lib/generators/file-generators/sw-injector.js +81 -110
  71. package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
  72. package/dist/lib/generators/file-generators/sw-template.js +1 -1
  73. package/dist/lib/generators/file-generators/type-definitions.js +33 -10
  74. package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
  75. package/dist/lib/generators/sw-generator.js +19 -10
  76. package/dist/lib/generators/sw-generator.js.map +1 -1
  77. package/dist/lib/generators/sw-sections/assemble-sw.js +32 -12
  78. package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
  79. package/dist/lib/generators/sw-sections/config-header.js +1 -1
  80. package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
  81. package/dist/lib/generators/sw-sections/fetch-handler.js +25 -10
  82. package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
  83. package/dist/lib/generators/sw-sections/tag-management.js +25 -5
  84. package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
  85. package/dist/lib/generators/swoff-files-generator.js +7 -5
  86. package/dist/lib/generators/swoff-files-generator.js.map +1 -1
  87. package/dist/lib/shared/config-types.js +21 -6
  88. package/dist/lib/shared/config-types.js.map +1 -1
  89. package/dist/lib/utils/build-script.js +35 -0
  90. package/dist/lib/utils/build-script.js.map +1 -0
  91. 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-store.${ext}";
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
- export async function queueMutation(mutation) {
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
- export async function processMutationQueue() {
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
- export async function flushMutations() {
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 (unchanged) are appended outside the template string
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
- async function rollbackMutation(item) {
187
- if (!item.storeName) return;
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;;CAEL;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;;;;;;;;;;;;;;;;;;EAkBrH,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqDX,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;EA0Bf,iBAAiB;;;;;;;;;;;;;;+DAc4C,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BjE,CAAC;IAEA,wEAAwE;IACxE,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;qDAyBmC,GAAG;;;;;;kDAMN,GAAG;;;kDAGH,GAAG;;;;;;;;;;;;;;;;;yEAiBoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4B3E,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,kBAAkB,GAAG,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,CAAC;AAC1D,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 Prompt
9
- * Manual install trigger and installability check.
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-install.${ext}';
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 (set by sw-injector):
24
+ * Window properties:
21
25
  * window.deferredInstallPrompt - The captured BeforeInstallPromptEvent
22
26
  */
23
27
 
24
- export function isInstallable() {
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
- export async function promptInstall() {
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-install.${ext}`, code);
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;IAEpB,MAAM,IAAI,GAAG;;;;;;yEAM0D,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0C3E,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,eAAe,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC7C,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmChE,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,aAAa,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC3C,CAAC"}
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
- export function openAppDB() {
23
- return new Promise((resolve, reject) => {
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
- export async function getRecord(storeName, id) {
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
- export async function putRecord(storeName, record) {
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
- export async function deleteRecord(storeName, id) {
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
- const request = store.delete(id);
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
- export async function getAllRecords(storeName) {
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDxB,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,SAAS,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC"}
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"}