ducjs 2.2.3 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/flatbuffers/duc/document-grid-align-items.d.ts +5 -0
  2. package/dist/flatbuffers/duc/document-grid-align-items.js +8 -0
  3. package/dist/flatbuffers/duc/document-grid-config.d.ts +24 -0
  4. package/dist/flatbuffers/duc/document-grid-config.js +81 -0
  5. package/dist/flatbuffers/duc/duc-doc-element.d.ts +6 -0
  6. package/dist/flatbuffers/duc/duc-doc-element.js +16 -1
  7. package/dist/flatbuffers/duc/duc-model-element.d.ts +26 -0
  8. package/dist/flatbuffers/duc/duc-model-element.js +79 -0
  9. package/dist/flatbuffers/duc/duc-pdf-element.d.ts +3 -1
  10. package/dist/flatbuffers/duc/duc-pdf-element.js +9 -7
  11. package/dist/flatbuffers/duc/element.d.ts +5 -3
  12. package/dist/flatbuffers/duc/element.js +4 -0
  13. package/dist/flatbuffers/duc.d.ts +3 -0
  14. package/dist/flatbuffers/duc.js +3 -0
  15. package/dist/index.d.ts +4 -3
  16. package/dist/index.js +4 -3
  17. package/dist/lazy-files.d.ts +84 -0
  18. package/dist/lazy-files.js +207 -0
  19. package/dist/parse.d.ts +35 -2
  20. package/dist/parse.js +244 -14
  21. package/dist/restore/restoreDataState.d.ts +1 -6
  22. package/dist/restore/restoreDataState.js +4 -11
  23. package/dist/restore/restoreElements.js +51 -26
  24. package/dist/serialize.js +56 -27
  25. package/dist/types/elements/index.d.ts +32 -31
  26. package/dist/types/elements/typeChecks.d.ts +4 -1
  27. package/dist/types/elements/typeChecks.js +7 -1
  28. package/dist/types/index.d.ts +3 -3
  29. package/dist/types/index.js +1 -1
  30. package/dist/utils/constants.d.ts +18 -14
  31. package/dist/utils/constants.js +28 -15
  32. package/dist/utils/elements/freedrawElement.d.ts +6 -0
  33. package/dist/utils/elements/freedrawElement.js +28 -11
  34. package/dist/utils/elements/newElement.d.ts +2 -2
  35. package/dist/utils/elements/newElement.js +14 -5
  36. package/dist/utils/elements/textElement.d.ts +3 -3
  37. package/dist/utils/elements/textElement.js +43 -16
  38. package/dist/utils/state/index.js +1 -1
  39. package/package.json +1 -1
