@git-stunts/git-warp 12.0.0 → 12.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.
- package/README.md +6 -9
- package/bin/warp-graph.js +6 -2
- package/index.d.ts +4 -4
- package/package.json +2 -1
- package/src/domain/WarpGraph.js +3 -0
- package/src/domain/crdt/ORSet.js +33 -4
- package/src/domain/errors/SyncError.js +1 -0
- package/src/domain/errors/TrustError.js +2 -0
- package/src/domain/services/CheckpointService.js +2 -7
- package/src/domain/services/Frontier.js +18 -0
- package/src/domain/services/GraphTraversal.js +8 -49
- package/src/domain/services/HttpSyncServer.js +18 -29
- package/src/domain/services/JoinReducer.js +23 -0
- package/src/domain/services/ObserverView.js +4 -32
- package/src/domain/services/PatchBuilderV2.js +29 -3
- package/src/domain/services/QueryBuilder.js +78 -74
- package/src/domain/services/SyncController.js +74 -11
- package/src/domain/services/SyncPayloadSchema.js +236 -0
- package/src/domain/services/SyncProtocol.js +27 -8
- package/src/domain/services/SyncTrustGate.js +146 -0
- package/src/domain/services/TranslationCost.js +8 -24
- package/src/domain/trust/TrustRecordService.js +119 -6
- package/src/domain/utils/matchGlob.js +51 -0
- package/src/domain/warp/Writer.js +7 -5
- package/src/domain/warp/checkpoint.methods.js +66 -9
- package/src/domain/warp/materialize.methods.js +3 -0
- package/src/domain/warp/materializeAdvanced.methods.js +2 -0
- package/src/domain/warp/patch.methods.js +8 -0
- package/src/domain/warp/query.methods.js +7 -5
- package/src/domain/warp/subscribe.methods.js +11 -19
- package/src/infrastructure/adapters/GitGraphAdapter.js +2 -2
|
@@ -530,6 +530,14 @@ export function join(otherState) {
|
|
|
530
530
|
// Update cached state
|
|
531
531
|
this._cachedState = mergedState;
|
|
532
532
|
|
|
533
|
+
// Invalidate derived caches (C1) — join changes underlying state
|
|
534
|
+
this._materializedGraph = null;
|
|
535
|
+
this._logicalIndex = null;
|
|
536
|
+
this._propertyReader = null;
|
|
537
|
+
this._cachedViewHash = null;
|
|
538
|
+
this._cachedIndexTree = null;
|
|
539
|
+
this._stateDirty = true;
|
|
540
|
+
|
|
533
541
|
return { state: mergedState, receipt };
|
|
534
542
|
}
|
|
535
543
|
|
|
@@ -312,14 +312,16 @@ export function query() {
|
|
|
312
312
|
* @this {import('../WarpGraph.js').default}
|
|
313
313
|
* @param {string} name - Observer name
|
|
314
314
|
* @param {Object} config - Observer configuration
|
|
315
|
-
* @param {string} config.match - Glob pattern for visible nodes
|
|
315
|
+
* @param {string|string[]} config.match - Glob pattern(s) for visible nodes
|
|
316
316
|
* @param {string[]} [config.expose] - Property keys to include
|
|
317
317
|
* @param {string[]} [config.redact] - Property keys to exclude
|
|
318
318
|
* @returns {Promise<import('../services/ObserverView.js').default>} A read-only observer view
|
|
319
319
|
*/
|
|
320
320
|
export async function observer(name, config) {
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
/** @param {unknown} m */
|
|
322
|
+
const isValidMatch = (m) => typeof m === 'string' || (Array.isArray(m) && m.length > 0 && m.every(/** @param {unknown} i */ i => typeof i === 'string'));
|
|
323
|
+
if (!config || !isValidMatch(config.match)) {
|
|
324
|
+
throw new Error('observer config.match must be a non-empty string or non-empty array of strings');
|
|
323
325
|
}
|
|
324
326
|
await this._ensureFreshState();
|
|
325
327
|
return new ObserverView({ name, config, graph: this });
|
|
@@ -330,11 +332,11 @@ export async function observer(name, config) {
|
|
|
330
332
|
*
|
|
331
333
|
* @this {import('../WarpGraph.js').default}
|
|
332
334
|
* @param {Object} configA - Observer configuration for A
|
|
333
|
-
* @param {string} configA.match - Glob pattern for visible nodes
|
|
335
|
+
* @param {string|string[]} configA.match - Glob pattern(s) for visible nodes
|
|
334
336
|
* @param {string[]} [configA.expose] - Property keys to include
|
|
335
337
|
* @param {string[]} [configA.redact] - Property keys to exclude
|
|
336
338
|
* @param {Object} configB - Observer configuration for B
|
|
337
|
-
* @param {string} configB.match - Glob pattern for visible nodes
|
|
339
|
+
* @param {string|string[]} configB.match - Glob pattern(s) for visible nodes
|
|
338
340
|
* @param {string[]} [configB.expose] - Property keys to include
|
|
339
341
|
* @param {string[]} [configB.redact] - Property keys to exclude
|
|
340
342
|
* @returns {Promise<{cost: number, breakdown: {nodeLoss: number, edgeLoss: number, propLoss: number}}>}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { diffStates, isEmptyDiff } from '../services/StateDiff.js';
|
|
9
|
+
import { matchGlob } from '../utils/matchGlob.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Subscribes to graph changes.
|
|
@@ -101,13 +102,13 @@ export function subscribe({ onChange, onError, replay = false }) {
|
|
|
101
102
|
* be at least 1000ms.
|
|
102
103
|
*
|
|
103
104
|
* @this {import('../WarpGraph.js').default}
|
|
104
|
-
* @param {string} pattern - Glob pattern (e.g., 'user:*', 'order:123', '*')
|
|
105
|
+
* @param {string|string[]} pattern - Glob pattern(s) (e.g., 'user:*', 'order:123', '*')
|
|
105
106
|
* @param {Object} options - Watch options
|
|
106
107
|
* @param {(diff: import('../services/StateDiff.js').StateDiffResult) => void} options.onChange - Called with filtered diff when matching changes occur
|
|
107
108
|
* @param {(error: Error) => void} [options.onError] - Called if onChange throws an error
|
|
108
109
|
* @param {number} [options.poll] - Poll interval in ms (min 1000); checks frontier and auto-materializes
|
|
109
110
|
* @returns {{unsubscribe: () => void}} Subscription handle
|
|
110
|
-
* @throws {Error} If pattern is not a string
|
|
111
|
+
* @throws {Error} If pattern is not a string or array of strings
|
|
111
112
|
* @throws {Error} If onChange is not a function
|
|
112
113
|
* @throws {Error} If poll is provided but less than 1000
|
|
113
114
|
*
|
|
@@ -130,31 +131,22 @@ export function subscribe({ onChange, onError, replay = false }) {
|
|
|
130
131
|
* unsubscribe();
|
|
131
132
|
*/
|
|
132
133
|
export function watch(pattern, { onChange, onError, poll }) {
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
const isValidPattern = (/** @type {string|string[]} */ p) => typeof p === 'string' || (Array.isArray(p) && p.length > 0 && p.every(i => typeof i === 'string'));
|
|
135
|
+
if (!isValidPattern(pattern)) {
|
|
136
|
+
throw new Error('pattern must be a non-empty string or non-empty array of strings');
|
|
135
137
|
}
|
|
136
138
|
if (typeof onChange !== 'function') {
|
|
137
139
|
throw new Error('onChange must be a function');
|
|
138
140
|
}
|
|
139
141
|
if (poll !== undefined) {
|
|
140
|
-
if (typeof poll !== 'number' || poll < 1000) {
|
|
141
|
-
throw new Error('poll must be a number >= 1000');
|
|
142
|
+
if (typeof poll !== 'number' || !Number.isFinite(poll) || poll < 1000) {
|
|
143
|
+
throw new Error('poll must be a finite number >= 1000');
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
|
|
145
|
-
// Pattern matching
|
|
146
|
-
// Pre-compile pattern matcher once for performance
|
|
147
|
+
// Pattern matching logic
|
|
147
148
|
/** @type {(nodeId: string) => boolean} */
|
|
148
|
-
|
|
149
|
-
if (pattern === '*') {
|
|
150
|
-
matchesPattern = () => true;
|
|
151
|
-
} else if (pattern.includes('*')) {
|
|
152
|
-
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
153
|
-
const regex = new RegExp(`^${escaped.replace(/\*/g, '.*')}$`);
|
|
154
|
-
matchesPattern = (/** @type {string} */ nodeId) => regex.test(nodeId);
|
|
155
|
-
} else {
|
|
156
|
-
matchesPattern = (/** @type {string} */ nodeId) => nodeId === pattern;
|
|
157
|
-
}
|
|
149
|
+
const matchesPattern = (nodeId) => matchGlob(pattern, nodeId);
|
|
158
150
|
|
|
159
151
|
// Filtered onChange that only passes matching changes
|
|
160
152
|
const filteredOnChange = (/** @type {import('../services/StateDiff.js').StateDiffResult} */ diff) => {
|
|
@@ -194,7 +186,7 @@ export function watch(pattern, { onChange, onError, poll }) {
|
|
|
194
186
|
/** @type {ReturnType<typeof setInterval>|null} */
|
|
195
187
|
let pollIntervalId = null;
|
|
196
188
|
let pollInFlight = false;
|
|
197
|
-
if (poll) {
|
|
189
|
+
if (poll !== undefined) {
|
|
198
190
|
pollIntervalId = setInterval(() => {
|
|
199
191
|
if (pollInFlight) {
|
|
200
192
|
return;
|
|
@@ -575,8 +575,8 @@ export default class GitGraphAdapter extends GraphPersistencePort {
|
|
|
575
575
|
async compareAndSwapRef(ref, newOid, expectedOid) {
|
|
576
576
|
this._validateRef(ref);
|
|
577
577
|
this._validateOid(newOid);
|
|
578
|
-
// null means "ref must not exist" → use zero OID
|
|
579
|
-
const oldArg = expectedOid || '0'.repeat(
|
|
578
|
+
// null means "ref must not exist" → use zero OID (always 40 chars for SHA-1)
|
|
579
|
+
const oldArg = expectedOid || '0'.repeat(40);
|
|
580
580
|
if (expectedOid) {
|
|
581
581
|
this._validateOid(expectedOid);
|
|
582
582
|
}
|