isomorphic-jj 1.0.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/LICENSE +21 -0
- package/README.md +971 -0
- package/dist/index.cjs +12609 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +12600 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +90 -0
- package/src/api/repository.js +6885 -0
- package/src/backend/isomorphic-git-backend.js +687 -0
- package/src/backend/lazy-git-backend.js +180 -0
- package/src/browser/helpers.js +189 -0
- package/src/browser/index.d.ts +174 -0
- package/src/browser/index.js +12 -0
- package/src/core/background-ops.js +272 -0
- package/src/core/bookmark-store.js +232 -0
- package/src/core/change-graph.js +285 -0
- package/src/core/conflict-model.js +505 -0
- package/src/core/jj-checkout.js +78 -0
- package/src/core/jj-operation-store.js +120 -0
- package/src/core/jj-tree-state.js +94 -0
- package/src/core/jj-view-store.js +105 -0
- package/src/core/merge-driver-registry.js +432 -0
- package/src/core/operation-log.js +272 -0
- package/src/core/revset-engine.js +1081 -0
- package/src/core/storage-manager.js +207 -0
- package/src/core/tag-store.js +167 -0
- package/src/core/user-config.js +142 -0
- package/src/core/working-copy.js +314 -0
- package/src/core/workspace-manager.js +283 -0
- package/src/drivers/built-in-drivers.js +296 -0
- package/src/index.js +15 -0
- package/src/protos/local_working_copy.proto +76 -0
- package/src/protos/simple_op_store.proto +147 -0
- package/src/types.d.ts +1129 -0
- package/src/utils/errors.js +21 -0
- package/src/utils/id-generation.js +54 -0
- package/src/utils/validation.js +124 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JJError - Custom error class for isomorphic-jj
|
|
3
|
+
*
|
|
4
|
+
* Provides structured errors with error codes, context, and user-actionable suggestions.
|
|
5
|
+
*/
|
|
6
|
+
export class JJError extends Error {
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} code - Error code (e.g., 'CHANGE_NOT_FOUND')
|
|
9
|
+
* @param {string} message - Human-readable error message
|
|
10
|
+
* @param {Object} [context={}] - Additional error context
|
|
11
|
+
* @param {string} [context.suggestion] - User-actionable suggestion
|
|
12
|
+
*/
|
|
13
|
+
constructor(code, message, context = {}) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'JJError';
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.context = context;
|
|
18
|
+
this.details = context; // Alias for better ergonomics
|
|
19
|
+
this.suggestion = context.suggestion;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ID Generation utilities for isomorphic-jj
|
|
3
|
+
*
|
|
4
|
+
* Generates stable change IDs and operation IDs using cryptographically secure random values.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate a stable change ID
|
|
9
|
+
*
|
|
10
|
+
* Uses 128-bit cryptographically random value encoded as 32-character hex string.
|
|
11
|
+
* Collision probability is negligible (2^128 space).
|
|
12
|
+
*
|
|
13
|
+
* @returns {string} 32-character lowercase hex string
|
|
14
|
+
*/
|
|
15
|
+
export function generateChangeId() {
|
|
16
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
17
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generate an operation ID from operation content
|
|
22
|
+
*
|
|
23
|
+
* Uses SHA-256 hash of operation content for integrity verification.
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} operation - Operation data
|
|
26
|
+
* @returns {Promise<string>} 64-character lowercase hex string (SHA-256)
|
|
27
|
+
*/
|
|
28
|
+
export async function generateOperationId(operation) {
|
|
29
|
+
const content = JSON.stringify({
|
|
30
|
+
timestamp: operation.timestamp,
|
|
31
|
+
user: operation.user,
|
|
32
|
+
description: operation.description,
|
|
33
|
+
parents: operation.parents,
|
|
34
|
+
view: operation.view,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const encoder = new TextEncoder();
|
|
38
|
+
const data = encoder.encode(content);
|
|
39
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
40
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
41
|
+
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generate a generic ID with optional prefix
|
|
46
|
+
*
|
|
47
|
+
* @param {string} prefix - Optional prefix (e.g., 'conflict', 'bookmark')
|
|
48
|
+
* @returns {string} ID string
|
|
49
|
+
*/
|
|
50
|
+
export function generateId(prefix = '') {
|
|
51
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
52
|
+
const id = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
53
|
+
return prefix ? `${prefix}-${id}` : id;
|
|
54
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation utilities for isomorphic-jj
|
|
3
|
+
*
|
|
4
|
+
* Provides validation functions for change IDs, paths, bookmark names, etc.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { JJError } from './errors.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validate a change ID
|
|
11
|
+
*
|
|
12
|
+
* @param {string} changeId - Change ID to validate
|
|
13
|
+
* @throws {JJError} If change ID is invalid
|
|
14
|
+
*/
|
|
15
|
+
export function validateChangeId(changeId) {
|
|
16
|
+
if (typeof changeId !== 'string') {
|
|
17
|
+
throw new JJError('INVALID_CHANGE_ID', 'Change ID must be a string', {
|
|
18
|
+
changeId,
|
|
19
|
+
suggestion: 'Provide a valid 32-character hex string',
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!/^[0-9a-f]{32}$/.test(changeId)) {
|
|
24
|
+
throw new JJError(
|
|
25
|
+
'INVALID_CHANGE_ID',
|
|
26
|
+
`Change ID must be 32 hexadecimal characters, got "${changeId}"`,
|
|
27
|
+
{
|
|
28
|
+
changeId,
|
|
29
|
+
suggestion: 'Use a valid change ID from log() output or use a revset expression',
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validate a file path
|
|
37
|
+
*
|
|
38
|
+
* @param {string} path - File path to validate
|
|
39
|
+
* @throws {JJError} If path is invalid or contains path traversal
|
|
40
|
+
*/
|
|
41
|
+
export function validatePath(path) {
|
|
42
|
+
if (typeof path !== 'string') {
|
|
43
|
+
throw new JJError('INVALID_PATH', 'Path must be a string', {
|
|
44
|
+
path,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (path.includes('..')) {
|
|
49
|
+
throw new JJError('INVALID_PATH', 'Path traversal (..) not allowed', {
|
|
50
|
+
path,
|
|
51
|
+
suggestion: 'Use relative paths without .. components',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (path.startsWith('/')) {
|
|
56
|
+
throw new JJError('INVALID_PATH', 'Absolute paths not allowed', {
|
|
57
|
+
path,
|
|
58
|
+
suggestion: 'Use relative paths from repository root',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (path.length > 4096) {
|
|
63
|
+
throw new JJError('INVALID_PATH', 'Path exceeds maximum length', {
|
|
64
|
+
path,
|
|
65
|
+
maxLength: 4096,
|
|
66
|
+
suggestion: 'Use shorter file paths',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Validate a bookmark name (follows Git ref name rules)
|
|
73
|
+
*
|
|
74
|
+
* @param {string} name - Bookmark name to validate
|
|
75
|
+
* @throws {JJError} If bookmark name is invalid
|
|
76
|
+
*/
|
|
77
|
+
export function validateBookmarkName(name) {
|
|
78
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
79
|
+
throw new JJError('INVALID_BOOKMARK_NAME', 'Bookmark name must be a non-empty string', {
|
|
80
|
+
name,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Git ref name validation rules
|
|
85
|
+
const invalidChars = /[\s*~^:?[\]\\]/;
|
|
86
|
+
if (invalidChars.test(name)) {
|
|
87
|
+
throw new JJError(
|
|
88
|
+
'INVALID_BOOKMARK_NAME',
|
|
89
|
+
'Bookmark name contains invalid characters (spaces, *, ~, ^, :, ?, [, ], \\)',
|
|
90
|
+
{
|
|
91
|
+
name,
|
|
92
|
+
suggestion: 'Use alphanumeric characters, hyphens, and forward slashes',
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (name.includes('..')) {
|
|
98
|
+
throw new JJError('INVALID_BOOKMARK_NAME', 'Bookmark name cannot contain ".."', {
|
|
99
|
+
name,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (name.startsWith('.') || name.endsWith('.')) {
|
|
104
|
+
throw new JJError(
|
|
105
|
+
'INVALID_BOOKMARK_NAME',
|
|
106
|
+
'Bookmark name cannot start or end with "."',
|
|
107
|
+
{
|
|
108
|
+
name,
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (name.endsWith('.lock')) {
|
|
114
|
+
throw new JJError('INVALID_BOOKMARK_NAME', 'Bookmark name cannot end with ".lock"', {
|
|
115
|
+
name,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (name.includes('//')) {
|
|
120
|
+
throw new JJError('INVALID_BOOKMARK_NAME', 'Bookmark name cannot contain "//"', {
|
|
121
|
+
name,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|