@@ -0,0 +1,207 @@
1
+ /**
2
+ * LazyExternalFileStore — Zero-copy, on-demand access to external file data from a FlatBuffer.
3
+ *
4
+ * Instead of eagerly parsing and copying every external file's binary data into JS memory,
5
+ * this store keeps a reference to the original FlatBuffer Uint8Array and reads file bytes
6
+ * only when explicitly requested. FlatBuffer `dataArray()` returns a zero-copy view
7
+ * (a Uint8Array pointing into the original buffer), so no allocation occurs until the
8
+ * consumer actually needs the data.
9
+ *
10
+ * Memory lifecycle:
11
+ * 1. On parse: only metadata (~200 bytes per file) enters JS heap.
12
+ * 2. On demand: `getFileData(fileId)` reads the zero-copy slice from the buffer.
13
+ * 3. The caller (renderer/worker) uses the data, then lets it GC naturally.
14
+ * 4. If the store is released, the buffer reference is dropped, freeing everything.
15
+ *
16
+ * This is the key to supporting 1000s of external files without RAM bloat.
17
+ */
18
+ var __rest = (this && this.__rest) || function (s, e) {
19
+ var t = {};
20
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
21
+ t[p] = s[p];
22
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
23
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
24
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
25
+ t[p[i]] = s[p[i]];
26
+ }
27
+ return t;
28
+ };
29
+ import * as flatbuffers from "flatbuffers";
30
+ import { ExportedDataState as ExportedDataStateFb } from "./flatbuffers/duc";
31
+ export class LazyExternalFileStore {
32
+ constructor(buffer) {
33
+ /** Map from file id → lazy entry */
34
+ this._entries = new Map();
35
+ /** Map from element key → file id (the external_files vector uses element id as key) */
36
+ this._keyToFileId = new Map();
37
+ /**
38
+ * Files that were added at runtime (e.g. user uploading a new image).
39
+ * These aren't in the original FlatBuffer so we hold their data directly.
40
+ */
41
+ this._runtimeFiles = new Map();
42
+ this._buffer = buffer;
43
+ this._byteBuffer = new flatbuffers.ByteBuffer(buffer);
44
+ this._dataState = ExportedDataStateFb.getRootAsExportedDataState(this._byteBuffer);
45
+ this._indexMetadata();
46
+ console.info(`[LazyExternalFileStore] indexed ${this._entries.size} files from ${buffer.byteLength} byte buffer, ids: [${[...this._entries.keys()].map(k => k.slice(0, 12)).join(', ')}]`);
47
+ }
48
+ _indexMetadata() {
49
+ if (!this._dataState)
50
+ return;
51
+ const count = this._dataState.externalFilesLength();
52
+ for (let i = 0; i < count; i++) {
53
+ const entry = this._dataState.externalFiles(i);
54
+ if (!entry)
55
+ continue;
56
+ const key = entry.key();
57
+ const fileData = entry.value();
58
+ if (!key || !fileData)
59
+ continue;
60
+ const id = fileData.id();
61
+ if (!id)
62
+ continue;
63
+ const metadata = {
64
+ id,
65
+ mimeType: fileData.mimeType() || "application/octet-stream",
66
+ created: Number(fileData.created()),
67
+ lastRetrieved: Number(fileData.lastRetrieved()) || undefined,
68
+ };
69
+ const lazyEntry = { metadata, vectorIndex: i };
70
+ this._entries.set(id, lazyEntry);
71
+ this._keyToFileId.set(key, id);
72
+ }
73
+ }
74
+ /** Total number of external files */
75
+ get size() {
76
+ return this._entries.size + this._runtimeFiles.size;
77
+ }
78
+ /** Whether a file with the given id exists */
79
+ has(fileId) {
80
+ return this._entries.has(fileId) || this._runtimeFiles.has(fileId);
81
+ }
82
+ /** Get metadata only (no binary data copied) — ~200 bytes per file */
83
+ getMetadata(fileId) {
84
+ var _a, _b;
85
+ const runtime = this._runtimeFiles.get(fileId);
86
+ if (runtime) {
87
+ const { data: _ } = runtime, meta = __rest(runtime, ["data"]);
88
+ return meta;
89
+ }
90
+ return (_b = (_a = this._entries.get(fileId)) === null || _a === void 0 ? void 0 : _a.metadata) !== null && _b !== void 0 ? _b : null;
91
+ }
92
+ /** Get all metadata entries (for UI listing, etc.) */
93
+ getAllMetadata() {
94
+ const result = {};
95
+ for (const [id, entry] of this._entries) {
96
+ result[id] = entry.metadata;
97
+ }
98
+ for (const [id, file] of this._runtimeFiles) {
99
+ const { data: _ } = file, meta = __rest(file, ["data"]);
100
+ result[id] = meta;
101
+ }
102
+ return result;
103
+ }
104
+ /**
105
+ * Get full file data (metadata + binary bytes) ON DEMAND.
106
+ *
107
+ * For files from the original FlatBuffer, this returns a zero-copy Uint8Array
108
+ * view into the original buffer — no allocation for the file bytes themselves.
109
+ * The view is valid as long as this store hasn't been released.
110
+ *
111
+ * For runtime-added files, returns the data directly.
112
+ */
113
+ getFileData(fileId) {
114
+ const runtime = this._runtimeFiles.get(fileId);
115
+ if (runtime)
116
+ return runtime;
117
+ const entry = this._entries.get(fileId);
118
+ if (!entry || !this._dataState)
119
+ return null;
120
+ const fbEntry = this._dataState.externalFiles(entry.vectorIndex);
121
+ if (!fbEntry)
122
+ return null;
123
+ const fileData = fbEntry.value();
124
+ if (!fileData)
125
+ return null;
126
+ const data = fileData.dataArray();
127
+ if (!data)
128
+ return null;
129
+ return Object.assign(Object.assign({}, entry.metadata), { data });
130
+ }
131
+ /**
132
+ * Get a detached copy of the file data (allocates new ArrayBuffer).
133
+ * Use this when you need to transfer data to a worker or keep it beyond store lifetime.
134
+ */
135
+ getFileDataCopy(fileId) {
136
+ const fileDataRef = this.getFileData(fileId);
137
+ if (!fileDataRef)
138
+ return null;
139
+ return Object.assign(Object.assign({}, fileDataRef), { data: new Uint8Array(fileDataRef.data) });
140
+ }
141
+ /**
142
+ * Add a file at runtime (user upload, paste, etc.).
143
+ * These files are held in memory since they aren't in the FlatBuffer.
144
+ */
145
+ addRuntimeFile(fileData) {
146
+ this._runtimeFiles.set(fileData.id, fileData);
147
+ }
148
+ /** Remove a runtime-added file */
149
+ removeRuntimeFile(fileId) {
150
+ this._runtimeFiles.delete(fileId);
151
+ }
152
+ /**
153
+ * Export all files as a standard DucExternalFiles record.
154
+ * This COPIES all file data eagerly — use only for serialization.
155
+ */
156
+ toExternalFiles() {
157
+ const result = {};
158
+ for (const [key, fileId] of this._keyToFileId) {
159
+ const fileData = this.getFileData(fileId);
160
+ if (fileData) {
161
+ result[key] = fileData;
162
+ }
163
+ }
164
+ for (const [id, file] of this._runtimeFiles) {
165
+ result[id] = file;
166
+ }
167
+ return result;
168
+ }
169
+ /**
170
+ * Merge runtime files from the given DucExternalFiles map.
171
+ * Only adds files not already present in the store.
172
+ */
173
+ mergeFiles(files) {
174
+ for (const [_key, fileData] of Object.entries(files)) {
175
+ if (!this.has(fileData.id)) {
176
+ this.addRuntimeFile(fileData);
177
+ }
178
+ }
179
+ }
180
+ /** Estimated RAM usage for metadata only (not counting the backing buffer) */
181
+ get estimatedMetadataBytes() {
182
+ var _a, _b;
183
+ let bytes = 0;
184
+ for (const [, entry] of this._entries) {
185
+ bytes += 200 + entry.metadata.id.length * 2 + entry.metadata.mimeType.length * 2;
186
+ }
187
+ for (const [, file] of this._runtimeFiles) {
188
+ bytes += 200 + ((_b = (_a = file.data) === null || _a === void 0 ? void 0 : _a.byteLength) !== null && _b !== void 0 ? _b : 0);
189
+ }
190
+ return bytes;
191
+ }
192
+ /**
193
+ * Release the FlatBuffer reference. After this, only runtime-added files remain accessible.
194
+ * Call this when switching documents or when the store is no longer needed.
195
+ */
196
+ release() {
197
+ this._buffer = null;
198
+ this._byteBuffer = null;
199
+ this._dataState = null;
200
+ this._entries.clear();
201
+ this._keyToFileId.clear();
202
+ }
203
+ /** Whether the store has been released */
204
+ get isReleased() {
205
+ return this._buffer === null;
206
+ }
207
+ }
package/dist/parse.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { FileSystemHandle } from 'browser-fs-access';
2
- import { CustomHatchPattern as CustomHatchPatternFb, DimensionToleranceStyle as DimensionToleranceStyleFb, DucBlock as DucBlockFb, DucDimensionStyle as DucDimensionStyleFb, DucDocStyle as DucDocStyleFb, DucExternalFileEntry, DucFeatureControlFrameStyle as DucFeatureControlFrameStyleFb, DucGlobalState as DucGlobalStateFb, DucGroup as DucGroupFb, DucHatchStyle as DucHatchStyleFb, DucHead as DucHeadFb, DucImageFilter as DucImageFilterFb, DucLayer as DucLayerFb, DucLeaderStyle as DucLeaderStyleFb, DucLine as DucLineFb, DucLineReference as DucLineReferenceFb, DucLocalState as DucLocalStateFb, DucPath as DucPathFb, DucPlotStyle as DucPlotStyleFb, DucPointBinding as DucPointBindingFb, DucPoint as DucPointFb, DucRegion as DucRegionFb, DucStackLikeStyles as DucStackLikeStylesFb, DucTableCellStyle as DucTableCellStyleFb, DucTableStyle as DucTableStyleFb, DucTextStyle as DucTextStyleFb, DucViewportStyle as DucViewportStyleFb, DucXRayStyle as DucXRayStyleFb, ElementBackground as ElementBackgroundFb, ElementContentBase as ElementContentBaseFb, ElementStroke as ElementStrokeFb, ElementWrapper, ExportedDataState as ExportedDataStateFb, GeometricPoint as GeometricPointFb, HatchPatternLine as HatchPatternLineFb, Margins as MarginsFb, PrimaryUnits as PrimaryUnitsFb, Standard as StandardFb, StrokeSides as StrokeSidesFb, StrokeStyle as StrokeStyleFb, TilingProperties as TilingPropertiesFb, VersionGraph as VersionGraphFb, _DucElementBase as _DucElementBaseFb, _DucElementStylesBase as _DucElementStylesBaseFb, _DucLinearElementBase as _DucLinearElementBaseFb, _DucStackBase as _DucStackBaseFb, _DucStackElementBase as _DucStackElementBaseFb } from "./flatbuffers/duc";
2
+ import { CustomHatchPattern as CustomHatchPatternFb, DimensionToleranceStyle as DimensionToleranceStyleFb, DocumentGridConfig as DocumentGridConfigFb, DucBlock as DucBlockFb, DucDimensionStyle as DucDimensionStyleFb, DucDocStyle as DucDocStyleFb, DucExternalFileEntry, DucFeatureControlFrameStyle as DucFeatureControlFrameStyleFb, DucGlobalState as DucGlobalStateFb, DucGroup as DucGroupFb, DucHatchStyle as DucHatchStyleFb, DucHead as DucHeadFb, DucImageFilter as DucImageFilterFb, DucLayer as DucLayerFb, DucLeaderStyle as DucLeaderStyleFb, DucLine as DucLineFb, DucLineReference as DucLineReferenceFb, DucLocalState as DucLocalStateFb, DucPath as DucPathFb, DucPlotStyle as DucPlotStyleFb, DucPointBinding as DucPointBindingFb, DucPoint as DucPointFb, DucRegion as DucRegionFb, DucStackLikeStyles as DucStackLikeStylesFb, DucTableCellStyle as DucTableCellStyleFb, DucTableStyle as DucTableStyleFb, DucTextStyle as DucTextStyleFb, DucViewportStyle as DucViewportStyleFb, DucXRayStyle as DucXRayStyleFb, ElementBackground as ElementBackgroundFb, ElementContentBase as ElementContentBaseFb, ElementStroke as ElementStrokeFb, ElementWrapper, ExportedDataState as ExportedDataStateFb, GeometricPoint as GeometricPointFb, HatchPatternLine as HatchPatternLineFb, Margins as MarginsFb, PrimaryUnits as PrimaryUnitsFb, Standard as StandardFb, StrokeSides as StrokeSidesFb, StrokeStyle as StrokeStyleFb, TilingProperties as TilingPropertiesFb, VersionGraph as VersionGraphFb, _DucElementBase as _DucElementBaseFb, _DucElementStylesBase as _DucElementStylesBaseFb, _DucLinearElementBase as _DucLinearElementBaseFb, _DucStackBase as _DucStackBaseFb, _DucStackElementBase as _DucStackElementBaseFb } from "./flatbuffers/duc";
3
3
  import { RestoreConfig, RestoredDataState } from "./restore";
