@michengai/dsh-archive-manager 1.1.6 → 1.1.7
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/CHANGELOG.md +12 -0
- package/CHANGELOG.zh-CN.md +12 -0
- package/README.md +7 -1
- package/README.ru.md +7 -1
- package/README.zh-CN.md +7 -1
- package/lib/archive-organizer.js +3 -6
- package/lib/client.js +4 -3
- package/lib/host-install.js +3 -3
- package/lib/projcache.js +125 -163
- package/lib/session-repair.js +41 -5
- package/lib/workspace.js +19 -173
- package/package.json +1 -1
package/lib/host-install.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { bindArchiveManagerRemote, installArchiveWorkspace, tolerateStaleFileUploadResolver } from "./workspace.js";
|
|
2
2
|
import { installArchiveProjectionCache, installArchiveProjectionCacheGuards } from "./projcache.js";
|
|
3
|
-
const inject = ["workspaceRegistry", "sessionProjectionCache", "typert"];
|
|
3
|
+
const inject = ["workspaceRegistry", "sessionProjectionCache", "sessionPersistence", "typert"];
|
|
4
4
|
async function apply(ctx) {
|
|
5
5
|
tolerateStaleFileUploadResolver(ctx);
|
|
6
6
|
const registry = ctx.workspaceRegistry;
|
|
@@ -23,9 +23,9 @@ async function apply(ctx) {
|
|
|
23
23
|
};
|
|
24
24
|
try {
|
|
25
25
|
try {
|
|
26
|
-
uninstallCache = await installArchiveProjectionCache(cache);
|
|
26
|
+
uninstallCache = await installArchiveProjectionCache(cache, Reflect.get(ctx, "sessionPersistence"));
|
|
27
27
|
} catch (error) {
|
|
28
|
-
ctx.logger?.warn?.(`archive-manager: projection cache
|
|
28
|
+
ctx.logger?.warn?.(`archive-manager: projection cache handoff skipped: ${String(error)}`);
|
|
29
29
|
uninstallCache = installArchiveProjectionCacheGuards(cache);
|
|
30
30
|
}
|
|
31
31
|
unbindRemote = bindArchiveManagerRemote(ctx);
|
package/lib/projcache.js
CHANGED
|
@@ -151,156 +151,146 @@ function normalizeLegacyRecord(record) {
|
|
|
151
151
|
checkpointRecord.parse(normalized);
|
|
152
152
|
return normalized;
|
|
153
153
|
}
|
|
154
|
-
|
|
154
|
+
function sameIdentity(left, right) {
|
|
155
|
+
return left.createdAt === right.createdAt && left.cwd === right.cwd && left.formatVersion === right.formatVersion && (left.isSeeded ?? false) === (right.isSeeded ?? false) && (left.inheritedEventCount ?? 0) === (right.inheritedEventCount ?? 0);
|
|
156
|
+
}
|
|
157
|
+
function mergeCheckpoint(current, incoming) {
|
|
158
|
+
if (current !== void 0 && incoming.identity.formatVersion === void 0 && current.identity.formatVersion !== void 0 && current.identity.createdAt === incoming.identity.createdAt && current.identity.cwd === incoming.identity.cwd && (current.identity.isSeeded ?? false) === (incoming.identity.isSeeded ?? false)) return current;
|
|
159
|
+
if (current === void 0 || !sameIdentity(current.identity, incoming.identity)) return incoming;
|
|
160
|
+
const rows = { ...current.rows };
|
|
161
|
+
let changed = false;
|
|
162
|
+
for (const [key, row] of Object.entries(incoming.rows)) {
|
|
163
|
+
const previous = rows[key];
|
|
164
|
+
if (previous === void 0 || previous.ver === row.ver && previous.seq < row.seq) {
|
|
165
|
+
rows[key] = row;
|
|
166
|
+
changed = true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return changed ? { identity: current.identity, rows } : current;
|
|
170
|
+
}
|
|
171
|
+
async function importPreviousProjectionCache(ctx, target, persistence, guards) {
|
|
172
|
+
if (persistence === void 0) return 0;
|
|
173
|
+
const sources = [
|
|
174
|
+
await readLegacySafeRecords(ctx, safeProjectionCacheDomainSpec, "archive-manager current"),
|
|
175
|
+
await readLegacySafeRecords(ctx, legacySafeV2ProjectionCacheDomainSpec, "archive-manager v2"),
|
|
176
|
+
// 逐记录后端会把不同版本当缺行,打开成功也须单独读取 v1。
|
|
177
|
+
await readLegacySafeRecords(ctx, legacySafeProjectionCacheDomainSpec, "archive-manager v1")
|
|
178
|
+
];
|
|
155
179
|
let imported = 0;
|
|
156
|
-
for (const [
|
|
157
|
-
if (
|
|
180
|
+
for (const source of sources) for (const [id, raw] of source.records) {
|
|
181
|
+
if (projectionCacheDomainSpec.layout === "per-record" && !/^[A-Za-z0-9_-]+$/.test(id)) continue;
|
|
182
|
+
guards?.revisions.set(id, 0);
|
|
183
|
+
const revision = 0;
|
|
184
|
+
const unchanged = () => !guards?.deleted.has(id) && (guards?.revisions.get(id) ?? 0) === revision;
|
|
158
185
|
try {
|
|
159
|
-
const
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
|
|
186
|
+
const record = normalizeLegacyRecord(raw);
|
|
187
|
+
if (record === void 0 || !unchanged()) continue;
|
|
188
|
+
const snapshot = await persistence.stat(id);
|
|
189
|
+
const header = snapshot?.header;
|
|
190
|
+
if (!header || header.id !== id || header.createdAt !== record.identity.createdAt || header.cwd !== record.identity.cwd || (header.isSeeded ?? false) !== (record.identity.isSeeded ?? false) || record.identity.formatVersion !== void 0 && record.identity.formatVersion !== header.version) continue;
|
|
191
|
+
if (header.isSeeded) {
|
|
192
|
+
if (!persistence.open) continue;
|
|
193
|
+
const handle = await persistence.open(id, "read");
|
|
194
|
+
try {
|
|
195
|
+
if (handle.inheritedEventCount !== record.identity.inheritedEventCount) continue;
|
|
196
|
+
} finally {
|
|
197
|
+
await handle.close();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (!unchanged()) continue;
|
|
201
|
+
const existing = target.get(id);
|
|
202
|
+
if (existing !== void 0 && mergeCheckpoint(existing, record) === existing) continue;
|
|
203
|
+
try {
|
|
204
|
+
await target.update(id, (current) => unchanged() ? mergeCheckpoint(current, record) : current);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (!(error instanceof Error) || Reflect.get(error, "code") !== "missing-key") throw error;
|
|
207
|
+
if (!unchanged()) continue;
|
|
208
|
+
await target.put(id, record);
|
|
163
209
|
}
|
|
164
|
-
await target.put(sessionId, normalized);
|
|
165
210
|
imported += 1;
|
|
166
211
|
} catch (error) {
|
|
167
|
-
ctx.logger.warn(`archive-manager projcache:
|
|
212
|
+
ctx.logger.warn(`archive-manager projcache: historical row "${id}" handoff failed: ${String(error)}`);
|
|
213
|
+
} finally {
|
|
214
|
+
guards?.revisions.delete(id);
|
|
168
215
|
}
|
|
169
216
|
}
|
|
170
217
|
return imported;
|
|
171
218
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const legacySafeV2 = await readLegacySafeRecords(ctx, legacySafeV2ProjectionCacheDomainSpec, "archive-manager v2");
|
|
175
|
-
const legacySafe = await readLegacySafeRecords(ctx, legacySafeProjectionCacheDomainSpec, "archive-manager v1");
|
|
176
|
-
const legacy = sameSpec && currentTable !== void 0 ? { opened: true, records: [...currentTable.entries()] } : await readSourceRecords(ctx, legacyProjectionCacheDomainSpec, "legacy v3");
|
|
177
|
-
let imported = 0;
|
|
178
|
-
if (legacySafeV2.opened) imported += await importMissingRecords(ctx, target, legacySafeV2.records, "archive-manager v2");
|
|
179
|
-
if (legacySafe.opened) imported += await importMissingRecords(ctx, target, legacySafe.records, "archive-manager v1");
|
|
180
|
-
if (!sameSpec) {
|
|
181
|
-
const current = currentTable !== void 0 ? { opened: true, records: [...currentTable.entries()] } : await readSourceRecords(ctx, projectionCacheDomainSpec, "current");
|
|
182
|
-
if (current.opened) imported += await importMissingRecords(ctx, target, current.records, "current");
|
|
183
|
-
}
|
|
184
|
-
if (legacy.opened) imported += await importMissingRecords(ctx, target, legacy.records, "legacy v3");
|
|
185
|
-
return imported;
|
|
186
|
-
}
|
|
187
|
-
var ArchiveProjectionCache = class extends CacheBase {
|
|
188
|
-
/** 已永久删除的会话:其投影缓存行不再允许写入。 */
|
|
189
|
-
deletedSessionIds = /* @__PURE__ */ new Set();
|
|
190
|
-
/** 墓碑插入顺序,用于在上限处淘汰最旧项。 */
|
|
191
|
-
deletedSessionOrder = [];
|
|
192
|
-
deletedSessionTombstoneLimit = 4096;
|
|
193
|
-
/** 在途写入的队尾(只含已落定的 promise)。 */
|
|
194
|
-
writeTail = Promise.resolve();
|
|
195
|
-
constructor(ctx, config) {
|
|
196
|
-
super(ctx, config);
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Replace the official cache domain with the path-safe compatibility
|
|
200
|
-
* domain, then preserve the upstream listener/write implementation.
|
|
201
|
-
*/
|
|
219
|
+
class ArchiveProjectionCache extends CacheBase {
|
|
220
|
+
static inject = [...SessionProjectionCache.inject, "sessionPersistence"];
|
|
202
221
|
async [Service.init]() {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
this.table = table;
|
|
207
|
-
await importPreviousProjectionCache(this.ctx, table);
|
|
208
|
-
this.installWritePath();
|
|
209
|
-
}
|
|
210
|
-
/** 跟踪公开写入路径,避免依赖上游私有 `flushSoft` 的实现细节。 */
|
|
211
|
-
write(session) {
|
|
212
|
-
const task = this.writeCore(session);
|
|
213
|
-
this.writeTail = Promise.allSettled([this.writeTail, task]).then(() => void 0);
|
|
214
|
-
return task;
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* 墓碑正确性依赖:super.write(session) 一次整体写入,返回后不再有后续异步落盘。
|
|
218
|
-
* 若上游改成多阶段异步,(C) 补删会漏掉后续写入,deletedSessionIds 挡不住复活。
|
|
219
|
-
*/
|
|
220
|
-
async writeCore(session) {
|
|
221
|
-
if (this.deletedSessionIds.has(session.id)) return;
|
|
222
|
-
await super.write(session);
|
|
223
|
-
if (this.deletedSessionIds.has(session.id)) await this.requireTable().delete(session.id);
|
|
224
|
-
}
|
|
225
|
-
put(id, identity, rows) {
|
|
226
|
-
if (this.deletedSessionIds.has(id)) return Promise.resolve();
|
|
227
|
-
const task = this.putCore(id, identity, rows);
|
|
228
|
-
this.writeTail = Promise.allSettled([this.writeTail, task]).then(() => void 0);
|
|
229
|
-
return task;
|
|
230
|
-
}
|
|
231
|
-
async putCore(id, identity, rows) {
|
|
232
|
-
await super.put(id, identity, rows);
|
|
233
|
-
if (this.deletedSessionIds.has(id)) await this.requireTable().delete(id);
|
|
222
|
+
await super[Service.init]();
|
|
223
|
+
const uninstall = await installArchiveProjectionCache(this, Reflect.get(this.ctx, "sessionPersistence"));
|
|
224
|
+
this.ctx.effect(() => uninstall, "archive-manager.projcacheGuards");
|
|
234
225
|
}
|
|
235
|
-
|
|
236
|
-
* 全部被跟踪的在途写入落定后 resolve(含失败)。空闲缓存上调用立即返回。
|
|
237
|
-
* @returns 跟踪写入落定后的 resolution。
|
|
238
|
-
*/
|
|
239
|
-
whenIdle() {
|
|
240
|
-
return this.writeTail;
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* 永久移除一个会话的缓存投影行。
|
|
244
|
-
* 同时清理当前表与底层原生表(如官方 session_projcache 域),防止残留孤儿文件。
|
|
245
|
-
* @param id - 要删除缓存行的会话。
|
|
246
|
-
* @returns 行删除完成后的 resolution。
|
|
247
|
-
*/
|
|
248
|
-
async delete(id) {
|
|
249
|
-
trackTombstone(this.deletedSessionIds, this.deletedSessionOrder, id, this.deletedSessionTombstoneLimit);
|
|
250
|
-
const tasks = [this.requireTable().delete(id)];
|
|
251
|
-
const secondary = this.secondaryTable;
|
|
252
|
-
if (secondary !== void 0 && typeof secondary.delete === "function") {
|
|
253
|
-
tasks.push(secondary.delete(id).catch(() => false));
|
|
254
|
-
}
|
|
255
|
-
await Promise.all(tasks);
|
|
256
|
-
}
|
|
257
|
-
/** 撤销墓碑(供测试与同 id 新生命周期复用路径使用)。 */
|
|
258
|
-
clearTombstone(id) {
|
|
259
|
-
this.deletedSessionIds.delete(id);
|
|
260
|
-
const idx = this.deletedSessionOrder.indexOf(id);
|
|
261
|
-
if (idx !== -1) this.deletedSessionOrder.splice(idx, 1);
|
|
262
|
-
}
|
|
263
|
-
};
|
|
226
|
+
}
|
|
264
227
|
const projectionGuards = /* @__PURE__ */ new WeakMap();
|
|
265
|
-
function installArchiveProjectionCacheGuards(cache
|
|
228
|
+
function installArchiveProjectionCacheGuards(cache) {
|
|
266
229
|
const target = Reflect.get(cache, symbols.original) ?? cache;
|
|
267
230
|
const existing = projectionGuards.get(target);
|
|
268
|
-
if (existing) return existing;
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
};
|
|
277
|
-
const receiver = Object.create(target, Object.fromEntries(Object.entries(fields).map(([name, value]) => [name, { value, writable: true, configurable: true }])));
|
|
278
|
-
const restoredMethods = [];
|
|
231
|
+
if (existing) return existing.uninstall;
|
|
232
|
+
const table = target.requireTable();
|
|
233
|
+
const originalWrite = target.write;
|
|
234
|
+
const originalPut = target.put;
|
|
235
|
+
const deleted = /* @__PURE__ */ new Set();
|
|
236
|
+
const order = [];
|
|
237
|
+
const revisions = /* @__PURE__ */ new Map();
|
|
238
|
+
let tail = Promise.resolve();
|
|
279
239
|
let active = true;
|
|
240
|
+
const track = (task) => {
|
|
241
|
+
tail = Promise.allSettled([tail, task]).then(() => void 0);
|
|
242
|
+
return task;
|
|
243
|
+
};
|
|
244
|
+
const changed = (id) => {
|
|
245
|
+
if (revisions.has(id)) revisions.set(id, revisions.get(id) + 1);
|
|
246
|
+
};
|
|
247
|
+
const put = (id, identity, rows) => {
|
|
248
|
+
changed(id);
|
|
249
|
+
if (deleted.has(id)) return Promise.resolve();
|
|
250
|
+
return track(Reflect.apply(originalPut, receiver, [id, identity, rows]));
|
|
251
|
+
};
|
|
252
|
+
const writeTable = Object.create(table, { put: { value: (id, record) => deleted.has(id) ? Promise.resolve() : table.put(id, record) } });
|
|
253
|
+
const receiver = Object.create(target, {
|
|
254
|
+
put: { value: put, configurable: true },
|
|
255
|
+
requireTable: { value: () => writeTable }
|
|
256
|
+
});
|
|
257
|
+
const methods = {
|
|
258
|
+
write(session) {
|
|
259
|
+
if (deleted.has(session.id)) return Promise.resolve();
|
|
260
|
+
return track(Reflect.apply(originalWrite, receiver, [session]));
|
|
261
|
+
},
|
|
262
|
+
put,
|
|
263
|
+
delete(id) {
|
|
264
|
+
changed(id);
|
|
265
|
+
trackTombstone(deleted, order, id, 4096);
|
|
266
|
+
return track(table.delete(id).then(() => void 0));
|
|
267
|
+
},
|
|
268
|
+
whenIdle: () => tail,
|
|
269
|
+
clearTombstone(id) {
|
|
270
|
+
changed(id);
|
|
271
|
+
deleted.delete(id);
|
|
272
|
+
const index = order.indexOf(id);
|
|
273
|
+
if (index !== -1) order.splice(index, 1);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const restored = [];
|
|
280
277
|
const restore = () => {
|
|
281
278
|
active = false;
|
|
282
|
-
for (const
|
|
283
|
-
if (
|
|
284
|
-
|
|
279
|
+
for (const { name, descriptor, wrapper } of [...restored].reverse()) {
|
|
280
|
+
if (target[name] !== wrapper) continue;
|
|
281
|
+
if (descriptor) Object.defineProperty(target, name, descriptor);
|
|
282
|
+
else delete target[name];
|
|
285
283
|
}
|
|
286
284
|
};
|
|
287
285
|
try {
|
|
288
|
-
for (const name of Object.
|
|
289
|
-
if (name === "constructor") continue;
|
|
290
|
-
const method = Reflect.get(ArchiveProjectionCache.prototype, name);
|
|
291
|
-
if (typeof method !== "function") continue;
|
|
292
|
-
Object.defineProperty(receiver, name, { value: method, writable: true, configurable: true });
|
|
286
|
+
for (const [name, method] of Object.entries(methods)) {
|
|
293
287
|
const previous = target[name];
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (typeof previous === "function") return Reflect.apply(previous, target, args);
|
|
298
|
-
throw new Error("archive-manager: projection cache guards are stopped");
|
|
299
|
-
}
|
|
300
|
-
const result = Reflect.apply(method, receiver, args);
|
|
301
|
-
if (name === "delete") receiver.writeTail = Promise.allSettled([receiver.writeTail, result]).then(() => void 0);
|
|
302
|
-
return result;
|
|
288
|
+
const wrapper = (...args) => {
|
|
289
|
+
if (!active && typeof previous === "function") return Reflect.apply(previous, target, args);
|
|
290
|
+
return Reflect.apply(method, target, args);
|
|
303
291
|
};
|
|
292
|
+
restored.push({ name, descriptor: Object.getOwnPropertyDescriptor(target, name), wrapper });
|
|
293
|
+
target[name] = wrapper;
|
|
304
294
|
}
|
|
305
295
|
} catch (error) {
|
|
306
296
|
restore();
|
|
@@ -309,50 +299,22 @@ function installArchiveProjectionCacheGuards(cache, table, secondaryTable) {
|
|
|
309
299
|
let disposal;
|
|
310
300
|
const uninstall = () => {
|
|
311
301
|
if (disposal) return disposal;
|
|
312
|
-
const drain =
|
|
302
|
+
const drain = tail;
|
|
313
303
|
restore();
|
|
314
304
|
projectionGuards.delete(target);
|
|
315
305
|
return disposal = drain;
|
|
316
306
|
};
|
|
317
|
-
projectionGuards.set(target, uninstall);
|
|
307
|
+
projectionGuards.set(target, { uninstall, deleted, revisions });
|
|
318
308
|
return uninstall;
|
|
319
309
|
}
|
|
320
|
-
|
|
321
|
-
async function installArchiveProjectionCache(cache) {
|
|
310
|
+
async function installArchiveProjectionCache(cache, persistence) {
|
|
322
311
|
const target = Reflect.get(cache, symbols.original) ?? cache;
|
|
323
|
-
|
|
324
|
-
};
|
|
325
|
-
const domain = await target.ctx.storageDomain.open(safeProjectionCacheDomainSpec);
|
|
326
|
-
let installed = false;
|
|
327
|
-
const previousTable = target.table;
|
|
328
|
-
let uninstallGuards;
|
|
312
|
+
const uninstall = installArchiveProjectionCacheGuards(cache);
|
|
329
313
|
try {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
uninstallGuards = installArchiveProjectionCacheGuards(cache, table, previousTable);
|
|
333
|
-
target.table = table;
|
|
334
|
-
target[projectionOverlay] = true;
|
|
335
|
-
installed = true;
|
|
336
|
-
let disposal;
|
|
337
|
-
return () => disposal ??= (async () => {
|
|
338
|
-
if (target[projectionOverlay] !== true) return;
|
|
339
|
-
const drain = uninstallGuards();
|
|
340
|
-
target.table = previousTable;
|
|
341
|
-
delete target[projectionOverlay];
|
|
342
|
-
await drain;
|
|
343
|
-
await domain.close();
|
|
344
|
-
})();
|
|
314
|
+
await importPreviousProjectionCache(target.ctx, target.requireTable(), persistence, projectionGuards.get(target));
|
|
315
|
+
return uninstall;
|
|
345
316
|
} catch (error) {
|
|
346
|
-
|
|
347
|
-
const drain = uninstallGuards?.();
|
|
348
|
-
target.table = previousTable;
|
|
349
|
-
delete target[projectionOverlay];
|
|
350
|
-
try {
|
|
351
|
-
await drain;
|
|
352
|
-
} finally {
|
|
353
|
-
await domain.close();
|
|
354
|
-
}
|
|
355
|
-
}
|
|
317
|
+
await uninstall();
|
|
356
318
|
throw error;
|
|
357
319
|
}
|
|
358
320
|
}
|
package/lib/session-repair.js
CHANGED
|
@@ -5,11 +5,45 @@ import { join, resolve, basename, dirname } from "node:path";
|
|
|
5
5
|
import * as zlib from "node:zlib";
|
|
6
6
|
import { classifySessionError } from "./archive-discovery.js";
|
|
7
7
|
const LIMIT = 32 * 1024 * 1024;
|
|
8
|
+
function scanFirstZstdFrame(buffer) {
|
|
9
|
+
if (buffer.length < 4 || buffer.readUInt32LE(0) !== 4247762216) return void 0;
|
|
10
|
+
let offset = 4;
|
|
11
|
+
if (offset >= buffer.length) return void 0;
|
|
12
|
+
const descriptor = buffer.readUInt8(offset++);
|
|
13
|
+
if ((descriptor & 24) !== 0) return void 0;
|
|
14
|
+
const contentSizeFlag = descriptor >>> 6;
|
|
15
|
+
const singleSegment = (descriptor & 32) !== 0;
|
|
16
|
+
const checksum = (descriptor & 4) !== 0;
|
|
17
|
+
const dictionaryFlag = descriptor & 3;
|
|
18
|
+
const dictionaryBytes = dictionaryFlag === 3 ? 4 : dictionaryFlag;
|
|
19
|
+
const contentSizeBytes = contentSizeFlag === 0 ? singleSegment ? 1 : 0 : 1 << contentSizeFlag;
|
|
20
|
+
const remainingHeaderBytes = (singleSegment ? 0 : 1) + dictionaryBytes + contentSizeBytes;
|
|
21
|
+
if (buffer.length - offset < remainingHeaderBytes) return void 0;
|
|
22
|
+
offset += remainingHeaderBytes;
|
|
23
|
+
for (; ; ) {
|
|
24
|
+
if (buffer.length - offset < 3) return void 0;
|
|
25
|
+
const blockHeader = buffer.readUIntLE(offset, 3);
|
|
26
|
+
offset += 3;
|
|
27
|
+
const lastBlock = (blockHeader & 1) !== 0;
|
|
28
|
+
const blockType = blockHeader >>> 1 & 3;
|
|
29
|
+
const blockSize = blockHeader >>> 3;
|
|
30
|
+
if (blockType === 3) return void 0;
|
|
31
|
+
const payloadBytes = blockType === 1 ? 1 : blockSize;
|
|
32
|
+
if (buffer.length - offset < payloadBytes) return void 0;
|
|
33
|
+
offset += payloadBytes;
|
|
34
|
+
if (lastBlock) break;
|
|
35
|
+
}
|
|
36
|
+
if (checksum) {
|
|
37
|
+
if (buffer.length - offset < 4) return void 0;
|
|
38
|
+
offset += 4;
|
|
39
|
+
}
|
|
40
|
+
return { start: 0, end: offset };
|
|
41
|
+
}
|
|
8
42
|
function decompressFrame(bytes, maxOutputLength) {
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
return { buffer
|
|
43
|
+
const frame = scanFirstZstdFrame(bytes);
|
|
44
|
+
const target = frame ? bytes.subarray(0, frame.end) : bytes;
|
|
45
|
+
const buffer = zlib.zstdDecompressSync(target, { maxOutputLength });
|
|
46
|
+
return { buffer, engine: { bytesWritten: frame ? frame.end : bytes.length } };
|
|
13
47
|
}
|
|
14
48
|
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
15
49
|
function decodeRepairLog(bytes, compressed) {
|
|
@@ -170,6 +204,8 @@ export {
|
|
|
170
204
|
childCatalogFact,
|
|
171
205
|
classifySessionError,
|
|
172
206
|
decodeRepairLog,
|
|
207
|
+
decompressFrame,
|
|
173
208
|
normalizeAutomationSources,
|
|
174
|
-
prepareAutomationRepair
|
|
209
|
+
prepareAutomationRepair,
|
|
210
|
+
scanFirstZstdFrame
|
|
175
211
|
};
|