Skip to content

Commit 4eafe52

Browse files
authored
Merge pull request #226 from ducflair/dev
Dev
2 parents 8d13029 + 7aa7b5e commit 4eafe52

36 files changed

Lines changed: 618 additions & 430 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<p align="center">
22
<br/>
3-
<a href="https://duc.ducflair.com" target="_blank"><img width="256px" src="https://raw.githubusercontent.com/ducflair/assets/refs/heads/main/src/duc/duc-extended.png" /></a>
3+
<a href="https://duc.ducflair.com" target="_blank"><img width="256px" src="https://cdn.jsdelivr.net/gh/ducflair/assets@main/src/duc/duc-extended.png" /></a>
44
<p align="center">2D CAD File Format</p>
55
<br/><br/>
66
<p align="center" style="align: center;">

packages/ducjs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<p align="center">
44
<br/>
5-
<a href="https://duc.ducflair.com" target="_blank"><img width="256px" src="https://raw.githubusercontent.com/ducflair/assets/refs/heads/main/src/duc/duc-extended.png" /></a>
5+
<a href="https://duc.ducflair.com" target="_blank"><img width="256px" src="https://cdn.jsdelivr.net/gh/ducflair/assets@main/src/duc/duc-extended.png" /></a>
66
<p align="center">2D CAD File Format</p>
77
<p align="center" style="align: center;">
88
<a href="https://www.npmjs.com/package/ducjs"><img src="https://shields.io/badge/NPM-cc3534?logo=Npm&logoColor=white&style=round-square" alt="NPM" /></a>

packages/ducjs/src/enums.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,6 @@ export enum IMAGE_STATUS {
110110
ERROR = 12,
111111
}
112112