4
4
  import { Standard, StandardUnits } from "./technical";
5
- import { CustomHatchPattern, Dictionary, DucBlock, DucDimensionStyle, DucDocStyle, DucElement, DucExternalFiles, DucFeatureControlFrameStyle, DucGlobalState, DucGroup, DucHatchStyle, DucHead, DucImageFilter, DucLayer, DucLeaderStyle, DucLine, DucLineReference, DucLocalState, DucPath, DucPlotStyle, DucPoint, DucPointBinding, DucRegion, DucStackLikeStyles, DucTableCellStyle, DucTableStyle, DucTextStyle, DucViewportStyle, DucXRayStyle, ElementBackground, ElementContentBase, ElementStroke, GeometricPoint, HatchPatternLine, PlotLayout, StrokeSides, StrokeStyle, TilingProperties, VersionGraph, _DucElementBase, _DucElementStylesBase, _DucLinearElementBase, _DucStackBase, _DucStackElementBase } from "./types";
5
+ import { CustomHatchPattern, Dictionary, DocumentGridConfig, DucBlock, DucDimensionStyle, DucDocStyle, DucElement, DucExternalFileMetadata, DucExternalFiles, DucFeatureControlFrameStyle, DucGlobalState, DucGroup, DucHatchStyle, DucHead, DucImageFilter, DucLayer, DucLeaderStyle, DucLine, DucLineReference, DucLocalState, DucPath, DucPlotStyle, DucPoint, DucPointBinding, DucRegion, DucStackLikeStyles, DucTableCellStyle, DucTableStyle, DucTextStyle, DucViewportStyle, DucXRayStyle, ElementBackground, ElementContentBase, ElementStroke, GeometricPoint, HatchPatternLine, PlotLayout, StrokeSides, StrokeStyle, TilingProperties, VersionGraph, _DucElementBase, _DucElementStylesBase, _DucLinearElementBase, _DucStackBase, _DucStackElementBase } from "./types";
6
6
  export declare function parseGeometricPoint(point: GeometricPointFb): GeometricPoint;
