@treygrr/nuxt-airplane 0.0.0-stage → 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.
Files changed (35) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +179 -2
  3. package/dist/module.d.mts +41 -0
  4. package/dist/module.json +12 -0
  5. package/dist/module.mjs +316 -0
  6. package/dist/runtime/client.d.ts +230 -0
  7. package/dist/runtime/client.js +361 -0
  8. package/dist/runtime/composables/defineAirplaneRequest.d.ts +7 -0
  9. package/dist/runtime/composables/defineAirplaneRequest.js +4 -0
  10. package/dist/runtime/composables/defineAirplaneStores.d.ts +8 -0
  11. package/dist/runtime/composables/defineAirplaneStores.js +4 -0
  12. package/dist/runtime/composables/defineMutationHandler.d.ts +6 -0
  13. package/dist/runtime/composables/defineMutationHandler.js +4 -0
  14. package/dist/runtime/composables/defineMutationHandlers.d.ts +6 -0
  15. package/dist/runtime/composables/defineMutationHandlers.js +4 -0
  16. package/dist/runtime/composables/defineQueryHandler.d.ts +6 -0
  17. package/dist/runtime/composables/defineQueryHandler.js +4 -0
  18. package/dist/runtime/composables/defineQueryHandlers.d.ts +6 -0
  19. package/dist/runtime/composables/defineQueryHandlers.js +4 -0
  20. package/dist/runtime/composables/useAirplane.d.ts +3 -0
  21. package/dist/runtime/composables/useAirplane.js +4 -0
  22. package/dist/runtime/composables/useMutation.d.ts +14 -0
  23. package/dist/runtime/composables/useMutation.js +21 -0
  24. package/dist/runtime/composables/useQuery.d.ts +19 -0
  25. package/dist/runtime/composables/useQuery.js +63 -0
  26. package/dist/runtime/composables/useSync.d.ts +37 -0
  27. package/dist/runtime/composables/useSync.js +48 -0
  28. package/dist/runtime/db.d.ts +26 -0
  29. package/dist/runtime/db.js +109 -0
  30. package/dist/runtime/plugin.client.d.ts +417 -0
  31. package/dist/runtime/plugin.client.js +43 -0
  32. package/dist/runtime/types.d.ts +218 -0
  33. package/dist/runtime/types.js +8 -0
  34. package/dist/types.d.mts +3 -0
  35. package/package.json +96 -4
