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.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +971 -0
  3. package/dist/index.cjs +12609 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.mjs +12600 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/package.json +90 -0
  8. package/src/api/repository.js +6885 -0
  9. package/src/backend/isomorphic-git-backend.js +687 -0
  10. package/src/backend/lazy-git-backend.js +180 -0
  11. package/src/browser/helpers.js +189 -0
  12. package/src/browser/index.d.ts +174 -0
  13. package/src/browser/index.js +12 -0
  14. package/src/core/background-ops.js +272 -0
  15. package/src/core/bookmark-store.js +232 -0
  16. package/src/core/change-graph.js +285 -0
  17. package/src/core/conflict-model.js +505 -0
  18. package/src/core/jj-checkout.js +78 -0
  19. package/src/core/jj-operation-store.js +120 -0
  20. package/src/core/jj-tree-state.js +94 -0
  21. package/src/core/jj-view-store.js +105 -0
  22. package/src/core/merge-driver-registry.js +432 -0
  23. package/src/core/operation-log.js +272 -0
  24. package/src/core/revset-engine.js +1081 -0
  25. package/src/core/storage-manager.js +207 -0
  26. package/src/core/tag-store.js +167 -0
  27. package/src/core/user-config.js +142 -0
  28. package/src/core/working-copy.js +314 -0
  29. package/src/core/workspace-manager.js +283 -0
  30. package/src/drivers/built-in-drivers.js +296 -0
  31. package/src/index.js +15 -0
  32. package/src/protos/local_working_copy.proto +76 -0
  33. package/src/protos/simple_op_store.proto +147 -0
  34. package/src/types.d.ts +1129 -0
  35. package/src/utils/errors.js +21 -0
  36. package/src/utils/id-generation.js +54 -0
  37. package/src/utils/validation.js +124 -0