7
7
  export declare function parsePoint(point: DucPointFb): DucPoint;
8
8
  export declare function parseMargins(margins: MarginsFb): PlotLayout["margins"];
9
+ export declare function parseDocumentGridConfig(gridConfig: DocumentGridConfigFb): DocumentGridConfig;
9
10
  export declare function parseHead(head: DucHeadFb): DucHead;
10
11
  export declare function parseHatchPatternLine(line: HatchPatternLineFb): HatchPatternLine;
11
12
  export declare function parseCustomHatchPattern(pattern: CustomHatchPatternFb): CustomHatchPattern;
@@ -42,6 +43,14 @@ export declare function parseElementFromBinary(wrapper: ElementWrapper): DucElem
42
43
  export declare function parseBlockFromBinary(block: DucBlockFb): DucBlock;
43
44
  export declare function parseDictionaryFromBinary(data: ExportedDataStateFb): Dictionary;
44
45
  export declare function parseExternalFilesFromBinary(entry: DucExternalFileEntry): DucExternalFiles;
46
+ /**
47
+ * Parse only metadata (no binary data) from an external file entry.
48
+ * Used by the lazy file store to avoid copying file bytes into JS memory.
49
+ */
50
+ export declare function parseExternalFileMetadataFromBinary(entry: DucExternalFileEntry): {
51
+ key: string;
52
+ metadata: DucExternalFileMetadata;
53
+ } | null;
45
54
  export declare function parseGlobalStateFromBinary(state: DucGlobalStateFb): DucGlobalState;
46
55
  export declare function parseGroupFromBinary(group: DucGroupFb): DucGroup;
47
56
  export declare function parseLayerFromBinary(layer: DucLayerFb): DucLayer;
@@ -52,3 +61,27 @@ export declare function parseStandardFromBinary(standard: StandardFb): Standard;
52
61
  export declare function parseThumbnailFromBinary(data: ExportedDataStateFb): Uint8Array | undefined;
53
62
  export declare function parseVersionGraphFromBinary(graph: VersionGraphFb | null): VersionGraph | null;
54
63
  export declare const parseDuc: (blob: Blob | File, fileHandle?: FileSystemHandle | null, restoreConfig?: RestoreConfig) => Promise<RestoredDataState>;
64
+ import { LazyExternalFileStore } from "./lazy-files";
65
+ export type LazyRestoredDataState = Omit<RestoredDataState, 'files'> & {
66
+ /** Lazy file store: only metadata is in memory, file bytes are read on-demand from the buffer */
67
+ lazyFileStore: LazyExternalFileStore;
68
+ /** Legacy `files` field — always empty. Use lazyFileStore instead. */
69
+ files: DucExternalFiles;
70
+ };
71
+ /**
72
+ * Parse a .duc binary with lazy external file loading.
73
+ *
74
+ * This is identical to `parseDuc` except:
75
+ * - External file BYTES are NOT copied into JS memory.
76
+ * - Only file metadata (~200 bytes per file) is parsed.
77
+ * - A `LazyExternalFileStore` is returned for on-demand data access.
78
+ * - The store holds a reference to the original Uint8Array buffer.
79
+ *
80
+ * Memory comparison for 500 PDFs averaging 5MB each:
81
+ * - parseDuc: files field holds 2.5GB of ArrayBuffer data in JS heap
82
+ * - parseDucLazy: ~100KB of metadata + the original buffer (referenced, not copied)
83
+ *
84
+ * @param buffer - The raw .duc file bytes (from storage/IndexedDB/filesystem)
85
+ * @param restoreConfig - Optional restore configuration
86
+ */
87
+ export declare const parseDucLazy: (buffer: Uint8Array, restoreConfig?: RestoreConfig) => Promise<LazyRestoredDataState>;
package/dist/parse.js CHANGED
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { decompressSync, strFromU8 } from 'fflate';
11
11
  import * as flatbuffers from "flatbuffers";
