@treygrr/nuxt-airplane 0.1.1 → 0.1.2
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 +3 -1
- package/dist/module.d.mts +7 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +47 -2
- package/dist/runtime/client.d.ts +1 -0
- package/dist/runtime/client.js +39 -8
- package/dist/runtime/composables/useSync.d.ts +2 -0
- package/dist/runtime/composables/useSync.js +2 -0
- package/dist/runtime/db.d.ts +8 -1
- package/dist/runtime/db.js +26 -3
- package/dist/runtime/plugin.client.d.ts +2 -0
- package/dist/runtime/plugin.client.js +1 -1
- package/dist/runtime/types.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,6 +36,7 @@ export default defineNuxtConfig({
|
|
|
36
36
|
| `documents` | `['**/*.gql', '**/*.graphql']` | Globs (relative to `srcDir`) of the files holding your operations. |
|
|
37
37
|
| `handlersDir` | `airplane` | Directory (relative to `srcDir`) scanned for mutation handler files. |
|
|
38
38
|
| `dbName` | `nuxt-airplane` | IndexedDB database name. |
|
|
39
|
+
| `stopOnFailure` | `true` | A failed mutation holds back the ones queued after it until retried or rolled back (`useSync().blocked` counts them). |
|
|
39
40
|
| `cacheKey` | – | Deploy key (git SHA, build id). A change wipes cached data on next load, outbox kept; a hash of your documents is always mixed in. `NUXT_PUBLIC_AIRPLANE_CACHE_KEY` overrides it. |
|
|
40
41
|
| `codegen` | `{}` | Extra config for the `typescript` / `typescript-operations` codegen plugins. |
|
|
41
42
|
|
|
@@ -152,8 +153,9 @@ const { online, syncing, synced, pending, syncTotal, syncCount, lastError, sync,
|
|
|
152
153
|
|
|
153
154
|
- `pending` lists the queued mutations oldest first, each with a human-readable `description` (from the handler's `describe` hook, or the humanized document name), its variables and status.
|
|
154
155
|
- `rollback(id)` undoes a queued mutation locally and drops it from the queue. Mutations queued after it are re-applied on top of the restored data.
|
|
155
|
-
- A mutation the server rejects is marked `failed` and left in the queue
|
|
156
|
+
- A mutation the server rejects is marked `failed` and left in the queue, and the mutations queued after it wait (`blocked`) until you `retry(id)` or `rollback(id)` it; set `stopOnFailure: false` to keep sending past failures.
|
|
156
157
|
- A network failure stops the batch and keeps everything queued; the next `online` event (or `sync()`) resumes it.
|
|
158
|
+
- Tabs share the database; a Web Lock makes sure only one of them sends the queue at a time.
|
|
157
159
|
- `goOffline()` holds the client offline whatever the network says: queries serve the cache only and mutations stay queued. `goOnline()` releases it and syncs. `online` reflects the effective state; `networkOnline` and `forcedOffline` expose the two inputs. Handy for a "work offline" switch or for testing.
|
|
158
160
|
|
|
159
161
|
## Security
|
package/dist/module.d.mts
CHANGED
|
@@ -25,6 +25,13 @@ interface ModuleOptions {
|
|
|
25
25
|
* Overridable at runtime with `NUXT_PUBLIC_AIRPLANE_CACHE_KEY`.
|
|
26
26
|
*/
|
|
27
27
|
cacheKey?: string;
|
|
28
|
+
/**
|
|
29
|
+
* When a mutation is rejected by the server, hold back the mutations queued
|
|
30
|
+
* after it until it is retried or rolled back, so the outbox keeps its
|
|
31
|
+
* order (an item added to a site that was never created would fail too).
|
|
32
|
+
* Set to `false` to skip failed mutations and keep sending the rest.
|
|
33
|
+
*/
|
|
34
|
+
stopOnFailure: boolean;
|
|
28
35
|
/** Extra options passed to the `typescript` / `typescript-operations` codegen plugins. */
|
|
29
36
|
codegen: Record<string, unknown>;
|
|
30
37
|
}
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { readFile, mkdir, writeFile } from 'node:fs/promises';
|
|
1
2
|
import { join, dirname, resolve, sep, basename } from 'node:path';
|
|
2
3
|
import { defineNuxtModule, createResolver, useLogger, addTemplate, addTypeTemplate, addImportsDir, addPlugin, updateTemplates } from '@nuxt/kit';
|
|
3
4
|
import { hash } from 'ohash';
|
|
4
5
|
import { glob } from 'tinyglobby';
|
|
5
6
|
import { existsSync } from 'node:fs';
|
|
6
|
-
import { readFile, mkdir, writeFile } from 'node:fs/promises';
|
|
7
7
|
import { codegen } from '@graphql-codegen/core';
|
|
8
8
|
import * as typescriptPlugin from '@graphql-codegen/typescript';
|
|
9
9
|
import * as typescriptOperationsPlugin from '@graphql-codegen/typescript-operations';
|
|
@@ -154,6 +154,39 @@ async function generateTypes(schema, docs, config = {}) {
|
|
|
154
154
|
].join("\n");
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
const DEFINE_HELPERS = [
|
|
158
|
+
"defineMutationHandler",
|
|
159
|
+
"defineMutationHandlers",
|
|
160
|
+
"defineQueryHandler",
|
|
161
|
+
"defineQueryHandlers",
|
|
162
|
+
"defineAirplaneStores",
|
|
163
|
+
"defineAirplaneRequest"
|
|
164
|
+
];
|
|
165
|
+
const WRAP_HINT = "Wrap it in defineMutationHandlers(...) / defineQueryHandler(name, ...) / defineAirplaneStores(...) / defineAirplaneRequest(...), or export it from outside the handlers directory.";
|
|
166
|
+
function stripComments(source) {
|
|
167
|
+
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/.*$/gm, "$1");
|
|
168
|
+
}
|
|
169
|
+
function findUntaggedDefaultExport(source) {
|
|
170
|
+
const code = stripComments(source);
|
|
171
|
+
const match = /\bexport\s+default\s+(?:await\s+)?/.exec(code);
|
|
172
|
+
if (!match) return null;
|
|
173
|
+
const rest = code.slice(match.index + match[0].length).trimStart();
|
|
174
|
+
if (rest.startsWith("{")) return { reason: "an object literal" };
|
|
175
|
+
if (rest.startsWith("[")) return { reason: "an array literal" };
|
|
176
|
+
if (/^(?:async\s+)?function\b/.test(rest)) return { reason: "a function" };
|
|
177
|
+
if (/^class\b/.test(rest)) return { reason: "a class" };
|
|
178
|
+
if (/^new\b/.test(rest)) return { reason: "a `new` expression" };
|
|
179
|
+
if (/^(?:async\s+)?(?:\([^)]*\)|[\w$]+)\s*=>/.test(rest)) return { reason: "an arrow function" };
|
|
180
|
+
if (/^(?:['"`\d]|true\b|false\b|null\b|undefined\b)/.test(rest)) return { reason: "a literal value" };
|
|
181
|
+
const call = /^([\w$]+(?:\s*\.\s*[\w$]+)*)\s*(?:<[^()]*>\s*)?\(/.exec(rest);
|
|
182
|
+
if (call) {
|
|
183
|
+
const callee = call[1].replace(/\s+/g, "");
|
|
184
|
+
if (DEFINE_HELPERS.includes(callee)) return null;
|
|
185
|
+
return { reason: `a call to \`${callee}()\`` };
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
157
190
|
const module$1 = defineNuxtModule({
|
|
158
191
|
meta: {
|
|
159
192
|
name: "nuxt-airplane",
|
|
@@ -165,6 +198,7 @@ const module$1 = defineNuxtModule({
|
|
|
165
198
|
documents: ["**/*.gql", "**/*.graphql"],
|
|
166
199
|
handlersDir: "airplane",
|
|
167
200
|
dbName: "nuxt-airplane",
|
|
201
|
+
stopOnFailure: true,
|
|
168
202
|
codegen: {}
|
|
169
203
|
},
|
|
170
204
|
async setup(options, nuxt) {
|
|
@@ -176,7 +210,8 @@ const module$1 = defineNuxtModule({
|
|
|
176
210
|
endpoint: options.endpoint,
|
|
177
211
|
dbName: options.dbName,
|
|
178
212
|
cacheKey: options.cacheKey ?? "",
|
|
179
|
-
headers: options.headers ?? {}
|
|
213
|
+
headers: options.headers ?? {},
|
|
214
|
+
stopOnFailure: options.stopOnFailure
|
|
180
215
|
};
|
|
181
216
|
nuxt.options.build.transpile.push(resolver.resolve("./runtime"));
|
|
182
217
|
nuxt.options.vite.optimizeDeps ||= {};
|
|
@@ -215,6 +250,16 @@ const module$1 = defineNuxtModule({
|
|
|
215
250
|
write: true,
|
|
216
251
|
getContents: async () => {
|
|
217
252
|
const files = await handlerFiles();
|
|
253
|
+
const offenders = [];
|
|
254
|
+
for (const file of files) {
|
|
255
|
+
const untagged = findUntaggedDefaultExport(await readFile(file, "utf8"));
|
|
256
|
+
if (untagged) offenders.push(` ${file}: default export is ${untagged.reason}`);
|
|
257
|
+
}
|
|
258
|
+
if (offenders.length) {
|
|
259
|
+
throw new Error(`[nuxt-airplane] Files in the handlers directory must default-export the result of a define* helper:
|
|
260
|
+
${offenders.join("\n")}
|
|
261
|
+
${WRAP_HINT}`);
|
|
262
|
+
}
|
|
218
263
|
const imports = files.map((file, i) => `import h${i} from ${JSON.stringify(file)}`);
|
|
219
264
|
const list = files.map((_, i) => `h${i}`).join(", ");
|
|
220
265
|
return `${imports.join("\n")}
|
package/dist/runtime/client.d.ts
CHANGED
package/dist/runtime/client.js
CHANGED
|
@@ -16,6 +16,7 @@ export function queryKey(name, variables) {
|
|
|
16
16
|
export function createAirplaneClient(options) {
|
|
17
17
|
const db = createDb(options.dbName, options.stores.stores, options.stores.version);
|
|
18
18
|
const { documents, mutationHandlers, queryHandlers } = options;
|
|
19
|
+
const stopOnFailure = options.stopOnFailure ?? true;
|
|
19
20
|
const opened = db.open().catch(async (error) => {
|
|
20
21
|
if (!["VersionError", "UpgradeError", "SchemaError"].includes(error.name)) throw error;
|
|
21
22
|
console.warn(`[nuxt-airplane] Could not open "${options.dbName}" (${error.name}), recreating it. Bump the store version to migrate instead.`);
|
|
@@ -36,8 +37,11 @@ export function createAirplaneClient(options) {
|
|
|
36
37
|
await clearCache();
|
|
37
38
|
}
|
|
38
39
|
if (stored !== cacheKey) await db.meta.put({ key: "cacheKey", value: cacheKey });
|
|
39
|
-
await
|
|
40
|
+
await requeueStuck();
|
|
40
41
|
});
|
|
42
|
+
async function requeueStuck() {
|
|
43
|
+
await db.mutations.where("status").equals("syncing").modify({ status: "pending" });
|
|
44
|
+
}
|
|
41
45
|
const state = reactive({
|
|
42
46
|
/** What the browser reports; assumed online where `navigator.onLine` does not exist (Node, some webviews). */
|
|
43
47
|
networkOnline: typeof navigator === "object" && typeof navigator.onLine === "boolean" ? navigator.onLine : true,
|
|
@@ -50,6 +54,12 @@ export function createAirplaneClient(options) {
|
|
|
50
54
|
syncing: false,
|
|
51
55
|
/** Mutations still waiting to be sent, oldest first. */
|
|
52
56
|
pending: [],
|
|
57
|
+
/** Pending mutations held back because an older one failed; `0` with `stopOnFailure: false`. */
|
|
58
|
+
get blocked() {
|
|
59
|
+
if (!stopOnFailure) return 0;
|
|
60
|
+
const failed = this.pending.findIndex((m) => m.status === "failed");
|
|
61
|
+
return failed < 0 ? 0 : this.pending.slice(failed + 1).filter((m) => m.status === "pending").length;
|
|
62
|
+
},
|
|
53
63
|
/** Size of the batch the current (or last) sync started with. */
|
|
54
64
|
syncTotal: 0,
|
|
55
65
|
/** Mutations of that batch that have reached the server so far. */
|
|
@@ -245,12 +255,13 @@ export function createAirplaneClient(options) {
|
|
|
245
255
|
if (h?.revert) {
|
|
246
256
|
await h.revert({ db, cache: createCacheApi(), variables: row.variables });
|
|
247
257
|
await db.mutations.delete(id);
|
|
248
|
-
|
|
258
|
+
} else {
|
|
259
|
+
const later = (await db.mutations.where("id").above(row.id).toArray()).filter((m) => m.status !== "syncing");
|
|
260
|
+
if (row.undo) await applyUndo(db, row.undo);
|
|
261
|
+
await db.mutations.delete(id);
|
|
262
|
+
for (const m of later) await applyMutation(m, { rebase: true });
|
|
249
263
|
}
|
|
250
|
-
|
|
251
|
-
if (row.undo) await applyUndo(db, row.undo);
|
|
252
|
-
await db.mutations.delete(id);
|
|
253
|
-
for (const m of later) await applyMutation(m, { rebase: true });
|
|
264
|
+
void sync();
|
|
254
265
|
}
|
|
255
266
|
async function retry(id) {
|
|
256
267
|
await db.mutations.update(id, { status: "pending", error: void 0 });
|
|
@@ -259,13 +270,32 @@ export function createAirplaneClient(options) {
|
|
|
259
270
|
let syncPromise = null;
|
|
260
271
|
function sync() {
|
|
261
272
|
if (syncPromise) return syncPromise;
|
|
262
|
-
syncPromise =
|
|
273
|
+
syncPromise = runSyncLocked().finally(() => syncPromise = null);
|
|
263
274
|
return syncPromise;
|
|
264
275
|
}
|
|
276
|
+
async function sendable() {
|
|
277
|
+
const rows = await db.mutations.orderBy("createdAt").toArray();
|
|
278
|
+
const queue = [];
|
|
279
|
+
for (const row of rows) {
|
|
280
|
+
if (row.status === "failed" && stopOnFailure) break;
|
|
281
|
+
if (row.status === "pending") queue.push(row);
|
|
282
|
+
}
|
|
283
|
+
return queue;
|
|
284
|
+
}
|
|
285
|
+
const syncLock = `nuxt-airplane:sync:${options.dbName}`;
|
|
286
|
+
async function runSyncLocked() {
|
|
287
|
+
const locks = typeof navigator === "object" ? navigator.locks : void 0;
|
|
288
|
+
if (!locks) return runSync();
|
|
289
|
+
await locks.request(syncLock, async () => {
|
|
290
|
+
await ready;
|
|
291
|
+
await requeueStuck();
|
|
292
|
+
await runSync();
|
|
293
|
+
});
|
|
294
|
+
}
|
|
265
295
|
async function runSync() {
|
|
266
296
|
await ready;
|
|
267
297
|
if (!state.online) return;
|
|
268
|
-
const queue = await
|
|
298
|
+
const queue = await sendable();
|
|
269
299
|
if (!queue.length) return;
|
|
270
300
|
state.syncing = true;
|
|
271
301
|
state.syncTotal = queue.length;
|
|
@@ -287,6 +317,7 @@ export function createAirplaneClient(options) {
|
|
|
287
317
|
} catch (error) {
|
|
288
318
|
if (error instanceof GraphQLRequestError) {
|
|
289
319
|
await db.mutations.update(row.id, { status: "failed", error: error.message });
|
|
320
|
+
if (stopOnFailure) break;
|
|
290
321
|
continue;
|
|
291
322
|
}
|
|
292
323
|
await db.mutations.update(row.id, { status: "pending" });
|
|
@@ -13,6 +13,8 @@ export interface UseSyncReturn {
|
|
|
13
13
|
synced: ComputedRef<boolean>;
|
|
14
14
|
/** Mutations still waiting to reach the server, oldest first. */
|
|
15
15
|
pending: Ref<MutationRow[]>;
|
|
16
|
+
/** Pending mutations held back because an older one failed (see the `stopOnFailure` option). */
|
|
17
|
+
blocked: Ref<number>;
|
|
16
18
|
/** Size of the batch the current (or last) sync started with. */
|
|
17
19
|
syncTotal: Ref<number>;
|
|
18
20
|
/** How many of that batch have been sent so far. */
|
|
@@ -12,6 +12,7 @@ export function useSync() {
|
|
|
12
12
|
syncing: computed(() => false),
|
|
13
13
|
synced: computed(() => true),
|
|
14
14
|
pending: computed(() => []),
|
|
15
|
+
blocked: computed(() => 0),
|
|
15
16
|
syncTotal: computed(() => 0),
|
|
16
17
|
syncCount: computed(() => 0),
|
|
17
18
|
lastError: computed(() => null),
|
|
@@ -33,6 +34,7 @@ export function useSync() {
|
|
|
33
34
|
syncing: toRef(state, "syncing"),
|
|
34
35
|
synced: client.synced,
|
|
35
36
|
pending: toRef(state, "pending"),
|
|
37
|
+
blocked: toRef(state, "blocked"),
|
|
36
38
|
syncTotal: toRef(state, "syncTotal"),
|
|
37
39
|
syncCount: toRef(state, "syncCount"),
|
|
38
40
|
lastError: toRef(state, "lastError"),
|
package/dist/runtime/db.d.ts
CHANGED
|
@@ -6,7 +6,14 @@ export declare const MODULE_STORES: {
|
|
|
6
6
|
};
|
|
7
7
|
/** Tables that hold module bookkeeping rather than cached data. */
|
|
8
8
|
export declare const BOOKKEEPING_TABLES: Set<string>;
|
|
9
|
-
export
|
|
9
|
+
export interface CreateDbOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Warn (once per table and index) when a boolean is written to an indexed
|
|
12
|
+
* key path, since IndexedDB cannot index booleans. Defaults to dev mode.
|
|
13
|
+
*/
|
|
14
|
+
warnOnBooleanIndex?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function createDb(name: string, stores?: Record<string, string>, version?: number, { warnOnBooleanIndex }?: CreateDbOptions): AirplaneDexie;
|
|
10
17
|
/**
|
|
11
18
|
* Runs `fn` in a read-write transaction over every table and returns the
|
|
12
19
|
* writes it made. If `fn` throws, the transaction aborts and nothing is kept.
|
package/dist/runtime/db.js
CHANGED
|
@@ -5,12 +5,33 @@ export const MODULE_STORES = {
|
|
|
5
5
|
meta: "&key"
|
|
6
6
|
};
|
|
7
7
|
export const BOOKKEEPING_TABLES = /* @__PURE__ */ new Set(["mutations", "meta"]);
|
|
8
|
-
export function createDb(name, stores = {}, version = 1) {
|
|
8
|
+
export function createDb(name, stores = {}, version = 1, { warnOnBooleanIndex = import.meta.dev } = {}) {
|
|
9
9
|
const db = new Dexie(name);
|
|
10
10
|
db.version(version).stores({ ...MODULE_STORES, ...stores });
|
|
11
|
-
installUndoLog(db);
|
|
11
|
+
installUndoLog(db, warnOnBooleanIndex);
|
|
12
12
|
return db;
|
|
13
13
|
}
|
|
14
|
+
function valueAt(row, keyPath) {
|
|
15
|
+
return keyPath.split(".").reduce((o, k) => o && typeof o === "object" ? o[k] : void 0, row);
|
|
16
|
+
}
|
|
17
|
+
function warnBooleanIndexes(table, values, warned) {
|
|
18
|
+
const { primaryKey, indexes } = table.schema;
|
|
19
|
+
for (const index of [primaryKey, ...indexes]) {
|
|
20
|
+
if (!index.keyPath || index.name?.includes("(virtual-from:")) continue;
|
|
21
|
+
const label = index.name ?? "primary key";
|
|
22
|
+
const id = `${table.name}/${label}`;
|
|
23
|
+
if (warned.has(id)) continue;
|
|
24
|
+
const paths = Array.isArray(index.keyPath) ? index.keyPath : [index.keyPath];
|
|
25
|
+
for (const value of values) {
|
|
26
|
+
const at = paths.findIndex((path) => typeof valueAt(value, path) === "boolean");
|
|
27
|
+
if (at === -1) continue;
|
|
28
|
+
warned.add(id);
|
|
29
|
+
const where = paths.length > 1 ? `index "${label}" at "${paths[at]}"` : `index "${label}"`;
|
|
30
|
+
console.warn(`[nuxt-airplane] Table "${table.name}": ${where} received a boolean, and IndexedDB cannot index booleans; store it as 0 | 1 (see the docs on stores).`);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
14
35
|
const logs = /* @__PURE__ */ new WeakMap();
|
|
15
36
|
function keysOf(index, values) {
|
|
16
37
|
return index.extractKey ? values.map((v) => index.extractKey(v)) : void 0;
|
|
@@ -36,7 +57,8 @@ async function recordBefore(table, req) {
|
|
|
36
57
|
const before = await table.getMany({ trans, keys });
|
|
37
58
|
return { entries: keys.map((key, i) => ({ table: name, key, before: before[i] })) };
|
|
38
59
|
}
|
|
39
|
-
function installUndoLog(db) {
|
|
60
|
+
function installUndoLog(db, warnOnBooleanIndex = false) {
|
|
61
|
+
const warned = /* @__PURE__ */ new Set();
|
|
40
62
|
db.use({
|
|
41
63
|
stack: "dbcore",
|
|
42
64
|
name: "nuxt-airplane-undo",
|
|
@@ -48,6 +70,7 @@ function installUndoLog(db) {
|
|
|
48
70
|
return {
|
|
49
71
|
...table,
|
|
50
72
|
async mutate(req) {
|
|
73
|
+
if (warnOnBooleanIndex && (req.type === "add" || req.type === "put")) warnBooleanIndexes(table, req.values, warned);
|
|
51
74
|
const log = logs.get(req.trans);
|
|
52
75
|
if (!log) return table.mutate(req);
|
|
53
76
|
const { entries, afterMutate } = await recordBefore(table, req);
|
|
@@ -171,6 +171,7 @@ declare const _default: import("#app").Plugin<{
|
|
|
171
171
|
before?: unknown;
|
|
172
172
|
}[] | undefined;
|
|
173
173
|
}[];
|
|
174
|
+
readonly blocked: number;
|
|
174
175
|
syncTotal: number;
|
|
175
176
|
syncCount: number;
|
|
176
177
|
lastError: string | null;
|
|
@@ -377,6 +378,7 @@ declare const _default: import("#app").Plugin<{
|
|
|
377
378
|
before?: unknown;
|
|
378
379
|
}[] | undefined;
|
|
379
380
|
}[];
|
|
381
|
+
readonly blocked: number;
|
|
380
382
|
syncTotal: number;
|
|
381
383
|
syncCount: number;
|
|
382
384
|
lastError: string | null;
|
|
@@ -14,7 +14,7 @@ function collect(exports) {
|
|
|
14
14
|
if (item === void 0) continue;
|
|
15
15
|
const kind = item && typeof item === "object" ? item[HANDLER_KIND] : void 0;
|
|
16
16
|
if (!kind) {
|
|
17
|
-
|
|
17
|
+
console.warn("[nuxt-airplane] Ignoring a default export in the handlers directory that was not created with a define* helper (defineMutationHandler(s), defineQueryHandler(s), defineAirplaneStores, defineAirplaneRequest).");
|
|
18
18
|
continue;
|
|
19
19
|
}
|
|
20
20
|
if (kind === "queries") Object.assign(queryHandlers, item);
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -213,4 +213,6 @@ export interface AirplaneRuntimeConfig {
|
|
|
213
213
|
headers?: Record<string, string>;
|
|
214
214
|
/** Deploy-specific part of the cache key; see the `cacheKey` module option. */
|
|
215
215
|
cacheKey?: string;
|
|
216
|
+
/** A failed mutation holds back the ones queued after it; see the `stopOnFailure` module option. Default `true`. */
|
|
217
|
+
stopOnFailure?: boolean;
|
|
216
218
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@treygrr/nuxt-airplane",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Airplane mode for your GraphQL app: typed composables from your .gql files, an IndexedDB cache and a mutation queue that lands when you are back online.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nuxt",
|