@syncular/client 0.0.6-168 → 0.0.6-177
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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pull-engine.d.ts.map +1 -1
- package/dist/pull-engine.js +61 -75
- package/dist/pull-engine.js.map +1 -1
- package/dist/query-public.d.ts +19 -0
- package/dist/query-public.d.ts.map +1 -0
- package/dist/query-public.js +152 -0
- package/dist/query-public.js.map +1 -0
- package/package.json +3 -3
- package/src/index.ts +1 -1
- package/src/pull-engine.test.ts +136 -0
- package/src/pull-engine.ts +90 -106
- package/src/query-public.ts +277 -0
- package/dist/query/QueryContext.d.ts +0 -33
- package/dist/query/QueryContext.d.ts.map +0 -1
- package/dist/query/QueryContext.js +0 -16
- package/dist/query/QueryContext.js.map +0 -1
- package/dist/query/index.d.ts +0 -7
- package/dist/query/index.d.ts.map +0 -1
- package/dist/query/index.js +0 -7
- package/dist/query/index.js.map +0 -1
- package/dist/query/tracked-select.d.ts +0 -18
- package/dist/query/tracked-select.d.ts.map +0 -1
- package/dist/query/tracked-select.js +0 -90
- package/dist/query/tracked-select.js.map +0 -1
- package/src/query/QueryContext.ts +0 -54
- package/src/query/index.ts +0 -10
- package/src/query/tracked-select.ts +0 -139
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @syncular/client - Query Context
|
|
3
|
-
*
|
|
4
|
-
* Provides a query context with tracked selectFrom for scope tracking
|
|
5
|
-
* and automatic fingerprint generation.
|
|
6
|
-
*/
|
|
7
|
-
import { createTrackedSelectFrom } from './tracked-select.js';
|
|
8
|
-
/**
|
|
9
|
-
* Create a query context with tracked selectFrom.
|
|
10
|
-
*/
|
|
11
|
-
export function createQueryContext(db, scopeCollector, fingerprintCollector, engine, keyField = 'id') {
|
|
12
|
-
return {
|
|
13
|
-
selectFrom: createTrackedSelectFrom(db, scopeCollector, fingerprintCollector, engine, keyField),
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
//# sourceMappingURL=QueryContext.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"QueryContext.js","sourceRoot":"","sources":["../../src/query/QueryContext.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAuB3D;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,EAAc,EACd,cAA2B,EAC3B,oBAA0C,EAC1C,MAA+B,EAC/B,QAAQ,GAAG,IAAI,EACG;IAClB,OAAO;QACL,UAAU,EAAE,uBAAuB,CACjC,EAAE,EACF,cAAc,EACd,oBAAoB,EACpB,MAAM,EACN,QAAQ,CACT;KACF,CAAC;AAAA,CACH"}
|
package/dist/query/index.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @syncular/client - Query utilities exports
|
|
3
|
-
*/
|
|
4
|
-
export { FingerprintCollector } from './FingerprintCollector';
|
|
5
|
-
export { canFingerprint, computeFingerprint, } from './fingerprint';
|
|
6
|
-
export { createQueryContext, type QueryContext } from './QueryContext';
|
|
7
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/query/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EACL,cAAc,EACd,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/query/index.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @syncular/client - Query utilities exports
|
|
3
|
-
*/
|
|
4
|
-
export { FingerprintCollector } from './FingerprintCollector.js';
|
|
5
|
-
export { canFingerprint, computeFingerprint, } from './fingerprint.js';
|
|
6
|
-
export { createQueryContext } from './QueryContext.js';
|
|
7
|
-
//# sourceMappingURL=index.js.map
|
package/dist/query/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/query/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EACL,cAAc,EACd,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,kBAAkB,EAAqB,MAAM,gBAAgB,CAAC"}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @syncular/client - Tracked SelectFrom
|
|
3
|
-
*
|
|
4
|
-
* Provides a wrapped selectFrom that:
|
|
5
|
-
* 1. Tracks tables as watched scopes when called
|
|
6
|
-
* 2. Intercepts .execute() to auto-generate fingerprints
|
|
7
|
-
*/
|
|
8
|
-
import type { Kysely } from 'kysely';
|
|
9
|
-
import type { FingerprintCollector } from './FingerprintCollector';
|
|
10
|
-
/** Portable type alias for Kysely's selectFrom method signature */
|
|
11
|
-
type TrackedSelectFrom<DB> = Kysely<DB>['selectFrom'];
|
|
12
|
-
import { type MutationTimestampSource } from './fingerprint';
|
|
13
|
-
/**
|
|
14
|
-
* Create a tracked selectFrom that registers scopes and generates fingerprints.
|
|
15
|
-
*/
|
|
16
|
-
export declare function createTrackedSelectFrom<DB>(db: Kysely<DB>, scopeCollector: Set<string>, fingerprintCollector: FingerprintCollector, engine: MutationTimestampSource, keyField?: string): TrackedSelectFrom<DB>;
|
|
17
|
-
export {};
|
|
18
|
-
//# sourceMappingURL=tracked-select.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tracked-select.d.ts","sourceRoot":"","sources":["../../src/query/tracked-select.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE,mEAAmE;AACnE,KAAK,iBAAiB,CAAC,EAAE,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC;AAEtD,OAAO,EAIL,KAAK,uBAAuB,EAC7B,MAAM,eAAe,CAAC;AA4FvB;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EACxC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EACd,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,EAC3B,oBAAoB,EAAE,oBAAoB,EAC1C,MAAM,EAAE,uBAAuB,EAC/B,QAAQ,SAAO,GACd,iBAAiB,CAAC,EAAE,CAAC,CAkBvB"}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @syncular/client - Tracked SelectFrom
|
|
3
|
-
*
|
|
4
|
-
* Provides a wrapped selectFrom that:
|
|
5
|
-
* 1. Tracks tables as watched scopes when called
|
|
6
|
-
* 2. Intercepts .execute() to auto-generate fingerprints
|
|
7
|
-
*/
|
|
8
|
-
import { computeRowFingerprint, computeValueFingerprint, hasKeyField, } from './fingerprint.js';
|
|
9
|
-
function isExecutableQuery(value) {
|
|
10
|
-
if (typeof value !== 'object' || value === null)
|
|
11
|
-
return false;
|
|
12
|
-
return (typeof Reflect.get(value, 'execute') === 'function' &&
|
|
13
|
-
typeof Reflect.get(value, 'executeTakeFirst') === 'function' &&
|
|
14
|
-
typeof Reflect.get(value, 'executeTakeFirstOrThrow') === 'function');
|
|
15
|
-
}
|
|
16
|
-
function createExecuteProxy(builder, table, collector, engine, keyField) {
|
|
17
|
-
return new Proxy(builder, {
|
|
18
|
-
get(target, prop) {
|
|
19
|
-
if (prop === 'execute') {
|
|
20
|
-
return async () => {
|
|
21
|
-
const rows = await target.execute();
|
|
22
|
-
// Auto-detect fingerprint mode based on result shape
|
|
23
|
-
if (Array.isArray(rows)) {
|
|
24
|
-
if (hasKeyField(rows, keyField)) {
|
|
25
|
-
// Row-level fingerprinting - result has keyField
|
|
26
|
-
const fp = computeRowFingerprint(rows, table, engine, keyField);
|
|
27
|
-
collector.add(fp);
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
// Value-based fingerprinting - for aggregates/scalars
|
|
31
|
-
const fp = computeValueFingerprint(table, rows);
|
|
32
|
-
collector.add(fp);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
// Unexpected, but keep rerender behavior deterministic.
|
|
37
|
-
const fp = computeValueFingerprint(table, rows);
|
|
38
|
-
collector.add(fp);
|
|
39
|
-
}
|
|
40
|
-
return rows;
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
if (prop === 'executeTakeFirst') {
|
|
44
|
-
return async () => {
|
|
45
|
-
const row = await target.executeTakeFirst();
|
|
46
|
-
// Value-based fingerprinting for single-row queries
|
|
47
|
-
const fp = computeValueFingerprint(table, row);
|
|
48
|
-
collector.add(fp);
|
|
49
|
-
return row;
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
if (prop === 'executeTakeFirstOrThrow') {
|
|
53
|
-
return async () => {
|
|
54
|
-
const row = await target.executeTakeFirstOrThrow();
|
|
55
|
-
// Value-based fingerprinting for single-row queries
|
|
56
|
-
const fp = computeValueFingerprint(table, row);
|
|
57
|
-
collector.add(fp);
|
|
58
|
-
return row;
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
// For other methods, return wrapped builder for chaining
|
|
62
|
-
const value = Reflect.get(target, prop, target);
|
|
63
|
-
if (typeof value === 'function') {
|
|
64
|
-
return (...args) => {
|
|
65
|
-
const result = Reflect.apply(value, target, args);
|
|
66
|
-
if (isExecutableQuery(result)) {
|
|
67
|
-
return createExecuteProxy(result, table, collector, engine, keyField);
|
|
68
|
-
}
|
|
69
|
-
return result;
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
return value;
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Create a tracked selectFrom that registers scopes and generates fingerprints.
|
|
78
|
-
*/
|
|
79
|
-
export function createTrackedSelectFrom(db, scopeCollector, fingerprintCollector, engine, keyField = 'id') {
|
|
80
|
-
const selectFrom = (table) => {
|
|
81
|
-
// 1. Register this table as a watched scope
|
|
82
|
-
scopeCollector.add(table);
|
|
83
|
-
// 2. Get the real query builder
|
|
84
|
-
const builder = db.selectFrom(table);
|
|
85
|
-
// 3. Return a proxy that intercepts .execute()
|
|
86
|
-
return createExecuteProxy(builder, table, fingerprintCollector, engine, keyField);
|
|
87
|
-
};
|
|
88
|
-
return selectFrom;
|
|
89
|
-
}
|
|
90
|
-
//# sourceMappingURL=tracked-select.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tracked-select.js","sourceRoot":"","sources":["../../src/query/tracked-select.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @syncular/client - Query Context
|
|
3
|
-
*
|
|
4
|
-
* Provides a query context with tracked selectFrom for scope tracking
|
|
5
|
-
* and automatic fingerprint generation.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { Kysely } from 'kysely';
|
|
9
|
-
import type { SyncClientDb } from '../schema';
|
|
10
|
-
import type { FingerprintCollector } from './FingerprintCollector';
|
|
11
|
-
import type { MutationTimestampSource } from './fingerprint';
|
|
12
|
-
import { createTrackedSelectFrom } from './tracked-select';
|
|
13
|
-
|
|
14
|
-
export type TrackedSelectFrom<DB> = ReturnType<
|
|
15
|
-
typeof createTrackedSelectFrom<DB>
|
|
16
|
-
>;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Query context provided to query functions.
|
|
20
|
-
*
|
|
21
|
-
* Only `selectFrom` is exposed to ensure proper scope tracking and fingerprinting.
|
|
22
|
-
* If you need raw database access, use the db directly outside the query function.
|
|
23
|
-
*/
|
|
24
|
-
export interface QueryContext<DB extends SyncClientDb = SyncClientDb> {
|
|
25
|
-
/**
|
|
26
|
-
* Wrapped selectFrom that:
|
|
27
|
-
* 1. Registers table as watched scope
|
|
28
|
-
* 2. Intercepts .execute() to auto-detect fingerprinting mode:
|
|
29
|
-
* - Result has keyField (default: 'id')? -> row-level fingerprinting
|
|
30
|
-
* - No keyField? -> value-based fingerprinting (for aggregates)
|
|
31
|
-
*/
|
|
32
|
-
selectFrom: TrackedSelectFrom<DB>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Create a query context with tracked selectFrom.
|
|
37
|
-
*/
|
|
38
|
-
export function createQueryContext<DB extends SyncClientDb>(
|
|
39
|
-
db: Kysely<DB>,
|
|
40
|
-
scopeCollector: Set<string>,
|
|
41
|
-
fingerprintCollector: FingerprintCollector,
|
|
42
|
-
engine: MutationTimestampSource,
|
|
43
|
-
keyField = 'id'
|
|
44
|
-
): QueryContext<DB> {
|
|
45
|
-
return {
|
|
46
|
-
selectFrom: createTrackedSelectFrom(
|
|
47
|
-
db,
|
|
48
|
-
scopeCollector,
|
|
49
|
-
fingerprintCollector,
|
|
50
|
-
engine,
|
|
51
|
-
keyField
|
|
52
|
-
),
|
|
53
|
-
};
|
|
54
|
-
}
|
package/src/query/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @syncular/client - Query utilities exports
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export { FingerprintCollector } from './FingerprintCollector';
|
|
6
|
-
export {
|
|
7
|
-
canFingerprint,
|
|
8
|
-
computeFingerprint,
|
|
9
|
-
} from './fingerprint';
|
|
10
|
-
export { createQueryContext, type QueryContext } from './QueryContext';
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @syncular/client - Tracked SelectFrom
|
|
3
|
-
*
|
|
4
|
-
* Provides a wrapped selectFrom that:
|
|
5
|
-
* 1. Tracks tables as watched scopes when called
|
|
6
|
-
* 2. Intercepts .execute() to auto-generate fingerprints
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { Kysely } from 'kysely';
|
|
10
|
-
import type { FingerprintCollector } from './FingerprintCollector';
|
|
11
|
-
|
|
12
|
-
/** Portable type alias for Kysely's selectFrom method signature */
|
|
13
|
-
type TrackedSelectFrom<DB> = Kysely<DB>['selectFrom'];
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
computeRowFingerprint,
|
|
17
|
-
computeValueFingerprint,
|
|
18
|
-
hasKeyField,
|
|
19
|
-
type MutationTimestampSource,
|
|
20
|
-
} from './fingerprint';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Create a proxy that intercepts execute() to compute fingerprints.
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
type ExecutableQuery = {
|
|
27
|
-
execute: () => Promise<unknown>;
|
|
28
|
-
executeTakeFirst: () => Promise<unknown>;
|
|
29
|
-
executeTakeFirstOrThrow: () => Promise<unknown>;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
function isExecutableQuery(value: unknown): value is ExecutableQuery {
|
|
33
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
34
|
-
return (
|
|
35
|
-
typeof Reflect.get(value, 'execute') === 'function' &&
|
|
36
|
-
typeof Reflect.get(value, 'executeTakeFirst') === 'function' &&
|
|
37
|
-
typeof Reflect.get(value, 'executeTakeFirstOrThrow') === 'function'
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function createExecuteProxy<B extends ExecutableQuery>(
|
|
42
|
-
builder: B,
|
|
43
|
-
table: string,
|
|
44
|
-
collector: FingerprintCollector,
|
|
45
|
-
engine: MutationTimestampSource,
|
|
46
|
-
keyField: string
|
|
47
|
-
): B {
|
|
48
|
-
return new Proxy(builder, {
|
|
49
|
-
get(target, prop: string | symbol) {
|
|
50
|
-
if (prop === 'execute') {
|
|
51
|
-
return async () => {
|
|
52
|
-
const rows = await target.execute();
|
|
53
|
-
// Auto-detect fingerprint mode based on result shape
|
|
54
|
-
if (Array.isArray(rows)) {
|
|
55
|
-
if (hasKeyField(rows, keyField)) {
|
|
56
|
-
// Row-level fingerprinting - result has keyField
|
|
57
|
-
const fp = computeRowFingerprint(rows, table, engine, keyField);
|
|
58
|
-
collector.add(fp);
|
|
59
|
-
} else {
|
|
60
|
-
// Value-based fingerprinting - for aggregates/scalars
|
|
61
|
-
const fp = computeValueFingerprint(table, rows);
|
|
62
|
-
collector.add(fp);
|
|
63
|
-
}
|
|
64
|
-
} else {
|
|
65
|
-
// Unexpected, but keep rerender behavior deterministic.
|
|
66
|
-
const fp = computeValueFingerprint(table, rows);
|
|
67
|
-
collector.add(fp);
|
|
68
|
-
}
|
|
69
|
-
return rows;
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
if (prop === 'executeTakeFirst') {
|
|
73
|
-
return async () => {
|
|
74
|
-
const row = await target.executeTakeFirst();
|
|
75
|
-
// Value-based fingerprinting for single-row queries
|
|
76
|
-
const fp = computeValueFingerprint(table, row);
|
|
77
|
-
collector.add(fp);
|
|
78
|
-
return row;
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
if (prop === 'executeTakeFirstOrThrow') {
|
|
82
|
-
return async () => {
|
|
83
|
-
const row = await target.executeTakeFirstOrThrow();
|
|
84
|
-
// Value-based fingerprinting for single-row queries
|
|
85
|
-
const fp = computeValueFingerprint(table, row);
|
|
86
|
-
collector.add(fp);
|
|
87
|
-
return row;
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
// For other methods, return wrapped builder for chaining
|
|
91
|
-
const value = Reflect.get(target, prop, target);
|
|
92
|
-
if (typeof value === 'function') {
|
|
93
|
-
return (...args: unknown[]) => {
|
|
94
|
-
const result = Reflect.apply(value, target, args);
|
|
95
|
-
if (isExecutableQuery(result)) {
|
|
96
|
-
return createExecuteProxy(
|
|
97
|
-
result,
|
|
98
|
-
table,
|
|
99
|
-
collector,
|
|
100
|
-
engine,
|
|
101
|
-
keyField
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
return result;
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
return value;
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Create a tracked selectFrom that registers scopes and generates fingerprints.
|
|
114
|
-
*/
|
|
115
|
-
export function createTrackedSelectFrom<DB>(
|
|
116
|
-
db: Kysely<DB>,
|
|
117
|
-
scopeCollector: Set<string>,
|
|
118
|
-
fingerprintCollector: FingerprintCollector,
|
|
119
|
-
engine: MutationTimestampSource,
|
|
120
|
-
keyField = 'id'
|
|
121
|
-
): TrackedSelectFrom<DB> {
|
|
122
|
-
const selectFrom = <TB extends keyof DB & string>(table: TB) => {
|
|
123
|
-
// 1. Register this table as a watched scope
|
|
124
|
-
scopeCollector.add(table);
|
|
125
|
-
|
|
126
|
-
// 2. Get the real query builder
|
|
127
|
-
const builder = db.selectFrom(table);
|
|
128
|
-
|
|
129
|
-
// 3. Return a proxy that intercepts .execute()
|
|
130
|
-
return createExecuteProxy(
|
|
131
|
-
builder,
|
|
132
|
-
table,
|
|
133
|
-
fingerprintCollector,
|
|
134
|
-
engine,
|
|
135
|
-
keyField
|
|
136
|
-
);
|
|
137
|
-
};
|
|
138
|
-
return selectFrom as TrackedSelectFrom<DB>;
|
|
139
|
-
}
|