fabricjs-document-engine 0.3.0 → 0.5.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 (50) hide show
  1. package/README.md +88 -3
  2. package/dist/assets/asset-pipeline.cjs +1 -0
  3. package/dist/assets/asset-pipeline.js +1 -1
  4. package/dist/engine/create-document-engine.cjs +150 -7
  5. package/dist/engine/create-document-engine.d.cts +40 -0
  6. package/dist/engine/create-document-engine.d.ts +40 -0
  7. package/dist/engine/create-document-engine.js +150 -7
  8. package/dist/engine/errors.cjs +1 -0
  9. package/dist/engine/errors.d.cts +4 -1
  10. package/dist/engine/errors.d.ts +4 -1
  11. package/dist/engine/errors.js +1 -0
  12. package/dist/export/download-export.cjs +14 -0
  13. package/dist/export/download-export.d.cts +7 -0
  14. package/dist/export/download-export.d.ts +7 -0
  15. package/dist/export/download-export.js +14 -0
  16. package/dist/export/export-area.cjs +46 -0
  17. package/dist/export/export-area.js +46 -0
  18. package/dist/export/export-options.cjs +47 -0
  19. package/dist/export/export-options.d.cts +20 -0
  20. package/dist/export/export-options.d.ts +20 -0
  21. package/dist/export/export-options.js +46 -0
  22. package/dist/export/preflight-export.cjs +105 -0
  23. package/dist/export/preflight-export.d.cts +17 -0
  24. package/dist/export/preflight-export.d.ts +17 -0
  25. package/dist/export/preflight-export.js +105 -0
  26. package/dist/export/render-export.cjs +69 -0
  27. package/dist/export/render-export.js +65 -0
  28. package/dist/history/apply-state.cjs +1 -1
  29. package/dist/history/apply-state.js +1 -1
  30. package/dist/index.cjs +2 -0
  31. package/dist/index.d.cts +7 -2
  32. package/dist/index.d.ts +7 -2
  33. package/dist/index.js +2 -1
  34. package/dist/recovery/indexed-db-recovery.cjs +80 -0
  35. package/dist/recovery/indexed-db-recovery.d.cts +8 -0
  36. package/dist/recovery/indexed-db-recovery.d.ts +8 -0
  37. package/dist/recovery/indexed-db-recovery.js +80 -0
  38. package/dist/recovery/recovery-controller.cjs +159 -0
  39. package/dist/recovery/recovery-controller.d.cts +19 -0
  40. package/dist/recovery/recovery-controller.d.ts +19 -0
  41. package/dist/recovery/recovery-controller.js +158 -0
  42. package/dist/recovery/recovery-store.cjs +19 -0
  43. package/dist/recovery/recovery-store.d.cts +10 -0
  44. package/dist/recovery/recovery-store.d.ts +10 -0
  45. package/dist/recovery/recovery-store.js +19 -0
  46. package/dist/recovery.cjs +5 -0
  47. package/dist/recovery.d.cts +4 -0
  48. package/dist/recovery.d.ts +4 -0
  49. package/dist/recovery.js +3 -0
  50. package/package.json +13 -2
