@cavulsqa/reactive-db 0.1.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.
- package/README.md +24 -0
- package/dist/index.d.mts +121 -0
- package/dist/index.mjs +274 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# @cavulsqa/reactive-db
|
|
2
|
+
|
|
3
|
+
Framework-agnostic primitives for keeping a UI in step with a local SQLite database.
|
|
4
|
+
|
|
5
|
+
- `createChangeBus` — publish and subscribe to per-table change events (`TableChangeEvent`).
|
|
6
|
+
- `createResultCache` — bounded, keyed result cache with staleness handling. A standalone
|
|
7
|
+
primitive: `@cavulsqa/reactive-vue` does not use it, so nothing shares results between call
|
|
8
|
+
sites unless you build that on top.
|
|
9
|
+
- `createVisibilityGate` — suppresses refetches while a view is hidden.
|
|
10
|
+
- `createReactiveDb` — wraps a Kysely instance so writes announce the tables they touched;
|
|
11
|
+
`executeWithEvent` does the same for a single query.
|
|
12
|
+
- `createQueryMetrics` — query duration, error, and cache-hit counters.
|
|
13
|
+
- `ReactiveQueryOptions`, `calcRetryDelay`, `noopMetrics` — the option contract and retry policy a
|
|
14
|
+
framework binding builds on.
|
|
15
|
+
|
|
16
|
+
**No framework dependency.** There is no `vue` import; `isVisible?: { value: boolean }` is a
|
|
17
|
+
structural stand-in for a ref, so any framework (or none) can drive it. Vue bindings live in
|
|
18
|
+
[`@cavulsqa/reactive-vue`](../reactive-vue).
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
vp install @cavulsqa/reactive-db
|
|
24
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Kysely } from "kysely";
|
|
2
|
+
//#region src/events.d.ts
|
|
3
|
+
type ChangeType = "insert" | "update" | "delete" | "bulk";
|
|
4
|
+
interface TableChangeMeta {
|
|
5
|
+
affectedRows?: number;
|
|
6
|
+
affectedIds?: (string | number)[];
|
|
7
|
+
transactionId?: string;
|
|
8
|
+
timestamp?: number;
|
|
9
|
+
}
|
|
10
|
+
interface TableChangeEvent extends TableChangeMeta {
|
|
11
|
+
table: string;
|
|
12
|
+
type: ChangeType;
|
|
13
|
+
}
|
|
14
|
+
interface ChangeBus {
|
|
15
|
+
emit: (table: string, type: ChangeType, meta?: TableChangeMeta) => void;
|
|
16
|
+
on: (tables: string[], listener: (event: TableChangeEvent) => void) => () => void;
|
|
17
|
+
}
|
|
18
|
+
declare function createChangeBus(): ChangeBus;
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/resultCache.d.ts
|
|
21
|
+
interface ResultCache {
|
|
22
|
+
get(key: string): unknown;
|
|
23
|
+
set(key: string, value: unknown, tables: string[]): void;
|
|
24
|
+
invalidateTable(table: string): void;
|
|
25
|
+
clear(): void;
|
|
26
|
+
}
|
|
27
|
+
declare function createResultCache(maxEntries: number): ResultCache;
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/visibilityGate.d.ts
|
|
30
|
+
type ChangeDecision = "refetch" | "defer";
|
|
31
|
+
type ShowDecision = "refetch" | "idle";
|
|
32
|
+
interface VisibilityGate {
|
|
33
|
+
recordChange(isVisible: boolean): ChangeDecision;
|
|
34
|
+
recordVisible(): ShowDecision;
|
|
35
|
+
}
|
|
36
|
+
declare function createVisibilityGate(): VisibilityGate;
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/mutationProxy.d.ts
|
|
39
|
+
interface ReactiveDbDeps<DB> {
|
|
40
|
+
getDb: () => Kysely<DB>;
|
|
41
|
+
emitChange: (table: string, changeType: ChangeType, meta?: {
|
|
42
|
+
affectedRows?: number;
|
|
43
|
+
}) => void;
|
|
44
|
+
}
|
|
45
|
+
declare function affectedRowsOf(result: unknown): number | null;
|
|
46
|
+
declare function createReactiveDb<DB>(deps: ReactiveDbDeps<DB>): Kysely<DB>;
|
|
47
|
+
declare function executeWithEvent<T>(emitChange: ReactiveDbDeps<unknown>["emitChange"], table: string, changeType: ChangeType, executor: () => Promise<T>): Promise<T>;
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/queryMetrics.d.ts
|
|
50
|
+
interface QueryMetric {
|
|
51
|
+
count: number;
|
|
52
|
+
totalTime: number;
|
|
53
|
+
avgTime: number;
|
|
54
|
+
lastTime: number;
|
|
55
|
+
lastSeen: number;
|
|
56
|
+
}
|
|
57
|
+
interface QueryMetricsState {
|
|
58
|
+
queries: Record<string, QueryMetric>;
|
|
59
|
+
errors: Record<string, number>;
|
|
60
|
+
cacheHits: number;
|
|
61
|
+
refetchesByTable: Record<string, number>;
|
|
62
|
+
activeListeners: number;
|
|
63
|
+
startTime: number;
|
|
64
|
+
isDevToolsOpen: boolean;
|
|
65
|
+
}
|
|
66
|
+
interface QueryMetricsRecorder {
|
|
67
|
+
recordQuery(queryKey: string, durationMs: number): void;
|
|
68
|
+
recordError(queryKey: string): void;
|
|
69
|
+
recordCacheHit(): void;
|
|
70
|
+
recordRefetch(table: string): void;
|
|
71
|
+
incrementListeners(): void;
|
|
72
|
+
decrementListeners(): void;
|
|
73
|
+
reset(): void;
|
|
74
|
+
getState(): QueryMetricsState;
|
|
75
|
+
toggleDevTools(isOpen?: boolean): void;
|
|
76
|
+
}
|
|
77
|
+
interface CreateQueryMetricsOptions {
|
|
78
|
+
maxEntries?: number;
|
|
79
|
+
wrapState?: (state: QueryMetricsState) => QueryMetricsState;
|
|
80
|
+
}
|
|
81
|
+
declare function createQueryMetrics(options?: CreateQueryMetricsOptions): QueryMetricsRecorder;
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/useReactiveQuery.d.ts
|
|
84
|
+
interface ReactiveQueryOptions<T = unknown> {
|
|
85
|
+
tables: string[];
|
|
86
|
+
queryKey: string;
|
|
87
|
+
debounce?: number;
|
|
88
|
+
debug?: boolean;
|
|
89
|
+
refetchOn?: TableChangeEvent["type"][];
|
|
90
|
+
shouldRefetch?: (event: TableChangeEvent) => boolean;
|
|
91
|
+
cacheTime?: number;
|
|
92
|
+
staleWhileRevalidate?: boolean;
|
|
93
|
+
enabled?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Run the query once on mount. Defaults to true. Set false when a caller-owned
|
|
96
|
+
* watcher already drives the initial load, so the mount fetch would only add a
|
|
97
|
+
* redundant run. Unlike `enabled: false`, the table-change subscription stays.
|
|
98
|
+
*/
|
|
99
|
+
fetchOnMount?: boolean;
|
|
100
|
+
onSuccess?: (data: T) => void;
|
|
101
|
+
onError?: (error: Error) => void;
|
|
102
|
+
cancelOnUnmount?: boolean;
|
|
103
|
+
retry?: number | false;
|
|
104
|
+
retryDelay?: number | ((attempt: number) => number);
|
|
105
|
+
isVisible?: {
|
|
106
|
+
value: boolean;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
interface QueryMetrics {
|
|
110
|
+
recordQuery(queryKey: string, durationMs: number): void;
|
|
111
|
+
recordError(queryKey: string): void;
|
|
112
|
+
recordCacheHit(): void;
|
|
113
|
+
recordRefetch(table: string): void;
|
|
114
|
+
incrementListeners(): void;
|
|
115
|
+
decrementListeners(): void;
|
|
116
|
+
}
|
|
117
|
+
type OnTableChangeFn = (tables: string[], callback: (event: TableChangeEvent) => void) => () => void;
|
|
118
|
+
declare function calcRetryDelay(attempt: number, retryDelay?: number | ((attempt: number) => number)): number;
|
|
119
|
+
declare const noopMetrics: QueryMetrics;
|
|
120
|
+
//#endregion
|
|
121
|
+
export { ChangeBus, ChangeDecision, ChangeType, CreateQueryMetricsOptions, type OnTableChangeFn, QueryMetric, type QueryMetrics, QueryMetricsRecorder, QueryMetricsState, ReactiveDbDeps, type ReactiveQueryOptions, ResultCache, ShowDecision, TableChangeEvent, TableChangeMeta, VisibilityGate, affectedRowsOf, calcRetryDelay, createChangeBus, createQueryMetrics, createReactiveDb, createResultCache, createVisibilityGate, executeWithEvent, noopMetrics };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
//#region src/events.ts
|
|
2
|
+
function createChangeBus() {
|
|
3
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
4
|
+
return {
|
|
5
|
+
emit(table, type, meta) {
|
|
6
|
+
const event = {
|
|
7
|
+
table,
|
|
8
|
+
type,
|
|
9
|
+
...meta
|
|
10
|
+
};
|
|
11
|
+
for (const listener of listeners) if (listener.tables.has("*") || listener.tables.has(table)) listener.fn(event);
|
|
12
|
+
},
|
|
13
|
+
on(tables, listener) {
|
|
14
|
+
const entry = {
|
|
15
|
+
tables: new Set(tables),
|
|
16
|
+
fn: listener
|
|
17
|
+
};
|
|
18
|
+
listeners.add(entry);
|
|
19
|
+
return () => {
|
|
20
|
+
listeners.delete(entry);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/resultCache.ts
|
|
27
|
+
function createResultCache(maxEntries) {
|
|
28
|
+
const entries = /* @__PURE__ */ new Map();
|
|
29
|
+
return {
|
|
30
|
+
get(key) {
|
|
31
|
+
const entry = entries.get(key);
|
|
32
|
+
if (entry === void 0) return void 0;
|
|
33
|
+
entries.delete(key);
|
|
34
|
+
entries.set(key, entry);
|
|
35
|
+
return entry.value;
|
|
36
|
+
},
|
|
37
|
+
set(key, value, tables) {
|
|
38
|
+
if (entries.has(key)) entries.delete(key);
|
|
39
|
+
entries.set(key, {
|
|
40
|
+
value,
|
|
41
|
+
tables
|
|
42
|
+
});
|
|
43
|
+
while (entries.size > maxEntries) {
|
|
44
|
+
const oldest = entries.keys().next().value;
|
|
45
|
+
entries.delete(oldest);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
invalidateTable(table) {
|
|
49
|
+
for (const [key, entry] of entries) if (entry.tables.includes(table)) entries.delete(key);
|
|
50
|
+
},
|
|
51
|
+
clear() {
|
|
52
|
+
entries.clear();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/visibilityGate.ts
|
|
58
|
+
function createVisibilityGate() {
|
|
59
|
+
let deferred = false;
|
|
60
|
+
return {
|
|
61
|
+
recordChange(isVisible) {
|
|
62
|
+
if (isVisible) return "refetch";
|
|
63
|
+
deferred = true;
|
|
64
|
+
return "defer";
|
|
65
|
+
},
|
|
66
|
+
recordVisible() {
|
|
67
|
+
if (!deferred) return "idle";
|
|
68
|
+
deferred = false;
|
|
69
|
+
return "refetch";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/mutationProxy.ts
|
|
75
|
+
const MUTATION_CHANGE_TYPE = {
|
|
76
|
+
insertInto: "insert",
|
|
77
|
+
updateTable: "update",
|
|
78
|
+
deleteFrom: "delete"
|
|
79
|
+
};
|
|
80
|
+
const ROW_COUNT_KEYS = [
|
|
81
|
+
"numUpdatedRows",
|
|
82
|
+
"numDeletedRows",
|
|
83
|
+
"numInsertedOrUpdatedRows",
|
|
84
|
+
"numAffectedRows"
|
|
85
|
+
];
|
|
86
|
+
function affectedRowsOf(result) {
|
|
87
|
+
const rows = Array.isArray(result) ? result : result == null ? [] : [result];
|
|
88
|
+
let counted = null;
|
|
89
|
+
for (const row of rows) {
|
|
90
|
+
if (!row || typeof row !== "object") continue;
|
|
91
|
+
for (const key of ROW_COUNT_KEYS) {
|
|
92
|
+
const value = row[key];
|
|
93
|
+
if (value === void 0 || value === null) continue;
|
|
94
|
+
counted = (counted ?? 0) + Number(value);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return counted ?? (rows.length > 0 ? rows.length : null);
|
|
99
|
+
}
|
|
100
|
+
function wrapExecutor(executor, table, changeType, onExecuted) {
|
|
101
|
+
return async () => {
|
|
102
|
+
const result = await executor();
|
|
103
|
+
onExecuted(table, changeType, result);
|
|
104
|
+
return result;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function wrapBuilder(builder, table, changeType, onExecuted) {
|
|
108
|
+
return new Proxy(builder, { get(target, prop) {
|
|
109
|
+
const value = target[prop];
|
|
110
|
+
if (typeof value !== "function") return value;
|
|
111
|
+
if (prop === "execute" || prop === "executeTakeFirst" || prop === "executeTakeFirstOrThrow") return wrapExecutor(value.bind(target), table, changeType, onExecuted);
|
|
112
|
+
return (...args) => {
|
|
113
|
+
const result = value.apply(target, args);
|
|
114
|
+
if (result && typeof result.execute === "function" && result !== target) return wrapBuilder(result, table, changeType, onExecuted);
|
|
115
|
+
return result;
|
|
116
|
+
};
|
|
117
|
+
} });
|
|
118
|
+
}
|
|
119
|
+
function createMutationProxy(db, onExecuted) {
|
|
120
|
+
return new Proxy(db, { get(target, prop) {
|
|
121
|
+
const value = target[prop];
|
|
122
|
+
if (prop === "insertInto" || prop === "updateTable" || prop === "deleteFrom") return (...args) => {
|
|
123
|
+
const table = args[0];
|
|
124
|
+
const builder = value.apply(target, args);
|
|
125
|
+
const changeType = MUTATION_CHANGE_TYPE[prop];
|
|
126
|
+
return wrapBuilder(builder, table, changeType, onExecuted);
|
|
127
|
+
};
|
|
128
|
+
if (typeof value === "function") return value.bind(target);
|
|
129
|
+
return value;
|
|
130
|
+
} });
|
|
131
|
+
}
|
|
132
|
+
function wrapTransactionBuilder(txBuilder, emitChange) {
|
|
133
|
+
return new Proxy(txBuilder, { get(target, prop) {
|
|
134
|
+
const value = target[prop];
|
|
135
|
+
if (prop === "execute" && typeof value === "function") return async (callback) => {
|
|
136
|
+
const touchedTables = /* @__PURE__ */ new Set();
|
|
137
|
+
const result = await value.call(target, (trx) => callback(createMutationProxy(trx, (table) => {
|
|
138
|
+
touchedTables.add(table);
|
|
139
|
+
})));
|
|
140
|
+
for (const table of touchedTables) emitChange(table, "bulk");
|
|
141
|
+
return result;
|
|
142
|
+
};
|
|
143
|
+
if (typeof value === "function") return (...args) => {
|
|
144
|
+
const result = value.apply(target, args);
|
|
145
|
+
if (result && typeof result.execute === "function") return wrapTransactionBuilder(result, emitChange);
|
|
146
|
+
return result;
|
|
147
|
+
};
|
|
148
|
+
return value;
|
|
149
|
+
} });
|
|
150
|
+
}
|
|
151
|
+
function createReactiveDb(deps) {
|
|
152
|
+
const { getDb, emitChange } = deps;
|
|
153
|
+
function emitMutationEvent(table, changeType, result) {
|
|
154
|
+
const affectedRows = affectedRowsOf(result);
|
|
155
|
+
if (affectedRows === 0) return;
|
|
156
|
+
emitChange(table, changeType, affectedRows === null ? void 0 : { affectedRows });
|
|
157
|
+
}
|
|
158
|
+
return new Proxy({}, { get(_target, prop) {
|
|
159
|
+
const db = getDb();
|
|
160
|
+
const value = db[prop];
|
|
161
|
+
if (prop === "insertInto" || prop === "updateTable" || prop === "deleteFrom") return (...args) => {
|
|
162
|
+
const table = args[0];
|
|
163
|
+
const builder = value.apply(db, args);
|
|
164
|
+
const changeType = MUTATION_CHANGE_TYPE[prop];
|
|
165
|
+
return wrapBuilder(builder, table, changeType, emitMutationEvent);
|
|
166
|
+
};
|
|
167
|
+
if (prop === "transaction") return () => {
|
|
168
|
+
return wrapTransactionBuilder(value.apply(db, []), emitChange);
|
|
169
|
+
};
|
|
170
|
+
if (typeof value === "function") return value.bind(db);
|
|
171
|
+
return value;
|
|
172
|
+
} });
|
|
173
|
+
}
|
|
174
|
+
async function executeWithEvent(emitChange, table, changeType, executor) {
|
|
175
|
+
const result = await executor();
|
|
176
|
+
emitChange(table, changeType);
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/queryMetrics.ts
|
|
181
|
+
const DEFAULT_MAX_ENTRIES = 200;
|
|
182
|
+
function createQueryMetrics(options = {}) {
|
|
183
|
+
const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
184
|
+
const state = (options.wrapState ?? ((s) => s))({
|
|
185
|
+
queries: {},
|
|
186
|
+
errors: {},
|
|
187
|
+
cacheHits: 0,
|
|
188
|
+
refetchesByTable: {},
|
|
189
|
+
activeListeners: 0,
|
|
190
|
+
startTime: Date.now(),
|
|
191
|
+
isDevToolsOpen: false
|
|
192
|
+
});
|
|
193
|
+
function pruneQueries() {
|
|
194
|
+
const entries = Object.entries(state.queries);
|
|
195
|
+
if (entries.length <= maxEntries) return;
|
|
196
|
+
const expired = entries.sort(([, a], [, b]) => a.lastSeen - b.lastSeen).slice(0, entries.length - maxEntries);
|
|
197
|
+
for (const [key] of expired) {
|
|
198
|
+
delete state.queries[key];
|
|
199
|
+
delete state.errors[key];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function pruneRecord(record) {
|
|
203
|
+
const keys = Object.keys(record);
|
|
204
|
+
if (keys.length <= maxEntries) return;
|
|
205
|
+
for (const key of keys.slice(0, keys.length - maxEntries)) delete record[key];
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
recordQuery(queryKey, durationMs) {
|
|
209
|
+
const now = Date.now();
|
|
210
|
+
if (!state.queries[queryKey]) state.queries[queryKey] = {
|
|
211
|
+
count: 0,
|
|
212
|
+
totalTime: 0,
|
|
213
|
+
avgTime: 0,
|
|
214
|
+
lastTime: 0,
|
|
215
|
+
lastSeen: now
|
|
216
|
+
};
|
|
217
|
+
const metric = state.queries[queryKey];
|
|
218
|
+
metric.count++;
|
|
219
|
+
metric.totalTime += durationMs;
|
|
220
|
+
metric.avgTime = metric.totalTime / metric.count;
|
|
221
|
+
metric.lastTime = durationMs;
|
|
222
|
+
metric.lastSeen = now;
|
|
223
|
+
pruneQueries();
|
|
224
|
+
},
|
|
225
|
+
recordError(queryKey) {
|
|
226
|
+
state.errors[queryKey] = (state.errors[queryKey] ?? 0) + 1;
|
|
227
|
+
pruneRecord(state.errors);
|
|
228
|
+
},
|
|
229
|
+
recordCacheHit() {
|
|
230
|
+
state.cacheHits++;
|
|
231
|
+
},
|
|
232
|
+
recordRefetch(table) {
|
|
233
|
+
state.refetchesByTable[table] = (state.refetchesByTable[table] ?? 0) + 1;
|
|
234
|
+
pruneRecord(state.refetchesByTable);
|
|
235
|
+
},
|
|
236
|
+
incrementListeners() {
|
|
237
|
+
state.activeListeners++;
|
|
238
|
+
},
|
|
239
|
+
decrementListeners() {
|
|
240
|
+
if (state.activeListeners > 0) state.activeListeners--;
|
|
241
|
+
},
|
|
242
|
+
reset() {
|
|
243
|
+
state.queries = {};
|
|
244
|
+
state.errors = {};
|
|
245
|
+
state.cacheHits = 0;
|
|
246
|
+
state.refetchesByTable = {};
|
|
247
|
+
state.startTime = Date.now();
|
|
248
|
+
},
|
|
249
|
+
getState() {
|
|
250
|
+
return state;
|
|
251
|
+
},
|
|
252
|
+
toggleDevTools(isOpen) {
|
|
253
|
+
state.isDevToolsOpen = isOpen ?? !state.isDevToolsOpen;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
//#endregion
|
|
258
|
+
//#region src/useReactiveQuery.ts
|
|
259
|
+
const DEFAULT_RETRY_BASE = 1e3;
|
|
260
|
+
function calcRetryDelay(attempt, retryDelay) {
|
|
261
|
+
if (typeof retryDelay === "function") return retryDelay(attempt);
|
|
262
|
+
if (typeof retryDelay === "number") return retryDelay;
|
|
263
|
+
return DEFAULT_RETRY_BASE * Math.pow(2, attempt);
|
|
264
|
+
}
|
|
265
|
+
const noopMetrics = {
|
|
266
|
+
recordQuery() {},
|
|
267
|
+
recordError() {},
|
|
268
|
+
recordCacheHit() {},
|
|
269
|
+
recordRefetch() {},
|
|
270
|
+
incrementListeners() {},
|
|
271
|
+
decrementListeners() {}
|
|
272
|
+
};
|
|
273
|
+
//#endregion
|
|
274
|
+
export { affectedRowsOf, calcRetryDelay, createChangeBus, createQueryMetrics, createReactiveDb, createResultCache, createVisibilityGate, executeWithEvent, noopMetrics };
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cavulsqa/reactive-db",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework-agnostic reactive query primitives: table-change bus, result cache, visibility gate, mutation proxy, query metrics.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cache",
|
|
7
|
+
"offline-first",
|
|
8
|
+
"query",
|
|
9
|
+
"reactive",
|
|
10
|
+
"sqlite"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/aybinv7/cavulsqa/tree/main/packages/reactive-db#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/aybinv7/cavulsqa/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/aybinv7/cavulsqa.git",
|
|
20
|
+
"directory": "packages/reactive-db"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": "./dist/index.mjs",
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "vp pack",
|
|
35
|
+
"dev": "vp pack --watch",
|
|
36
|
+
"test": "vp test",
|
|
37
|
+
"check": "vp check",
|
|
38
|
+
"prepublishOnly": "vp run build"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^26.1.1",
|
|
42
|
+
"bumpp": "^11.1.0",
|
|
43
|
+
"kysely": "^0.28.9",
|
|
44
|
+
"typescript": "^7.0.2",
|
|
45
|
+
"vite": "catalog:",
|
|
46
|
+
"vite-plus": "catalog:"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"kysely": ">=0.28"
|
|
50
|
+
},
|
|
51
|
+
"peerDependenciesMeta": {
|
|
52
|
+
"kysely": {
|
|
53
|
+
"optional": true
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|