12
12
  import { nanoid } from 'nanoid';
13
- import { DucArrowElement as DucArrowElementFb, DucDimensionElement as DucDimensionElementFb, DucDocElement as DucDocElementFb, DucEllipseElement as DucEllipseElementFb, DucEmbeddableElement as DucEmbeddableElementFb, DucFeatureControlFrameElement as DucFeatureControlFrameElementFb, DucFrameElement as DucFrameElementFb, DucFreeDrawElement as DucFreeDrawElementFb, DucImageElement as DucImageElementFb, DucLeaderElement as DucLeaderElementFb, DucLinearElement as DucLinearElementFb, DucMermaidElement as DucMermaidElementFb, DucParametricElement as DucParametricElementFb, DucPdfElement as DucPdfElementFb, DucPlotElement as DucPlotElementFb, DucPolygonElement as DucPolygonElementFb, DucRectangleElement as DucRectangleElementFb, DucTableElement as DucTableElementFb, DucTextDynamicDictionarySource as DucTextDynamicDictionarySourceFb, DucTextDynamicElementSource as DucTextDynamicElementSourceFb, DucTextElement as DucTextElementFb, DucViewportElement as DucViewportElementFb, DucXRayElement as DucXRayElementFb, Element as ElementUnion, ExportedDataState, LEADER_CONTENT_TYPE as LEADER_CONTENT_TYPE_ENUM, LeaderBlockContent as LeaderBlockContentFb, LeaderTextBlockContent as LeaderTextBlockContentFb, TEXT_FIELD_SOURCE_TYPE } from "./flatbuffers/duc";
13
+ import { DOCUMENT_GRID_ALIGN_ITEMS, DucArrowElement as DucArrowElementFb, DucDimensionElement as DucDimensionElementFb, DucDocElement as DucDocElementFb, DucEllipseElement as DucEllipseElementFb, DucEmbeddableElement as DucEmbeddableElementFb, DucFeatureControlFrameElement as DucFeatureControlFrameElementFb, DucFrameElement as DucFrameElementFb, DucFreeDrawElement as DucFreeDrawElementFb, DucImageElement as DucImageElementFb, DucLeaderElement as DucLeaderElementFb, DucLinearElement as DucLinearElementFb, DucMermaidElement as DucMermaidElementFb, DucModelElement as DucModelElementFb, DucPdfElement as DucPdfElementFb, DucPlotElement as DucPlotElementFb, DucPolygonElement as DucPolygonElementFb, DucRectangleElement as DucRectangleElementFb, DucTableElement as DucTableElementFb, DucTextDynamicDictionarySource as DucTextDynamicDictionarySourceFb, DucTextDynamicElementSource as DucTextDynamicElementSourceFb, DucTextElement as DucTextElementFb, DucViewportElement as DucViewportElementFb, DucXRayElement as DucXRayElementFb, Element as ElementUnion, ExportedDataState, LEADER_CONTENT_TYPE as LEADER_CONTENT_TYPE_ENUM, LeaderBlockContent as LeaderBlockContentFb, LeaderTextBlockContent as LeaderTextBlockContentFb, TEXT_FIELD_SOURCE_TYPE } from "./flatbuffers/duc";
14
14
  import { restore } from "./restore";
15
15
  // #region HELPERS & LOW-LEVEL CASTS
16
16
  // Helper functions for type casting
@@ -70,6 +70,25 @@ export function parseMargins(margins) {
70
70
  left: toPrecisionValue(margins.left()),
71
71
  };
72
72
  }
73
+ export function parseDocumentGridConfig(gridConfig) {
74
+ return {
75
+ columns: gridConfig.columns(),
76
+ gapX: gridConfig.gapX(),
77
+ gapY: gridConfig.gapY(),
78
+ alignItems: (() => {
79
+ const align = gridConfig.alignItems();
80
+ if (align === DOCUMENT_GRID_ALIGN_ITEMS.START)
81
+ return 'start';
82
+ if (align === DOCUMENT_GRID_ALIGN_ITEMS.CENTER)
83
+ return 'center';
84
+ if (align === DOCUMENT_GRID_ALIGN_ITEMS.END)
85
+ return 'end';
86
+ return 'start';
87
+ })(),
88
+ firstPageAlone: gridConfig.firstPageAlone(),
89
+ scale: gridConfig.scale(),
90
+ };
91
+ }
73
92
  export function parseHead(head) {
74
93
  return {
75
94
  type: head.type(),
@@ -257,7 +276,15 @@ function parseEmbeddableElement(element) {
257
276
  return Object.assign({ type: "embeddable" }, parseElementBase(element.base()));
258
277
  }
259
278
  function parsePdfElement(element) {
260
- return Object.assign(Object.assign({ type: "pdf" }, parseElementBase(element.base())), { fileId: element.fileId() });
279
+ const gridConfig = element.gridConfig();
280
+ return Object.assign(Object.assign({ type: "pdf" }, parseElementBase(element.base())), { fileId: element.fileId(), gridConfig: gridConfig ? parseDocumentGridConfig(gridConfig) : {
281
+ columns: 1,
282
+ gapX: 0,
283
+ gapY: 0,
284
+ alignItems: 'start',
285
+ firstPageAlone: false,
286
+ scale: 1,
287
+ } });
261
288
  }
262
289
  function parseMermaidElement(element) {
263
290
  return Object.assign(Object.assign({ type: "mermaid" }, parseElementBase(element.base())), { source: element.source(), theme: element.theme() || undefined, svgPath: element.svgPath() });
@@ -545,6 +572,7 @@ function parseDocElement(element) {
545
572
  });
546
573
  }
547
574
  const columns = element.columns();
575
+ const gridConfig = element.gridConfig();
548
576
  return Object.assign(Object.assign(Object.assign({ type: "doc" }, parseElementBase(element.base())), parseDocStyle(element.style())), { text: element.text(), dynamic: dynamicParts, flowDirection: element.flowDirection(), columns: {
549
577
  type: columns.type(),
550
578
  definitions: Array.from({ length: columns.definitionsLength() }, (_, i) => {
@@ -555,16 +583,18 @@ function parseDocElement(element) {
555
583
  };
556
584
  }),
557
585
  autoHeight: columns.autoHeight(),
558
- }, autoResize: element.autoResize() });
559
- }
560
- function parseParametricElement(element) {
561
- const source = element.source();
562
- return Object.assign(Object.assign({ type: "parametric" }, parseElementBase(element.base())), { source: {
563
- type: source.type(),
564
- code: source.code(),
565
- fileId: source.fileId(),
586
+ }, autoResize: element.autoResize(), fileId: element.fileId(), gridConfig: gridConfig ? parseDocumentGridConfig(gridConfig) : {
587
+ columns: 1,
588
+ gapX: 0,
589
+ gapY: 0,
590
+ alignItems: 'start',
591
+ firstPageAlone: false,
592
+ scale: 1,
566
593
  } });
567
594
  }