@@ -0,0 +1,105 @@
1
+ const require_font_check = require("../assets/font-check.cjs");
2
+ const require_image_check = require("../assets/image-check.cjs");
3
+ const require_export_options = require("./export-options.cjs");
4
+ const require_object_ids = require("../fabric/object-ids.cjs");
5
+ const require_walk_objects = require("../fabric/walk-objects.cjs");
6
+ //#region src/export/preflight-export.ts
7
+ function imageElementsOf(object) {
8
+ const elements = [];
9
+ const withElement = object;
10
+ if (typeof withElement.getElement === "function") elements.push(withElement.getElement());
11
+ for (const paint of [object.fill, object.stroke]) {
12
+ const source = paint?.source;
13
+ if (source !== void 0) elements.push(source);
14
+ }
15
+ return elements.filter((element) => typeof HTMLImageElement !== "undefined" && element instanceof HTMLImageElement);
16
+ }
17
+ function collectImageUses(objects) {
18
+ const uses = /* @__PURE__ */ new Map();
19
+ const pending = objects.map((object) => ({
20
+ object,
21
+ ownerId: require_object_ids.readObjectId(object)
22
+ }));
23
+ while (pending.length > 0) {
24
+ const { object, ownerId } = pending.pop();
25
+ const id = require_object_ids.readObjectId(object) ?? ownerId;
26
+ for (const element of imageElementsOf(object)) {
27
+ const url = element.currentSrc || element.src;
28
+ const use = uses.get(url) ?? {
29
+ element,
30
+ objectIds: /* @__PURE__ */ new Set()
31
+ };
32
+ if (id !== void 0) use.objectIds.add(id);
33
+ uses.set(url, use);
34
+ }
35
+ require_walk_objects.childrenOf(object).forEach((child) => pending.push({
36
+ object: child,
37
+ ownerId: id
38
+ }));
39
+ if (object.clipPath) pending.push({
40
+ object: object.clipPath,
41
+ ownerId: id
42
+ });
43
+ }
44
+ return uses;
45
+ }
46
+ function isBroken(element) {
47
+ return element.complete && element.naturalWidth === 0;
48
+ }
49
+ function shortUrl(url) {
50
+ return url.length > 80 ? `${url.slice(0, 77)}...` : url;
51
+ }
52
+ async function preflightExport(canvas, format, fonts, assetOptions) {
53
+ const problems = [];
54
+ const warnings = [];
55
+ if (format === "json") return {
56
+ ok: true,
57
+ problems,
58
+ warnings
59
+ };
60
+ for (const [url, use] of collectImageUses(canvas.getObjects())) {
61
+ const objectIds = [...use.objectIds];
62
+ if (isBroken(use.element)) problems.push({
63
+ code: "MISSING_IMAGE",
64
+ message: `The image ${shortUrl(url)} did not load`,
65
+ url,
66
+ objectIds
67
+ });
68
+ else if (require_export_options.isRasterFormat(format) && require_image_check.isCrossOriginUrl(url) && !use.element.crossOrigin) problems.push({
69
+ code: "CROSS_ORIGIN_IMAGE",
70
+ message: `The image ${shortUrl(url)} comes from another site without crossOrigin, so the browser blocks exporting it. Load it with crossOrigin: 'anonymous' from a server that allows CORS.`,
71
+ url,
72
+ objectIds
73
+ });
74
+ else if (format === "svg" && !require_image_check.isPortableUrl(url)) warnings.push({
75
+ code: "ASSET_NOT_PORTABLE",
76
+ message: `The SVG links to ${shortUrl(url)}, which only exists in this tab`,
77
+ url,
78
+ objectIds
79
+ });
80
+ }
81
+ const fontSet = typeof document === "undefined" ? void 0 : document.fonts;
82
+ if (fontSet) await fontSet.ready;
83
+ for (const font of await require_font_check.findUnavailableFonts(fonts, assetOptions.loadFont)) {
84
+ const message = `The font "${font.family}" (${font.weight}, ${font.style}) is not available`;
85
+ if (assetOptions.requireFonts) problems.push({
86
+ code: "MISSING_FONT",
87
+ message,
88
+ family: font.family,
89
+ objectIds: font.objectIds
90
+ });
91
+ else warnings.push({
92
+ code: "FONT_UNAVAILABLE",
93
+ message: `${message}, text will use a fallback font`,
94
+ family: font.family,
95
+ objectIds: font.objectIds
96
+ });
97
+ }
98
+ return {
99
+ ok: problems.length === 0,
100
+ problems,
101
+ warnings
102
+ };
103
+ }
104
+ //#endregion
105
+ exports.preflightExport = preflightExport;
@@ -0,0 +1,17 @@
1
+ import { AssetWarning } from "../assets/asset-pipeline.cjs";
2
+ import { StaticCanvas } from "fabric";
3
+ //#region src/export/preflight-export.d.ts
4
+ export type ExportProblemCode = "MISSING_IMAGE" | "CROSS_ORIGIN_IMAGE" | "MISSING_FONT";
5
+ export interface ExportProblem {
6
+ code: ExportProblemCode;
7
+ message: string;
8
+ url?: string;
9
+ family?: string;
10
+ objectIds: string[];
11
+ }
12
+ export interface ExportPreflight {
13
+ ok: boolean;
14
+ problems: ExportProblem[];
15
+ warnings: AssetWarning[];
16
+ }
17
+ //#endregion
@@ -0,0 +1,17 @@
1
+ import { AssetWarning } from "../assets/asset-pipeline.js";
2
+ import { StaticCanvas } from "fabric";
3
+ //#region src/export/preflight-export.d.ts
4
+ export type ExportProblemCode = "MISSING_IMAGE" | "CROSS_ORIGIN_IMAGE" | "MISSING_FONT";
5
+ export interface ExportProblem {
6
+ code: ExportProblemCode;
7
+ message: string;
8
+ url?: string;
9
+ family?: string;
10
+ objectIds: string[];
11
+ }
12
+ export interface ExportPreflight {
13
+ ok: boolean;
14
+ problems: ExportProblem[];
15
+ warnings: AssetWarning[];
16
+ }
17
+ //#endregion
@@ -0,0 +1,105 @@
1
+ import { findUnavailableFonts } from "../assets/font-check.js";
2
+ import { isCrossOriginUrl, isPortableUrl } from "../assets/image-check.js";
3
+ import { isRasterFormat } from "./export-options.js";
4
+ import { readObjectId } from "../fabric/object-ids.js";
5
+ import { childrenOf } from "../fabric/walk-objects.js";
6
+ //#region src/export/preflight-export.ts
7
+ function imageElementsOf(object) {
8
+ const elements = [];
9
+ const withElement = object;
10
+ if (typeof withElement.getElement === "function") elements.push(withElement.getElement());
11
+ for (const paint of [object.fill, object.stroke]) {
12
+ const source = paint?.source;
13
+ if (source !== void 0) elements.push(source);
14
+ }
15
+ return elements.filter((element) => typeof HTMLImageElement !== "undefined" && element instanceof HTMLImageElement);
16
+ }
17
+ function collectImageUses(objects) {
18
+ const uses = /* @__PURE__ */ new Map();
19
+ const pending = objects.map((object) => ({
20
+ object,
21
+ ownerId: readObjectId(object)
22
+ }));
23
+ while (pending.length > 0) {
24
+ const { object, ownerId } = pending.pop();
25
+ const id = readObjectId(object) ?? ownerId;
26
+ for (const element of imageElementsOf(object)) {
27
+ const url = element.currentSrc || element.src;
28
+ const use = uses.get(url) ?? {
29
+ element,
30
+ objectIds: /* @__PURE__ */ new Set()
31
+ };
32
+ if (id !== void 0) use.objectIds.add(id);
33
+ uses.set(url, use);
34
+ }
35
+ childrenOf(object).forEach((child) => pending.push({
36
+ object: child,
37
+ ownerId: id
38
+ }));
39
+ if (object.clipPath) pending.push({
40
+ object: object.clipPath,
41
+ ownerId: id
42
+ });
43
+ }
44
+ return uses;
45
+ }
46
+ function isBroken(element) {
47
+ return element.complete && element.naturalWidth === 0;
48
+ }
49
+ function shortUrl(url) {
50
+ return url.length > 80 ? `${url.slice(0, 77)}...` : url;
51
+ }
52
+ async function preflightExport(canvas, format, fonts, assetOptions) {
53
+ const problems = [];
54
+ const warnings = [];
55
+ if (format === "json") return {
56
+ ok: true,
57
+ problems,
58
+ warnings
59
+ };
60
+ for (const [url, use] of collectImageUses(canvas.getObjects())) {
61
+ const objectIds = [...use.objectIds];
62
+ if (isBroken(use.element)) problems.push({
63
+ code: "MISSING_IMAGE",
64
+ message: `The image ${shortUrl(url)} did not load`,
65
+ url,
66
+ objectIds
67
+ });
68
+ else if (isRasterFormat(format) && isCrossOriginUrl(url) && !use.element.crossOrigin) problems.push({
69
+ code: "CROSS_ORIGIN_IMAGE",
70
+ message: `The image ${shortUrl(url)} comes from another site without crossOrigin, so the browser blocks exporting it. Load it with crossOrigin: 'anonymous' from a server that allows CORS.`,
71
+ url,
72
+ objectIds
73
+ });
74
+ else if (format === "svg" && !isPortableUrl(url)) warnings.push({
75
+ code: "ASSET_NOT_PORTABLE",
76
+ message: `The SVG links to ${shortUrl(url)}, which only exists in this tab`,
77
+ url,
78
+ objectIds
79
+ });
80
+ }
81
+ const fontSet = typeof document === "undefined" ? void 0 : document.fonts;
82
+ if (fontSet) await fontSet.ready;
83
+ for (const font of await findUnavailableFonts(fonts, assetOptions.loadFont)) {
84
+ const message = `The font "${font.family}" (${font.weight}, ${font.style}) is not available`;
85
+ if (assetOptions.requireFonts) problems.push({
86
+ code: "MISSING_FONT",
87
+ message,
88
+ family: font.family,
89
+ objectIds: font.objectIds
90
+ });
91
+ else warnings.push({
92
+ code: "FONT_UNAVAILABLE",
93
+ message: `${message}, text will use a fallback font`,
94
+ family: font.family,
95
+ objectIds: font.objectIds
96
+ });
97
+ }
98
+ return {
99
+ ok: problems.length === 0,
100
+ problems,
101
+ warnings
102
+ };
103
+ }
104
+ //#endregion
105
+ export { preflightExport };
@@ -0,0 +1,69 @@
1
+ //#region src/export/render-export.ts
2
+ const identity = [
3
+ 1,
4
+ 0,
5
+ 0,
6
+ 1,
7
+ 0,
8
+ 0
9
+ ];
10
+ const mimeTypes = {
11
+ png: "image/png",
12
+ jpeg: "image/jpeg",
13
+ webp: "image/webp",
14
+ svg: "image/svg+xml",
15
+ json: "application/json"
16
+ };
17
+ function chooseBackground(background, format, current) {
18
+ if (background === "transparent") return format === "jpeg" ? "white" : "";
19
+ if (background !== "keep") return background;
20
+ return format === "jpeg" && !(current !== void 0 && current !== null && current !== "") ? "white" : current;
21
+ }
22
+ function withExportView(canvas, background, format, work) {
23
+ const savedTransform = [...canvas.viewportTransform];
24
+ const savedBackground = canvas.backgroundColor;
25
+ canvas.setViewportTransform(identity);
26
+ canvas.backgroundColor = chooseBackground(background, format, savedBackground);
27
+ try {
28
+ return work();
29
+ } finally {
30
+ canvas.backgroundColor = savedBackground;
31
+ canvas.setViewportTransform(savedTransform);
32
+ canvas.requestRenderAll();
33
+ }
34
+ }
35
+ function renderRaster(canvas, area, format, scale, quality) {
36
+ return canvas.toDataURL({
37
+ ...area,
38
+ format,
39
+ quality,
40
+ multiplier: scale,
41
+ enableRetinaScaling: false
42
+ });
43
+ }
44
+ function renderSvg(canvas, area, scale) {
45
+ return canvas.toSVG({
46
+ viewBox: {
47
+ x: area.left,
48
+ y: area.top,
49
+ width: area.width,
50
+ height: area.height
51
+ },
52
+ width: String(area.width * scale),
53
+ height: String(area.height * scale)
54
+ });
55
+ }
56
+ function dataUrlToBlob(dataUrl) {
57
+ const [header, payload = ""] = dataUrl.split(",");
58
+ const mimeType = /data:([^;]+)/.exec(header ?? "")?.[1] ?? "application/octet-stream";
59
+ const binary = atob(payload);
60
+ const bytes = new Uint8Array(binary.length);
61
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
62
+ return new Blob([bytes], { type: mimeType });
63
+ }
64
+ //#endregion
65
+ exports.dataUrlToBlob = dataUrlToBlob;
66
+ exports.mimeTypes = mimeTypes;
67
+ exports.renderRaster = renderRaster;
68
+ exports.renderSvg = renderSvg;
69
+ exports.withExportView = withExportView;
@@ -0,0 +1,65 @@
1
+ //#region src/export/render-export.ts
2
+ const identity = [
3
+ 1,
4
+ 0,
5
+ 0,
6
+ 1,
7
+ 0,
8
+ 0
9
+ ];
10
+ const mimeTypes = {
11
+ png: "image/png",
12
+ jpeg: "image/jpeg",
13
+ webp: "image/webp",
14
+ svg: "image/svg+xml",
15
+ json: "application/json"
16
+ };
17
+ function chooseBackground(background, format, current) {
18
+ if (background === "transparent") return format === "jpeg" ? "white" : "";
19
+ if (background !== "keep") return background;
20
+ return format === "jpeg" && !(current !== void 0 && current !== null && current !== "") ? "white" : current;
21
+ }
22
+ function withExportView(canvas, background, format, work) {
23
+ const savedTransform = [...canvas.viewportTransform];
24
+ const savedBackground = canvas.backgroundColor;
25
+ canvas.setViewportTransform(identity);
26
+ canvas.backgroundColor = chooseBackground(background, format, savedBackground);
27
+ try {
28
+ return work();
29
+ } finally {
30
+ canvas.backgroundColor = savedBackground;
31
+ canvas.setViewportTransform(savedTransform);
32
+ canvas.requestRenderAll();
33
+ }
34
+ }
35
+ function renderRaster(canvas, area, format, scale, quality) {
36
+ return canvas.toDataURL({
37
+ ...area,
38
+ format,
39
+ quality,
40
+ multiplier: scale,
41
+ enableRetinaScaling: false
42
+ });
43
+ }
44
+ function renderSvg(canvas, area, scale) {
45
+ return canvas.toSVG({
46
+ viewBox: {
47
+ x: area.left,
48
+ y: area.top,
49
+ width: area.width,
50
+ height: area.height
51
+ },
52
+ width: String(area.width * scale),
53
+ height: String(area.height * scale)
54
+ });
55
+ }
56
+ function dataUrlToBlob(dataUrl) {
57
+ const [header, payload = ""] = dataUrl.split(",");
58
+ const mimeType = /data:([^;]+)/.exec(header ?? "")?.[1] ?? "application/octet-stream";
59
+ const binary = atob(payload);
60
+ const bytes = new Uint8Array(binary.length);
61
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
62
+ return new Blob([bytes], { type: mimeType });
63
+ }
64
+ //#endregion
65
+ export { dataUrlToBlob, mimeTypes, renderRaster, renderSvg, withExportView };
@@ -1,5 +1,5 @@
1
- const require_fabric_adapter = require("../fabric/fabric-adapter.cjs");
2
1
  const require_object_ids = require("../fabric/object-ids.cjs");