113-
export enum PRUNING_LEVEL {
114-
CONSERVATIVE = 10,
115-
BALANCED = 20,
116-
AGGRESSIVE = 30,
117-
}
118-
119113
export enum BOOLEAN_OPERATION {
120114
UNION = 10,
121115
SUBTRACT = 11,

packages/ducjs/src/lazy-files.ts

Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { DucExternalFile, DucExternalFiles, ExternalFileId, ExternalFileRevision } from "./types";
1+
import type { DucExternalFile, DucExternalFiles, ExternalFileId, ExternalFileLoaded, ResolvedFileData, ExternalFilesData } from "./types";
22
import { wasmGetExternalFile, wasmListExternalFiles } from "./wasm";
33

44
export type LazyFileMetadata = {
@@ -19,7 +19,7 @@ export type LazyFileMetadata = {
1919
export class LazyExternalFileStore {
2020
private buffer: Uint8Array | null;
2121
private metadataCache: Map<string, LazyFileMetadata> | null = null;
22-
private runtimeFiles: Map<string, DucExternalFile> = new Map();
22+
private runtimeFiles: Map<string, ExternalFileLoaded> = new Map();
2323

2424
constructor(buffer: Uint8Array) {
2525
this.buffer = buffer;
@@ -80,26 +80,30 @@ export class LazyExternalFileStore {
8080
}
8181

8282
/** Fetch the full file (including data blobs for all revisions) for a specific file. */
83-
getFile(fileId: string): DucExternalFile | null {
83+
getFile(fileId: string): ExternalFileLoaded | null {
8484
const rt = this.runtimeFiles.get(fileId);
8585
if (rt) return rt;
8686

8787
if (!this.buffer) return null;
8888
const result = wasmGetExternalFile(this.buffer, fileId);
8989
if (!result) return null;
9090

91-
return result as DucExternalFile;
91+
return result as ExternalFileLoaded;
9292
}
9393

9494
/** Get the active revision data for a specific file. */
95-
getFileData(fileId: string): ExternalFileRevision | null {
96-
const file = this.getFile(fileId);
97-
if (!file) return null;
98-
return file.revisions[file.activeRevisionId] ?? null;
95+
getFileData(fileId: string): ResolvedFileData | null {
96+
const loaded = this.getFile(fileId);
97+
if (!loaded) return null;
98+
const meta = loaded.revisions[loaded.activeRevisionId];
99+
if (!meta) return null;
100+
const dataBlob = loaded.data[loaded.activeRevisionId];
101+
if (!dataBlob) return null;
102+
return { data: dataBlob, mimeType: meta.mimeType };
99103
}
100104

101105
/** Fetch active revision data and return a copy of the data buffer (safe for transfer). */
102-
getFileDataCopy(fileId: string): ExternalFileRevision | null {
106+
getFileDataCopy(fileId: string): ResolvedFileData | null {
103107
const data = this.getFileData(fileId);
104108
if (!data) return null;
105109
return {
@@ -109,66 +113,119 @@ export class LazyExternalFileStore {
109113
}
110114

111115
/** Add a file at runtime (not persisted in .duc until next serialize). */
112-
addRuntimeFile(fileId: string, file: DucExternalFile): void {
113-
this.runtimeFiles.set(fileId, file);
116+
addRuntimeFile(fileId: string, file: DucExternalFile, data: Record<string, Uint8Array>): void {
117+
this.runtimeFiles.set(fileId, { ...file, data });
114118
}
115119

116120
/** Remove a runtime file. */
117121
removeRuntimeFile(fileId: string): boolean {
118122
return this.runtimeFiles.delete(fileId);
119123
}
120124

121-
/** Export all files eagerly as a DucExternalFiles record. */
125+
/** Export all files metadata as a DucExternalFiles record. */
122126
toExternalFiles(): DucExternalFiles {
123127
const result: DucExternalFiles = {};
124128

125129
if (this.buffer) {
126130
for (const [id] of this.getMetadataMap()) {
127-
const file = this.getFile(id);
128-
if (file) {
131+
const loaded = this.getFile(id);
132+
if (loaded) {
133+
const { data: _, ...file } = loaded;
129134
result[id as ExternalFileId] = file;
130135
}
131136
}
132137
}
133138

134-
for (const [id, file] of this.runtimeFiles) {
139+
for (const [id, loaded] of this.runtimeFiles) {
140+
const { data: _, ...file } = loaded;
135141
result[id as ExternalFileId] = file;
136142
}
137143

138144
return result;
139145
}
140146

141-
/** Merge files from another source. Adds missing files and merges new revisions into existing ones. */
142-
mergeFiles(files: DucExternalFiles): void {
143-
for (const [id, file] of Object.entries(files)) {
144-
if (!this.has(id)) {
145-
this.runtimeFiles.set(id, file);
146-
continue;
147+
/** Export all revision data blobs as an ExternalFilesData record. */
148+
toExternalFilesData(): ExternalFilesData {
149+
const result: ExternalFilesData = {};
150+
151+
if (this.buffer) {
152+
for (const [id] of this.getMetadataMap()) {
153+
const loaded = this.getFile(id);
154+
if (loaded) {
155+
for (const [revId, blob] of Object.entries(loaded.data)) {
156+
result[revId] = blob;
157+
}
158+
}
147159
}
160+
}
148161

162+
for (const [, loaded] of this.runtimeFiles) {
163+
for (const [revId, blob] of Object.entries(loaded.data)) {
164+
result[revId] = blob;
165+
}
166+
}
167+
168+
return result;
169+
}
170+
171+
/** Merge files from another source. Adds missing files and merges new revisions into existing ones. */
172+
mergeFiles(files: DucExternalFiles, filesData?: ExternalFilesData): void {
173+
for (const [id, file] of Object.entries(files)) {
149174
const existing = this.runtimeFiles.get(id) ?? this.getFile(id);
175+
150176
if (!existing) {
151-
this.runtimeFiles.set(id, file);
177+
const dataMap: Record<string, Uint8Array> = {};
178+
if (filesData) {
179+
for (const revId of Object.keys(file.revisions)) {
180+
if (filesData[revId]) {
181+
dataMap[revId] = filesData[revId];
182+
}
183+
}
184+
}
185+
this.runtimeFiles.set(id, { ...file, data: dataMap });
152186
continue;
153187
}
154188

155-
// Merge: add any new revisions that don't exist yet, and update metadata
156189
let merged = false;
157190
const mergedRevisions = { ...existing.revisions };
191+
const mergedData = { ...existing.data };
158192
for (const [revId, rev] of Object.entries(file.revisions)) {
159193
if (!mergedRevisions[revId]) {
194+
mergedRevisions[revId] = rev;
195+
if (filesData?.[revId]) {
196+
mergedData[revId] = filesData[revId];
197+
}
198+
merged = true;
199+
continue;
200+
}
201+
202+
if (filesData?.[revId] && mergedData[revId] !== filesData[revId]) {
203+
mergedData[revId] = filesData[revId];
204+
merged = true;
205+
}
206+
207+
if (
208+
rev.sizeBytes !== mergedRevisions[revId].sizeBytes ||
209+
rev.lastRetrieved !== mergedRevisions[revId].lastRetrieved ||
210+
rev.created !== mergedRevisions[revId].created ||
211+
rev.mimeType !== mergedRevisions[revId].mimeType ||
212+
rev.sourceName !== mergedRevisions[revId].sourceName ||
213+
rev.message !== mergedRevisions[revId].message
214+
) {
160215
mergedRevisions[revId] = rev;
161216
merged = true;
162217
}
163218
}
164219

165-
if (merged || file.updated > existing.updated) {
220+
const activeRevisionChanged = file.activeRevisionId !== existing.activeRevisionId;
221+
if (merged || activeRevisionChanged || file.updated > existing.updated) {
166222
this.runtimeFiles.set(id, {
167223
...existing,
168224
activeRevisionId: file.activeRevisionId,
169225
updated: Math.max(file.updated, existing.updated),
170226
version: Math.max(file.version ?? 0, existing.version ?? 0),
171227
revisions: mergedRevisions,
228+
data: mergedData,
172229
});
173230
}
174231
}

packages/ducjs/src/parse.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { restore, type ElementsConfig, type RestoreConfig, type RestoredDataState } from "./restore";
22
import { transformFromRust } from "./transform";
3-
import type { DucExternalFiles, ExportedDataState } from "./types";
3+
import type { DucExternalFiles, ExportedDataState, ExternalFilesData } from "./types";
44
import { ensureWasm, wasmParseDuc, wasmParseDucLazy } from "./wasm";
55

66
export type { RestoredDataState };
@@ -109,11 +109,13 @@ export async function parseDucLazy(
109109

110110
const lazyFileStore = new LazyExternalFileStore(buffer);
111111
const files: DucExternalFiles = {};
112+
const filesData: ExternalFilesData = {};
112113

113114
const restored = restore(
114115
{
115116
...data,
116117
files,
118+
filesData,
117119
// Preserve versionGraph from Rust separately; do not run it through restore()
118120
versionGraph: undefined,
119121
},

0 commit comments

Comments
 (0)