595
+ function parseModelElement(element) {
596
+ return Object.assign(Object.assign({ type: "model" }, parseElementBase(element.base())), { source: element.source(), svgPath: element.svgPath(), fileIds: Array.from({ length: element.fileIdsLength() }, (_, i) => element.fileIds(i)) });
597
+ }
568
598
  // #endregion
569
599
  // #region STYLE PARSERS (ELEMENT-LEVEL)
570
600
  export function parseTextStyle(style) {
@@ -772,8 +802,8 @@ export function parseElementFromBinary(wrapper) {
772
802
  case ElementUnion.DucDocElement:
773
803
  element = wrapper.element(new DucDocElementFb());
774
804
  break;
775
- case ElementUnion.DucParametricElement:
776
- element = wrapper.element(new DucParametricElementFb());
805
+ case ElementUnion.DucModelElement:
806
+ element = wrapper.element(new DucModelElementFb());
777
807
  break;
778
808
  default:
779
809
  return null;
@@ -823,8 +853,8 @@ export function parseElementFromBinary(wrapper) {
823
853
  return parseFeatureControlFrameElement(element);
824
854
  case ElementUnion.DucDocElement:
825
855
  return parseDocElement(element);
826
- case ElementUnion.DucParametricElement:
827
- return parseParametricElement(element);
856
+ case ElementUnion.DucModelElement:
857
+ return parseModelElement(element);
828
858
  default:
829
859
  return null;
830
860
  }
@@ -880,6 +910,28 @@ export function parseExternalFilesFromBinary(entry) {
880
910
  }
881
911
  };
882
912
  }