2
+ const require_fabric_adapter = require("../fabric/fabric-adapter.cjs");
3
3
  //#region src/history/apply-state.ts
4
4
  function arrangeObjects(current, change, created) {
5
5
  const arranged = [];
@@ -1,5 +1,5 @@
1
- import { createObjects } from "../fabric/fabric-adapter.js";
2
1
  import { readObjectId } from "../fabric/object-ids.js";
2
+ import { createObjects } from "../fabric/fabric-adapter.js";
3
3
  //#region src/history/apply-state.ts
4
4
  function arrangeObjects(current, change, created) {
5
5
  const arranged = [];
package/dist/index.cjs CHANGED
@@ -5,11 +5,13 @@ const require_validate_document = require("./document/validate-document.cjs");
5
5
  const require_create_document_engine = require("./engine/create-document-engine.cjs");
6
6
  const require_keyboard_shortcuts = require("./history/keyboard-shortcuts.cjs");
7
7
  const require_unsaved_changes_warning = require("./save/unsaved-changes-warning.cjs");
8
+ const require_download_export = require("./export/download-export.cjs");
8
9
  exports.CURRENT_SCHEMA_VERSION = require_document_format.CURRENT_SCHEMA_VERSION;
9
10
  exports.DocumentEngineError = require_errors.DocumentEngineError;
10
11
  exports.bindKeyboardShortcuts = require_keyboard_shortcuts.bindKeyboardShortcuts;
11
12
  exports.bindUnsavedChangesWarning = require_unsaved_changes_warning.bindUnsavedChangesWarning;
12
13
  exports.createConflictError = require_errors.createConflictError;
13
14
  exports.createDocumentEngine = require_create_document_engine.createDocumentEngine;
15
+ exports.downloadExport = require_download_export.downloadExport;
14
16
  exports.isDocumentEngineError = require_errors.isDocumentEngineError;
15
17
  exports.validateDocument = require_validate_document.validateDocument;
package/dist/index.d.cts CHANGED
@@ -2,17 +2,22 @@ import { CURRENT_SCHEMA_VERSION, DocumentCanvas, DocumentInfo, FabricDocument, S
2
2
  import { AssetManifest, FontAsset, ImageAsset } from "./assets/asset-manifest.cjs";
3
3
  import { FontLoader } from "./assets/font-check.cjs";
4
4
  import { AssetOptions, AssetReport, AssetWarning, AssetWarningCode, UploadRequest } from "./assets/asset-pipeline.cjs";
5
+ import { ExportArea, ExportBackground, ExportFormat, ExportOptions, ExportRect } from "./export/export-options.cjs";
6
+ import { ExportPreflight, ExportProblem, ExportProblemCode } from "./export/preflight-export.cjs";
5
7
  import { NewDocumentOptions } from "./document/create-document.cjs";
6
8
  import { CustomObjectDefinition } from "./fabric/object-registry.cjs";
7
9
  import { HistoryOptions, HistoryState } from "./history/create-history.cjs";
10
+ import { RecoveryStore } from "./recovery/recovery-store.cjs";
11
+ import { InterruptedLoad, RecoveryOptions, RecoveryRecord } from "./recovery/recovery-controller.cjs";
8
12
  import { AutosaveOptions } from "./save/autosave-scheduler.cjs";
9
13
  import { RetryOptions } from "./save/retry.cjs";
10
14
  import { DocumentEngineError, DocumentEngineErrorDetails, DocumentErrorCode, DocumentIssue, createConflictError, isDocumentEngineError } from "./engine/errors.cjs";
11
15
  import { DocumentStorage, SaveContext, SaveResult } from "./storage/storage-contract.cjs";
12
16
  import { SaveOptions, SaveRetryEvent, SaveState, SaveStatus } from "./save/save-controller.cjs";
13
17
  import { Unsubscribe } from "./engine/event-emitter.cjs";
14
- import { DocumentEngine, DocumentEngineEvents, DocumentEngineOptions, LoadOptions, createDocumentEngine } from "./engine/create-document-engine.cjs";
18
+ import { DocumentEngine, DocumentEngineEvents, DocumentEngineOptions, ExportResult, LoadOptions, createDocumentEngine } from "./engine/create-document-engine.cjs";
15
19
  import { validateDocument } from "./document/validate-document.cjs";
16
20
  import { KeyboardShortcutOptions, UndoRedoTarget, bindKeyboardShortcuts } from "./history/keyboard-shortcuts.cjs";
17
21
  import { UnsavedChangesSource, bindUnsavedChangesWarning } from "./save/unsaved-changes-warning.cjs";
18
- export { type AssetManifest, type AssetOptions, type AssetReport, type AssetWarning, type AssetWarningCode, type AutosaveOptions, CURRENT_SCHEMA_VERSION, type CustomObjectDefinition, type DocumentCanvas, type DocumentEngine, DocumentEngineError, type DocumentEngineErrorDetails, type DocumentEngineEvents, type DocumentEngineOptions, type DocumentErrorCode, type DocumentInfo, type DocumentIssue, type DocumentStorage, type FabricDocument, type FontAsset, type FontLoader, type HistoryOptions, type HistoryState, type ImageAsset, type KeyboardShortcutOptions, type LoadOptions, type NewDocumentOptions, type RetryOptions, type SaveContext, type SaveOptions, type SaveResult, type SaveRetryEvent, type SaveState, type SaveStatus, type SerializedFabricObject, type UndoRedoTarget, type UnsavedChangesSource, type Unsubscribe, type UploadRequest, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, isDocumentEngineError, validateDocument };
22
+ import { DownloadableExport, downloadExport } from "./export/download-export.cjs";
23
+ export { type AssetManifest, type AssetOptions, type AssetReport, type AssetWarning, type AssetWarningCode, type AutosaveOptions, CURRENT_SCHEMA_VERSION, type CustomObjectDefinition, type DocumentCanvas, type DocumentEngine, DocumentEngineError, type DocumentEngineErrorDetails, type DocumentEngineEvents, type DocumentEngineOptions, type DocumentErrorCode, type DocumentInfo, type DocumentIssue, type DocumentStorage, type DownloadableExport, type ExportArea, type ExportBackground, type ExportFormat, type ExportOptions, type ExportPreflight, type ExportProblem, type ExportProblemCode, type ExportRect, type ExportResult, type FabricDocument, type FontAsset, type FontLoader, type HistoryOptions, type HistoryState, type ImageAsset, type InterruptedLoad, type KeyboardShortcutOptions, type LoadOptions, type NewDocumentOptions, type RecoveryOptions, type RecoveryRecord, type RecoveryStore, type RetryOptions, type SaveContext, type SaveOptions, type SaveResult, type SaveRetryEvent, type SaveState, type SaveStatus, type SerializedFabricObject, type UndoRedoTarget, type UnsavedChangesSource, type Unsubscribe, type UploadRequest, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, downloadExport, isDocumentEngineError, validateDocument };
package/dist/index.d.ts CHANGED
@@ -2,17 +2,22 @@ import { CURRENT_SCHEMA_VERSION, DocumentCanvas, DocumentInfo, FabricDocument, S
2
2
  import { AssetManifest, FontAsset, ImageAsset } from "./assets/asset-manifest.js";
3
3
  import { FontLoader } from "./assets/font-check.js";
4
4
  import { AssetOptions, AssetReport, AssetWarning, AssetWarningCode, UploadRequest } from "./assets/asset-pipeline.js";
5
+ import { ExportArea, ExportBackground, ExportFormat, ExportOptions, ExportRect } from "./export/export-options.js";
6
+ import { ExportPreflight, ExportProblem, ExportProblemCode } from "./export/preflight-export.js";
5
7
  import { NewDocumentOptions } from "./document/create-document.js";
6
8
  import { CustomObjectDefinition } from "./fabric/object-registry.js";
7
9
  import { HistoryOptions, HistoryState } from "./history/create-history.js";
10
+ import { RecoveryStore } from "./recovery/recovery-store.js";
11
+ import { InterruptedLoad, RecoveryOptions, RecoveryRecord } from "./recovery/recovery-controller.js";
8
12
  import { AutosaveOptions } from "./save/autosave-scheduler.js";
9
13
  import { RetryOptions } from "./save/retry.js";
10
14
  import { DocumentEngineError, DocumentEngineErrorDetails, DocumentErrorCode, DocumentIssue, createConflictError, isDocumentEngineError } from "./engine/errors.js";
11
15
  import { DocumentStorage, SaveContext, SaveResult } from "./storage/storage-contract.js";
12
16
  import { SaveOptions, SaveRetryEvent, SaveState, SaveStatus } from "./save/save-controller.js";
13
17
  import { Unsubscribe } from "./engine/event-emitter.js";
14
- import { DocumentEngine, DocumentEngineEvents, DocumentEngineOptions, LoadOptions, createDocumentEngine } from "./engine/create-document-engine.js";
18
+ import { DocumentEngine, DocumentEngineEvents, DocumentEngineOptions, ExportResult, LoadOptions, createDocumentEngine } from "./engine/create-document-engine.js";
15
19
  import { validateDocument } from "./document/validate-document.js";
16
20
  import { KeyboardShortcutOptions, UndoRedoTarget, bindKeyboardShortcuts } from "./history/keyboard-shortcuts.js";
17
21
  import { UnsavedChangesSource, bindUnsavedChangesWarning } from "./save/unsaved-changes-warning.js";
18
- export { type AssetManifest, type AssetOptions, type AssetReport, type AssetWarning, type AssetWarningCode, type AutosaveOptions, CURRENT_SCHEMA_VERSION, type CustomObjectDefinition, type DocumentCanvas, type DocumentEngine, DocumentEngineError, type DocumentEngineErrorDetails, type DocumentEngineEvents, type DocumentEngineOptions, type DocumentErrorCode, type DocumentInfo, type DocumentIssue, type DocumentStorage, type FabricDocument, type FontAsset, type FontLoader, type HistoryOptions, type HistoryState, type ImageAsset, type KeyboardShortcutOptions, type LoadOptions, type NewDocumentOptions, type RetryOptions, type SaveContext, type SaveOptions, type SaveResult, type SaveRetryEvent, type SaveState, type SaveStatus, type SerializedFabricObject, type UndoRedoTarget, type UnsavedChangesSource, type Unsubscribe, type UploadRequest, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, isDocumentEngineError, validateDocument };
22
+ import { DownloadableExport, downloadExport } from "./export/download-export.js";
23
+ export { type AssetManifest, type AssetOptions, type AssetReport, type AssetWarning, type AssetWarningCode, type AutosaveOptions, CURRENT_SCHEMA_VERSION, type CustomObjectDefinition, type DocumentCanvas, type DocumentEngine, DocumentEngineError, type DocumentEngineErrorDetails, type DocumentEngineEvents, type DocumentEngineOptions, type DocumentErrorCode, type DocumentInfo, type DocumentIssue, type DocumentStorage, type DownloadableExport, type ExportArea, type ExportBackground, type ExportFormat, type ExportOptions, type ExportPreflight, type ExportProblem, type ExportProblemCode, type ExportRect, type ExportResult, type FabricDocument, type FontAsset, type FontLoader, type HistoryOptions, type HistoryState, type ImageAsset, type InterruptedLoad, type KeyboardShortcutOptions, type LoadOptions, type NewDocumentOptions, type RecoveryOptions, type RecoveryRecord, type RecoveryStore, type RetryOptions, type SaveContext, type SaveOptions, type SaveResult, type SaveRetryEvent, type SaveState, type SaveStatus, type SerializedFabricObject, type UndoRedoTarget, type UnsavedChangesSource, type Unsubscribe, type UploadRequest, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, downloadExport, isDocumentEngineError, validateDocument };
package/dist/index.js CHANGED
@@ -4,4 +4,5 @@ import { validateDocument } from "./document/validate-document.js";
4
4
  import { createDocumentEngine } from "./engine/create-document-engine.js";
5
5
  import { bindKeyboardShortcuts } from "./history/keyboard-shortcuts.js";
6
6
  import { bindUnsavedChangesWarning } from "./save/unsaved-changes-warning.js";
7
- export { CURRENT_SCHEMA_VERSION, DocumentEngineError, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, isDocumentEngineError, validateDocument };
7
+ import { downloadExport } from "./export/download-export.js";
8
+ export { CURRENT_SCHEMA_VERSION, DocumentEngineError, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, downloadExport, isDocumentEngineError, validateDocument };
@@ -0,0 +1,80 @@
1
+ //#region src/recovery/indexed-db-recovery.ts
2
+ function requestToPromise(request) {
3
+ return new Promise((resolve, reject) => {
4
+ request.onsuccess = () => resolve(request.result);
5
+ request.onerror = () => reject(request.error);
6
+ });
7
+ }
8
+ function browserStorage() {
9
+ try {
10
+ return globalThis.localStorage;
11
+ } catch {
12
+ return;
13
+ }
14
+ }
15
+ function createIndexedDbRecovery(options = {}) {
16
+ const databaseName = options.databaseName ?? "fabricjs-document-engine";
17
+ const storeName = options.storeName ?? "recovery";
18
+ let opening;
19
+ let database;
20
+ const emergencyPrefix = `${databaseName}/${storeName}:`;
21
+ const emergencyStorage = browserStorage();
22
+ function readEmergency(key) {
23
+ const json = emergencyStorage?.getItem(`${emergencyPrefix}${key}`);
24
+ return json === null || json === void 0 ? void 0 : JSON.parse(json);
25
+ }
26
+ function emergencyKeys() {
27
+ if (!emergencyStorage) return [];
28
+ return Array.from({ length: emergencyStorage.length }, (_, index) => emergencyStorage.key(index) ?? "").filter((key) => key.startsWith(emergencyPrefix)).map((key) => key.slice(emergencyPrefix.length));
29
+ }
30
+ function openDatabase() {
31
+ opening ?? (opening = new Promise((resolve, reject) => {
32
+ const request = indexedDB.open(databaseName, 1);
33
+ request.onupgradeneeded = () => {
34
+ if (!request.result.objectStoreNames.contains(storeName)) request.result.createObjectStore(storeName);
35
+ };
36
+ request.onsuccess = () => {
37
+ database = request.result;
38
+ resolve(request.result);
39
+ };
40
+ request.onerror = () => {
41
+ opening = void 0;
42
+ reject(request.error);
43
+ };
44
+ }));
45
+ return opening;
46
+ }
47
+ async function run(mode, work) {
48
+ const transaction = (database ?? await openDatabase()).transaction(storeName, mode);
49
+ const result = requestToPromise(work(transaction.objectStore(storeName)));
50
+ await new Promise((resolve, reject) => {
51
+ transaction.oncomplete = () => resolve();
52
+ transaction.onerror = () => reject(transaction.error);
53
+ transaction.onabort = () => reject(transaction.error);
54
+ });
55
+ return result;
56
+ }
57
+ return {
58
+ get: async (key) => await run("readonly", (store) => store.get(key)) ?? readEmergency(key),
59
+ set: async (key, value) => {
60
+ await run("readwrite", (store) => store.put(value, key));
61
+ },
62
+ setNow(key, value) {
63
+ try {
64
+ emergencyStorage?.setItem(`${emergencyPrefix}${key}`, JSON.stringify(value));
65
+ } catch {
66
+ return;
67
+ }
68
+ },
69
+ delete: async (key) => {
70
+ emergencyStorage?.removeItem(`${emergencyPrefix}${key}`);
71
+ await run("readwrite", (store) => store.delete(key));
72
+ },
73
+ keys: async () => {
74
+ const stored = (await run("readonly", (store) => store.getAllKeys())).map(String);
75
+ return [.../* @__PURE__ */ new Set([...stored, ...emergencyKeys()])];
76
+ }
77
+ };
78
+ }
79
+ //#endregion
80
+ exports.createIndexedDbRecovery = createIndexedDbRecovery;
@@ -0,0 +1,8 @@
1
+ import { RecoveryStore } from "./recovery-store.cjs";
2
+ //#region src/recovery/indexed-db-recovery.d.ts
3
+ export interface IndexedDbRecoveryOptions {
4
+ databaseName?: string;
5
+ storeName?: string;
6
+ }
7
+ export declare function createIndexedDbRecovery(options?: IndexedDbRecoveryOptions): RecoveryStore;
8
+ //#endregion
@@ -0,0 +1,8 @@
1
+ import { RecoveryStore } from "./recovery-store.js";
2
+ //#region src/recovery/indexed-db-recovery.d.ts
3
+ export interface IndexedDbRecoveryOptions {
4
+ databaseName?: string;
5
+ storeName?: string;
6
+ }
7
+ export declare function createIndexedDbRecovery(options?: IndexedDbRecoveryOptions): RecoveryStore;
8
+ //#endregion
@@ -0,0 +1,80 @@
1
+ //#region src/recovery/indexed-db-recovery.ts
2
+ function requestToPromise(request) {
3
+ return new Promise((resolve, reject) => {
4
+ request.onsuccess = () => resolve(request.result);
5
+ request.onerror = () => reject(request.error);
6
+ });
7
+ }
8
+ function browserStorage() {
9
+ try {
10
+ return globalThis.localStorage;
11
+ } catch {
12
+ return;
13
+ }
14
+ }
15
+ function createIndexedDbRecovery(options = {}) {
16
+ const databaseName = options.databaseName ?? "fabricjs-document-engine";
17
+ const storeName = options.storeName ?? "recovery";
18
+ let opening;
19
+ let database;
20
+ const emergencyPrefix = `${databaseName}/${storeName}:`;
21
+ const emergencyStorage = browserStorage();
22
+ function readEmergency(key) {
23
+ const json = emergencyStorage?.getItem(`${emergencyPrefix}${key}`);
24
+ return json === null || json === void 0 ? void 0 : JSON.parse(json);
25
+ }
26
+ function emergencyKeys() {
27
+ if (!emergencyStorage) return [];
28
+ return Array.from({ length: emergencyStorage.length }, (_, index) => emergencyStorage.key(index) ?? "").filter((key) => key.startsWith(emergencyPrefix)).map((key) => key.slice(emergencyPrefix.length));
29
+ }
30
+ function openDatabase() {
31
+ opening ?? (opening = new Promise((resolve, reject) => {
32
+ const request = indexedDB.open(databaseName, 1);
33
+ request.onupgradeneeded = () => {
34
+ if (!request.result.objectStoreNames.contains(storeName)) request.result.createObjectStore(storeName);
35
+ };
36
+ request.onsuccess = () => {
37
+ database = request.result;
38
+ resolve(request.result);
39
+ };
40
+ request.onerror = () => {
41
+ opening = void 0;
42
+ reject(request.error);
43
+ };
44
+ }));
45
+ return opening;
46
+ }
47
+ async function run(mode, work) {
48
+ const transaction = (database ?? await openDatabase()).transaction(storeName, mode);
49
+ const result = requestToPromise(work(transaction.objectStore(storeName)));
50
+ await new Promise((resolve, reject) => {
51
+ transaction.oncomplete = () => resolve();
52
+ transaction.onerror = () => reject(transaction.error);
53
+ transaction.onabort = () => reject(transaction.error);
54
+ });
55
+ return result;
56
+ }
57
+ return {
58
+ get: async (key) => await run("readonly", (store) => store.get(key)) ?? readEmergency(key),
59
+ set: async (key, value) => {
60
+ await run("readwrite", (store) => store.put(value, key));
61
+ },
62
+ setNow(key, value) {
63
+ try {
64
+ emergencyStorage?.setItem(`${emergencyPrefix}${key}`, JSON.stringify(value));
65
+ } catch {
66
+ return;
67
+ }
68
+ },
69
+ delete: async (key) => {
70
+ emergencyStorage?.removeItem(`${emergencyPrefix}${key}`);
71
+ await run("readwrite", (store) => store.delete(key));
72
+ },
73
+ keys: async () => {
74
+ const stored = (await run("readonly", (store) => store.getAllKeys())).map(String);
75
+ return [.../* @__PURE__ */ new Set([...stored, ...emergencyKeys()])];
76
+ }
77
+ };
78
+ }
79
+ //#endregion
80
+ export { createIndexedDbRecovery };