@git-stunts/git-warp 10.8.0 → 11.3.3
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 +53 -32
- package/SECURITY.md +64 -0
- package/bin/cli/commands/check.js +168 -0
- package/bin/cli/commands/doctor/checks.js +422 -0
- package/bin/cli/commands/doctor/codes.js +46 -0
- package/bin/cli/commands/doctor/index.js +239 -0
- package/bin/cli/commands/doctor/types.js +89 -0
- package/bin/cli/commands/history.js +80 -0
- package/bin/cli/commands/info.js +139 -0
- package/bin/cli/commands/install-hooks.js +128 -0
- package/bin/cli/commands/materialize.js +99 -0
- package/bin/cli/commands/patch.js +142 -0
- package/bin/cli/commands/path.js +88 -0
- package/bin/cli/commands/query.js +235 -0
- package/bin/cli/commands/registry.js +32 -0
- package/bin/cli/commands/seek.js +598 -0
- package/bin/cli/commands/tree.js +230 -0
- package/bin/cli/commands/trust.js +154 -0
- package/bin/cli/commands/verify-audit.js +114 -0
- package/bin/cli/commands/view.js +46 -0
- package/bin/cli/infrastructure.js +350 -0
- package/bin/cli/schemas.js +177 -0
- package/bin/cli/shared.js +244 -0
- package/bin/cli/types.js +96 -0
- package/bin/presenters/index.js +41 -9
- package/bin/presenters/json.js +14 -12
- package/bin/presenters/text.js +286 -28
- package/bin/warp-graph.js +5 -2346
- package/index.d.ts +111 -21
- package/index.js +2 -0
- package/package.json +10 -8
- package/src/domain/WarpGraph.js +109 -3252
- package/src/domain/crdt/ORSet.js +8 -8
- package/src/domain/errors/EmptyMessageError.js +2 -2
- package/src/domain/errors/ForkError.js +1 -1
- package/src/domain/errors/IndexError.js +1 -1
- package/src/domain/errors/OperationAbortedError.js +1 -1
- package/src/domain/errors/QueryError.js +3 -3
- package/src/domain/errors/SchemaUnsupportedError.js +1 -1
- package/src/domain/errors/ShardCorruptionError.js +2 -2
- package/src/domain/errors/ShardLoadError.js +2 -2
- package/src/domain/errors/ShardValidationError.js +4 -4
- package/src/domain/errors/StorageError.js +2 -2
- package/src/domain/errors/SyncError.js +1 -1
- package/src/domain/errors/TraversalError.js +1 -1
- package/src/domain/errors/TrustError.js +29 -0
- package/src/domain/errors/WarpError.js +2 -2
- package/src/domain/errors/WormholeError.js +1 -1
- package/src/domain/errors/index.js +1 -0
- package/src/domain/services/AuditMessageCodec.js +137 -0
- package/src/domain/services/AuditReceiptService.js +471 -0
- package/src/domain/services/AuditVerifierService.js +707 -0
- package/src/domain/services/BitmapIndexBuilder.js +3 -3
- package/src/domain/services/BitmapIndexReader.js +28 -19
- package/src/domain/services/BoundaryTransitionRecord.js +18 -17
- package/src/domain/services/CheckpointSerializerV5.js +17 -16
- package/src/domain/services/CheckpointService.js +2 -2
- package/src/domain/services/CommitDagTraversalService.js +13 -13
- package/src/domain/services/DagPathFinding.js +7 -7
- package/src/domain/services/DagTopology.js +1 -1
- package/src/domain/services/DagTraversal.js +1 -1
- package/src/domain/services/HealthCheckService.js +1 -1
- package/src/domain/services/HookInstaller.js +1 -1
- package/src/domain/services/HttpSyncServer.js +120 -55
- package/src/domain/services/IndexRebuildService.js +7 -7
- package/src/domain/services/IndexStalenessChecker.js +4 -3
- package/src/domain/services/JoinReducer.js +11 -11
- package/src/domain/services/LogicalTraversal.js +1 -1
- package/src/domain/services/MessageCodecInternal.js +4 -1
- package/src/domain/services/MessageSchemaDetector.js +2 -2
- package/src/domain/services/MigrationService.js +1 -1
- package/src/domain/services/ObserverView.js +8 -8
- package/src/domain/services/PatchBuilderV2.js +42 -26
- package/src/domain/services/ProvenanceIndex.js +1 -1
- package/src/domain/services/ProvenancePayload.js +1 -1
- package/src/domain/services/QueryBuilder.js +3 -3
- package/src/domain/services/StateDiff.js +14 -11
- package/src/domain/services/StateSerializerV5.js +2 -2
- package/src/domain/services/StreamingBitmapIndexBuilder.js +26 -24
- package/src/domain/services/SyncAuthService.js +71 -4
- package/src/domain/services/SyncProtocol.js +25 -11
- package/src/domain/services/TemporalQuery.js +9 -6
- package/src/domain/services/TranslationCost.js +7 -5
- package/src/domain/services/WarpMessageCodec.js +4 -1
- package/src/domain/services/WormholeService.js +16 -7
- package/src/domain/trust/TrustCanonical.js +42 -0
- package/src/domain/trust/TrustCrypto.js +111 -0
- package/src/domain/trust/TrustEvaluator.js +195 -0
- package/src/domain/trust/TrustRecordService.js +281 -0
- package/src/domain/trust/TrustStateBuilder.js +222 -0
- package/src/domain/trust/canonical.js +68 -0
- package/src/domain/trust/reasonCodes.js +64 -0
- package/src/domain/trust/schemas.js +160 -0
- package/src/domain/trust/verdict.js +42 -0
- package/src/domain/types/TickReceipt.js +1 -1
- package/src/domain/types/WarpErrors.js +45 -0
- package/src/domain/types/WarpOptions.js +29 -0
- package/src/domain/types/WarpPersistence.js +41 -0
- package/src/domain/types/WarpTypes.js +2 -2
- package/src/domain/types/WarpTypesV2.js +2 -2
- package/src/domain/types/git-cas.d.ts +20 -0
- package/src/domain/utils/MinHeap.js +6 -5
- package/src/domain/utils/RefLayout.js +59 -0
- package/src/domain/utils/canonicalStringify.js +5 -4
- package/src/domain/utils/roaring.js +31 -5
- package/src/domain/warp/PatchSession.js +26 -17
- package/src/domain/warp/Writer.js +18 -3
- package/src/domain/warp/_internal.js +26 -0
- package/src/domain/warp/_wire.js +58 -0
- package/src/domain/warp/_wiredMethods.d.ts +254 -0
- package/src/domain/warp/checkpoint.methods.js +401 -0
- package/src/domain/warp/fork.methods.js +323 -0
- package/src/domain/warp/materialize.methods.js +238 -0
- package/src/domain/warp/materializeAdvanced.methods.js +350 -0
- package/src/domain/warp/patch.methods.js +554 -0
- package/src/domain/warp/provenance.methods.js +286 -0
- package/src/domain/warp/query.methods.js +280 -0
- package/src/domain/warp/subscribe.methods.js +272 -0
- package/src/domain/warp/sync.methods.js +554 -0
- package/src/globals.d.ts +64 -0
- package/src/infrastructure/adapters/BunHttpAdapter.js +14 -9
- package/src/infrastructure/adapters/CasSeekCacheAdapter.js +9 -4
- package/src/infrastructure/adapters/DenoHttpAdapter.js +5 -6
- package/src/infrastructure/adapters/GitGraphAdapter.js +79 -11
- package/src/infrastructure/adapters/InMemoryGraphAdapter.js +36 -0
- package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
- package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
- package/src/ports/CommitPort.js +10 -0
- package/src/ports/RefPort.js +17 -0
- package/src/visualization/layouts/converters.js +2 -2
- package/src/visualization/layouts/elkAdapter.js +1 -1
- package/src/visualization/layouts/elkLayout.js +10 -7
- package/src/visualization/layouts/index.js +1 -1
- package/src/visualization/renderers/ascii/seek.js +16 -6
- package/src/visualization/renderers/svg/index.js +1 -1
- package/src/hooks/post-merge.sh +0 -60
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module domain/warp/subscribe.methods
|
|
3
|
+
*
|
|
4
|
+
* Extracted subscribe, watch, and _notifySubscribers methods from WarpGraph.
|
|
5
|
+
* Each function is bound to a WarpGraph instance at runtime via `this`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { diffStates, isEmptyDiff } from '../services/StateDiff.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Subscribes to graph changes.
|
|
12
|
+
*
|
|
13
|
+
* The `onChange` handler is called after each `materialize()` that results in
|
|
14
|
+
* state changes. The handler receives a diff object describing what changed.
|
|
15
|
+
*
|
|
16
|
+
* When `replay: true` is set and `_cachedState` is available, immediately
|
|
17
|
+
* fires `onChange` with a diff from empty state to current state. If
|
|
18
|
+
* `_cachedState` is null, replay is deferred until the first materialize.
|
|
19
|
+
*
|
|
20
|
+
* Errors thrown by handlers are caught and forwarded to `onError` if provided.
|
|
21
|
+
* One handler's error does not prevent other handlers from being called.
|
|
22
|
+
*
|
|
23
|
+
* @this {import('../WarpGraph.js').default}
|
|
24
|
+
* @param {Object} options - Subscription options
|
|
25
|
+
* @param {(diff: import('../services/StateDiff.js').StateDiffResult) => void} options.onChange - Called with diff when graph changes
|
|
26
|
+
* @param {(error: Error) => void} [options.onError] - Called if onChange throws an error
|
|
27
|
+
* @param {boolean} [options.replay=false] - If true, immediately fires onChange with initial state diff
|
|
28
|
+
* @returns {{unsubscribe: () => void}} Subscription handle
|
|
29
|
+
* @throws {Error} If onChange is not a function
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const { unsubscribe } = graph.subscribe({
|
|
33
|
+
* onChange: (diff) => {
|
|
34
|
+
* console.log('Nodes added:', diff.nodes.added);
|
|
35
|
+
* console.log('Nodes removed:', diff.nodes.removed);
|
|
36
|
+
* },
|
|
37
|
+
* onError: (err) => console.error('Handler error:', err),
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* // Later, to stop receiving updates:
|
|
41
|
+
* unsubscribe();
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* // With replay: get initial state immediately
|
|
45
|
+
* await graph.materialize();
|
|
46
|
+
* graph.subscribe({
|
|
47
|
+
* onChange: (diff) => console.log('Initial or changed:', diff),
|
|
48
|
+
* replay: true, // Immediately fires with current state as additions
|
|
49
|
+
* });
|
|
50
|
+
*/
|
|
51
|
+
export function subscribe({ onChange, onError, replay = false }) {
|
|
52
|
+
if (typeof onChange !== 'function') {
|
|
53
|
+
throw new Error('onChange must be a function');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const subscriber = { onChange, onError, pendingReplay: replay && !this._cachedState };
|
|
57
|
+
this._subscribers.push(subscriber);
|
|
58
|
+
|
|
59
|
+
// Immediate replay if requested and cached state is available
|
|
60
|
+
if (replay && this._cachedState) {
|
|
61
|
+
const diff = diffStates(null, this._cachedState);
|
|
62
|
+
if (!isEmptyDiff(diff)) {
|
|
63
|
+
try {
|
|
64
|
+
onChange(diff);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (onError) {
|
|
67
|
+
try {
|
|
68
|
+
onError(/** @type {Error} */ (err));
|
|
69
|
+
} catch {
|
|
70
|
+
// onError itself threw — swallow to prevent cascade
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
unsubscribe: () => {
|
|
79
|
+
const index = this._subscribers.indexOf(subscriber);
|
|
80
|
+
if (index !== -1) {
|
|
81
|
+
this._subscribers.splice(index, 1);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Watches for graph changes matching a pattern.
|
|
89
|
+
*
|
|
90
|
+
* Like `subscribe()`, but only fires for changes where node IDs match the
|
|
91
|
+
* provided glob pattern. Uses the same pattern syntax as `query().match()`.
|
|
92
|
+
*
|
|
93
|
+
* - Nodes: filters `added` and `removed` to matching IDs
|
|
94
|
+
* - Edges: filters to edges where `from` or `to` matches the pattern
|
|
95
|
+
* - Props: filters to properties where `nodeId` matches the pattern
|
|
96
|
+
*
|
|
97
|
+
* If all changes are filtered out, the handler is not called.
|
|
98
|
+
*
|
|
99
|
+
* When `poll` is set, periodically checks `hasFrontierChanged()` and auto-materializes
|
|
100
|
+
* if the frontier has changed (e.g., remote writes detected). The poll interval must
|
|
101
|
+
* be at least 1000ms.
|
|
102
|
+
*
|
|
103
|
+
* @this {import('../WarpGraph.js').default}
|
|
104
|
+
* @param {string} pattern - Glob pattern (e.g., 'user:*', 'order:123', '*')
|
|
105
|
+
* @param {Object} options - Watch options
|
|
106
|
+
* @param {(diff: import('../services/StateDiff.js').StateDiffResult) => void} options.onChange - Called with filtered diff when matching changes occur
|
|
107
|
+
* @param {(error: Error) => void} [options.onError] - Called if onChange throws an error
|
|
108
|
+
* @param {number} [options.poll] - Poll interval in ms (min 1000); checks frontier and auto-materializes
|
|
109
|
+
* @returns {{unsubscribe: () => void}} Subscription handle
|
|
110
|
+
* @throws {Error} If pattern is not a string
|
|
111
|
+
* @throws {Error} If onChange is not a function
|
|
112
|
+
* @throws {Error} If poll is provided but less than 1000
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* const { unsubscribe } = graph.watch('user:*', {
|
|
116
|
+
* onChange: (diff) => {
|
|
117
|
+
* // Only user node changes arrive here
|
|
118
|
+
* console.log('User nodes added:', diff.nodes.added);
|
|
119
|
+
* },
|
|
120
|
+
* });
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* // With polling: checks every 5s for remote changes
|
|
124
|
+
* const { unsubscribe } = graph.watch('user:*', {
|
|
125
|
+
* onChange: (diff) => console.log('User changed:', diff),
|
|
126
|
+
* poll: 5000,
|
|
127
|
+
* });
|
|
128
|
+
*
|
|
129
|
+
* // Later, to stop receiving updates:
|
|
130
|
+
* unsubscribe();
|
|
131
|
+
*/
|
|
132
|
+
export function watch(pattern, { onChange, onError, poll }) {
|
|
133
|
+
if (typeof pattern !== 'string') {
|
|
134
|
+
throw new Error('pattern must be a string');
|
|
135
|
+
}
|
|
136
|
+
if (typeof onChange !== 'function') {
|
|
137
|
+
throw new Error('onChange must be a function');
|
|
138
|
+
}
|
|
139
|
+
if (poll !== undefined) {
|
|
140
|
+
if (typeof poll !== 'number' || poll < 1000) {
|
|
141
|
+
throw new Error('poll must be a number >= 1000');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Pattern matching: same logic as QueryBuilder.match()
|
|
146
|
+
// Pre-compile pattern matcher once for performance
|
|
147
|
+
/** @type {(nodeId: string) => boolean} */
|
|
148
|
+
let matchesPattern;
|
|
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
|
+
}
|
|
158
|
+
|
|
159
|
+
// Filtered onChange that only passes matching changes
|
|
160
|
+
const filteredOnChange = (/** @type {import('../services/StateDiff.js').StateDiffResult} */ diff) => {
|
|
161
|
+
const filteredDiff = {
|
|
162
|
+
nodes: {
|
|
163
|
+
added: diff.nodes.added.filter(matchesPattern),
|
|
164
|
+
removed: diff.nodes.removed.filter(matchesPattern),
|
|
165
|
+
},
|
|
166
|
+
edges: {
|
|
167
|
+
added: diff.edges.added.filter((/** @type {import('../services/StateDiff.js').EdgeChange} */ e) => matchesPattern(e.from) || matchesPattern(e.to)),
|
|
168
|
+
removed: diff.edges.removed.filter((/** @type {import('../services/StateDiff.js').EdgeChange} */ e) => matchesPattern(e.from) || matchesPattern(e.to)),
|
|
169
|
+
},
|
|
170
|
+
props: {
|
|
171
|
+
set: diff.props.set.filter((/** @type {import('../services/StateDiff.js').PropSet} */ p) => matchesPattern(p.nodeId)),
|
|
172
|
+
removed: diff.props.removed.filter((/** @type {import('../services/StateDiff.js').PropRemoved} */ p) => matchesPattern(p.nodeId)),
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Only call handler if there are matching changes
|
|
177
|
+
const hasChanges =
|
|
178
|
+
filteredDiff.nodes.added.length > 0 ||
|
|
179
|
+
filteredDiff.nodes.removed.length > 0 ||
|
|
180
|
+
filteredDiff.edges.added.length > 0 ||
|
|
181
|
+
filteredDiff.edges.removed.length > 0 ||
|
|
182
|
+
filteredDiff.props.set.length > 0 ||
|
|
183
|
+
filteredDiff.props.removed.length > 0;
|
|
184
|
+
|
|
185
|
+
if (hasChanges) {
|
|
186
|
+
onChange(filteredDiff);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Reuse subscription infrastructure
|
|
191
|
+
const subscription = this.subscribe({ onChange: filteredOnChange, onError });
|
|
192
|
+
|
|
193
|
+
// Polling: periodically check frontier and auto-materialize if changed
|
|
194
|
+
/** @type {ReturnType<typeof setInterval>|null} */
|
|
195
|
+
let pollIntervalId = null;
|
|
196
|
+
let pollInFlight = false;
|
|
197
|
+
if (poll) {
|
|
198
|
+
pollIntervalId = setInterval(() => {
|
|
199
|
+
if (pollInFlight) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
pollInFlight = true;
|
|
203
|
+
this.hasFrontierChanged()
|
|
204
|
+
.then(async (changed) => {
|
|
205
|
+
if (changed) {
|
|
206
|
+
await this.materialize();
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
.catch((err) => {
|
|
210
|
+
if (onError) {
|
|
211
|
+
try {
|
|
212
|
+
onError(err);
|
|
213
|
+
} catch {
|
|
214
|
+
// onError itself threw — swallow to prevent cascade
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
.finally(() => {
|
|
219
|
+
pollInFlight = false;
|
|
220
|
+
});
|
|
221
|
+
}, poll);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
unsubscribe: () => {
|
|
226
|
+
if (pollIntervalId !== null) {
|
|
227
|
+
clearInterval(pollIntervalId);
|
|
228
|
+
pollIntervalId = null;
|
|
229
|
+
}
|
|
230
|
+
subscription.unsubscribe();
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Notifies all subscribers of state changes.
|
|
237
|
+
* Handles deferred replay for subscribers added with `replay: true` before
|
|
238
|
+
* cached state was available.
|
|
239
|
+
*
|
|
240
|
+
* @this {import('../WarpGraph.js').default}
|
|
241
|
+
* @param {import('../services/StateDiff.js').StateDiffResult} diff
|
|
242
|
+
* @param {import('../services/JoinReducer.js').WarpStateV5} currentState - The current state for deferred replay
|
|
243
|
+
* @private
|
|
244
|
+
*/
|
|
245
|
+
export function _notifySubscribers(diff, currentState) {
|
|
246
|
+
for (const subscriber of [...this._subscribers]) {
|
|
247
|
+
try {
|
|
248
|
+
// Handle deferred replay: on first notification, send full state diff instead
|
|
249
|
+
if (subscriber.pendingReplay) {
|
|
250
|
+
subscriber.pendingReplay = false;
|
|
251
|
+
const replayDiff = diffStates(null, currentState);
|
|
252
|
+
if (!isEmptyDiff(replayDiff)) {
|
|
253
|
+
subscriber.onChange(replayDiff);
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
// Skip non-replay subscribers when diff is empty
|
|
257
|
+
if (isEmptyDiff(diff)) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
subscriber.onChange(diff);
|
|
261
|
+
}
|
|
262
|
+
} catch (err) {
|
|
263
|
+
if (subscriber.onError) {
|
|
264
|
+
try {
|
|
265
|
+
subscriber.onError(err);
|
|
266
|
+
} catch {
|
|
267
|
+
// onError itself threw — swallow to prevent cascade
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|