@@ -0,0 +1,218 @@
1
+ import type { Dexie, EntityTable, IndexableType, Table } from 'dexie';
2
+ declare global {
3
+ /**
4
+ * Augmented by the generated `types/nuxt-airplane.d.ts` with one entry per
5
+ * query document found in the user's `.gql` / `.graphql` files.
6
+ */
7
+ interface NuxtAirplaneQueries {
8
+ }
9
+ /** Same as {@link NuxtAirplaneQueries}, for mutation documents. */
10
+ interface NuxtAirplaneMutations {
11
+ }
12
+ /**
13
+ * Row types of the tables declared with `defineAirplaneStores`. Augment it
14
+ * yourself so `db.<table>` is typed inside handlers:
15
+ *
16
+ * ```ts
17
+ * declare global {
18
+ * interface NuxtAirplaneStores { sites: { id: string, name: string } }
19
+ * }
20
+ * ```
21
+ */
22
+ interface NuxtAirplaneStores {
23
+ }
24
+ }
25
+ export type QueryName = keyof NuxtAirplaneQueries & string;
26
+ export type MutationName = keyof NuxtAirplaneMutations & string;
27
+ export type QueryResult<N extends QueryName> = NuxtAirplaneQueries[N] extends {
28
+ result: infer R;
29
+ } ? R : never;
30
+ export type QueryVariables<N extends QueryName> = NuxtAirplaneQueries[N] extends {
31
+ variables: infer V;
32
+ } ? V : never;
33
+ export type MutationResult<N extends MutationName> = NuxtAirplaneMutations[N] extends {
34
+ result: infer R;
35
+ } ? R : never;
36
+ export type MutationVariables<N extends MutationName> = NuxtAirplaneMutations[N] extends {
37
+ variables: infer V;
38
+ } ? V : never;
39
+ export interface OperationDocument {
40
+ name: string;
41
+ kind: 'query' | 'mutation' | 'subscription';
42
+ source: string;
43
+ }
44
+ /**
45
+ * A cached query response, keyed by document name + variables. For queries
46
+ * with a `read` handler only the metadata is kept and `data` is `null`.
47
+ */
48
+ export interface QueryRow {
49
+ key: string;
50
+ document: string;
51
+ variables: unknown;
52
+ data: unknown;
53
+ updatedAt: number;
54
+ }
55
+ export type MutationStatus = 'pending' | 'syncing' | 'failed';
56
+ /** One write recorded while `apply` ran, with the row as it was before. */
57
+ export interface UndoEntry {
58
+ table: string;
59
+ key: IndexableType;
60
+ /** `undefined` when the row did not exist, in which case undo deletes it. */
61
+ before?: unknown;
62
+ }
63
+ /** A queued mutation waiting to reach the server. */
64
+ export interface MutationRow {
65
+ id: number;
66
+ document: string;
67
+ variables: unknown;
68
+ status: MutationStatus;
69
+ createdAt: number;
70
+ /** Human-readable summary from the handler's `describe`, or the humanized document name. */
71
+ description: string;
72
+ error?: string;
73
+ /** Writes made by `apply`, oldest first, used to roll the mutation back. */
74
+ undo?: UndoEntry[];
75
+ }
76
+ /** Module bookkeeping, e.g. the cache key the data was written under. */
77
+ export interface MetaRow {
78
+ key: string;
79
+ value: string;
80
+ }
81
+ export type AirplaneDexie = Dexie & {
82
+ queries: EntityTable<QueryRow, 'key'>;
83
+ mutations: EntityTable<MutationRow, 'id'>;
84
+ meta: EntityTable<MetaRow, 'key'>;
85
+ } & {
86
+ [K in keyof NuxtAirplaneStores]: Table<NuxtAirplaneStores[K], IndexableType>;
87
+ };
88
+ /** Mutate `data` in place, or return a replacement. */
89
+ export type Updater<T> = (data: T, variables: unknown) => T | undefined | void;
90
+ /** Read and write the cached query responses from inside a mutation handler. */
91
+ export interface CacheApi {
92
+ /** Update every cached response of a query document. */
93
+ update<N extends QueryName>(name: N, updater: Updater<QueryResult<N>>): Promise<void>;
94
+ /** Update the cached response for one exact set of variables. */
95
+ update<N extends QueryName>(name: N, variables: QueryVariables<N>, updater: Updater<QueryResult<N>>): Promise<void>;
96
+ get<N extends QueryName>(name: N, variables?: QueryVariables<N>): Promise<QueryResult<N> | undefined>;
97
+ set<N extends QueryName>(name: N, variables: QueryVariables<N>, data: QueryResult<N>): Promise<void>;
98
+ /** Drop the cached responses of a query document and refetch it once `apply` is done. */
99
+ invalidate(name: QueryName): Promise<void>;
100
+ }
101
+ export interface HandlerContext<V = unknown> {
102
+ db: AirplaneDexie;
103
+ cache: CacheApi;
104
+ variables: V;
105
+ }
106
+ export interface MutationHandler<N extends MutationName = MutationName> {
107
+ /**
108
+ * A human-readable summary for the pending list, e.g. "Archive North Warehouse".
109
+ * Computed once when the mutation is queued, before `apply`, so it can read
110
+ * the data as it was. Defaults to the humanized document name.
111
+ */
112
+ describe?: string | ((ctx: HandlerContext<MutationVariables<N>>) => string | Promise<string>);
113
+ /**
114
+ * Runs as soon as the mutation is queued, so the UI reflects it immediately.
115
+ * It runs inside a Dexie transaction and every write is recorded for rollback.
116
+ */
117
+ apply?: (ctx: HandlerContext<MutationVariables<N>>) => unknown;
118
+ /** Runs with the server's response once the mutation has been sent. */
119
+ reconcile?: (ctx: HandlerContext<MutationVariables<N>>, result: MutationResult<N>) => unknown;
120
+ /**
121
+ * Runs when a queued mutation is rolled back. When omitted the writes made
122
+ * by `apply` are undone from the recorded log instead.
123
+ */
124
+ revert?: (ctx: HandlerContext<MutationVariables<N>>) => unknown;
125
+ /** Queries to refetch after the mutation has been sent. Defaults to every active query. */
126
+ refetch?: QueryName[];
127
+ }
128
+ export type MutationHandlers = {
129
+ [N in MutationName]?: MutationHandler<N>;
130
+ };
131
+ export interface QueryHandlerContext<V = unknown> {
132
+ db: AirplaneDexie;
133
+ variables: V;
134
+ }
135
+ /**
136
+ * Walk a paginated query to the end after any page of it is fetched, so the
137
+ * whole set is available offline.
138
+ */
139
+ export interface QueryPrefetch<N extends QueryName = QueryName> {
140
+ /** Variables of the page after `data`, or `null` / `undefined` when there is none. */
141
+ next: (data: QueryResult<N>, variables: QueryVariables<N>) => QueryVariables<N> | null | undefined;
142
+ /** Safety cap on pages fetched per run. Default 1000. */
143
+ maxPages?: number;
144
+ /** Skip the run when this returns `false`: metered connection, feature flag, user setting… */
145
+ when?: () => boolean;
146
+ }
147
+ export interface QueryHandler<N extends QueryName = QueryName> {
148
+ /** Fetch every following page in the background once a page of this query is fetched. */
149
+ prefetch?: QueryPrefetch<N>;
150
+ /** Writes a fresh response into your own tables. Runs inside a Dexie transaction. */
151
+ store?: (ctx: QueryHandlerContext<QueryVariables<N>>, data: QueryResult<N>) => unknown;
152
+ /**
153
+ * Builds the query's result from your tables. Runs inside Dexie's
154
+ * `liveQuery`, so whatever it reads is observed and `data` stays live.
155
+ */
156
+ read?: (ctx: QueryHandlerContext<QueryVariables<N>>) => QueryResult<N> | null | undefined | Promise<QueryResult<N> | null | undefined>;
157
+ }
158
+ export type QueryHandlers = {
159
+ [N in QueryName]?: QueryHandler<N>;
160
+ };
161
+ /** Extra Dexie tables, in Dexie's `stores()` syntax. */
162
+ export interface AirplaneStores {
163
+ stores: Record<string, string>;
164
+ /** Schema version. Bump it when changing indexes of existing tables. */
165
+ version?: number;
166
+ }
167
+ /** What is being sent, for `headers` / `onResponse` to decide on. */
168
+ export interface AirplaneRequestContext {
169
+ document: string;
170
+ kind: OperationDocument['kind'];
171
+ variables: unknown;
172
+ }
173
+ /** How requests to the GraphQL endpoint are made; see `defineAirplaneRequest`. */
174
+ export interface AirplaneRequestOptions {
175
+ /**
176
+ * Extra headers, or a function returning them. The function runs for every
177
+ * request, inside the Nuxt app context, so it can read a cookie or state and
178
+ * always sends the current token, also for mutations synced later.
179
+ */
180
+ headers?: HeadersInit | ((ctx: AirplaneRequestContext) => HeadersInit | undefined | Promise<HeadersInit | undefined>);
181
+ /** `fetch` credentials mode, e.g. `'include'` for cookie auth across origins. */
182
+ credentials?: RequestCredentials;
183
+ /** A fetch implementation to use instead of the global one. */
184
+ fetch?: typeof globalThis.fetch;
185
+ /**
186
+ * Abort a request after this many milliseconds (default 30 000). A request
187
+ * that never settles would otherwise hold the sync loop for the session.
188
+ */
189
+ timeout?: number;
190
+ /** Sees every response before it is parsed, e.g. to react to a 401. */
191
+ onResponse?: (response: Response, ctx: AirplaneRequestContext) => unknown;
192
+ }
193
+ /** Marks what a file under the handlers directory default-exports. */
194
+ export declare const HANDLER_KIND: unique symbol;
195
+ export type HandlerKind = 'mutations' | 'queries' | 'stores' | 'request';
196
+ export interface GraphQLError {
197
+ message: string;
198
+ path?: (string | number)[];
199
+ extensions?: Record<string, unknown>;
200
+ }
201
+ export interface GraphQLResponse<T = unknown> {
202
+ data?: T | null;
203
+ errors?: GraphQLError[];
204
+ }
205
+ export declare class GraphQLRequestError extends Error {
206
+ errors: GraphQLError[];
207
+ constructor(errors: GraphQLError[]);
208
+ }
209
+ export interface AirplaneRuntimeConfig {
210
+ endpoint: string;
211
+ dbName: string;
212
+ /** Static headers from the `headers` module option. */
213
+ headers?: Record<string, string>;
214
+ /** Deploy-specific part of the cache key; see the `cacheKey` module option. */
215
+ cacheKey?: string;
216
+ /** A failed mutation holds back the ones queued after it; see the `stopOnFailure` module option. Default `true`. */
217
+ stopOnFailure?: boolean;
218
+ }
@@ -0,0 +1,8 @@
1
+ export const HANDLER_KIND = Symbol.for("nuxt-airplane:kind");
2
+ export class GraphQLRequestError extends Error {
3
+ constructor(errors) {
4
+ super(errors.map((e) => e.message).join("\n"));
5
+ this.errors = errors;
6
+ this.name = "GraphQLRequestError";
7
+ }
8
+ }
@@ -0,0 +1,3 @@
1
+ export { default } from './module.mjs'
2
+
3
+ export { type ModuleOptions } from './module.mjs'
package/package.json CHANGED
@@ -1,6 +1,98 @@
1
1
  {
2
2
  "name": "@treygrr/nuxt-airplane",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.2",
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
+ "keywords": [
6
+ "nuxt",
7
+ "nuxt-module",
8
+ "graphql",
9
+ "offline",
10
+ "offline-first",
11
+ "dexie",
12
+ "indexeddb",
13
+ "sync"
14
+ ],
15
+ "repository": "treygrr/nuxt-airplane",
16
+ "homepage": "https://github.com/treygrr/nuxt-airplane#readme",
17
+ "bugs": "https://github.com/treygrr/nuxt-airplane/issues",
18
+ "license": "MIT",
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "engines": {
23
+ "node": "^20.19.0 || >=22.12.0"
24
+ },
25
+ "type": "module",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/types.d.mts",
29
+ "import": "./dist/module.mjs"
30
+ }
31
+ },
32
+ "main": "./dist/module.mjs",
33
+ "typesVersions": {
34
+ "*": {
35
+ ".": [
36
+ "./dist/types.d.mts"
37
+ ]
38
+ }
39
+ },
40
+ "files": [
41
+ "dist"
42
+ ],
43
+ "workspaces": [
44
+ "playground",
45
+ "docs"
46
+ ],
47
+ "scripts": {
48
+ "prepack": "nuxt-module-build build",
49
+ "dev": "npm run dev:prepare && nuxt dev playground",
50
+ "dev:build": "nuxt build playground",
51
+ "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground",
52
+ "release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
53
+ "lint": "eslint .",
54
+ "test": "vitest run",
55
+ "test:watch": "vitest watch",
56
+ "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
57
+ "docs:dev": "nuxt dev docs",
58
+ "docs:build": "nuxt build docs"
59
+ },
60
+ "dependencies": {
61
+ "@graphql-codegen/core": "^6.2.2",
62
+ "@graphql-codegen/typescript": "^6.1.2",
63
+ "@graphql-codegen/typescript-operations": "^6.2.0",
64
+ "@nuxt/kit": "^4.6.3",
65
+ "dexie": "^4.4.7",
66
+ "ohash": "^2.0.12",
67
+ "tinyglobby": "^0.2.17"
68
+ },
69
+ "peerDependencies": {
70
+ "graphql": "^16.8.0 || ^17.0.0"
71
+ },
72
+ "devDependencies": {
73
+ "@nuxt/devtools": "^3.4.2",
74
+ "@nuxt/eslint-config": "^1.17.0",
75
+ "@nuxt/module-builder": "^1.0.3",
76
+ "@nuxt/schema": "^4.6.3",
77
+ "@nuxt/test-utils": "^4.3.3",
78
+ "@types/node": "latest",
79
+ "changelogen": "^0.6.2",
80
+ "eslint": "^10.12.0",
81
+ "fake-indexeddb": "^6.2.7",
82
+ "graphql": "^17.0.2",
83
+ "nuxt": "^4.6.3",
84
+ "typescript": "^6.0.3",
85
+ "vitest": "^5.0.3",
86
+ "vue-tsc": "^3.3.12"
87
+ },
88
+ "overrides": {
89
+ "minimatch": "^11.0.1",
90
+ "eslint-plugin-import-x": {
91
+ "minimatch": "^10.2.6"
92
+ },
93
+ "glob": "^14.0.2",
94
+ "@nuxt/content": {
95
+ "zod": "^4.6.7"
96
+ }
97
+ }
98
+ }