913
+ /**
914
+ * Parse only metadata (no binary data) from an external file entry.
915
+ * Used by the lazy file store to avoid copying file bytes into JS memory.
916
+ */
917
+ export function parseExternalFileMetadataFromBinary(entry) {
918
+ const fileData = entry.value();
919
+ const key = entry.key();
920
+ if (!fileData || !key)
921
+ return null;
922
+ const id = fileData.id();
923
+ if (!id)
924
+ return null;
925
+ return {
926
+ key,
927
+ metadata: {
928
+ id,
929
+ mimeType: fileData.mimeType() || "application/octet-stream",
930
+ created: Number(fileData.created()),
931
+ lastRetrieved: Number(fileData.lastRetrieved()) || undefined,
932
+ },
933
+ };
934
+ }
883
935
  export function parseGlobalStateFromBinary(state) {
884
936
  return {
885
937
  name: state.name(),
@@ -1432,3 +1484,181 @@ export const parseDuc = (blob_1, ...args_1) => __awaiter(void 0, [blob_1, ...arg
1432
1484
  };
1433
1485
  });
1434
1486
  // #endregion
1487
+ // #region LAZY ROOT PARSER
1488
+ import { LazyExternalFileStore } from "./lazy-files";
1489
+ /**
1490
+ * Parse a .duc binary with lazy external file loading.
1491
+ *
1492
+ * This is identical to `parseDuc` except:
1493
+ * - External file BYTES are NOT copied into JS memory.
1494
+ * - Only file metadata (~200 bytes per file) is parsed.
1495
+ * - A `LazyExternalFileStore` is returned for on-demand data access.
1496
+ * - The store holds a reference to the original Uint8Array buffer.
1497
+ *
1498
+ * Memory comparison for 500 PDFs averaging 5MB each:
1499
+ * - parseDuc: files field holds 2.5GB of ArrayBuffer data in JS heap
1500
+ * - parseDucLazy: ~100KB of metadata + the original buffer (referenced, not copied)
1501
+ *
1502
+ * @param buffer - The raw .duc file bytes (from storage/IndexedDB/filesystem)
1503
+ * @param restoreConfig - Optional restore configuration
1504
+ */
1505
+ export const parseDucLazy = (buffer_1, ...args_1) => __awaiter(void 0, [buffer_1, ...args_1], void 0, function* (buffer, restoreConfig = {}) {
1506
+ var _a;
1507
+ if (!buffer || buffer.byteLength === 0) {
1508
+ throw new Error('Invalid DUC buffer: empty file');
1509
+ }
1510
+ const byteBuffer = new flatbuffers.ByteBuffer(buffer);
1511
+ let data;
1512
+ try {
1513
+ data = ExportedDataState.getRootAsExportedDataState(byteBuffer);
1514
+ }
1515
+ catch (e) {
1516
+ throw new Error('Invalid DUC buffer: cannot read root table');
1517
+ }
1518
+ const legacyVersion = data.versionLegacy();
1519
+ if (legacyVersion) {
1520
+ throw new Error(`Unsupported DUC version: ${legacyVersion}. Please use version ducjs@2.0.1 or lower to support this file.`);
1521
+ }
1522
+ const localState = data.ducLocalState();
1523
+ const parsedLocalState = localState && parseLocalStateFromBinary(localState);
1524
+ const globalState = data.ducGlobalState();
1525
+ const parsedGlobalState = globalState && parseGlobalStateFromBinary(globalState);
1526
+ // Parse elements
1527
+ const elements = [];
1528
+ for (let i = 0; i < data.elementsLength(); i++) {
1529
+ const e = data.elements(i);
1530
+ if (e) {
1531
+ const element = parseElementFromBinary(e);
1532
+ if (element) {
1533
+ elements.push(element);
1534
+ }
1535
+ }
1536
+ }
1537
+ // Create lazy file store — only metadata is parsed, no file bytes copied
1538
+ const lazyFileStore = new LazyExternalFileStore(buffer);
1539
+ // Parse blocks
1540
+ const blocks = [];
1541
+ for (let i = 0; i < data.blocksLength(); i++) {
1542
+ const block = data.blocks(i);
1543
+ if (block) {
1544
+ const parsedBlock = parseBlockFromBinary(block);
1545
+ if (parsedBlock) {
1546
+ blocks.push(parsedBlock);
1547
+ }
1548
+ }
1549
+ }
1550
+ // Parse block instances
1551
+ const blockInstances = [];
1552
+ for (let i = 0; i < data.blockInstancesLength(); i++) {
1553
+ const blockInstance = data.blockInstances(i);
1554
+ if (blockInstance) {
1555
+ const parsedBlockInstance = parseBlockInstance(blockInstance);
1556
+ if (parsedBlockInstance) {
1557
+ blockInstances.push(parsedBlockInstance);
1558
+ }
1559
+ }
1560
+ }
1561
+ // Parse block collections
1562
+ const blockCollections = [];
1563
+ for (let i = 0; i < data.blockCollectionsLength(); i++) {
1564
+ const blockCollection = data.blockCollections(i);
1565
+ if (blockCollection) {
1566
+ const parsedBlockCollection = parseBlockCollection(blockCollection);
1567
+ if (parsedBlockCollection) {
1568
+ blockCollections.push(parsedBlockCollection);
1569
+ }
1570
+ }
1571
+ }
1572
+ // Parse groups
1573
+ const groups = [];
1574
+ for (let i = 0; i < data.groupsLength(); i++) {
1575
+ const group = data.groups(i);
1576
+ if (group) {
1577
+ const parsedGroup = parseGroupFromBinary(group);
1578
+ if (parsedGroup) {
1579
+ groups.push(parsedGroup);
1580
+ }
1581
+ }
1582
+ }
1583
+ // Parse dictionary
1584
+ const dictionary = parseDictionaryFromBinary(data);
1585
+ // Parse thumbnail
1586
+ const thumbnail = parseThumbnailFromBinary(data);
1587
+ // Parse version graph
1588
+ const versionGraphData = data.versionGraph();
1589
+ const versionGraph = parseVersionGraphFromBinary(versionGraphData);
1590
+ // Parse regions
1591
+ const regions = [];
1592
+ for (let i = 0; i < data.regionsLength(); i++) {
1593
+ const region = data.regions(i);
1594
+ if (region) {
1595
+ const parsedRegion = parseRegionFromBinary(region);
1596
+ if (parsedRegion) {
1597
+ regions.push(parsedRegion);
1598
+ }
1599
+ }
1600
+ }
1601
+ // Parse layers
1602
+ const layers = [];
1603
+ for (let i = 0; i < data.layersLength(); i++) {
1604
+ const layer = data.layers(i);
1605
+ if (layer) {
1606
+ const parsedLayer = parseLayerFromBinary(layer);
1607
+ if (parsedLayer) {
1608
+ layers.push(parsedLayer);
1609
+ }
1610
+ }
1611
+ }
1612
+ // Parse standards
1613
+ const standards = [];
1614
+ for (let i = 0; i < data.standardsLength(); i++) {
1615
+ const standard = data.standards(i);
1616
+ if (standard) {
1617
+ const parsedStandard = parseStandardFromBinary(standard);
1618
+ if (parsedStandard) {
1619
+ standards.push(parsedStandard);
1620
+ }
1621
+ }
1622
+ }
1623
+ const exportData = {
1624
+ thumbnail,
1625
+ dictionary,
1626
+ elements: elements,
1627
+ localState: parsedLocalState,
1628
+ globalState: parsedGlobalState,
1629
+ blocks,
1630
+ blockInstances,
1631
+ blockCollections,
1632
+ groups,
1633
+ regions,
1634
+ layers,
1635
+ standards,
1636
+ files: {}, // empty — use lazyFileStore
1637
+ versionGraph: versionGraph !== null && versionGraph !== void 0 ? versionGraph : undefined,
1638
+ id: (_a = data.id()) !== null && _a !== void 0 ? _a : nanoid(),
1639
+ };
1640
+ const sanitized = restore(exportData, {
1641
+ syncInvalidIndices: (elements) => elements,
1642
+ repairBindings: true,
1643
+ refreshDimensions: false,
1644
+ }, restoreConfig);
1645
+ return {
1646
+ thumbnail: sanitized.thumbnail,
1647
+ dictionary: sanitized.dictionary,
1648
+ elements: sanitized.elements,
1649
+ localState: sanitized.localState,
1650
+ globalState: sanitized.globalState,
1651
+ files: {},
1652
+ lazyFileStore,
1653
+ blocks: sanitized.blocks,
1654
+ blockInstances: sanitized.blockInstances,
1655
+ groups: sanitized.groups,
1656
+ regions: sanitized.regions,
1657
+ layers: sanitized.layers,
1658
+ blockCollections: sanitized.blockCollections,
1659
+ standards: sanitized.standards,
1660
+ versionGraph: sanitized.versionGraph,
1661
+ id: sanitized.id,
1662
+ };
1663
+ });
1664
+ // #endregion
@@ -97,12 +97,7 @@ export declare const restoreBlockCollections: (collections: unknown) => Restored
97
97
  * Restores the blockInstances array from imported data, ensuring each item
98
98
  * conforms to the DucBlockInstance type.
99
99
  */
100
- export declare const restoreBlockInstances: (blockInstances: unknown) => RestoredDataState["blockInstances"];
101
- /**
102
- * Restores instances from block instances data (alias for restoreBlockInstances)
103
- * This method provides a consistent naming convention for instance restoration.
104
- */
105
- export declare const restoreInstances: (instances: unknown) => RestoredDataState["blockInstances"];
100
+ export declare const restoreBlockInstances: (blockInstances: unknown, currentScope: Scope) => RestoredDataState["blockInstances"];
106
101
  /**
107
102
  * Restores the global state of the document from imported data.
108
103
  * It validates and provides defaults for missing or invalid properties.
@@ -20,7 +20,7 @@ export const restore = (data, elementsConfig, restoreConfig = {}) => {
20
20
  const restoredRegions = restoreRegions(data === null || data === void 0 ? void 0 : data.regions);
21
21
  const restoredGroups = restoreGroups(data === null || data === void 0 ? void 0 : data.groups);
22
22
  const restoredLayers = restoreLayers(data === null || data === void 0 ? void 0 : data.layers, restoredLocalState.scope);
23
- const restoredBlockInstances = restoreBlockInstances(data === null || data === void 0 ? void 0 : data.blockInstances);
23
+ const restoredBlockInstances = restoreBlockInstances(data === null || data === void 0 ? void 0 : data.blockInstances, restoredLocalState.scope);
24
24
  const restoredElements = restoreElements(data === null || data === void 0 ? void 0 : data.elements, restoredLocalState.scope, restoredBlocks, restoredElementsConfig);
25
25
  const restoredVersionGraph = restoreVersionGraph(data === null || data === void 0 ? void 0 : data.versionGraph);
26
26
  // Generate a new ID if none exists or if it's empty
@@ -247,7 +247,7 @@ export const restoreBlockCollections = (collections) => {
247
247
  * Restores the blockInstances array from imported data, ensuring each item
248
248
  * conforms to the DucBlockInstance type.
249
249
  */
250
- export const restoreBlockInstances = (blockInstances) => {
250
+ export const restoreBlockInstances = (blockInstances, currentScope) => {
251
251
  if (!Array.isArray(blockInstances)) {
252
252
  return [];
253
253
  }
@@ -301,8 +301,8 @@ export const restoreBlockInstances = (blockInstances) => {
301
301
  ? {
302
302
  rows: typeof dupArray.rows === "number" ? dupArray.rows : 1,
303
303
  cols: typeof dupArray.cols === "number" ? dupArray.cols : 1,
304
- rowSpacing: typeof dupArray.rowSpacing === "number" ? dupArray.rowSpacing : 0,
305
- colSpacing: typeof dupArray.colSpacing === "number" ? dupArray.colSpacing : 0,
304
+ rowSpacing: restorePrecisionValue(dupArray.rowSpacing, NEUTRAL_SCOPE, currentScope),
305
+ colSpacing: restorePrecisionValue(dupArray.colSpacing, NEUTRAL_SCOPE, currentScope),
306
306
  }
307
307
  : null,
308
308
  };
@@ -337,13 +337,6 @@ const restoreBlockMetadata = (metadata) => {
337
337
  : undefined;
338
338
  return Object.assign(Object.assign({}, (source ? { source } : {})), { usageCount: typeof metadataObj.usageCount === "number" ? metadataObj.usageCount : 0, createdAt: typeof metadataObj.createdAt === "number" ? metadataObj.createdAt : Date.now(), updatedAt: typeof metadataObj.updatedAt === "number" ? metadataObj.updatedAt : Date.now(), localization });
339
339
  };
340
- /**
341
- * Restores instances from block instances data (alias for restoreBlockInstances)
342
- * This method provides a consistent naming convention for instance restoration.
343
- */
344
- export const restoreInstances = (instances) => {
345
- return restoreBlockInstances(instances);
346
- };
347
340
  /**
348
341
  * Restores the global state of the document from imported data.
349
342
  * It validates and provides defaults for missing or invalid properties.