browser-git-ops 0.0.4 → 0.0.5

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.js CHANGED
@@ -53,23 +53,28 @@ var APIGitLib = (() => {
53
53
  ok = true;
54
54
  return ok;
55
55
  }
56
- /** 利用可能なサブディレクトリ名の候補を返す
57
- * @returns {Promise<string[]>} available root directories
58
- */
59
56
  /**
60
57
  * Return available root folder names for OPFS. This method is synchronous
61
58
  * to satisfy the StorageBackendConstructor contract; it returns a cached
62
59
  * hint if available and kicks off an async probe to populate the cache.
63
60
  * If no information is available synchronously an empty array is returned.
64
- * @returns {Promise<string[]>} available root directories
61
+ * @returns {Promise<string[]>} available root directories
65
62
  */
66
- static async availableRoots() {
63
+ static async availableRoots(namespace) {
67
64
  try {
68
65
  const root = await OpfsStorage2._getNavigatorStorageRoot();
69
66
  if (!root)
70
67
  return [];
71
- return await OpfsStorage2._collectDirectoryNames(root);
72
- } catch {
68
+ for await (const handle of root.values()) {
69
+ const name = OpfsStorage2._extractHandleName(handle);
70
+ if (name === namespace && OpfsStorage2._isDirectoryHandle(handle)) {
71
+ return await OpfsStorage2._collectDirectoryNames(handle);
72
+ }
73
+ }
74
+ return [];
75
+ } catch (error) {
76
+ if (typeof console !== "undefined" && console.debug)
77
+ console.debug("availableRoots probe failed", error);
73
78
  return [];
74
79
  }
75
80
  }
@@ -120,6 +125,7 @@ var APIGitLib = (() => {
120
125
  }
121
126
  rootDir = "apigit_storage";
122
127
  currentBranch = null;
128
+ namespace = "";
123
129
  /**
124
130
  * Calculate SHA-1 hex digest of given content.
125
131
  * @param content Input string
@@ -132,10 +138,21 @@ var APIGitLib = (() => {
132
138
  const hashArray = Array.from(new Uint8Array(hashBuffer));
133
139
  return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
134
140
  }
135
- /** コンストラクタ(OPFS は初期化不要)。`root` は OPFS ルート直下に作成するサブディレクトリ名です。 */
136
- constructor(root) {
137
- if (root)
138
- this.rootDir = root;
141
+ /**
142
+ * コンストラクタ(OPFS は初期化不要)。
143
+ * `namespace` は必須。挙動:
144
+ * - `new(namespace)` のみの場合は OPFS ルートとして `namespace` を使う(テストの期待値に合わせる)。
145
+ * - `new(namespace, root)` の場合は `namespace/root` を使う。
146
+ */
147
+ constructor(namespace, root) {
148
+ this.namespace = namespace || "";
149
+ if (root) {
150
+ this.rootDir = this.namespace ? `${this.namespace}/${root}` : root;
151
+ } else if (this.namespace) {
152
+ this.rootDir = this.namespace;
153
+ } else {
154
+ this.rootDir = this.rootDir;
155
+ }
139
156
  }
140
157
  /**
141
158
  * 初期化(OPFS はランタイム判定のみ)
@@ -163,7 +180,7 @@ var APIGitLib = (() => {
163
180
  }
164
181
  /**
165
182
  * Map logical segment to concrete prefix used on OPFS.
166
- * @returns {string} concrete prefix path for the given segment
183
+ * @returns {string} concrete prefix path for the given segment
167
184
  */
168
185
  _segmentToPrefix(segment) {
169
186
  if (segment === "workspace")
@@ -172,7 +189,6 @@ var APIGitLib = (() => {
172
189
  const branch = this.currentBranch || "main";
173
190
  return `.git/${branch}/${segName}`;
174
191
  }
175
- // legacy canUseOpfs removed; use static canUse() instead
176
192
  /**
177
193
  * Try to get OPFS root from navigator.storage.getDirectory().
178
194
  * @returns {Promise<any|null>}
@@ -186,7 +202,9 @@ var APIGitLib = (() => {
186
202
  const maybe = nav.storage.getDirectory();
187
203
  const d = await Promise.resolve(maybe);
188
204
  return d || null;
189
- } catch {
205
+ } catch (error) {
206
+ if (typeof console !== "undefined" && console.debug)
207
+ console.debug("_tryNavigatorStorage failed", error);
190
208
  return null;
191
209
  }
192
210
  }
@@ -201,7 +219,9 @@ var APIGitLib = (() => {
201
219
  }
202
220
  try {
203
221
  return await opfs.getDirectory();
204
- } catch {
222
+ } catch (error) {
223
+ if (typeof console !== "undefined" && console.debug)
224
+ console.debug("_tryOriginPrivateFileSystem failed", error);
205
225
  return null;
206
226
  }
207
227
  }
@@ -232,10 +252,6 @@ var APIGitLib = (() => {
232
252
  }
233
253
  return directory;
234
254
  }
235
- /**
236
- * index を読み出す
237
- * @returns {Promise<IndexFile|null>} 読み出した IndexFile、存在しなければ null
238
- */
239
255
  /**
240
256
  * Read index metadata file from OPFS.
241
257
  * @returns {Promise<string|null>}
@@ -252,7 +268,9 @@ var APIGitLib = (() => {
252
268
  const fh = await root.getFileHandle("index");
253
269
  const file = await fh.getFile();
254
270
  return await file.text();
255
- } catch {
271
+ } catch (error) {
272
+ if (typeof console !== "undefined" && console.debug)
273
+ console.debug("_readIndexMetadata failed", error);
256
274
  return null;
257
275
  }
258
276
  }
@@ -611,8 +629,8 @@ var APIGitLib = (() => {
611
629
  * @param root root directory handle
612
630
  * @param prefix prefix dir
613
631
  * @param filepath path relative to prefix
614
- * @returns {Promise<string|null>} file contents or null when not found
615
- */
632
+ * @returns {Promise<string|null>} file contents or null when not found
633
+ */
616
634
  async readFromPrefix(root, prefix, filepath) {
617
635
  try {
618
636
  const fullPath = this.rootDir ? `${this.rootDir}/${prefix}/${filepath}` : `${prefix}/${filepath}`;
@@ -696,7 +714,6 @@ var APIGitLib = (() => {
696
714
  await this._handleChildEntry(d, name, base, results);
697
715
  }
698
716
  }
699
- /** Handle a single child entry name: try file first, then directory. */
700
717
  /**
701
718
  * @returns {Promise<void>}
702
719
  */
@@ -750,7 +767,7 @@ var APIGitLib = (() => {
750
767
  * @param prefix プレフィックス(例: 'dir/sub')。省略時はルート
751
768
  * @param segment セグメント('workspace' 等)。省略時は 'workspace'
752
769
  * @param recursive サブディレクトリも含めるか。省略時は true
753
- * @returns {Promise<Array<{ path: string; info: string | null }>>}
770
+ * @returns {Promise<Array<{ path: string; info: string | null }>>}
754
771
  */
755
772
  async listFiles(prefix, segment, recursive = true) {
756
773
  const root = await this.getOpfsRoot();
@@ -794,12 +811,9 @@ var APIGitLib = (() => {
794
811
  */
795
812
  async _findStorageDirectory(navRoot) {
796
813
  try {
797
- for await (const handle of navRoot.values()) {
798
- const name = OpfsStorage2._extractHandleName(handle);
799
- if (name === this.rootDir && OpfsStorage2._isDirectoryHandle(handle))
800
- return handle;
801
- }
802
- return null;
814
+ const parts = this.rootDir.split("/").filter(Boolean);
815
+ const directoryHandle = await this.traverseDir(navRoot, parts);
816
+ return directoryHandle;
803
817
  } catch {
804
818
  return null;
805
819
  }
@@ -983,7 +997,6 @@ var APIGitLib = (() => {
983
997
  setLogger(logger) {
984
998
  this.logger = logger;
985
999
  }
986
- // Internal helpers to forward logs only when a logger is injected
987
1000
  /**
988
1001
  * Log debug messages when a logger is present.
989
1002
  * @param _messages messages to log (unused when no logger)
@@ -1032,14 +1045,6 @@ var APIGitLib = (() => {
1032
1045
  }
1033
1046
  }
1034
1047
  }
1035
- /**
1036
- * Proxy to shared fetchWithRetry implementation
1037
- * @param input RequestInfo
1038
- * @param init RequestInit
1039
- * @param attempts retry attempts
1040
- * @param baseDelay base delay ms
1041
- * @returns Promise resolving to Response
1042
- */
1043
1048
  /**
1044
1049
  * Normalize different header-like shapes into a plain object.
1045
1050
  * @param headerLike headers in Headers, array, or plain object form
@@ -1148,14 +1153,6 @@ var APIGitLib = (() => {
1148
1153
  const jitter = Math.floor(Math.random() * base * 0.3);
1149
1154
  return base + jitter;
1150
1155
  }
1151
- /**
1152
- * Delegate to shared mapWithConcurrency implementation
1153
- * @template T,R
1154
- * @param items items to map
1155
- * @param mapper async mapper
1156
- * @param concurrency concurrency limit
1157
- * @returns Promise resolving to mapped results
1158
- */
1159
1156
  /**
1160
1157
  * Map items with limited concurrency by delegating to the shared helper.
1161
1158
  * @template T,R
@@ -1263,12 +1260,6 @@ var APIGitLib = (() => {
1263
1260
  parents
1264
1261
  };
1265
1262
  }
1266
- /**
1267
- * コンテンツから sha1 を算出します。
1268
- * @param {string} content コンテンツ
1269
- * @returns {string} sha1 ハッシュ
1270
- */
1271
- // shaOf is inherited from AbstractGitAdapter
1272
1263
  /**
1273
1264
  * ブロブを作成またはキャッシュから取得します。
1274
1265
  * @param {any[]} changes 変更一覧(create/update を含む)
@@ -1283,11 +1274,6 @@ var APIGitLib = (() => {
1283
1274
  map[r.path] = r.sha;
1284
1275
  return map;
1285
1276
  }
1286
- /**
1287
- * ブロブ作成用のヘルパー(createBlobs から抽出)
1288
- * @param {any} ch 変更エントリ
1289
- * @returns {Promise<{path:string,sha:string}>}
1290
- */
1291
1277
  /**
1292
1278
  * Create a blob for a change or return cached blobSha.
1293
1279
  * @param {any} ch change entry
@@ -1494,10 +1480,6 @@ var APIGitLib = (() => {
1494
1480
  }
1495
1481
  throw new NonRetryableError("getRef: sha not found");
1496
1482
  }
1497
- /**
1498
- * Helper to fetch a ref URL and extract a SHA if present.
1499
- * Returns null when SHA not found.
1500
- */
1501
1483
  /**
1502
1484
  * Fetch a ref URL and extract a SHA if present.
1503
1485
  * @param {string} url API URL to fetch
@@ -1592,18 +1574,6 @@ var APIGitLib = (() => {
1592
1574
  const enc = index.encoding || "utf-8";
1593
1575
  return { content: index.content, encoding: enc };
1594
1576
  }
1595
- /**
1596
- * Resolve a commit-ish (branch, tag, or SHA) to a commit SHA.
1597
- * Resolution order: branch -> tag -> commit endpoint -> treat as SHA
1598
- * Throws if resolution fails.
1599
- */
1600
- /**
1601
- * Resolve a commit-ish (branch, tag, or SHA) to a commit SHA.
1602
- * Resolution order: branch -> tag -> commit endpoint -> treat as SHA
1603
- * Throws if resolution fails.
1604
- * @param {string} reference commit-ish to resolve
1605
- * @returns {Promise<string>} resolved commit SHA
1606
- */
1607
1577
  /**
1608
1578
  * Resolve a commit-ish (branch, tag, or SHA) to a commit SHA.
1609
1579
  * Resolution order: branch -> tag -> commit endpoint -> treat as SHA
@@ -1643,11 +1613,6 @@ var APIGitLib = (() => {
1643
1613
  }
1644
1614
  return null;
1645
1615
  }
1646
- /**
1647
- * Try to resolve reference as a branch and return its sha.
1648
- * @param {string} reference branch name
1649
- * @returns {Promise<string|null>} resolved sha or null
1650
- */
1651
1616
  /**
1652
1617
  * Try to resolve a branch name to a commit SHA.
1653
1618
  * @param {string} reference branch name
@@ -1657,11 +1622,6 @@ var APIGitLib = (() => {
1657
1622
  const sha = await this.getRef(`heads/${reference}`);
1658
1623
  return sha || null;
1659
1624
  }
1660
- /**
1661
- * Try to resolve reference as a tag and return its sha.
1662
- * @param {string} reference tag name
1663
- * @returns {Promise<string|null>} resolved sha or null
1664
- */
1665
1625
  /**
1666
1626
  * Try to resolve a tag name to a commit SHA.
1667
1627
  * @param {string} reference tag name
@@ -1671,11 +1631,6 @@ var APIGitLib = (() => {
1671
1631
  const sha = await this.getRef(`tags/${reference}`);
1672
1632
  return sha || null;
1673
1633
  }
1674
- /**
1675
- * Try to resolve reference using commits endpoint (could be SHA or other form).
1676
- * @param {string} reference commit-ish
1677
- * @returns {Promise<string|null>} resolved sha or null
1678
- */
1679
1634
  /**
1680
1635
  * Try to resolve via commits endpoint (may accept SHA or other forms).
1681
1636
  * @param {string} reference commit-ish
@@ -1691,11 +1646,6 @@ var APIGitLib = (() => {
1691
1646
  }
1692
1647
  return null;
1693
1648
  }
1694
- /**
1695
- * Blob を取得して content を返す。取得失敗時は content=null を返す。
1696
- * @param {{sha:string,path:string}} f blob 情報
1697
- * @returns {Promise<{path:string,content:string|null}>}
1698
- */
1699
1649
  /**
1700
1650
  * Fetch a blob's content; return null content on failure.
1701
1651
  * @param {any} f blob metadata
@@ -1735,16 +1685,6 @@ var APIGitLib = (() => {
1735
1685
  }
1736
1686
  return safe;
1737
1687
  }
1738
- /**
1739
- * Fetch a blob's content; return null content on failure.
1740
- * @param {any} f blob metadata
1741
- * @returns {Promise<{path:string,content:string|null}>}
1742
- */
1743
- /**
1744
- * リポジトリのスナップショットを取得します。
1745
- * @param {string} branch ブランチ名 (default: 'main')
1746
- * @returns {Promise<{headSha:string,shas:Record<string,string>,fetchContent:Function,snapshot:Record<string,string>}>}
1747
- */
1748
1688
  /**
1749
1689
  * Fetch repository snapshot: headSha, shas map and a fetchContent helper.
1750
1690
  * @param {string} branch branch name
@@ -1819,22 +1759,6 @@ var APIGitLib = (() => {
1819
1759
  out[p] = content;
1820
1760
  return null;
1821
1761
  }
1822
- /**
1823
- * 指定パスのコンテンツを取得し、キャッシュと snapshot を更新します。
1824
- * @param {Map<string, any>} fileMap ファイルメタ情報マップ
1825
- * @param {Map<string, string>} contentCache キャッシュマップ
1826
- * @param {Record<string,string>} snapshot スナップショット出力マップ
1827
- * @param {string} p 取得対象パス
1828
- * @returns {Promise<string|null>} ファイル内容または null
1829
- */
1830
- /**
1831
- * Fetch single path content, update cache and snapshot.
1832
- * @param {Map<string, any>} fileMap file map
1833
- * @param {Map<string,string>} contentCache cache map
1834
- * @param {Record<string,string>} snapshot snapshot map
1835
- * @param {string} p path to fetch
1836
- * @returns {Promise<string|null>} content or null
1837
- */
1838
1762
  /**
1839
1763
  * Fetch single path content, update cache and snapshot.
1840
1764
  * @param {Map<string, any>} fileMap file map
@@ -1914,7 +1838,7 @@ var APIGitLib = (() => {
1914
1838
  }
1915
1839
  /**
1916
1840
  * GitLab のページングヘッダを解析します(x-next-page / x-total-pages)。
1917
- * @returns {{nextPage?: number, lastPage?: number}} ページ番号情報
1841
+ * @returns {{nextPage?: number, lastPage?: number}} ページ番号情報
1918
1842
  */
1919
1843
  _parsePagingHeaders(resp) {
1920
1844
  const out = {};
@@ -1946,12 +1870,6 @@ var APIGitLib = (() => {
1946
1870
  parents
1947
1871
  };
1948
1872
  }
1949
- /**
1950
- * コンテンツから sha1 を算出します。
1951
- * @param {string} content コンテンツ
1952
- * @returns {string} sha1 ハッシュ
1953
- */
1954
- // shaOf is inherited from AbstractGitAdapter
1955
1873
  /**
1956
1874
  * 変更一覧から blob sha のマップを作成します(疑似実装)。
1957
1875
  * @param {any[]} changes 変更一覧
@@ -2031,9 +1949,6 @@ var APIGitLib = (() => {
2031
1949
  }
2032
1950
  return await this.postCommit(url, body);
2033
1951
  }
2034
- /**
2035
- * Retrieve project metadata (default branch, name, id) and cache it.
2036
- */
2037
1952
  /**
2038
1953
  * Retrieve project metadata (default branch, name, id) and cache it.
2039
1954
  * @returns {Promise<import('../virtualfs/types.ts').RepositoryMetadata>} repository metadata
@@ -2132,7 +2047,7 @@ var APIGitLib = (() => {
2132
2047
  }
2133
2048
  /**
2134
2049
  * Convert change descriptors to GitLab API actions
2135
- * @returns {Array<any>} API-compatible actions array
2050
+ * @returns {Array<any>} API-compatible actions array
2136
2051
  */
2137
2052
  createActions(changes) {
2138
2053
  return changes.map((c) => {
@@ -2143,11 +2058,6 @@ var APIGitLib = (() => {
2143
2058
  return { action: "update", file_path: c.path, content: c.content };
2144
2059
  });
2145
2060
  }
2146
- /**
2147
- * Verify remote branch head matches expected parent SHA.
2148
- * @throws Error if non-fast-forward
2149
- * @returns {Promise<void>}
2150
- */
2151
2061
  /**
2152
2062
  * Verify that remote branch head matches expected parent SHA.
2153
2063
  * Throws when non-fast-forward detected.
@@ -2182,10 +2092,6 @@ var APIGitLib = (() => {
2182
2092
  }
2183
2093
  return index.id || index.commit || index;
2184
2094
  }
2185
- /**
2186
- * Post commit request and parse response
2187
- * @returns {Promise<any>}
2188
- */
2189
2095
  /**
2190
2096
  * Post commit request and return parsed commit response.
2191
2097
  * @param {string} url endpoint URL
@@ -2197,19 +2103,6 @@ var APIGitLib = (() => {
2197
2103
  const text = await response.text().catch(() => "");
2198
2104
  return this.parseCommitResponse(text);
2199
2105
  }
2200
- /**
2201
- * fetch をリトライ付きで実行します。
2202
- * @param {string} url リクエスト URL
2203
- * @param {RequestInit} opts fetch オプション
2204
- * @param {number} [retries] 最大リトライ回数
2205
- * @returns {Promise<Response>} レスポンス
2206
- */
2207
- // fetchWithRetry is provided by AbstractGitAdapter
2208
- /**
2209
- * Wait helper for fetch retry backoff.
2210
- * @param attempt Attempt number
2211
- * @returns {Promise<void>} resolves after backoff
2212
- */
2213
2106
  /**
2214
2107
  * Wait helper for retry backoff.
2215
2108
  * @param {number} attempt attempt number
@@ -2219,28 +2112,6 @@ var APIGitLib = (() => {
2219
2112
  const wait = this.backoffMs(attempt);
2220
2113
  await new Promise((r) => setTimeout(r, wait));
2221
2114
  }
2222
- /**
2223
- * ステータスが再試行対象か判定します。
2224
- * @param {number} status ステータスコード
2225
- * @returns {boolean}
2226
- */
2227
- // isRetryableStatus is provided by AbstractGitAdapter
2228
- /**
2229
- * バックオフ時間を計算します。
2230
- * @param {number} attempt 試行回数(1..)
2231
- * @returns {number} ミリ秒
2232
- */
2233
- // backoffMs provided by AbstractGitAdapter
2234
- // small concurrency mapper used for fetching files
2235
- /**
2236
- * 並列マッピングユーティリティ
2237
- * @template T, R
2238
- * @param {T[]} items 入力配列
2239
- * @param {(t:T)=>Promise<R>} mapper マッピング関数
2240
- * @param {number} concurrency 同時実行数
2241
- * @returns {Promise<R[]>}
2242
- */
2243
- // mapWithConcurrency provided by AbstractGitAdapter
2244
2115
  /**
2245
2116
  * Prepare JSON body for commit API call.
2246
2117
  * @returns {string} JSON body
@@ -2248,9 +2119,6 @@ var APIGitLib = (() => {
2248
2119
  _prepareCommitBody(branch, message, actions) {
2249
2120
  return JSON.stringify({ branch, commit_message: message, actions });
2250
2121
  }
2251
- /**
2252
- * Optionally verify parent SHA; swallow non-422 errors.
2253
- */
2254
2122
  /**
2255
2123
  * Optionally verify parent SHA; rethrow errors after logging.
2256
2124
  * @param {string} expectedParentSha expected SHA
@@ -2269,7 +2137,7 @@ var APIGitLib = (() => {
2269
2137
  /**
2270
2138
  * リポジトリのスナップショットを取得します。
2271
2139
  * @param {string} branch ブランチ名 (default: 'main')
2272
- * @returns {Promise<{headSha:string,shas:Record<string,string>,fetchContent:(paths:string[])=>Promise<Record<string,string>>}>}
2140
+ * @returns {Promise<{headSha:string,shas:Record<string,string>,fetchContent:(paths:string[])=>Promise<Record<string,string>>}>}
2273
2141
  */
2274
2142
  async fetchSnapshot(branch = "main", concurrency = 5) {
2275
2143
  const headSha = await this._determineHeadSha(branch);
@@ -2279,11 +2147,6 @@ var APIGitLib = (() => {
2279
2147
  const fetchContent = (paths) => this._fetchContentFromFileSet(fileSet, cache, snapshot, paths, branch, concurrency);
2280
2148
  return { headSha, shas, fetchContent, snapshot };
2281
2149
  }
2282
- /**
2283
- * Determine the remote head SHA for a branch. Falls back to branch name on error.
2284
- * @param {string} branch Branch name
2285
- * @returns {Promise<string>} head SHA or branch
2286
- */
2287
2150
  /**
2288
2151
  * Determine the head SHA for a branch; fallback to branch name if unavailable.
2289
2152
  * @param {string} branch branch name
@@ -2306,11 +2169,6 @@ var APIGitLib = (() => {
2306
2169
  }
2307
2170
  return branch;
2308
2171
  }
2309
- /**
2310
- * Fetch repository tree and build shas/fileSet.
2311
- * @param {string} branch Branch name
2312
- * @returns {Promise<{shas:Record<string,string>,fileSet:Set<string>}>}
2313
- */
2314
2172
  /**
2315
2173
  * Fetch repository tree and build shas map and fileSet.
2316
2174
  * @param {string} branch branch name
@@ -2322,10 +2180,6 @@ var APIGitLib = (() => {
2322
2180
  const files = Array.isArray(treeJ) ? treeJ.filter((t) => t.type === "blob") : [];
2323
2181
  return this._buildShasAndFileSet(files);
2324
2182
  }
2325
- /**
2326
- * Helper to fetch files from the repository tree with caching and concurrency.
2327
- * @returns {Promise<Record<string,string>>}
2328
- */
2329
2183
  /**
2330
2184
  * Fetch contents for requested paths from a FileSet with caching.
2331
2185
  * @param {Set<string>} fileSet set of available files
@@ -2347,14 +2201,6 @@ var APIGitLib = (() => {
2347
2201
  }, concurrency);
2348
2202
  return out;
2349
2203
  }
2350
- /**
2351
- * 指定パスのファイル内容を取得し、キャッシュと snapshot を更新します。
2352
- * @param {Map<string,string>} cache キャッシュマップ
2353
- * @param {Record<string,string>} snapshot スナップショットマップ
2354
- * @param {string} p ファイルパス
2355
- * @param {string} branch ブランチ名
2356
- * @returns {Promise<string|null>} ファイル内容または null
2357
- */
2358
2204
  /**
2359
2205
  * Fetch the content for a single file path, updating cache and snapshot.
2360
2206
  * @param {Map<string,string>} cache cache map
@@ -2377,12 +2223,6 @@ var APIGitLib = (() => {
2377
2223
  }
2378
2224
  return null;
2379
2225
  }
2380
- /**
2381
- * ファイルの raw コンテンツを取得して返します。失敗時は null を返します。
2382
- * @param {string} path ファイルパス
2383
- * @param {string} branch ブランチ名
2384
- * @returns {Promise<string|null>} ファイル内容または null
2385
- */
2386
2226
  /**
2387
2227
  * Fetch raw file content from GitLab; return null on failure.
2388
2228
  * @param {string} path file path
@@ -2404,7 +2244,6 @@ var APIGitLib = (() => {
2404
2244
  return null;
2405
2245
  }
2406
2246
  }
2407
- /** Build shas map and fileSet from tree entries */
2408
2247
  /**
2409
2248
  * Build shas map and fileSet from tree entries
2410
2249
  * @returns {{shas:Record<string,string>,fileSet:Set<string>}}
@@ -2421,18 +2260,6 @@ var APIGitLib = (() => {
2421
2260
  }
2422
2261
  return { shas, fileSet };
2423
2262
  }
2424
- /**
2425
- * Resolve a commit-ish (branch name, tag name, or SHA) to a commit SHA.
2426
- * Resolution order: branch -> tag -> commits endpoint -> treat as SHA
2427
- * Throws if not resolvable.
2428
- */
2429
- /**
2430
- * Resolve a commit-ish (branch, tag, or SHA) to a commit SHA.
2431
- * Resolution order: branch -> tag -> commits endpoint -> treat as SHA
2432
- * Throws if not resolvable.
2433
- * @param {string} reference commit-ish to resolve
2434
- * @returns {Promise<string>} resolved commit SHA
2435
- */
2436
2263
  /**
2437
2264
  * Resolve a commit-ish (branch, tag, or SHA) to a commit SHA.
2438
2265
  * Resolution order: branch -> tag -> commits endpoint -> treat as SHA
@@ -3536,7 +3363,7 @@ var APIGitLib = (() => {
3536
3363
  /**
3537
3364
  * Read and parse the stored info entry for `path`.
3538
3365
  * Returns parsed object or null when missing/invalid.
3539
- * @returns {Promise<any|null>} parsed object or null
3366
+ * @returns {Promise<any|null>} parsed object or null
3540
3367
  */
3541
3368
  async _readInfoEntry(path) {
3542
3369
  try {
@@ -3559,7 +3386,7 @@ var APIGitLib = (() => {
3559
3386
  /**
3560
3387
  * Attempt to fetch base content using adapterInstance.getBlob.
3561
3388
  * Returns string when fetched, null when fetch attempted but failed, or undefined when adapter not supported.
3562
- * @returns {Promise<string|null|undefined>} fetched content, null, or undefined
3389
+ * @returns {Promise<string|null|undefined>} fetched content, null, or undefined
3563
3390
  */
3564
3391
  async _tryFetchBaseWithAdapter(adapterInstance, baseSha, path) {
3565
3392
  if (!adapterInstance || typeof adapterInstance.getBlob !== "function")
@@ -3595,7 +3422,7 @@ var APIGitLib = (() => {
3595
3422
  /**
3596
3423
  * Attempt to fetch raw file using adapterInstance._fetchFileRaw if available.
3597
3424
  * Returns string when fetched, null when attempted but failed, or undefined when adapter not supported.
3598
- * @returns {Promise<string|null|undefined>} fetched content, null, or undefined
3425
+ * @returns {Promise<string|null|undefined>} fetched content, null, or undefined
3599
3426
  */
3600
3427
  async _tryFetchRawFile(adapterInstance, path, ie) {
3601
3428
  if (!adapterInstance || typeof adapterInstance._fetchFileRaw !== "function")
@@ -3703,7 +3530,7 @@ var APIGitLib = (() => {
3703
3530
  if (options?.backend)
3704
3531
  this.backend = options.backend;
3705
3532
  else
3706
- this.backend = new OpfsStorage();
3533
+ this.backend = new OpfsStorage("default");
3707
3534
  if (options && options.logger)
3708
3535
  this.logger = options.logger;
3709
3536
  this.applier = new LocalChangeApplier(this.backend);
@@ -3743,22 +3570,6 @@ var APIGitLib = (() => {
3743
3570
  set lastCommitKey(k) {
3744
3571
  this.indexManager.setLastCommitKey(k);
3745
3572
  }
3746
- /**
3747
- * SHA-1 helper wrapper (delegates to ./hashUtils)
3748
- * @param {string} content - ハッシュ対象の文字列
3749
- * @returns {Promise<string>} SHA-1 ハッシュの16進表現
3750
- */
3751
- async shaOf(content) {
3752
- return await shaOf2(content);
3753
- }
3754
- /**
3755
- * SHA helper for Git blob formatting
3756
- * @param {string} content - blob コンテンツ
3757
- * @returns {Promise<string>} SHA-1 ハッシュの16進表現(git blob 用)
3758
- */
3759
- async shaOfGitBlob(content) {
3760
- return await shaOfGitBlob(content);
3761
- }
3762
3573
  /**
3763
3574
  * VirtualFS の初期化を行います(バックエンド初期化と index 読み込み)。
3764
3575
  * @returns {Promise<void>}
@@ -3898,6 +3709,312 @@ var APIGitLib = (() => {
3898
3709
  }
3899
3710
  return null;
3900
3711
  }
3712
+ /**
3713
+ * Helper: obtain backend listFilesRaw in a safe manner.
3714
+ * @returns {Promise<any[]>}
3715
+ */
3716
+ async _getBackendFilesRaw() {
3717
+ try {
3718
+ if (this.backend && typeof this.backend.listFilesRaw === "function") {
3719
+ return await this.backend.listFilesRaw();
3720
+ }
3721
+ } catch (error) {
3722
+ if (typeof console !== "undefined" && console.debug)
3723
+ console.debug("_getBackendFilesRaw failed", error);
3724
+ }
3725
+ return [];
3726
+ }
3727
+ /**
3728
+ * Helper: apply parsed info text into stats object when possible.
3729
+ * @param infoTxt raw info text
3730
+ * @param stats stats object to mutate
3731
+ * @returns {void}
3732
+ */
3733
+ _applyInfoTxtToStats(infoTxt, stats) {
3734
+ if (!infoTxt)
3735
+ return;
3736
+ try {
3737
+ const info = JSON.parse(infoTxt);
3738
+ if (typeof info.baseSha === "string")
3739
+ stats.gitBlobSha = info.baseSha;
3740
+ if (typeof info.updatedAt === "number")
3741
+ stats.mtime = new Date(info.updatedAt);
3742
+ if (typeof info.size === "number")
3743
+ stats.size = info.size;
3744
+ } catch (error) {
3745
+ if (typeof console !== "undefined" && console.debug)
3746
+ console.debug("parse info failed", error);
3747
+ }
3748
+ }
3749
+ /**
3750
+ * Find a matched backend entry for the given filepath.
3751
+ * @param filepath target filepath
3752
+ * @param filesRaw backend raw listing
3753
+ * @returns {any|null}
3754
+ */
3755
+ _findMatchedFile(filepath, filesRaw) {
3756
+ if (!Array.isArray(filesRaw))
3757
+ return null;
3758
+ return filesRaw.find((f) => {
3759
+ if (!f || !f.path)
3760
+ return false;
3761
+ return f.path === filepath || f.path.endsWith("/" + filepath) || f.path.endsWith("/" + filepath);
3762
+ });
3763
+ }
3764
+ /**
3765
+ * Create default stats object with consistent shape.
3766
+ * @param now current Date
3767
+ * @returns {any}
3768
+ */
3769
+ _createDefaultStats(now) {
3770
+ return {
3771
+ dev: 0,
3772
+ ino: 0,
3773
+ mode: 33188,
3774
+ nlink: 1,
3775
+ uid: 0,
3776
+ gid: 0,
3777
+ rdev: 0,
3778
+ size: 0,
3779
+ blksize: void 0,
3780
+ blocks: void 0,
3781
+ atime: now,
3782
+ mtime: now,
3783
+ ctime: now,
3784
+ birthtime: now,
3785
+ /** @returns {boolean} */
3786
+ isFile: () => true,
3787
+ /** @returns {boolean} */
3788
+ isDirectory: () => false
3789
+ };
3790
+ }
3791
+ /**
3792
+ * Populate stats.gitCommitSha from adapterMeta if available.
3793
+ * @param stats stats object to mutate
3794
+ * @returns {void}
3795
+ */
3796
+ _populateCommitShaFromMeta(stats) {
3797
+ if (!stats.gitCommitSha && this.adapterMeta && this.adapterMeta.opts && this.adapterMeta.opts.branch) {
3798
+ stats.gitCommitSha = this.adapterMeta.opts.branch;
3799
+ }
3800
+ }
3801
+ /**
3802
+ * Try to resolve commit SHA from an instantiated adapter when needed.
3803
+ * @param stats stats object to mutate
3804
+ * @returns {Promise<void>}
3805
+ */
3806
+ async _resolveCommitShaFromAdapter(stats) {
3807
+ const instAdapter = await this._safeGetAdapterInstance();
3808
+ if (!instAdapter || stats.gitCommitSha)
3809
+ return;
3810
+ if (typeof instAdapter.resolveRef !== "function")
3811
+ return;
3812
+ try {
3813
+ const branch = this.adapterMeta && this.adapterMeta.opts && this.adapterMeta.opts.branch || "main";
3814
+ const resolved = await instAdapter.resolveRef(branch);
3815
+ if (resolved)
3816
+ stats.gitCommitSha = resolved;
3817
+ } catch (error) {
3818
+ if (typeof console !== "undefined" && console.debug)
3819
+ console.debug("_resolveCommitShaFromAdapter resolveRef failed", error);
3820
+ }
3821
+ }
3822
+ /**
3823
+ * Safely get adapter instance, returning null on error.
3824
+ * @returns {Promise<any|null>}
3825
+ */
3826
+ async _safeGetAdapterInstance() {
3827
+ try {
3828
+ return await this.getAdapterInstance();
3829
+ } catch (error) {
3830
+ if (typeof console !== "undefined" && console.debug)
3831
+ console.debug("_safeGetAdapterInstance failed", error);
3832
+ return null;
3833
+ }
3834
+ }
3835
+ /**
3836
+ * Helper: populate stats.gitCommitSha using adapterMeta or adapter.resolveRef when available.
3837
+ * @param stats stats object to mutate
3838
+ * @returns {Promise<void>}
3839
+ */
3840
+ async _resolveAdapterCommitShaIfNeeded(stats) {
3841
+ this._populateCommitShaFromMeta(stats);
3842
+ if (!stats.gitCommitSha)
3843
+ await this._resolveCommitShaFromAdapter(stats);
3844
+ }
3845
+ /**
3846
+ * Determine whether a normalized path is an exact file entry in the provided entries.
3847
+ * @param normalizedDirectory normalized directory string
3848
+ * @param keys index keys array
3849
+ * @param entries index entries object
3850
+ * @returns {boolean}
3851
+ */
3852
+ _isExactFile(normalizedDirectory, keys, entries) {
3853
+ return keys.includes(normalizedDirectory) && entries[normalizedDirectory] && entries[normalizedDirectory].state !== "deleted";
3854
+ }
3855
+ /**
3856
+ * Collect immediate child names from index entries for given directory.
3857
+ * @param normalizedDirectory normalized directory string
3858
+ * @param entries index entries object
3859
+ * @returns {Set<string>} set of immediate child names
3860
+ */
3861
+ _collectNamesFromIndex(normalizedDirectory, entries) {
3862
+ const outNames = /* @__PURE__ */ new Set();
3863
+ const keys = Object.keys(entries || {});
3864
+ for (const k of keys) {
3865
+ const v = entries[k];
3866
+ if (v && v.state === "deleted")
3867
+ continue;
3868
+ if (normalizedDirectory === "." || normalizedDirectory === "") {
3869
+ this._collectNamesFromIndexRoot(k, outNames);
3870
+ continue;
3871
+ }
3872
+ this._processIndexKeyForDirectory(k, normalizedDirectory, outNames);
3873
+ }
3874
+ return outNames;
3875
+ }
3876
+ /**
3877
+ * Process a single index key for a non-root directory and add immediate child when applicable.
3878
+ * @param key index key
3879
+ * @param normalizedDirectory normalized directory string
3880
+ * @param outNames set to mutate
3881
+ * @returns {void}
3882
+ */
3883
+ _processIndexKeyForDirectory(key, normalizedDirectory, outNames) {
3884
+ if (key === normalizedDirectory)
3885
+ return;
3886
+ if (key.startsWith(normalizedDirectory + "/")) {
3887
+ const rest = key.slice(normalizedDirectory.length + 1);
3888
+ const first = rest.indexOf("/") === -1 ? rest : rest.slice(0, rest.indexOf("/"));
3889
+ outNames.add(first);
3890
+ }
3891
+ }
3892
+ /**
3893
+ * Safe wrapper for backend.listFiles returning [] on failure.
3894
+ * @param normalizedDirectory directory path
3895
+ * @returns {Promise<any[]>}
3896
+ */
3897
+ async _getBackendList(normalizedDirectory) {
3898
+ try {
3899
+ return await this.backend.listFiles(normalizedDirectory, void 0, false);
3900
+ } catch (error) {
3901
+ if (typeof console !== "undefined" && console.debug)
3902
+ console.debug("_getBackendList failed", error);
3903
+ return [];
3904
+ }
3905
+ }
3906
+ /**
3907
+ * Helper for collecting names when scanning root directory entries.
3908
+ * @param key index key
3909
+ * @param outNames set to mutate
3910
+ * @returns {void}
3911
+ */
3912
+ _collectNamesFromIndexRoot(key, outNames) {
3913
+ const first = key.indexOf("/") === -1 ? key : key.slice(0, key.indexOf("/"));
3914
+ outNames.add(first);
3915
+ }
3916
+ /**
3917
+ * Check whether normalizedDirectory corresponds to an exact file entry.
3918
+ * @param normalizedDirectory normalized directory string
3919
+ * @param entries index entries object
3920
+ * @returns {boolean}
3921
+ */
3922
+ _hasExactEntry(normalizedDirectory, entries) {
3923
+ const keys = Object.keys(entries || {});
3924
+ return this._isExactFile(normalizedDirectory, keys, entries);
3925
+ }
3926
+ /**
3927
+ * Consult backend.listFiles to collect immediate child names for given directory.
3928
+ * Best-effort: logs and returns empty set on failure.
3929
+ * @param normalizedDirectory normalized directory string
3930
+ * @returns {Promise<Set<string>>}
3931
+ */
3932
+ async _collectNamesFromBackend(normalizedDirectory) {
3933
+ const outNames = /* @__PURE__ */ new Set();
3934
+ if (!this._backendSupportsListFiles())
3935
+ return outNames;
3936
+ const backendList = await this._getBackendList(normalizedDirectory);
3937
+ if (!Array.isArray(backendList) || backendList.length === 0)
3938
+ return outNames;
3939
+ for (const it of backendList)
3940
+ this._processBackendEntry(it, normalizedDirectory, outNames);
3941
+ return outNames;
3942
+ }
3943
+ /**
3944
+ * Return true when backend supports listFiles
3945
+ * @returns {boolean}
3946
+ */
3947
+ _backendSupportsListFiles() {
3948
+ return !!(this.backend && typeof this.backend.listFiles === "function");
3949
+ }
3950
+ /**
3951
+ * Process a single backend listFiles entry and add immediate child name to outNames when applicable.
3952
+ * @param it backend entry
3953
+ * @param normalizedDirectory normalized directory string
3954
+ * @param outNames set to mutate
3955
+ * @returns {void}
3956
+ */
3957
+ _processBackendEntry(it, normalizedDirectory, outNames) {
3958
+ try {
3959
+ if (!it || !it.path)
3960
+ return;
3961
+ const p = it.path;
3962
+ if (p === normalizedDirectory)
3963
+ return;
3964
+ if (p.startsWith(normalizedDirectory + "/")) {
3965
+ const rest = p.slice(normalizedDirectory.length + 1);
3966
+ const first = rest.indexOf("/") === -1 ? rest : rest.slice(0, rest.indexOf("/"));
3967
+ outNames.add(first);
3968
+ }
3969
+ } catch (error) {
3970
+ if (typeof console !== "undefined" && console.debug)
3971
+ console.debug("_processBackendEntry failed", error);
3972
+ }
3973
+ }
3974
+ /**
3975
+ * Build Dirent-like lightweight objects for given names.
3976
+ * @param names array of names
3977
+ * @param keys array of index keys
3978
+ * @param entries index entries object
3979
+ * @param normalizedDirectory normalized directory string
3980
+ * @returns {Array<any>} array of Dirent-like objects
3981
+ */
3982
+ _buildDirentTypes(names, keys, entries, normalizedDirectory) {
3983
+ const out = [];
3984
+ for (const name of names) {
3985
+ const childPath = normalizedDirectory === "." ? name : `${normalizedDirectory}/${name}`;
3986
+ const { isFile, isDirectory } = this._determineChildType(childPath, keys, entries);
3987
+ const _isFileFunction = function() {
3988
+ return isFile && !isDirectory;
3989
+ };
3990
+ const _isDirectoryFunction = function() {
3991
+ return isDirectory;
3992
+ };
3993
+ out.push({ name, isFile: _isFileFunction, isDirectory: _isDirectoryFunction });
3994
+ }
3995
+ return out;
3996
+ }
3997
+ /**
3998
+ * Determine whether a childPath corresponds to a file, directory, or both.
3999
+ * @param childPath path of child
4000
+ * @param keys index keys
4001
+ * @param entries index entries
4002
+ * @returns {{isFile:boolean,isDirectory:boolean}}
4003
+ */
4004
+ _determineChildType(childPath, keys, entries) {
4005
+ let isDirectory = false;
4006
+ let isFile = false;
4007
+ for (const k of keys) {
4008
+ if (k === childPath && entries[k] && entries[k].state !== "deleted") {
4009
+ isFile = true;
4010
+ }
4011
+ if (k.startsWith(childPath + "/")) {
4012
+ isDirectory = true;
4013
+ break;
4014
+ }
4015
+ }
4016
+ return { isFile, isDirectory };
4017
+ }
3901
4018
  /**
3902
4019
  * Return persisted adapter metadata (if any).
3903
4020
  * @returns {any|null}
@@ -3915,15 +4032,6 @@ var APIGitLib = (() => {
3915
4032
  await this.localFileManager.writeFile(filepath, content);
3916
4033
  await this.loadIndex();
3917
4034
  }
3918
- /**
3919
- * ファイルを削除します(トゥームストーン作成を含む)。
3920
- * @param {string} filepath ファイルパス
3921
- * @returns {Promise<void>}
3922
- */
3923
- async deleteFile(filepath) {
3924
- await this.localFileManager.deleteFile(filepath);
3925
- await this.loadIndex();
3926
- }
3927
4035
  /**
3928
4036
  * rename を delete + create の合成で行うヘルパ
3929
4037
  * @param from 元パス
@@ -3934,7 +4042,7 @@ var APIGitLib = (() => {
3934
4042
  if (content === null)
3935
4043
  throw new Error("source not found");
3936
4044
  await this.writeFile(to, content);
3937
- await this.deleteFile(from);
4045
+ await this.unlink(from);
3938
4046
  }
3939
4047
  /**
3940
4048
  * ワークスペース/ベースからファイル内容を読み出します。
@@ -3951,7 +4059,9 @@ var APIGitLib = (() => {
3951
4059
  await this.remoteSynchronizer.fetchBaseIfMissing(filepath, adapter);
3952
4060
  content = await this.localFileManager.readFile(filepath);
3953
4061
  }
3954
- } catch {
4062
+ } catch (error) {
4063
+ if (typeof console !== "undefined" && console.debug)
4064
+ console.debug("readFile on-demand fetch failed", error);
3955
4065
  }
3956
4066
  return content;
3957
4067
  }
@@ -3963,6 +4073,151 @@ var APIGitLib = (() => {
3963
4073
  async readConflict(filepath) {
3964
4074
  return await this.conflictManager.readConflict(filepath);
3965
4075
  }
4076
+ /**
4077
+ * fs.stat 互換: 指定ファイルのメタ情報を返す
4078
+ * ワークスペース上の情報を優先し、未取得時は Git のメタ情報で補完する。
4079
+ * @returns {Promise<any>} stats オブジェクト
4080
+ */
4081
+ async stat(filepath) {
4082
+ if (!filepath || typeof filepath !== "string")
4083
+ throw new TypeError("filepath is required");
4084
+ const filesRaw = await this._getBackendFilesRaw();
4085
+ const matched = this._findMatchedFile(filepath, filesRaw);
4086
+ const now = /* @__PURE__ */ new Date();
4087
+ const stats = this._createDefaultStats(now);
4088
+ try {
4089
+ const infoTxt = await this.backend.readBlob(filepath, "info");
4090
+ this._applyInfoTxtToStats(infoTxt, stats);
4091
+ } catch (error) {
4092
+ if (typeof console !== "undefined" && console.debug)
4093
+ console.debug("stat readBlob failed", error);
4094
+ }
4095
+ if (matched) {
4096
+ stats.workspacePath = matched.path;
4097
+ }
4098
+ await this._resolveAdapterCommitShaIfNeeded(stats);
4099
+ return stats;
4100
+ }
4101
+ /**
4102
+ * fs.unlink 互換: ファイルを削除する
4103
+ */
4104
+ async unlink(filepath) {
4105
+ if (!filepath || typeof filepath !== "string")
4106
+ throw new TypeError("filepath is required");
4107
+ await this.localFileManager.deleteFile(filepath);
4108
+ await this.loadIndex();
4109
+ }
4110
+ /**
4111
+ * fs.mkdir 互換 (簡易実装): workspace 側にディレクトリ情報を書き込む
4112
+ */
4113
+ async mkdir(dirpath, _options) {
4114
+ if (!dirpath || typeof dirpath !== "string")
4115
+ throw new TypeError("dirpath is required");
4116
+ const info = { path: dirpath, state: "dir", createdAt: Date.now() };
4117
+ if (this.backend && typeof this.backend.writeBlob === "function") {
4118
+ await this.backend.writeBlob(dirpath, JSON.stringify(info), "info-workspace").catch((error) => {
4119
+ throw Object.assign(new Error("\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u4F5C\u6210\u5931\u6557"), { code: "EEXIST", cause: error });
4120
+ });
4121
+ }
4122
+ }
4123
+ /**
4124
+ * fs.rmdir 互換 (簡易実装)
4125
+ */
4126
+ async rmdir(dirpath, options) {
4127
+ if (!dirpath || typeof dirpath !== "string")
4128
+ throw new TypeError("dirpath is required");
4129
+ const children = await this._listChildrenOfDir(dirpath);
4130
+ if (children.length > 0 && !(options && options.recursive)) {
4131
+ const errorObject = new Error("Directory not empty");
4132
+ errorObject.code = "ENOTEMPTY";
4133
+ throw errorObject;
4134
+ }
4135
+ if (options && options.recursive)
4136
+ await this._deleteChildrenRecursive(children);
4137
+ }
4138
+ /**
4139
+ * Return list of child paths for given dirpath based on index entries.
4140
+ * @param dirpath directory path
4141
+ * @returns {Promise<string[]>}
4142
+ */
4143
+ async _listChildrenOfDir(dirpath) {
4144
+ const paths = await this.listPaths();
4145
+ return paths.filter((p) => p === dirpath || p.startsWith(dirpath + "/"));
4146
+ }
4147
+ /**
4148
+ * Delete array of children using localFileManager, logging failures per-child.
4149
+ * @param children array of paths
4150
+ * @returns {Promise<void>}
4151
+ */
4152
+ async _deleteChildrenRecursive(children) {
4153
+ for (const p of children) {
4154
+ try {
4155
+ await this.localFileManager.deleteFile(p);
4156
+ } catch (error) {
4157
+ if (typeof console !== "undefined" && console.debug)
4158
+ console.debug("rmdir recursive delete failed for", p, error);
4159
+ }
4160
+ }
4161
+ }
4162
+ /**
4163
+ * fs.readdir 互換 (簡易実装)
4164
+ * @returns {Promise<string[]|Array<any>>}
4165
+ */
4166
+ async readdir(dirpath, options) {
4167
+ if (!dirpath || typeof dirpath !== "string")
4168
+ throw new TypeError("dirpath is required");
4169
+ const index = await this.indexManager.getIndex();
4170
+ const entries = index && index.entries || {};
4171
+ const keys = Object.keys(entries);
4172
+ const names = await this._gatherDirectoryNames(dirpath, entries, keys);
4173
+ const maybeEmpty = this._returnIfNoNames(names, options);
4174
+ if (maybeEmpty !== null)
4175
+ return maybeEmpty;
4176
+ const normalizedDirectory = dirpath === "" ? "." : dirpath;
4177
+ if (options && options.withFileTypes)
4178
+ return this._buildDirentTypes(names, keys, entries, normalizedDirectory);
4179
+ return names;
4180
+ }
4181
+ /**
4182
+ * Return an empty array when names is empty according to options, else null to continue.
4183
+ * @param names array of names
4184
+ * @param options readdir options
4185
+ * @returns {Array<any>|null}
4186
+ */
4187
+ _returnIfNoNames(names, options) {
4188
+ if (!names || names.length === 0)
4189
+ return options && options.withFileTypes ? [] : [];
4190
+ return null;
4191
+ }
4192
+ /**
4193
+ * Gather immediate child names for a directory using index and backend as fallback.
4194
+ * Throws ENOTDIR when the path represents a file.
4195
+ * @param dirpath original directory path
4196
+ * @param entries index entries object
4197
+ * @param keys array of index keys
4198
+ * @returns {Promise<string[]>} immediate child names
4199
+ */
4200
+ async _gatherDirectoryNames(dirpath, entries, keys) {
4201
+ const normalizedDirectory = dirpath === "" ? "." : dirpath;
4202
+ const outNames = /* @__PURE__ */ new Set();
4203
+ const isExactFile = this._isExactFile(normalizedDirectory, keys, entries);
4204
+ const indexNames = this._collectNamesFromIndex(normalizedDirectory, entries);
4205
+ for (const n of indexNames)
4206
+ outNames.add(n);
4207
+ if (outNames.size === 0 && normalizedDirectory !== "." && !isExactFile) {
4208
+ const backendNames = await this._collectNamesFromBackend(normalizedDirectory);
4209
+ for (const n of backendNames)
4210
+ outNames.add(n);
4211
+ }
4212
+ if (isExactFile && outNames.size === 0) {
4213
+ const errorObject = new Error("\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3067\u306F\u3042\u308A\u307E\u305B\u3093");
4214
+ errorObject.code = "ENOTDIR";
4215
+ throw errorObject;
4216
+ }
4217
+ if (outNames.size === 0)
4218
+ return [];
4219
+ return Array.from(outNames);
4220
+ }
3966
4221
  /**
3967
4222
  * 指定パスのリモート衝突ファイル (.git-conflict/) を削除して
3968
4223
  * 競合を解消済とマークします。
@@ -4006,12 +4261,9 @@ var APIGitLib = (() => {
4006
4261
  const entries = index && index.entries || {};
4007
4262
  const out = [];
4008
4263
  for (const k of Object.keys(entries)) {
4009
- try {
4010
- const v = entries[k];
4011
- if (v && v.state === "deleted")
4012
- continue;
4013
- } catch {
4014
- }
4264
+ const v = entries[k];
4265
+ if (v && v.state === "deleted")
4266
+ continue;
4015
4267
  out.push(k);
4016
4268
  }
4017
4269
  return out;
@@ -4067,7 +4319,9 @@ var APIGitLib = (() => {
4067
4319
  if (input.parentSha && typeof adapter.getCommitTreeSha === "function") {
4068
4320
  try {
4069
4321
  baseTreeSha = await adapter.getCommitTreeSha(input.parentSha);
4070
- } catch {
4322
+ } catch (error) {
4323
+ if (typeof console !== "undefined" && console.debug)
4324
+ console.debug("getCommitTreeSha failed, continuing without baseTree", error);
4071
4325
  baseTreeSha = void 0;
4072
4326
  }
4073
4327
  }
@@ -4226,7 +4480,7 @@ apigit-commit-key:${input.commitKey}`;
4226
4480
  /**
4227
4481
  * Persist the requested branch into adapter metadata (best-effort).
4228
4482
  * @param {string} branch branch name to persist
4229
- * @returns {Promise<void>}
4483
+ * @returns {Promise<void>}
4230
4484
  */
4231
4485
  async _persistAdapterBranchMeta(branch, adapterInstance) {
4232
4486
  const meta = this.adapterMeta && this.adapterMeta.opts ? { ...this.adapterMeta } : await this.getAdapter();
@@ -4430,7 +4684,7 @@ apigit-commit-key:${input.commitKey}`;
4430
4684
  /**
4431
4685
  * Convenience to get default branch name from adapter repository metadata.
4432
4686
  * Returns null when adapter not available.
4433
- * @returns {Promise<string|null>}
4687
+ * @returns {Promise<string|null>}
4434
4688
  */
4435
4689
  async getDefaultBranch() {
4436
4690
  const instAdapter = await this.getAdapterInstance();
@@ -4470,11 +4724,6 @@ apigit-commit-key:${input.commitKey}`;
4470
4724
  console.debug("persist repository metadata aborted", error);
4471
4725
  }
4472
4726
  }
4473
- /**
4474
- * Normalize a resolved descriptor (string headSha or object) into a
4475
- * RemoteSnapshotDescriptor or null. Helper to reduce cognitive complexity.
4476
- * @returns {Promise<RemoteSnapshotDescriptor|null>} 正規化された descriptor または null
4477
- */
4478
4727
  /**
4479
4728
  * Try persisting repository metadata when available. Best-effort.
4480
4729
  * @param instAdapter adapter instance or null
@@ -4578,7 +4827,7 @@ apigit-commit-key:${input.commitKey}`;
4578
4827
  input.changes = await this.getChangeSet();
4579
4828
  }
4580
4829
  if (!input.commitKey) {
4581
- input.commitKey = await this.shaOf((input.parentSha || "") + JSON.stringify(input.changes));
4830
+ input.commitKey = await shaOf2((input.parentSha || "") + JSON.stringify(input.changes));
4582
4831
  }
4583
4832
  const instAdapter = await this.getAdapterInstance();
4584
4833
  if (!instAdapter) {
@@ -4607,7 +4856,7 @@ apigit-commit-key:${input.commitKey}`;
4607
4856
  var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
4608
4857
  /**
4609
4858
  * 環境に IndexedDB が存在するかを同期検査します。
4610
- * @returns {boolean} 利用可能なら true
4859
+ * @returns {boolean} 利用可能なら true
4611
4860
  */
4612
4861
  static canUse() {
4613
4862
  try {
@@ -4619,6 +4868,8 @@ apigit-commit-key:${input.commitKey}`;
4619
4868
  dbName;
4620
4869
  dbPromise;
4621
4870
  currentBranch = null;
4871
+ root;
4872
+ rootPrefix = "";
4622
4873
  static VAR_WORKSPACE_BASE = "workspace";
4623
4874
  // Historically this was a separate workspace-info store, but some test
4624
4875
  // fakes expect info entries to be available in 'git-info'. Alias the
@@ -4632,7 +4883,7 @@ apigit-commit-key:${input.commitKey}`;
4632
4883
  /** 利用可能な DB 名の一覧を返す
4633
4884
  * @returns {string[]} available root names
4634
4885
  */
4635
- static async availableRoots() {
4886
+ static async availableRoots(namespace) {
4636
4887
  const g = globalThis;
4637
4888
  const idb = g.indexedDB;
4638
4889
  if (!idb)
@@ -4640,7 +4891,12 @@ apigit-commit-key:${input.commitKey}`;
4640
4891
  if (typeof idb.databases !== "function")
4641
4892
  return [];
4642
4893
  try {
4643
- return await IndexedDatabaseStorage2._namesFromDatabases(idb);
4894
+ const names = await IndexedDatabaseStorage2._namesFromDatabases(idb);
4895
+ if (!namespace)
4896
+ return names;
4897
+ if (names.includes(namespace))
4898
+ return ["apigit_storage"];
4899
+ return [];
4644
4900
  } catch {
4645
4901
  return [];
4646
4902
  }
@@ -4660,8 +4916,10 @@ apigit-commit-key:${input.commitKey}`;
4660
4916
  return Array.from(new Set(names));
4661
4917
  }
4662
4918
  /** コンストラクタ */
4663
- constructor(root) {
4664
- this.dbName = root ?? IndexedDatabaseStorage2.DEFAULT_DB_NAME;
4919
+ constructor(namespace, _root) {
4920
+ this.dbName = namespace || IndexedDatabaseStorage2.DEFAULT_DB_NAME;
4921
+ this.root = _root || void 0;
4922
+ this.rootPrefix = this.root ? `${this.root}_` : "";
4665
4923
  this.dbPromise = this.openDb();
4666
4924
  }
4667
4925
  /**
@@ -4693,27 +4951,26 @@ apigit-commit-key:${input.commitKey}`;
4693
4951
  */
4694
4952
  /**
4695
4953
  * Handle DB upgrade event and create required object stores.
4696
- * @param ev Upgrade event
4954
+ * Creates the object stores used by this backend, names are resolved
4955
+ * through `_storeName` to include any configured `_root` prefix.
4956
+ * @param {Event} event - Upgrade event from `indexedDB.open`
4697
4957
  * @returns {void}
4698
4958
  */
4699
4959
  _handleUpgrade(event) {
4700
4960
  const database = event.target.result;
4701
- if (!database.objectStoreNames.contains(IndexedDatabaseStorage2.VAR_WORKSPACE_BASE))
4702
- database.createObjectStore(IndexedDatabaseStorage2.VAR_WORKSPACE_BASE);
4703
- if (!database.objectStoreNames.contains(IndexedDatabaseStorage2.VAR_WORKSPACE_INFO))
4704
- database.createObjectStore(IndexedDatabaseStorage2.VAR_WORKSPACE_INFO);
4705
- if (!database.objectStoreNames.contains(IndexedDatabaseStorage2.VAR_BASE))
4706
- database.createObjectStore(IndexedDatabaseStorage2.VAR_BASE);
4707
- if (!database.objectStoreNames.contains(IndexedDatabaseStorage2.VAR_CONFLICT))
4708
- database.createObjectStore(IndexedDatabaseStorage2.VAR_CONFLICT);
4709
- if (!database.objectStoreNames.contains(IndexedDatabaseStorage2.VAR_INFO))
4710
- database.createObjectStore(IndexedDatabaseStorage2.VAR_INFO);
4711
- if (!database.objectStoreNames.contains("index"))
4712
- database.createObjectStore("index");
4961
+ if (!database.objectStoreNames.contains(this._storeName(IndexedDatabaseStorage2.VAR_WORKSPACE_BASE)))
4962
+ database.createObjectStore(this._storeName(IndexedDatabaseStorage2.VAR_WORKSPACE_BASE));
4963
+ if (!database.objectStoreNames.contains(this._storeName(IndexedDatabaseStorage2.VAR_WORKSPACE_INFO)))
4964
+ database.createObjectStore(this._storeName(IndexedDatabaseStorage2.VAR_WORKSPACE_INFO));
4965
+ if (!database.objectStoreNames.contains(this._storeName(IndexedDatabaseStorage2.VAR_BASE)))
4966
+ database.createObjectStore(this._storeName(IndexedDatabaseStorage2.VAR_BASE));
4967
+ if (!database.objectStoreNames.contains(this._storeName(IndexedDatabaseStorage2.VAR_CONFLICT)))
4968
+ database.createObjectStore(this._storeName(IndexedDatabaseStorage2.VAR_CONFLICT));
4969
+ if (!database.objectStoreNames.contains(this._storeName(IndexedDatabaseStorage2.VAR_INFO)))
4970
+ database.createObjectStore(this._storeName(IndexedDatabaseStorage2.VAR_INFO));
4971
+ if (!database.objectStoreNames.contains(this._storeName("index")))
4972
+ database.createObjectStore(this._storeName("index"));
4713
4973
  }
4714
- /**
4715
- * 指定 DB に対する onversionchange ハンドラを生成します。
4716
- */
4717
4974
  /**
4718
4975
  * Create a handler to close DB on version change.
4719
4976
  * @param dbParam Target DB
@@ -4724,9 +4981,6 @@ apigit-commit-key:${input.commitKey}`;
4724
4981
  databaseParameter.close();
4725
4982
  };
4726
4983
  }
4727
- /**
4728
- * DB open の成功ハンドラ
4729
- */
4730
4984
  /**
4731
4985
  * Called when DB open succeeds.
4732
4986
  * @param req IDB open request
@@ -4738,9 +4992,6 @@ apigit-commit-key:${input.commitKey}`;
4738
4992
  database.onversionchange = this._makeVersionChangeHandler(database);
4739
4993
  resolve(database);
4740
4994
  }
4741
- /**
4742
- * DB open のエラーハンドラ
4743
- */
4744
4995
  /**
4745
4996
  * Called when DB open errors.
4746
4997
  * @param req IDB open request
@@ -4750,22 +5001,19 @@ apigit-commit-key:${input.commitKey}`;
4750
5001
  _onOpenError(request, reject) {
4751
5002
  reject(request.error);
4752
5003
  }
4753
- /**
4754
- * トランザクションラッパー。cb 内の処理をトランザクションで実行し、
4755
- * 必要なら再試行します。
4756
- */
4757
5004
  /**
4758
5005
  * トランザクションラッパー。cb 内の処理をトランザクションで実行し、必要なら再試行します。
4759
5006
  * @returns {Promise<void>} トランザクション処理完了時に解決
4760
5007
  */
4761
5008
  async tx(storeName, mode, callback) {
5009
+ const physical = this._storeName(storeName);
4762
5010
  try {
4763
- return await this._performTxAttempt(storeName, mode, callback);
5011
+ return await this._performTxAttempt(physical, mode, callback);
4764
5012
  } catch (error) {
4765
5013
  const isInvalidState = error && (error.name === "InvalidStateError" || /closing/i.test(String(error.message || "")));
4766
5014
  if (isInvalidState) {
4767
5015
  this.dbPromise = this.openDb();
4768
- return await this._performTxAttempt(storeName, mode, callback);
5016
+ return await this._performTxAttempt(physical, mode, callback);
4769
5017
  }
4770
5018
  throw error;
4771
5019
  }
@@ -4862,10 +5110,6 @@ apigit-commit-key:${input.commitKey}`;
4862
5110
  return orig.apply(target, arguments_);
4863
5111
  };
4864
5112
  }
4865
- /**
4866
- * Schedule a microtask to invoke tx.oncomplete in case fake IndexedDB
4867
- * implementations never fire it.
4868
- */
4869
5113
  /**
4870
5114
  * Schedule a microtask to invoke tx.oncomplete in case fake IndexedDB
4871
5115
  * implementations never fire it.
@@ -4881,7 +5125,6 @@ apigit-commit-key:${input.commitKey}`;
4881
5125
  }
4882
5126
  }, 0);
4883
5127
  }
4884
- // legacy canUseOpfs removed; use static canUse() instead
4885
5128
  /**
4886
5129
  * index を読み出す
4887
5130
  * @returns {Promise<IndexFile|null>} 読み出した IndexFile、存在しなければ null
@@ -4913,10 +5156,6 @@ apigit-commit-key:${input.commitKey}`;
4913
5156
  this.currentBranch = null;
4914
5157
  }
4915
5158
  }
4916
- /**
4917
- * Apply metadata to result. @returns void
4918
- * @returns {void}
4919
- */
4920
5159
  /**
4921
5160
  * Load workspace-local info entries into result.entries (workspace overrides branch-scoped)
4922
5161
  * @param result IndexFile being populated
@@ -4971,8 +5210,9 @@ apigit-commit-key:${input.commitKey}`;
4971
5210
  async _readIndexMeta(database) {
4972
5211
  return await new Promise((resolve) => {
4973
5212
  try {
4974
- const tx = database.transaction("index", "readonly");
4975
- const store = tx.objectStore("index");
5213
+ const indexName = this._storeName("index");
5214
+ const tx = database.transaction(indexName, "readonly");
5215
+ const store = tx.objectStore(indexName);
4976
5216
  const request = store.get("index");
4977
5217
  request.onsuccess = () => {
4978
5218
  resolve(request.result ?? null);
@@ -5152,14 +5392,7 @@ apigit-commit-key:${input.commitKey}`;
5152
5392
  return await this._getFromStore(storeName, key);
5153
5393
  }
5154
5394
  /**
5155
- * Read blob when a segment is provided. Handles info-workspace/info-git/info and other segments.
5156
- * @param segment Segment name
5157
- * @param filepath Path to read
5158
- * @param branch Current branch
5159
- * @returns {Promise<string|null>} blob content or null
5160
- */
5161
- /**
5162
- * blob を削除する
5395
+ * blob を削除する
5163
5396
  * @returns {Promise<void>} 削除完了時に解決
5164
5397
  */
5165
5398
  async deleteBlob(filepath, segment) {
@@ -5210,10 +5443,6 @@ apigit-commit-key:${input.commitKey}`;
5210
5443
  }
5211
5444
  return toWrite;
5212
5445
  }
5213
- /**
5214
- * Gather entries that should be written to workspace-info: those that exist in workspace-base.
5215
- * @returns {Promise<Array<{k:string,v:any}>>}
5216
- */
5217
5446
  /**
5218
5447
  * Compute store name and key for a given segment and filepath.
5219
5448
  * @returns {{storeName:string,key:string}}
@@ -5223,10 +5452,6 @@ apigit-commit-key:${input.commitKey}`;
5223
5452
  const key = seg === "workspace" ? filepath : seg === "conflictBlob" ? `${branch}::conflictBlob::${filepath}` : `${branch}::${filepath}`;
5224
5453
  return { storeName, key };
5225
5454
  }
5226
- /**
5227
- * Compute store name and key for a given segment and filepath.
5228
- * @returns {{storeName:string,key:string}}
5229
- */
5230
5455
  /**
5231
5456
  * Delete a filepath from all relevant stores for the given branch.
5232
5457
  * @returns {Promise<void>}
@@ -5239,10 +5464,6 @@ apigit-commit-key:${input.commitKey}`;
5239
5464
  await this._deleteFromStore(IndexedDatabaseStorage2.VAR_INFO, filepath);
5240
5465
  await this._deleteFromStore(IndexedDatabaseStorage2.VAR_WORKSPACE_INFO, filepath);
5241
5466
  }
5242
- /**
5243
- * Delete a filepath from all relevant stores for the given branch.
5244
- * @returns {Promise<void>}
5245
- */
5246
5467
  /**
5247
5468
  * Read a value from a specific object store.
5248
5469
  * @param storeName Object store name
@@ -5253,8 +5474,9 @@ apigit-commit-key:${input.commitKey}`;
5253
5474
  const database = await this.dbPromise;
5254
5475
  return new Promise((resolve) => {
5255
5476
  try {
5256
- const tx = database.transaction(storeName, "readonly");
5257
- const store = tx.objectStore(storeName);
5477
+ const physical = this._storeName(storeName);
5478
+ const tx = database.transaction(physical, "readonly");
5479
+ const store = tx.objectStore(physical);
5258
5480
  const request = store.get(filepath);
5259
5481
  request.onsuccess = () => {
5260
5482
  const result = request.result ?? null;
@@ -5284,8 +5506,9 @@ apigit-commit-key:${input.commitKey}`;
5284
5506
  const database = await this.dbPromise;
5285
5507
  return new Promise((resolve) => {
5286
5508
  try {
5287
- const tx = database.transaction(storeName, "readonly");
5288
- const store = tx.objectStore(storeName);
5509
+ const physical = this._storeName(storeName);
5510
+ const tx = database.transaction(physical, "readonly");
5511
+ const store = tx.objectStore(physical);
5289
5512
  const keys = [];
5290
5513
  const request = store.openKeyCursor();
5291
5514
  request.onsuccess = this._makeCursorSuccessHandler(resolve, keys);
@@ -5320,8 +5543,13 @@ apigit-commit-key:${input.commitKey}`;
5320
5543
  });
5321
5544
  }
5322
5545
  /**
5323
- * Create a cursor success handler bound to resolve and keys array.
5546
+ * Map a logical store name to its physical store name including root prefix.
5547
+ * @param name logical store identifier
5548
+ * @returns physical store name used in IndexedDB
5324
5549
  */
5550
+ _storeName(name) {
5551
+ return this.rootPrefix ? `${this.rootPrefix}${name}` : name;
5552
+ }
5325
5553
  /**
5326
5554
  * Create a cursor success handler bound to resolve and keys array.
5327
5555
  * @param resolve resolver
@@ -5362,9 +5590,6 @@ apigit-commit-key:${input.commitKey}`;
5362
5590
  keys = this._filterKeys(keys, p, recursive);
5363
5591
  return await this._collectFiles(keys, seg);
5364
5592
  }
5365
- /**
5366
- * Raw listing that returns implementation-specific URIs and a normalized path.
5367
- */
5368
5593
  /**
5369
5594
  * Raw listing that returns implementation-specific URIs and a normalized path.
5370
5595
  * @param prefix optional prefix to filter
@@ -5395,9 +5620,6 @@ apigit-commit-key:${input.commitKey}`;
5395
5620
  }
5396
5621
  return out;
5397
5622
  }
5398
- /**
5399
- * Helper: build entries from store keys with filtering and path normalization
5400
- */
5401
5623
  /**
5402
5624
  * Helper: build entries from store keys with filtering and path normalization
5403
5625
  * @returns {Promise<Array<{uri:string,path:string,info?:string|null}>>}
@@ -5474,10 +5696,6 @@ apigit-commit-key:${input.commitKey}`;
5474
5696
  }
5475
5697
  return keys;
5476
5698
  }
5477
- /**
5478
- * Collect file info objects for keys array.
5479
- * @returns {Promise<Array<{path:string, info:string|null}>>}
5480
- */
5481
5699
  /**
5482
5700
  * Collect file info objects for keys array.
5483
5701
  * @param keys list of keys
@@ -5493,15 +5711,6 @@ apigit-commit-key:${input.commitKey}`;
5493
5711
  }
5494
5712
  return out;
5495
5713
  }
5496
- /**
5497
- * Collect file info objects for keys array.
5498
- * @param keys key list
5499
- * @param _seg segment (unused)
5500
- * @returns {Promise<Array<{path:string, info:string|null}>>}
5501
- */
5502
- /**
5503
- * Resolve info value for a given key: prefer workspace-local, then branch-scoped.
5504
- */
5505
5714
  /**
5506
5715
  * Resolve info value for a given key: prefer workspace-local, then branch-scoped.
5507
5716
  * @param k key
@@ -5514,10 +5723,6 @@ apigit-commit-key:${input.commitKey}`;
5514
5723
  info = await this._getFromStore(IndexedDatabaseStorage2.VAR_INFO, k);
5515
5724
  return info;
5516
5725
  }
5517
- /**
5518
- * Resolve info value for a given key: prefer workspace-local, then branch-scoped.
5519
- * @returns {Promise<string|null>}
5520
- */
5521
5726
  /**
5522
5727
  * Calculate SHA-1 hex digest of given content.
5523
5728
  * @param content Input string
@@ -5596,6 +5801,23 @@ apigit-commit-key:${input.commitKey}`;
5596
5801
  for (let index = 0; index < bytes.length; index += 4) {
5597
5802
  words.push(bytes[index] << 24 | bytes[index + 1] << 16 | bytes[index + 2] << 8 | bytes[index + 3]);
5598
5803
  }
5804
+ let h0 = 1732584193;
5805
+ let h1 = 4023233417;
5806
+ let h2 = 2562383102;
5807
+ let h3 = 271733878;
5808
+ let h4 = 3285377520;
5809
+ const results = processWords(words);
5810
+ h0 = results[0];
5811
+ h1 = results[1];
5812
+ h2 = results[2];
5813
+ h3 = results[3];
5814
+ h4 = results[4];
5815
+ function toHex(n) {
5816
+ return n.toString(16).padStart(8, "0");
5817
+ }
5818
+ return (toHex(h0) + toHex(h1) + toHex(h2) + toHex(h3) + toHex(h4)).toLowerCase();
5819
+ }
5820
+ function processWords(words) {
5599
5821
  let h0 = 1732584193;
5600
5822
  let h1 = 4023233417;
5601
5823
  let h2 = 2562383102;
@@ -5609,37 +5831,40 @@ apigit-commit-key:${input.commitKey}`;
5609
5831
  const temporary = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16];
5610
5832
  w[t] = (temporary << 1 | temporary >>> 31) >>> 0;
5611
5833
  }
5612
- let a = h0, b = h1, c = h2, d = h3, errorReg = h4;
5613
- for (let t = 0; t < 80; t++) {
5614
- let f, k;
5615
- if (t < 20) {
5616
- f = b & c | ~b & d;
5617
- k = 1518500249;
5618
- } else if (t < 40) {
5619
- f = b ^ c ^ d;
5620
- k = 1859775393;
5621
- } else if (t < 60) {
5622
- f = b & c | b & d | c & d;
5623
- k = 2400959708;
5624
- } else {
5625
- f = b ^ c ^ d;
5626
- k = 3395469782;
5627
- }
5628
- const temporaryValue = (a << 5 | a >>> 27) + f + errorReg + k + (w[t] >>> 0) >>> 0;
5629
- errorReg = d;
5630
- d = c;
5631
- c = (b << 30 | b >>> 2) >>> 0;
5632
- b = a;
5633
- a = temporaryValue;
5634
- }
5635
- h0 = h0 + a >>> 0;
5636
- h1 = h1 + b >>> 0;
5637
- h2 = h2 + c >>> 0;
5638
- h3 = h3 + d >>> 0;
5639
- h4 = h4 + errorReg >>> 0;
5640
- }
5641
- const toHex = (n) => n.toString(16).padStart(8, "0");
5642
- return (toHex(h0) + toHex(h1) + toHex(h2) + toHex(h3) + toHex(h4)).toLowerCase();
5834
+ const updated = processChunk(w, [h0, h1, h2, h3, h4]);
5835
+ h0 = updated[0];
5836
+ h1 = updated[1];
5837
+ h2 = updated[2];
5838
+ h3 = updated[3];
5839
+ h4 = updated[4];
5840
+ }
5841
+ return [h0, h1, h2, h3, h4];
5842
+ }
5843
+ function processChunk(w, h) {
5844
+ let a = h[0], b = h[1], c = h[2], d = h[3], registerE = h[4];
5845
+ for (let t = 0; t < 80; t++) {
5846
+ let f, k;
5847
+ if (t < 20) {
5848
+ f = b & c | ~b & d;
5849
+ k = 1518500249;
5850
+ } else if (t < 40) {
5851
+ f = b ^ c ^ d;
5852
+ k = 1859775393;
5853
+ } else if (t < 60) {
5854
+ f = b & c | b & d | c & d;
5855
+ k = 2400959708;
5856
+ } else {
5857
+ f = b ^ c ^ d;
5858
+ k = 3395469782;
5859
+ }
5860
+ const temporaryValue = (a << 5 | a >>> 27) + f + registerE + k + (w[t] >>> 0) >>> 0;
5861
+ registerE = d;
5862
+ d = c;
5863
+ c = (b << 30 | b >>> 2) >>> 0;
5864
+ b = a;
5865
+ a = temporaryValue;
5866
+ }
5867
+ return [h[0] + a >>> 0, h[1] + b >>> 0, h[2] + c >>> 0, h[3] + d >>> 0, h[4] + registerE >>> 0];
5643
5868
  }
5644
5869
  function parseExistingInfo(store, filepath) {
5645
5870
  const existingTxt = store.infoBlobs.has(filepath) ? store.infoBlobs.get(filepath) : null;
@@ -5687,31 +5912,36 @@ apigit-commit-key:${input.commitKey}`;
5687
5912
  const now = Date.now();
5688
5913
  const existing = parseExistingInfo(store, filepath);
5689
5914
  if (seg === "info") {
5690
- try {
5691
- const parsed = JSON.parse(content);
5692
- store.infoBlobs.set(filepath, JSON.stringify(parsed));
5693
- return;
5694
- } catch {
5695
- store.infoBlobs.set(filepath, String(content));
5696
- return;
5697
- }
5915
+ await handleInfoSegment(store, filepath, content);
5916
+ return;
5698
5917
  }
5699
5918
  const sha = await shaOf3(content);
5700
5919
  let entry;
5701
- if (seg === "workspace") {
5920
+ if (seg === "workspace")
5702
5921
  entry = buildWorkspaceEntry(existing, filepath, sha, now);
5703
- } else if (seg === "base") {
5922
+ else if (seg === "base")
5704
5923
  entry = buildBaseEntry(existing, filepath, sha, now);
5705
- } else if (seg === "conflict") {
5924
+ else if (seg === "conflict")
5706
5925
  entry = buildConflictEntry(existing, filepath, now);
5707
- } else {
5926
+ else
5708
5927
  entry = { path: filepath, updatedAt: now };
5709
- }
5710
5928
  store.infoBlobs.set(filepath, JSON.stringify(entry));
5711
- } catch {
5929
+ } catch (error) {
5930
+ if (typeof console !== "undefined" && console.debug)
5931
+ console.debug("updateInfoForWrite failed", error);
5712
5932
  return;
5713
5933
  }
5714
5934
  }
5935
+ async function handleInfoSegment(store, filepath, content) {
5936
+ try {
5937
+ const parsed = JSON.parse(content);
5938
+ store.infoBlobs.set(filepath, JSON.stringify(parsed));
5939
+ } catch (error) {
5940
+ store.infoBlobs.set(filepath, String(content));
5941
+ if (typeof console !== "undefined" && console.debug)
5942
+ console.debug("handleInfoSegment: stored raw content due to parse error", error);
5943
+ }
5944
+ }
5715
5945
 
5716
5946
  // src/virtualfs/inmemoryStorage.ts
5717
5947
  var BRANCH_SEP = "::";
@@ -5735,17 +5965,26 @@ apigit-commit-key:${input.commitKey}`;
5735
5965
  * 利用可能なルート名を返します。
5736
5966
  * @returns {string[]} ルート名の配列
5737
5967
  */
5738
- static availableRoots() {
5968
+ static availableRoots(namespace) {
5739
5969
  const keys = Array.from(InMemoryStorage2.stores.keys());
5740
- return keys.length ? keys : ["apigit_storage"];
5970
+ if (namespace) {
5971
+ const filtered = keys.filter((k) => k.startsWith(namespace + "/")).map((k) => k.slice((namespace + "/").length));
5972
+ return filtered.length ? filtered : ["apigit_storage"];
5973
+ }
5974
+ const roots = keys.map((k) => {
5975
+ const parts = k.split("/");
5976
+ return parts.length > 1 ? parts.slice(1).join("/") : parts[0];
5977
+ });
5978
+ const uniq = Array.from(new Set(roots));
5979
+ return uniq.length ? uniq : ["apigit_storage"];
5741
5980
  }
5742
- // legacy canUseOpfs removed; use static canUse() instead
5743
5981
  /**
5744
5982
  * コンストラクタ。互換性のためにディレクトリ名を受け取るが無視する。
5745
5983
  * @param directory 任意のディレクトリ文字列(使用しない)
5746
5984
  */
5747
- constructor(directory) {
5748
- this.rootKey = directory ?? `__inmem_${Math.random().toString(36).slice(2)}`;
5985
+ constructor(namespace, directory) {
5986
+ const directoryName = directory ?? `__inmem_${Math.random().toString(36).slice(2)}`;
5987
+ this.rootKey = namespace ? `${namespace}/${directoryName}` : directoryName;
5749
5988
  if (!InMemoryStorage2.stores.has(this.rootKey)) {
5750
5989
  InMemoryStorage2.stores.set(this.rootKey, {
5751
5990
  index: { head: "", entries: {} },
@@ -5787,7 +6026,7 @@ apigit-commit-key:${input.commitKey}`;
5787
6026
  }
5788
6027
  /**
5789
6028
  * Load workspace-local info entries into result.entries (unprefixed keys)
5790
- * @returns {void}
6029
+ * @returns {void}
5791
6030
  */
5792
6031
  _loadInMemoryWorkspaceInfo(store, result, branch) {
5793
6032
  for (const [k, v] of store.infoBlobs.entries()) {
@@ -5800,7 +6039,7 @@ apigit-commit-key:${input.commitKey}`;
5800
6039
  }
5801
6040
  /**
5802
6041
  * Load branch-scoped info entries into result.entries without overwriting workspace-local entries
5803
- * @returns {void}
6042
+ * @returns {void}
5804
6043
  */
5805
6044
  _loadInMemoryBranchInfo(store, result, branch) {
5806
6045
  for (const [k, v] of store.infoBlobs.entries()) {
@@ -5873,10 +6112,6 @@ apigit-commit-key:${input.commitKey}`;
5873
6112
  const wrapped = seg === SEG_WORKSPACE || seg === "conflict" ? this._wrapStoreForInfoNoPrefix(store) : this._wrapStoreForInfoPrefix(store);
5874
6113
  await updateInfoForWrite(wrapped, filepath, seg, content);
5875
6114
  }
5876
- /**
5877
- * Handle workspace blob writes that should be based on existing git-scoped info.
5878
- * Returns true when the operation is handled and caller should return early.
5879
- */
5880
6115
  /**
5881
6116
  * Handle workspace blob writes that should be based on existing git-scoped info.
5882
6117
  * Returns true when the operation is handled and caller should return early.
@@ -6168,7 +6403,7 @@ apigit-commit-key:${input.commitKey}`;
6168
6403
  * @param prefix プレフィックス(例: 'dir/sub')
6169
6404
  * @param segment セグメント('workspace' 等)。省略時は 'workspace'
6170
6405
  * @param recursive サブディレクトリも含めるか。省略時は true
6171
- * @returns {Promise<Array<{ path: string; info: string | null }>>}
6406
+ * @returns {Promise<Array<{ path: string; info: string | null }>>}
6172
6407
  */
6173
6408
  async listFiles(prefix, segment, recursive = true) {
6174
6409
  const seg = segment || SEG_WORKSPACE;
@@ -6187,9 +6422,6 @@ apigit-commit-key:${input.commitKey}`;
6187
6422
  const keys = this._resolveKeysForList(allKeys, String(seg), p, recursive);
6188
6423
  return this._collectFilesInMemory(keys, store);
6189
6424
  }
6190
- /**
6191
- * Resolve and filter keys for `listFiles` into final key list.
6192
- */
6193
6425
  /**
6194
6426
  * Resolve and filter keys for `listFiles` into final key list.
6195
6427
  * @returns {string[]} filtered keys
@@ -6326,9 +6558,15 @@ apigit-commit-key:${input.commitKey}`;
6326
6558
  static delete(rootName) {
6327
6559
  if (InMemoryStorage2.stores.has(rootName)) {
6328
6560
  InMemoryStorage2.stores.delete(rootName);
6329
- } else {
6330
- throw new Error(`InMemory root "${rootName}" not found`);
6561
+ return;
6562
+ }
6563
+ for (const key of Array.from(InMemoryStorage2.stores.keys())) {
6564
+ if (key === rootName || key.endsWith("/" + rootName)) {
6565
+ InMemoryStorage2.stores.delete(key);
6566
+ return;
6567
+ }
6331
6568
  }
6569
+ throw new Error(`InMemory root "${rootName}" not found`);
6332
6570
  }
6333
6571
  };
6334
6572
  var inmemoryStorage_default = InMemoryStorage;