@@ -0,0 +1,180 @@
1
+ /**
2
+ * LazyGitBackend - Git backend with lazy object loading (v0.4)
3
+ *
4
+ * Extends IsomorphicGitBackend to support on-demand object fetching.
5
+ * Perfect for shallow clones and browser environments with limited storage.
6
+ */
7
+
8
+ import { IsomorphicGitBackend } from './isomorphic-git-backend.js';
9
+ import { JJError } from '../utils/errors.js';
10
+ import git from 'isomorphic-git';
11
+
12
+ /**
13
+ * Git backend with lazy object loading
14
+ *
15
+ * @class LazyGitBackend
16
+ * @extends IsomorphicGitBackend
17
+ */
18
+ export class LazyGitBackend extends IsomorphicGitBackend {
19
+ /**
20
+ * Create lazy Git backend
21
+ *
22
+ * @param {Object} options - Backend options
23
+ * @param {Object} options.fs - Filesystem implementation
24
+ * @param {Object} [options.http] - HTTP client for network operations
25
+ * @param {string} options.dir - Repository directory path
26
+ * @param {boolean} [options.lazyLoad=false] - Enable lazy object loading
27
+ * @param {string} [options.remote='origin'] - Remote to fetch from
28
+ */
29
+ constructor({ fs, http, dir, lazyLoad = false, remote = 'origin' }) {
30
+ super({ fs, http, dir });
31
+
32
+ this.lazyLoad = lazyLoad;
33
+ this.remote = remote;
34
+ this.missingObjects = new Set();
35
+ this.fetchedObjects = new Set();
36
+ }
37
+
38
+ /**
39
+ * Read blob with lazy loading support
40
+ *
41
+ * @param {string} oid - Object ID (SHA-1)
42
+ * @returns {Promise<Object>} Blob object with { blob: Buffer }
43
+ */
44
+ async readBlob(oid) {
45
+ // Validate OID format
46
+ if (!/^[0-9a-f]{40}$/.test(oid)) {
47
+ throw new JJError(
48
+ 'INVALID_OID',
49
+ `Invalid object ID format: ${oid}`,
50
+ { oid }
51
+ );
52
+ }
53
+
54
+ try {
55
+ // Try local read first
56
+ return await this._gitReadBlob(oid);
57
+ } catch (error) {
58
+ // If not found and lazy loading enabled, fetch from remote
59
+ if (error.code === 'NotFoundError' && this.lazyLoad) {
60
+ // Track as missing BEFORE attempting fetch
61
+ this.missingObjects.add(oid);
62
+
63
+ // Only fetch if we haven't already tried
64
+ if (!this.fetchedObjects.has(oid)) {
65
+ try {
66
+ await this._fetchObject(oid);
67
+ this.fetchedObjects.add(oid);
68
+
69
+ // Try reading again after fetch
70
+ return await this._gitReadBlob(oid);
71
+ } catch (fetchError) {
72
+ // Fetch failed, but we still track as missing
73
+ // Re-throw the fetch error
74
+ throw fetchError;
75
+ }
76
+ }
77
+ }
78
+
79
+ // Re-throw if lazy loading disabled or already tried fetching
80
+ throw error;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Internal git.readBlob wrapper (for testing/mocking)
86
+ *
87
+ * @private
88
+ * @param {string} oid - Object ID
89
+ * @returns {Promise<Object>} Blob object
90
+ */
91
+ async _gitReadBlob(oid) {
92
+ return await git.readBlob({
93
+ fs: this.fs,
94
+ dir: this.dir,
95
+ oid,
96
+ });
97
+ }
98
+
99
+ /**
100
+ * Fetch a single object from remote
101
+ *
102
+ * Note: isomorphic-git doesn't support fetching individual objects.
103
+ * This is a placeholder for the fetch logic. In practice, you may need to:
104
+ * 1. Fetch entire packfile containing the object
105
+ * 2. Use depth-based fetch to get more history
106
+ * 3. Implement custom Git protocol for single-object fetch
107
+ *
108
+ * @private
109
+ * @param {string} oid - Object ID to fetch
110
+ * @returns {Promise<void>}
111
+ */
112
+ async _fetchObject(oid) {
113
+ if (!this.http) {
114
+ throw new JJError(
115
+ 'NETWORK_NOT_AVAILABLE',
116
+ 'Lazy object loading requires http client for fetching missing objects',
117
+ {
118
+ suggestion: 'Provide http option when creating backend',
119
+ oid,
120
+ }
121
+ );
122
+ }
123
+
124
+ // Implementation note: isomorphic-git doesn't support fetching single objects.
125
+ // In a real implementation, you would:
126
+ //
127
+ // Option 1: Fetch with increased depth
128
+ // await git.fetch({
129
+ // fs: this.fs,
130
+ // http: this.http,
131
+ // dir: this.dir,
132
+ // remote: this.remote,
133
+ // depth: 10, // Fetch more history
134
+ // relative: true
135
+ // });
136
+ //
137
+ // Option 2: Custom Git protocol implementation
138
+ // - Send want/have negotiation
139
+ // - Receive packfile containing requested object
140
+ // - Unpack into .git/objects
141
+ //
142
+ // Option 3: Use Git HTTP API directly
143
+ // - GET /info/refs
144
+ // - POST /git-upload-pack with want list
145
+ //
146
+ // For now, throw an error indicating this needs implementation
147
+ throw new JJError(
148
+ 'NOT_IMPLEMENTED',
149
+ 'Single object fetch not yet implemented. Consider using shallow fetch with increased depth instead.',
150
+ {
151
+ oid,
152
+ suggestion: 'Use git.fetch({ depth: N, relative: true }) to fetch more history',
153
+ }
154
+ );
155
+ }
156
+
157
+ /**
158
+ * Get statistics about lazy loading
159
+ *
160
+ * @returns {Object} Statistics object
161
+ */
162
+ getStats() {
163
+ return {
164
+ lazyLoad: this.lazyLoad,
165
+ missingObjects: this.missingObjects.size,
166
+ fetchedObjects: this.fetchedObjects.size,
167
+ hitRate: this.fetchedObjects.size > 0
168
+ ? (this.fetchedObjects.size - this.missingObjects.size) / this.fetchedObjects.size
169
+ : 0,
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Clear lazy loading caches
175
+ */
176
+ clearCaches() {
177
+ this.missingObjects.clear();
178
+ this.fetchedObjects.clear();
179
+ }
180
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Browser helpers for isomorphic-jj
3
+ *
4
+ * Utilities for setting up isomorphic-jj in browser environments.
5
+ */
6
+
7
+ /**
8
+ * Create a browser-compatible filesystem using LightningFS
9
+ *
10
+ * Requires: @isomorphic-git/lightning-fs
11
+ *
12
+ * @param {Object} opts - Options
13
+ * @param {string} [opts.backend='idb'] - Storage backend ('idb' or 'memory')
14
+ * @param {string} [opts.name='jj'] - Database name for IndexedDB
15
+ * @param {boolean} [opts.wipe=false] - Wipe existing data
16
+ * @returns {Object} LightningFS instance
17
+ *
18
+ * @example
19
+ * ```javascript
20
+ * import { createBrowserFS } from 'isomorphic-jj/browser';
21
+ * import { createJJ } from 'isomorphic-jj';
22
+ * import git from 'isomorphic-git';
23
+ * import http from 'isomorphic-git/http/web';
24
+ *
25
+ * const fs = createBrowserFS({ backend: 'idb', name: 'my-repo' });
26
+ * const jj = await createJJ({
27
+ * fs,
28
+ * dir: '/repo',
29
+ * git,
30
+ * http
31
+ * });
32
+ * ```
33
+ */
34
+ export function createBrowserFS(opts = {}) {
35
+ // Check if we're in a browser environment
36
+ if (typeof window === 'undefined') {
37
+ throw new Error('createBrowserFS() should only be used in browser environments');
38
+ }
39
+
40
+ // Dynamic import to avoid issues in Node.js
41
+ // Users must install @isomorphic-git/lightning-fs separately
42
+ try {
43
+ // This will be resolved by the bundler
44
+ const LightningFS = require('@isomorphic-git/lightning-fs');
45
+
46
+ const fs = new LightningFS(opts.name || 'jj', {
47
+ wipe: opts.wipe || false,
48
+ });
49
+
50
+ return fs;
51
+ } catch (error) {
52
+ throw new Error(
53
+ 'LightningFS not found. Install it with: npm install @isomorphic-git/lightning-fs\n' +
54
+ 'Original error: ' + error.message
55
+ );
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Get storage quota information (browser only)
61
+ *
62
+ * @returns {Promise<Object|null>} Quota information or null if not supported
63
+ *
64
+ * @example
65
+ * ```javascript
66
+ * const quota = await getStorageQuota();
67
+ * console.log(`Using ${quota.usage} of ${quota.quota} bytes (${quota.percentage}%)`);
68
+ * ```
69
+ */
70
+ export async function getStorageQuota() {
71
+ if (typeof navigator === 'undefined' || !navigator.storage || !navigator.storage.estimate) {
72
+ return null;
73
+ }
74
+
75
+ const estimate = await navigator.storage.estimate();
76
+ return {
77
+ usage: estimate.usage || 0,
78
+ quota: estimate.quota || 0,
79
+ available: (estimate.quota || 0) - (estimate.usage || 0),
80
+ percentage: estimate.quota ? ((estimate.usage || 0) / estimate.quota) * 100 : 0,
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Request persistent storage (browser only)
86
+ *
87
+ * Prevents the browser from evicting the storage.
88
+ *
89
+ * @returns {Promise<boolean>} Whether persistent storage was granted
90
+ *
91
+ * @example
92
+ * ```javascript
93
+ * const persistent = await requestPersistentStorage();
94
+ * if (persistent) {
95
+ * console.log('Storage will not be evicted');
96
+ * }
97
+ * ```
98
+ */
99
+ export async function requestPersistentStorage() {
100
+ if (typeof navigator === 'undefined' || !navigator.storage || !navigator.storage.persist) {
101
+ return false;
102
+ }
103
+
104
+ return await navigator.storage.persist();
105
+ }
106
+
107
+ /**
108
+ * Check if persistent storage is already granted
109
+ *
110
+ * @returns {Promise<boolean>} Whether persistent storage is granted
111
+ */
112
+ export async function isPersistentStorage() {
113
+ if (typeof navigator === 'undefined' || !navigator.storage || !navigator.storage.persisted) {
114
+ return false;
115
+ }
116
+
117
+ return await navigator.storage.persisted();
118
+ }
119
+
120
+ /**
121
+ * ServiceWorker utilities for offline support
122
+ */
123
+ export const serviceWorker = {
124
+ /**
125
+ * Register a service worker for offline support
126
+ *
127
+ * @param {string} scriptURL - Service worker script URL
128
+ * @param {Object} [options] - Registration options
129
+ * @returns {Promise<ServiceWorkerRegistration>}
130
+ */
131
+ async register(scriptURL, options = {}) {
132
+ if (typeof navigator === 'undefined' || !navigator.serviceWorker) {
133
+ throw new Error('Service Workers not supported in this browser');
134
+ }
135
+
136
+ return await navigator.serviceWorker.register(scriptURL, options);
137
+ },
138
+
139
+ /**
140
+ * Unregister service worker
141
+ */
142
+ async unregister() {
143
+ if (typeof navigator === 'undefined' || !navigator.serviceWorker) {
144
+ return false;
145
+ }
146
+
147
+ const registration = await navigator.serviceWorker.ready;
148
+ return await registration.unregister();
149
+ },
150
+
151
+ /**
152
+ * Check if service worker is registered
153
+ */
154
+ async isRegistered() {
155
+ if (typeof navigator === 'undefined' || !navigator.serviceWorker) {
156
+ return false;
157
+ }
158
+
159
+ const registration = await navigator.serviceWorker.getRegistration();
160
+ return !!registration;
161
+ },
162
+ };
163
+
164
+ /**
165
+ * Detect browser capabilities
166
+ *
167
+ * @returns {Object} Capability detection results
168
+ */
169
+ export function detectCapabilities() {
170
+ if (typeof window === 'undefined') {
171
+ return {
172
+ environment: 'node',
173
+ indexedDB: false,
174
+ serviceWorker: false,
175
+ persistentStorage: false,
176
+ sharedArrayBuffer: false,
177
+ webWorker: false,
178
+ };
179
+ }
180
+
181
+ return {
182
+ environment: 'browser',
183
+ indexedDB: typeof indexedDB !== 'undefined',
184
+ serviceWorker: typeof navigator !== 'undefined' && 'serviceWorker' in navigator,
185
+ persistentStorage: typeof navigator !== 'undefined' && navigator.storage && typeof navigator.storage.persist === 'function',
186
+ sharedArrayBuffer: typeof SharedArrayBuffer !== 'undefined',
187
+ webWorker: typeof Worker !== 'undefined',
188
+ };
189
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * TypeScript type definitions for isomorphic-jj/browser
3
+ *
4
+ * Browser-specific utilities for isomorphic-jj
5
+ */
6
+
7
+ /**
8
+ * Browser filesystem creation options
9
+ */
10
+ export interface BrowserFSOptions {
11
+ /** Storage backend ('idb' for IndexedDB or 'memory') */
12
+ backend?: 'idb' | 'memory';
13
+ /** Database name for IndexedDB */
14
+ name?: string;
15
+ /** Wipe existing data on initialization */
16
+ wipe?: boolean;
17
+ }
18
+
19
+ /**
20
+ * Storage quota information
21
+ */
22
+ export interface StorageQuota {
23
+ /** Bytes currently used */
24
+ usage: number;
25
+ /** Total quota in bytes */
26
+ quota: number;
27
+ /** Available bytes remaining */
28
+ available: number;
29
+ /** Percentage of quota used (0-100) */
30
+ percentage: number;
31
+ }
32
+
33
+ /**
34
+ * Browser capabilities detection result
35
+ */
36
+ export interface BrowserCapabilities {
37
+ /** IndexedDB is available */
38
+ indexedDB: boolean;
39
+ /** Storage Manager API is available */
40
+ storageManager: boolean;
41
+ /** Persistent storage is available */
42
+ persistentStorage: boolean;
43
+ /** Service Workers are supported */
44
+ serviceWorker: boolean;
45
+ /** Origin Private File System (OPFS) is available */
46
+ opfs: boolean;
47
+ /** SharedArrayBuffer is available */
48
+ sharedArrayBuffer: boolean;
49
+ /** Web Workers are supported */
50
+ webWorker: boolean;
51
+ }
52
+
53
+ /**
54
+ * Service Worker utilities
55
+ */
56
+ export interface ServiceWorkerUtils {
57
+ /**
58
+ * Register a service worker
59
+ * @param scriptURL - URL to service worker script
60
+ * @param options - Registration options
61
+ */
62
+ register(scriptURL: string, options?: RegistrationOptions): Promise<ServiceWorkerRegistration>;
63
+
64
+ /**
65
+ * Unregister the active service worker
66
+ */
67
+ unregister(): Promise<boolean>;
68
+
69
+ /**
70
+ * Get the active service worker registration
71
+ */
72
+ getRegistration(): Promise<ServiceWorkerRegistration | undefined>;
73
+
74
+ /**
75
+ * Post a message to the service worker
76
+ * @param message - Message to send
77
+ */
78
+ postMessage(message: any): void;
79
+
80
+ /**
81
+ * Listen for messages from the service worker
82
+ * @param handler - Message handler function
83
+ */
84
+ onMessage(handler: (event: MessageEvent) => void): void;
85
+ }
86
+
87
+ /**
88
+ * Create a browser-compatible filesystem using LightningFS
89
+ *
90
+ * Requires: @isomorphic-git/lightning-fs
91
+ *
92
+ * @param opts - Filesystem options
93
+ * @returns LightningFS instance compatible with isomorphic-jj
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * import { createBrowserFS } from 'isomorphic-jj/browser';
98
+ * import { createJJ } from 'isomorphic-jj';
99
+ * import git from 'isomorphic-git';
100
+ * import http from 'isomorphic-git/http/web';
101
+ *
102
+ * const fs = createBrowserFS({ backend: 'idb', name: 'my-repo' });
103
+ * const jj = await createJJ({ fs, dir: '/repo', git, http });
104
+ * ```
105
+ */
106
+ export function createBrowserFS(opts?: BrowserFSOptions): any;
107
+
108
+ /**
109
+ * Get storage quota information (browser only)
110
+ *
111
+ * @returns Quota information or null if not supported
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * const quota = await getStorageQuota();
116
+ * if (quota) {
117
+ * console.log(`Using ${quota.usage} of ${quota.quota} bytes`);
118
+ * }
119
+ * ```
120
+ */
121
+ export function getStorageQuota(): Promise<StorageQuota | null>;
122
+
123
+ /**
124
+ * Request persistent storage to prevent browser eviction
125
+ *
126
+ * @returns true if persistent storage was granted, false otherwise
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * const persistent = await requestPersistentStorage();
131
+ * if (persistent) {
132
+ * console.log('Storage will not be evicted by the browser');
133
+ * }
134
+ * ```
135
+ */
136
+ export function requestPersistentStorage(): Promise<boolean>;
137
+
138
+ /**
139
+ * Check if persistent storage is currently enabled
140
+ *
141
+ * @returns true if storage is persistent, false otherwise
142
+ *
143
+ * @example
144
+ * ```typescript
145
+ * const isPersistent = await isPersistentStorage();
146
+ * if (!isPersistent) {
147
+ * await requestPersistentStorage();
148
+ * }
149
+ * ```
150
+ */
151
+ export function isPersistentStorage(): Promise<boolean>;
152
+
153
+ /**
154
+ * Service worker utilities for offline support
155
+ */
156
+ export const serviceWorker: ServiceWorkerUtils;
157
+
158
+ /**
159
+ * Detect browser capabilities
160
+ *
161
+ * @returns Object indicating which features are available
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * const caps = detectCapabilities();
166
+ * if (!caps.indexedDB) {
167
+ * console.warn('IndexedDB not available, using memory backend');
168
+ * }
169
+ * if (caps.serviceWorker) {
170
+ * // Enable offline support
171
+ * }
172
+ * ```
173
+ */
174
+ export function detectCapabilities(): BrowserCapabilities;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Browser-specific exports for isomorphic-jj
3
+ */
4
+
5
+ export {
6
+ createBrowserFS,
7
+ getStorageQuota,
8
+ requestPersistentStorage,
9
+ isPersistentStorage,
10
+ serviceWorker,
11
+ detectCapabilities,
12
+ } from './helpers.js';