@swoff/cli 0.0.2 → 0.2.0

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 (100) hide show
  1. package/README.md +49 -0
  2. package/bin/swoff +0 -0
  3. package/dist/__tests__/assemble-sw.test.js +145 -0
  4. package/dist/__tests__/assemble-sw.test.js.map +1 -0
  5. package/dist/__tests__/cli-integration.test.js +128 -0
  6. package/dist/__tests__/cli-integration.test.js.map +1 -0
  7. package/dist/__tests__/config-types.test.js +82 -0
  8. package/dist/__tests__/config-types.test.js.map +1 -0
  9. package/dist/__tests__/detect-language.test.js +51 -0
  10. package/dist/__tests__/detect-language.test.js.map +1 -0
  11. package/dist/__tests__/file-generators.test.js +271 -0
  12. package/dist/__tests__/file-generators.test.js.map +1 -0
  13. package/dist/__tests__/invalidation-tags.test.js +99 -0
  14. package/dist/__tests__/invalidation-tags.test.js.map +1 -0
  15. package/dist/__tests__/loader.test.js +77 -0
  16. package/dist/__tests__/loader.test.js.map +1 -0
  17. package/dist/__tests__/validator.test.js +232 -0
  18. package/dist/__tests__/validator.test.js.map +1 -0
  19. package/dist/index.js +36 -348
  20. package/dist/index.js.map +1 -1
  21. package/dist/lib/cli/colors.js +14 -0
  22. package/dist/lib/cli/colors.js.map +1 -0
  23. package/dist/lib/cli/help.js +63 -0
  24. package/dist/lib/cli/help.js.map +1 -0
  25. package/dist/lib/cli/logger.js +13 -0
  26. package/dist/lib/cli/logger.js.map +1 -0
  27. package/dist/lib/commands/add.js +58 -0
  28. package/dist/lib/commands/add.js.map +1 -0
  29. package/dist/lib/commands/clean.js +39 -0
  30. package/dist/lib/commands/clean.js.map +1 -0
  31. package/dist/lib/commands/generate.js +84 -0
  32. package/dist/lib/commands/generate.js.map +1 -0
  33. package/dist/lib/commands/info.js +44 -0
  34. package/dist/lib/commands/info.js.map +1 -0
  35. package/dist/lib/commands/init.js +46 -0
  36. package/dist/lib/commands/init.js.map +1 -0
  37. package/dist/lib/commands/validate.js +30 -0
  38. package/dist/lib/commands/validate.js.map +1 -0
  39. package/dist/lib/config/loader.js +80 -0
  40. package/dist/lib/config/loader.js.map +1 -0
  41. package/dist/lib/config/validator.js +102 -0
  42. package/dist/lib/config/validator.js.map +1 -0
  43. package/dist/lib/generators/file-generators/background-sync.js +59 -0
  44. package/dist/lib/generators/file-generators/background-sync.js.map +1 -0
  45. package/dist/lib/generators/file-generators/cache.js +55 -0
  46. package/dist/lib/generators/file-generators/cache.js.map +1 -0
  47. package/dist/lib/generators/file-generators/context.js +16 -0
  48. package/dist/lib/generators/file-generators/context.js.map +1 -0
  49. package/dist/lib/generators/file-generators/fetch-wrapper.js +80 -0
  50. package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -0
  51. package/dist/lib/generators/file-generators/indexeddb.js +73 -0
  52. package/dist/lib/generators/file-generators/indexeddb.js.map +1 -0
  53. package/dist/lib/generators/file-generators/invalidation-tags.js +91 -0
  54. package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -0
  55. package/dist/lib/generators/file-generators/manifest.js +26 -0
  56. package/dist/lib/generators/file-generators/manifest.js.map +1 -0
  57. package/dist/lib/generators/file-generators/mutation-queue.js +226 -0
  58. package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -0
  59. package/dist/lib/generators/file-generators/pwa-install.js +102 -0
  60. package/dist/lib/generators/file-generators/pwa-install.js.map +1 -0
  61. package/dist/lib/generators/file-generators/reconcile.js +54 -0
  62. package/dist/lib/generators/file-generators/reconcile.js.map +1 -0
  63. package/dist/lib/generators/file-generators/store.js +75 -0
  64. package/dist/lib/generators/file-generators/store.js.map +1 -0
  65. package/dist/lib/generators/file-generators/sw-generator-build.js +70 -0
  66. package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -0
  67. package/dist/lib/generators/file-generators/sw-injector.js +195 -0
  68. package/dist/lib/generators/file-generators/sw-injector.js.map +1 -0
  69. package/dist/lib/generators/file-generators/sw-template.js +143 -0
  70. package/dist/lib/generators/file-generators/sw-template.js.map +1 -0
  71. package/dist/lib/generators/file-generators/type-definitions.js +69 -0
  72. package/dist/lib/generators/file-generators/type-definitions.js.map +1 -0
  73. package/dist/lib/generators/sw-generator.js +37 -239
  74. package/dist/lib/generators/sw-generator.js.map +1 -1
  75. package/dist/lib/generators/sw-sections/activate-handler.js +16 -0
  76. package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -0
  77. package/dist/lib/generators/sw-sections/assemble-sw.js +38 -0
  78. package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -0
  79. package/dist/lib/generators/sw-sections/background-sync-handler.js +106 -0
  80. package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -0
  81. package/dist/lib/generators/sw-sections/config-header.js +15 -0
  82. package/dist/lib/generators/sw-sections/config-header.js.map +1 -0
  83. package/dist/lib/generators/sw-sections/default-template.js +51 -0
  84. package/dist/lib/generators/sw-sections/default-template.js.map +1 -0
  85. package/dist/lib/generators/sw-sections/fetch-handler.js +210 -0
  86. package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -0
  87. package/dist/lib/generators/sw-sections/install-handler.js +35 -0
  88. package/dist/lib/generators/sw-sections/install-handler.js.map +1 -0
  89. package/dist/lib/generators/sw-sections/message-handler.js +20 -0
  90. package/dist/lib/generators/sw-sections/message-handler.js.map +1 -0
  91. package/dist/lib/generators/sw-sections/tag-management.js +69 -0
  92. package/dist/lib/generators/sw-sections/tag-management.js.map +1 -0
  93. package/dist/lib/generators/swoff-files-generator.js +80 -1043
  94. package/dist/lib/generators/swoff-files-generator.js.map +1 -1
  95. package/dist/lib/shared/config-types.js +87 -0
  96. package/dist/lib/shared/config-types.js.map +1 -0
  97. package/dist/lib/utils/detect-language.js +33 -0
  98. package/dist/lib/utils/detect-language.js.map +1 -0
  99. package/package.json +6 -2
  100. package/src/lib/templates/sw-template.js +23 -6
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Generates mutation-queue.js - offline mutation queue with IndexedDB.
3
+ */
4
+ import { writeFile } from "./context.js";
5
+ export function generateMutationQueue(ctx) {
6
+ const code = `/**
7
+ * Swoff Mutation Queue
8
+ * Queue offline writes and sync when connection returns.
9
+ *
10
+ * Usage:
11
+ * import { queueMutation, processMutationQueue, getPendingCount } from './swoff/mutation-queue.js';
12
+ *
13
+ * // Queue a mutation
14
+ * await queueMutation({
15
+ * method: "POST",
16
+ * url: "/api/todos",
17
+ * body: { title: "Grocery" },
18
+ * tags: ["todos"],
19
+ * storeName: "todos",
20
+ * tempId: "temp_abc123",
21
+ * });
22
+ *
23
+ * // Auto-processes on online event
24
+ */
25
+
26
+ const DB_NAME = "swoff-queue";
27
+ const STORE_NAME = "mutations";
28
+ const MAX_RETRIES = 5;
29
+
30
+ function openQueueDB() {
31
+ return new Promise((resolve, reject) => {
32
+ const request = indexedDB.open(DB_NAME, 1);
33
+ request.onupgradeneeded = (e) => {
34
+ const db = e.target.result;
35
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
36
+ const store = db.createObjectStore(STORE_NAME, { keyPath: "id" });
37
+ store.createIndex("by-timestamp", "timestamp");
38
+ }
39
+ };
40
+ request.onsuccess = (e) => resolve(e.target.result);
41
+ request.onerror = (e) => reject(e.target.error);
42
+ });
43
+ }
44
+
45
+ let isSyncing = false;
46
+
47
+ export async function queueMutation(mutation) {
48
+ const db = await openQueueDB();
49
+ const tx = db.transaction(STORE_NAME, "readwrite");
50
+ const store = tx.objectStore(STORE_NAME);
51
+
52
+ store.add({
53
+ id: crypto.randomUUID(),
54
+ method: mutation.method,
55
+ url: mutation.url,
56
+ body: mutation.body,
57
+ headers: mutation.headers || {},
58
+ previousData: mutation.previousData || null,
59
+ timestamp: Date.now(),
60
+ retryCount: 0,
61
+ tags: mutation.tags || [],
62
+ storeName: mutation.storeName || null,
63
+ tempId: mutation.tempId || null,
64
+ });
65
+
66
+ await new Promise((resolve, reject) => {
67
+ tx.oncomplete = () => resolve();
68
+ tx.onerror = () => reject(tx.error);
69
+ });
70
+
71
+ window.dispatchEvent(new CustomEvent("mutation-queue-changed"));
72
+ }
73
+
74
+ export async function processMutationQueue() {
75
+ if (!navigator.onLine || isSyncing) return;
76
+ isSyncing = true;
77
+
78
+ try {
79
+ const db = await openQueueDB();
80
+ const tx = db.transaction(STORE_NAME, "readonly");
81
+ const store = tx.objectStore(STORE_NAME);
82
+ const index = store.index("by-timestamp");
83
+ const queue = await new Promise((resolve, reject) => {
84
+ const request = index.getAll();
85
+ request.onsuccess = () => resolve(request.result);
86
+ request.onerror = () => reject(request.error);
87
+ });
88
+
89
+ let succeeded = 0;
90
+ let failed = 0;
91
+
92
+ for (const item of queue) {
93
+ if (item.retryCount >= MAX_RETRIES) {
94
+ await rollbackMutation(item);
95
+ await removeFromQueue(item.id);
96
+ failed++;
97
+ continue;
98
+ }
99
+
100
+ try {
101
+ const response = await fetch(item.url, {
102
+ method: item.method,
103
+ headers: {
104
+ "Content-Type": "application/json",
105
+ ...item.headers,
106
+ },
107
+ body: JSON.stringify(item.body),
108
+ });
109
+
110
+ if (!response.ok) throw new Error(\`HTTP \${response.status}\`);
111
+
112
+ const serverData = await response.json();
113
+
114
+ if (item.tempId && item.storeName) {
115
+ await reconcileRecord(item.storeName, item.tempId, serverData);
116
+ }
117
+
118
+ if (item.tags && item.tags.length > 0) {
119
+ const { invalidateByTags } = await import("./cache.js");
120
+ await invalidateByTags(item.tags);
121
+ }
122
+
123
+ await removeFromQueue(item.id);
124
+ succeeded++;
125
+ } catch {
126
+ item.retryCount++;
127
+ await updateInQueue(item);
128
+ failed++;
129
+ }
130
+ }
131
+
132
+ window.dispatchEvent(
133
+ new CustomEvent("mutation-sync-complete", {
134
+ detail: { succeeded, failed },
135
+ })
136
+ );
137
+ } finally {
138
+ isSyncing = false;
139
+ window.dispatchEvent(new CustomEvent("mutation-queue-changed"));
140
+ }
141
+ }
142
+
143
+ async function removeFromQueue(id) {
144
+ const db = await openQueueDB();
145
+ const tx = db.transaction(STORE_NAME, "readwrite");
146
+ tx.objectStore(STORE_NAME).delete(id);
147
+ await new Promise((resolve, reject) => {
148
+ tx.oncomplete = () => resolve();
149
+ tx.onerror = () => reject(tx.error);
150
+ });
151
+ }
152
+
153
+ async function updateInQueue(item) {
154
+ const db = await openQueueDB();
155
+ const tx = db.transaction(STORE_NAME, "readwrite");
156
+ tx.objectStore(STORE_NAME).put(item);
157
+ await new Promise((resolve, reject) => {
158
+ tx.oncomplete = () => resolve();
159
+ tx.onerror = () => reject(tx.error);
160
+ });
161
+ }
162
+
163
+ async function rollbackMutation(item) {
164
+ if (!item.storeName) return;
165
+
166
+ if (item.method === "POST" && item.tempId) {
167
+ const { deleteRecord } = await import("./store.js");
168
+ await deleteRecord(item.storeName, item.tempId);
169
+ } else if (
170
+ (item.method === "PUT" || item.method === "PATCH") &&
171
+ item.previousData
172
+ ) {
173
+ const { putRecord } = await import("./store.js");
174
+ await putRecord(item.storeName, { ...item.previousData, $synced: true });
175
+ } else if (item.method === "DELETE" && item.tempId && item.previousData) {
176
+ const { putRecord } = await import("./store.js");
177
+ await putRecord(item.storeName, { ...item.previousData, $synced: true });
178
+ }
179
+
180
+ window.dispatchEvent(
181
+ new CustomEvent("mutation-rollback", {
182
+ detail: {
183
+ method: item.method,
184
+ url: item.url,
185
+ tempId: item.tempId,
186
+ previousData: item.previousData,
187
+ },
188
+ })
189
+ );
190
+ }
191
+
192
+ async function reconcileRecord(storeName, tempId, serverData) {
193
+ const { getRecord, putRecord, deleteRecord } = await import("./store.js");
194
+ const existing = await getRecord(storeName, tempId);
195
+ if (!existing) return;
196
+
197
+ const reconciled = {
198
+ ...existing,
199
+ ...serverData,
200
+ id: serverData.id,
201
+ $synced: true,
202
+ $syncedAt: Date.now(),
203
+ };
204
+
205
+ await putRecord(storeName, reconciled);
206
+
207
+ if (String(tempId) !== String(serverData.id)) {
208
+ await deleteRecord(storeName, tempId);
209
+ }
210
+ }
211
+
212
+ export async function getPendingCount() {
213
+ const db = await openQueueDB();
214
+ return new Promise((resolve, reject) => {
215
+ const tx = db.transaction(STORE_NAME, "readonly");
216
+ const request = tx.objectStore(STORE_NAME).count();
217
+ request.onsuccess = () => resolve(request.result);
218
+ request.onerror = () => reject(request.error);
219
+ });
220
+ }
221
+
222
+ window.addEventListener("online", processMutationQueue);
223
+ `;
224
+ writeFile(ctx, "mutation-queue.js", code);
225
+ }
226
+ //# sourceMappingURL=mutation-queue.js.map
@@ -0,0 +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,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyNd,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,mBAAmB,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Generates pwa-install.js - PWA install prompt handler.
3
+ */
4
+ import { writeFile } from "./context.js";
5
+ export function generatePwaInstall(ctx) {
6
+ const preventDefault = ctx.config.pwa?.preventDefaultInstall ?? false;
7
+ const code = `/**
8
+ * Swoff PWA Install Prompt Handler
9
+ * Captures beforeinstallprompt event and provides manual install trigger.
10
+ *
11
+ * Usage:
12
+ * import { isInstallable, promptInstall } from './swoff/pwa-install.js';
13
+ *
14
+ * // Listen for installable event
15
+ * window.addEventListener('pwa-installable', (e) => {
16
+ * console.log('PWA can be installed:', e.detail.isInstallable);
17
+ * // Show your custom install button
18
+ * });
19
+ *
20
+ * // When user clicks install button
21
+ * async function onInstallClick() {
22
+ * const result = await promptInstall();
23
+ * console.log('User choice:', result);
24
+ * }
25
+ *
26
+ * Window events:
27
+ * pwa-installable - PWA can be installed (detail: { isInstallable: true })
28
+ * pwa-installed - User accepted install (detail: { outcome: 'accepted' })
29
+ * pwa-dismissed - User dismissed install (detail: { outcome: 'dismissed' })
30
+ *
31
+ * Window properties:
32
+ * window.deferredInstallPrompt - The captured BeforeInstallPromptEvent
33
+ * window.pwaInstallable - Whether PWA can be installed
34
+ */
35
+
36
+ const PREVENT_DEFAULT_INSTALL = ${preventDefault};
37
+
38
+ window.addEventListener("beforeinstallprompt", (e) => {
39
+ // Always capture the event so we never lose it
40
+ window.deferredInstallPrompt = e;
41
+ window.pwaInstallable = true;
42
+
43
+ if (PREVENT_DEFAULT_INSTALL) {
44
+ // Suppress browser's native prompt
45
+ e.preventDefault();
46
+ }
47
+ // When false, browser shows native prompt naturally
48
+ // but we still capture the event for manual triggering
49
+
50
+ window.dispatchEvent(
51
+ new CustomEvent("pwa-installable", {
52
+ detail: { isInstallable: true },
53
+ })
54
+ );
55
+ });
56
+
57
+ window.addEventListener("appinstalled", () => {
58
+ window.deferredInstallPrompt = null;
59
+ window.pwaInstallable = false;
60
+
61
+ window.dispatchEvent(
62
+ new CustomEvent("pwa-installed", {
63
+ detail: { outcome: "accepted" },
64
+ })
65
+ );
66
+ });
67
+
68
+ export function isInstallable() {
69
+ return !!window.deferredInstallPrompt;
70
+ }
71
+
72
+ export async function promptInstall() {
73
+ if (!window.deferredInstallPrompt) {
74
+ throw new Error("Install prompt not available");
75
+ }
76
+
77
+ const promptEvent = window.deferredInstallPrompt;
78
+ await promptEvent.prompt();
79
+
80
+ const choice = await promptEvent.userChoice;
81
+
82
+ if (choice.outcome === "accepted") {
83
+ window.dispatchEvent(
84
+ new CustomEvent("pwa-installed", {
85
+ detail: { outcome: "accepted" },
86
+ })
87
+ );
88
+ } else {
89
+ window.dispatchEvent(
90
+ new CustomEvent("pwa-dismissed", {
91
+ detail: { outcome: "dismissed" },
92
+ })
93
+ );
94
+ }
95
+
96
+ window.deferredInstallPrompt = null;
97
+ return choice;
98
+ }
99
+ `;
100
+ writeFile(ctx, "pwa-install.js", code);
101
+ }
102
+ //# sourceMappingURL=pwa-install.js.map
@@ -0,0 +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,cAAc,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,qBAAqB,IAAI,KAAK,CAAC;IAEtE,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCA6BmB,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+D/C,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,gBAAgB,EAAE,IAAI,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Generates reconcile.js - ID reconciliation after mutation sync.
3
+ */
4
+ import { writeFile } from "./context.js";
5
+ export function generateReconcile(ctx) {
6
+ const code = `/**
7
+ * Swoff ID Reconciliation
8
+ * Update local records with server data after mutation sync.
9
+ *
10
+ * Usage:
11
+ * import { reconcileRecord } from './swoff/reconcile.js';
12
+ *
13
+ * await reconcileRecord('todos', 'temp_abc123', serverData);
14
+ */
15
+
16
+ import { getRecord, putRecord, deleteRecord } from './store.js';
17
+
18
+ export async function reconcileRecord(storeName, tempId, serverData) {
19
+ const existing = await getRecord(storeName, tempId);
20
+ if (!existing) return;
21
+
22
+ const reconciled = {
23
+ ...existing,
24
+ ...serverData,
25
+ id: serverData.id,
26
+ $synced: true,
27
+ $syncedAt: Date.now(),
28
+ };
29
+
30
+ await putRecord(storeName, reconciled);
31
+
32
+ if (String(tempId) !== String(serverData.id)) {
33
+ await deleteRecord(storeName, tempId);
34
+ }
35
+
36
+ await reconcileReferences(storeName, tempId, serverData.id);
37
+ }
38
+
39
+ export async function reconcileReferences(storeName, oldId, newId) {
40
+ // Override this for your app's schema.
41
+ // Example: update foreign-key references in related stores.
42
+ //
43
+ // const txns = await getAllRecords('transactions');
44
+ // for (const txn of txns) {
45
+ // if (txn.todoId === oldId) {
46
+ // txn.todoId = newId;
47
+ // await putRecord('transactions', txn);
48
+ // }
49
+ // }
50
+ }
51
+ `;
52
+ writeFile(ctx, "reconcile.js", code);
53
+ }
54
+ //# sourceMappingURL=reconcile.js.map
@@ -0,0 +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,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6Cd,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Generates store.js - IndexedDB CRUD operations.
3
+ */
4
+ import { writeFile } from "./context.js";
5
+ export function generateStore(ctx) {
6
+ const dbName = ctx.config.database?.name || "app-db";
7
+ const code = `/**
8
+ * Swoff IndexedDB Store
9
+ * Generic CRUD operations for app's IndexedDB database.
10
+ *
11
+ * Usage:
12
+ * import { getRecord, putRecord, deleteRecord, openAppDB } from './swoff/store.js';
13
+ *
14
+ * const record = await getRecord('todos', 'todo-123');
15
+ * await putRecord('todos', { id: 'todo-123', title: 'New task', $synced: false });
16
+ * await deleteRecord('todos', 'todo-123');
17
+ */
18
+
19
+ const DB_NAME = "${dbName}";
20
+
21
+ export function openAppDB() {
22
+ return new Promise((resolve, reject) => {
23
+ const request = indexedDB.open(DB_NAME);
24
+ request.onsuccess = (e) => resolve(e.target.result);
25
+ request.onerror = (e) => reject(e.target.error);
26
+ });
27
+ }
28
+
29
+ export async function getRecord(storeName, id) {
30
+ const db = await openAppDB();
31
+ return new Promise((resolve, reject) => {
32
+ const tx = db.transaction(storeName, "readonly");
33
+ const store = tx.objectStore(storeName);
34
+ const request = store.get(id);
35
+ request.onsuccess = () => resolve(request.result);
36
+ request.onerror = () => reject(request.error);
37
+ });
38
+ }
39
+
40
+ export async function putRecord(storeName, record) {
41
+ const db = await openAppDB();
42
+ return new Promise((resolve, reject) => {
43
+ const tx = db.transaction(storeName, "readwrite");
44
+ const store = tx.objectStore(storeName);
45
+ const request = store.put(record);
46
+ tx.oncomplete = () => resolve(request.result);
47
+ tx.onerror = () => reject(tx.error);
48
+ });
49
+ }
50
+
51
+ export async function deleteRecord(storeName, id) {
52
+ const db = await openAppDB();
53
+ return new Promise((resolve, reject) => {
54
+ const tx = db.transaction(storeName, "readwrite");
55
+ const store = tx.objectStore(storeName);
56
+ const request = store.delete(id);
57
+ tx.oncomplete = () => resolve();
58
+ tx.onerror = () => reject(tx.error);
59
+ });
60
+ }
61
+
62
+ export async function getAllRecords(storeName) {
63
+ const db = await openAppDB();
64
+ return new Promise((resolve, reject) => {
65
+ const tx = db.transaction(storeName, "readonly");
66
+ const store = tx.objectStore(storeName);
67
+ const request = store.getAll();
68
+ request.onsuccess = () => resolve(request.result);
69
+ request.onerror = () => reject(request.error);
70
+ });
71
+ }
72
+ `;
73
+ writeFile(ctx, "store.js", code);
74
+ }
75
+ //# sourceMappingURL=store.js.map
@@ -0,0 +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,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,IAAI,QAAQ,CAAC;IAErD,MAAM,IAAI,GAAG;;;;;;;;;;;;mBAYI,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDxB,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Generates sw-generator.js - build script that processes sw-template.js.
3
+ */
4
+ import { writeFile } from "./context.js";
5
+ export function generateSwGeneratorBuild(ctx) {
6
+ const code = `#!/usr/bin/env node
7
+ /**
8
+ * Swoff SW Generator Build Script
9
+ * Reads swoff/sw-template.js and generates versioned SW output.
10
+ *
11
+ * Add to package.json:
12
+ * "build": "your-build && node swoff/sw-generator.js"
13
+ */
14
+
15
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
16
+ import { join, dirname } from 'path';
17
+ import { fileURLToPath } from 'url';
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const projectRoot = join(__dirname, '..');
21
+
22
+ const pkgPath = join(projectRoot, 'package.json');
23
+ const templatePath = join(__dirname, 'sw-template.js');
24
+ const configPath = join(projectRoot, 'swoff.config.json');
25
+
26
+ if (!existsSync(configPath)) {
27
+ console.error('Error: swoff.config.json not found');
28
+ console.log('Run "npx @swoff/cli init" to create one');
29
+ process.exit(1);
30
+ }
31
+
32
+ if (!existsSync(templatePath)) {
33
+ console.error('Error: swoff/sw-template.js not found');
34
+ process.exit(1);
35
+ }
36
+
37
+ const pkg = existsSync(pkgPath) ? JSON.parse(readFileSync(pkgPath, 'utf8')) : { version: '1.0.0' };
38
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
39
+ const template = readFileSync(templatePath, 'utf8');
40
+
41
+ const version = config.version === 'from-package' ? pkg.version || '1.0.0' : config.version;
42
+ const outputDir = config.build?.outputDir || 'dist';
43
+ const swFilename = config.build?.swFilename || 'sw';
44
+
45
+ let sw = template;
46
+ sw = sw.replace('// [[CACHE_NAME]]', \`CACHE_NAME = 'sw-v\${version}'\`);
47
+
48
+ const baseAssets = ['/', '/index.html'];
49
+ const pwaAssets = config.features?.pwa ? ['/manifest.json'] : [];
50
+ const assetsToCache = [...baseAssets, ...pwaAssets];
51
+ sw = sw.replace('// [[ASSETS_LIST]]', \`ASSETS_TO_CACHE = \${JSON.stringify(assetsToCache.map(url => ({ url, options: {} })), null, 2)}\`);
52
+ sw = sw.replace('// [[AUTO_SKIP_WAITING]]', \`const AUTO_SKIP_WAITING = \${config.serviceWorker?.autoUpdate || false};\`);
53
+
54
+ const outDir = join(projectRoot, outputDir);
55
+ if (!existsSync(outDir)) {
56
+ mkdirSync(outDir, { recursive: true });
57
+ }
58
+
59
+ writeFileSync(join(projectRoot, outputDir, \`\${swFilename}-v\${version}.js\`), sw);
60
+ writeFileSync(join(projectRoot, outputDir, 'version.json'), JSON.stringify({
61
+ version,
62
+ minSupportedVersion: config.minSupportedVersion || '0.0.0',
63
+ generatedAt: new Date().toISOString(),
64
+ }, null, 2));
65
+
66
+ console.log(\`Service worker built: \${outputDir}/\${swFilename}-v\${version}.js\`);
67
+ `;
68
+ writeFile(ctx, "sw-generator.js", code);
69
+ }
70
+ //# sourceMappingURL=sw-generator-build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sw-generator-build.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/sw-generator-build.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,wBAAwB,CAAC,GAAqB;IAC5D,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6Dd,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,iBAAiB,EAAE,IAAI,CAAC,CAAC;AAC1C,CAAC"}