fabricjs-document-engine 0.5.0 → 0.7.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 (52) hide show
  1. package/README.md +82 -3
  2. package/dist/engine/create-document-engine.cjs +107 -4
  3. package/dist/engine/create-document-engine.d.cts +23 -0
  4. package/dist/engine/create-document-engine.d.ts +23 -0
  5. package/dist/engine/create-document-engine.js +107 -4
  6. package/dist/engine/errors.cjs +1 -0
  7. package/dist/engine/errors.d.cts +3 -1
  8. package/dist/engine/errors.d.ts +3 -1
  9. package/dist/engine/errors.js +1 -0
  10. package/dist/index.cjs +5 -0
  11. package/dist/index.d.cts +5 -2
  12. package/dist/index.d.ts +5 -2
  13. package/dist/index.js +3 -1
  14. package/dist/migrations/migrate-document.cjs +65 -0
  15. package/dist/migrations/migrate-document.d.cts +15 -0
  16. package/dist/migrations/migrate-document.d.ts +15 -0
  17. package/dist/migrations/migrate-document.js +63 -0
  18. package/dist/react/engine-context.cjs +12 -0
  19. package/dist/react/engine-context.d.cts +10 -0
  20. package/dist/react/engine-context.d.ts +10 -0
  21. package/dist/react/engine-context.js +11 -0
  22. package/dist/react/use-document-engine.cjs +23 -0
  23. package/dist/react/use-document-engine.d.cts +6 -0
  24. package/dist/react/use-document-engine.d.ts +6 -0
  25. package/dist/react/use-document-engine.js +23 -0
  26. package/dist/react/use-document-event.cjs +12 -0
  27. package/dist/react/use-document-event.d.cts +4 -0
  28. package/dist/react/use-document-event.d.ts +4 -0
  29. package/dist/react/use-document-event.js +12 -0
  30. package/dist/react/use-document-state.cjs +11 -0
  31. package/dist/react/use-document-state.d.cts +5 -0
  32. package/dist/react/use-document-state.d.ts +5 -0
  33. package/dist/react/use-document-state.js +11 -0
  34. package/dist/react.cjs +11 -0
  35. package/dist/react.d.cts +6 -0
  36. package/dist/react.d.ts +6 -0
  37. package/dist/react.js +6 -0
  38. package/dist/state/document-state-store.cjs +114 -0
  39. package/dist/state/document-state-store.d.cts +28 -0
  40. package/dist/state/document-state-store.d.ts +28 -0
  41. package/dist/state/document-state-store.js +114 -0
  42. package/dist/storage/key-value-storage.cjs +27 -1
  43. package/dist/storage/key-value-storage.d.cts +2 -1
  44. package/dist/storage/key-value-storage.d.ts +2 -1
  45. package/dist/storage/key-value-storage.js +27 -1
  46. package/dist/storage.d.cts +2 -1
  47. package/dist/storage.d.ts +2 -1
  48. package/dist/versions/document-version.cjs +20 -0
  49. package/dist/versions/document-version.d.cts +25 -0
  50. package/dist/versions/document-version.d.ts +25 -0
  51. package/dist/versions/document-version.js +17 -0
  52. package/package.json +26 -4
@@ -1,7 +1,7 @@
1
1
  import { FontAsset, ImageAsset } from "../assets/asset-manifest.js";
2
2
  import { ExportProblem } from "../export/preflight-export.js";
3
3
  //#region src/engine/errors.d.ts
4
- export type DocumentErrorCode = "INVALID_DOCUMENT" | "UNSUPPORTED_SCHEMA" | "UNKNOWN_OBJECT_TYPE" | "INVALID_CUSTOM_OBJECT" | "LOAD_ABORTED" | "LOAD_FAILED" | "MISSING_ASSETS" | "MISSING_FONTS" | "ASSET_UPLOAD_FAILED" | "STORAGE_MISSING" | "SAVE_FAILED" | "SAVE_CONFLICT" | "SAVE_CANCELLED" | "DOCUMENT_NOT_FOUND" | "HISTORY_FAILED" | "RECOVERY_MISSING" | "RECOVERY_NOT_FOUND" | "RECOVERY_FAILED" | "INVALID_EXPORT_OPTIONS" | "EXPORT_BLOCKED" | "EXPORT_FAILED" | "EXPORT_ABORTED" | "ENGINE_DESTROYED";
4
+ export type DocumentErrorCode = "INVALID_DOCUMENT" | "UNSUPPORTED_SCHEMA" | "UNKNOWN_OBJECT_TYPE" | "INVALID_CUSTOM_OBJECT" | "LOAD_ABORTED" | "LOAD_FAILED" | "MISSING_ASSETS" | "MISSING_FONTS" | "ASSET_UPLOAD_FAILED" | "STORAGE_MISSING" | "SAVE_FAILED" | "SAVE_CONFLICT" | "SAVE_CANCELLED" | "DOCUMENT_NOT_FOUND" | "HISTORY_FAILED" | "RECOVERY_MISSING" | "RECOVERY_NOT_FOUND" | "RECOVERY_FAILED" | "MIGRATION_FAILED" | "VERSIONS_UNSUPPORTED" | "VERSION_NOT_FOUND" | "VERSION_FAILED" | "INVALID_EXPORT_OPTIONS" | "EXPORT_BLOCKED" | "EXPORT_FAILED" | "EXPORT_ABORTED" | "ENGINE_DESTROYED";
5
5
  export interface DocumentIssue {
6
6
  code: DocumentErrorCode;
7
7
  path: string;
@@ -13,6 +13,7 @@ export interface DocumentEngineErrorDetails {
13
13
  missingAssets?: ImageAsset[];
14
14
  missingFonts?: FontAsset[];
15
15
  problems?: ExportProblem[];
16
+ migrationFrom?: number;
16
17
  cause?: unknown;
17
18
  retryable?: boolean;
18
19
  }
@@ -23,6 +24,7 @@ export declare class DocumentEngineError extends Error {
23
24
  readonly missingAssets: ImageAsset[];
24
25
  readonly missingFonts: FontAsset[];
25
26
  readonly problems: ExportProblem[];
27
+ readonly migrationFrom: number | undefined;
26
28
  readonly cause: unknown;
27
29
  readonly retryable: boolean;
28
30
  constructor(code: DocumentErrorCode, message: string, details?: DocumentEngineErrorDetails);
@@ -9,6 +9,7 @@ var DocumentEngineError = class extends Error {
9
9
  this.missingAssets = details.missingAssets ?? [];
10
10
  this.missingFonts = details.missingFonts ?? [];
11
11
  this.problems = details.problems ?? [];
12
+ this.migrationFrom = details.migrationFrom;
12
13
  this.cause = details.cause;
13
14
  this.retryable = details.retryable ?? false;
14
15
  }
package/dist/index.cjs CHANGED
@@ -2,16 +2,21 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_errors = require("./engine/errors.cjs");
3
3
  const require_document_format = require("./document/document-format.cjs");
4
4
  const require_validate_document = require("./document/validate-document.cjs");
5
+ const require_migrate_document = require("./migrations/migrate-document.cjs");
5
6
  const require_create_document_engine = require("./engine/create-document-engine.cjs");
6
7
  const require_keyboard_shortcuts = require("./history/keyboard-shortcuts.cjs");
7
8
  const require_unsaved_changes_warning = require("./save/unsaved-changes-warning.cjs");
8
9
  const require_download_export = require("./export/download-export.cjs");
10
+ const require_document_state_store = require("./state/document-state-store.cjs");
9
11
  exports.CURRENT_SCHEMA_VERSION = require_document_format.CURRENT_SCHEMA_VERSION;
10
12
  exports.DocumentEngineError = require_errors.DocumentEngineError;
11
13
  exports.bindKeyboardShortcuts = require_keyboard_shortcuts.bindKeyboardShortcuts;
12
14
  exports.bindUnsavedChangesWarning = require_unsaved_changes_warning.bindUnsavedChangesWarning;
13
15
  exports.createConflictError = require_errors.createConflictError;
14
16
  exports.createDocumentEngine = require_create_document_engine.createDocumentEngine;
17
+ exports.createDocumentStateStore = require_document_state_store.createDocumentStateStore;
18
+ exports.detectSchemaVersion = require_migrate_document.detectSchemaVersion;
15
19
  exports.downloadExport = require_download_export.downloadExport;
16
20
  exports.isDocumentEngineError = require_errors.isDocumentEngineError;
21
+ exports.migrateDocument = require_migrate_document.migrateDocument;
17
22
  exports.validateDocument = require_validate_document.validateDocument;
package/dist/index.d.cts CHANGED
@@ -7,6 +7,7 @@ import { ExportPreflight, ExportProblem, ExportProblemCode } from "./export/pref
7
7
  import { NewDocumentOptions } from "./document/create-document.cjs";
8
8
  import { CustomObjectDefinition } from "./fabric/object-registry.cjs";
9
9
  import { HistoryOptions, HistoryState } from "./history/create-history.cjs";
10
+ import { DocumentVersion, VersionKind, VersionOptions, VersionStorage, VersionSummary } from "./versions/document-version.cjs";
10
11
  import { RecoveryStore } from "./recovery/recovery-store.cjs";
11
12
  import { InterruptedLoad, RecoveryOptions, RecoveryRecord } from "./recovery/recovery-controller.cjs";
12
13
  import { AutosaveOptions } from "./save/autosave-scheduler.cjs";
@@ -15,9 +16,11 @@ import { DocumentEngineError, DocumentEngineErrorDetails, DocumentErrorCode, Doc
15
16
  import { DocumentStorage, SaveContext, SaveResult } from "./storage/storage-contract.cjs";
16
17
  import { SaveOptions, SaveRetryEvent, SaveState, SaveStatus } from "./save/save-controller.cjs";
17
18
  import { Unsubscribe } from "./engine/event-emitter.cjs";
18
- import { DocumentEngine, DocumentEngineEvents, DocumentEngineOptions, ExportResult, LoadOptions, createDocumentEngine } from "./engine/create-document-engine.cjs";
19
+ import { DocumentEngine, DocumentEngineEvents, DocumentEngineOptions, ExportResult, ImportOptions, LoadOptions, createDocumentEngine } from "./engine/create-document-engine.cjs";
19
20
  import { validateDocument } from "./document/validate-document.cjs";
20
21
  import { KeyboardShortcutOptions, UndoRedoTarget, bindKeyboardShortcuts } from "./history/keyboard-shortcuts.cjs";
21
22
  import { UnsavedChangesSource, bindUnsavedChangesWarning } from "./save/unsaved-changes-warning.cjs";
22
23
  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 };
24
+ import { Migration, MigrationContext, MigrationResult, detectSchemaVersion, migrateDocument } from "./migrations/migrate-document.cjs";
25
+ import { DocumentState, DocumentStateStore, createDocumentStateStore } from "./state/document-state-store.cjs";
26
+ 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 DocumentState, type DocumentStateStore, type DocumentStorage, type DocumentVersion, 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 ImportOptions, type InterruptedLoad, type KeyboardShortcutOptions, type LoadOptions, type Migration, type MigrationContext, type MigrationResult, 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, type VersionKind, type VersionOptions, type VersionStorage, type VersionSummary, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, createDocumentStateStore, detectSchemaVersion, downloadExport, isDocumentEngineError, migrateDocument, validateDocument };
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ import { ExportPreflight, ExportProblem, ExportProblemCode } from "./export/pref
7
7
  import { NewDocumentOptions } from "./document/create-document.js";
8
8
  import { CustomObjectDefinition } from "./fabric/object-registry.js";
9
9
  import { HistoryOptions, HistoryState } from "./history/create-history.js";
10
+ import { DocumentVersion, VersionKind, VersionOptions, VersionStorage, VersionSummary } from "./versions/document-version.js";
10
11
  import { RecoveryStore } from "./recovery/recovery-store.js";
11
12
  import { InterruptedLoad, RecoveryOptions, RecoveryRecord } from "./recovery/recovery-controller.js";
12
13
  import { AutosaveOptions } from "./save/autosave-scheduler.js";
@@ -15,9 +16,11 @@ import { DocumentEngineError, DocumentEngineErrorDetails, DocumentErrorCode, Doc
15
16
  import { DocumentStorage, SaveContext, SaveResult } from "./storage/storage-contract.js";
16
17
  import { SaveOptions, SaveRetryEvent, SaveState, SaveStatus } from "./save/save-controller.js";
17
18
  import { Unsubscribe } from "./engine/event-emitter.js";
18
- import { DocumentEngine, DocumentEngineEvents, DocumentEngineOptions, ExportResult, LoadOptions, createDocumentEngine } from "./engine/create-document-engine.js";
19
+ import { DocumentEngine, DocumentEngineEvents, DocumentEngineOptions, ExportResult, ImportOptions, LoadOptions, createDocumentEngine } from "./engine/create-document-engine.js";
19
20
  import { validateDocument } from "./document/validate-document.js";
20
21
  import { KeyboardShortcutOptions, UndoRedoTarget, bindKeyboardShortcuts } from "./history/keyboard-shortcuts.js";
21
22
  import { UnsavedChangesSource, bindUnsavedChangesWarning } from "./save/unsaved-changes-warning.js";
22
23
  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 };
24
+ import { Migration, MigrationContext, MigrationResult, detectSchemaVersion, migrateDocument } from "./migrations/migrate-document.js";
25
+ import { DocumentState, DocumentStateStore, createDocumentStateStore } from "./state/document-state-store.js";
26
+ 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 DocumentState, type DocumentStateStore, type DocumentStorage, type DocumentVersion, 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 ImportOptions, type InterruptedLoad, type KeyboardShortcutOptions, type LoadOptions, type Migration, type MigrationContext, type MigrationResult, 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, type VersionKind, type VersionOptions, type VersionStorage, type VersionSummary, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, createDocumentStateStore, detectSchemaVersion, downloadExport, isDocumentEngineError, migrateDocument, validateDocument };
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { DocumentEngineError, createConflictError, isDocumentEngineError } from "./engine/errors.js";
2
2
  import { CURRENT_SCHEMA_VERSION } from "./document/document-format.js";
3
3
  import { validateDocument } from "./document/validate-document.js";
4
+ import { detectSchemaVersion, migrateDocument } from "./migrations/migrate-document.js";
4
5
  import { createDocumentEngine } from "./engine/create-document-engine.js";
5
6
  import { bindKeyboardShortcuts } from "./history/keyboard-shortcuts.js";
6
7
  import { bindUnsavedChangesWarning } from "./save/unsaved-changes-warning.js";
7
8
  import { downloadExport } from "./export/download-export.js";
8
- export { CURRENT_SCHEMA_VERSION, DocumentEngineError, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, downloadExport, isDocumentEngineError, validateDocument };
9
+ import { createDocumentStateStore } from "./state/document-state-store.js";
10
+ export { CURRENT_SCHEMA_VERSION, DocumentEngineError, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, createDocumentStateStore, detectSchemaVersion, downloadExport, isDocumentEngineError, migrateDocument, validateDocument };
@@ -0,0 +1,65 @@
1
+ const require_errors = require("../engine/errors.cjs");
2
+ require("../document/document-format.cjs");
3
+ const require_ids = require("../document/ids.cjs");
4
+ //#region src/migrations/migrate-document.ts
5
+ function isPlainObject(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ function positiveNumberOr(value, fallback) {
9
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
10
+ }
11
+ function fromFabricJson(json, context) {
12
+ const now = (/* @__PURE__ */ new Date()).toISOString();
13
+ const document = {
14
+ schemaVersion: 1,
15
+ id: context.id ?? (typeof json.id === "string" && json.id.length > 0 ? json.id : require_ids.createId()),
16
+ createdAt: now,
17
+ updatedAt: now,
18
+ revision: 0,
19
+ canvas: {
20
+ width: positiveNumberOr(json.width, context.canvasWidth),
21
+ height: positiveNumberOr(json.height, context.canvasHeight),
22
+ ...json.background === void 0 ? {} : { background: json.background }
23
+ },
24
+ objects: json.objects,
25
+ metadata: { ...context.metadata }
26
+ };
27
+ if (typeof json.version === "string") document.fabricVersion = json.version;
28
+ return document;
29
+ }
30
+ const migrations = { 0: fromFabricJson };
31
+ function detectSchemaVersion(value) {
32
+ if (!isPlainObject(value)) return void 0;
33
+ if (typeof value.schemaVersion === "number" && Number.isInteger(value.schemaVersion)) return value.schemaVersion;
34
+ if (value.schemaVersion === void 0 && Array.isArray(value.objects)) return 0;
35
+ }
36
+ function migrateDocument(value, context, steps = migrations, targetVersion = 1) {
37
+ const startVersion = detectSchemaVersion(value);
38
+ if (startVersion === void 0 || startVersion >= targetVersion || startVersion < 0) return {
39
+ document: value,
40
+ migratedFrom: void 0
41
+ };
42
+ let document = JSON.parse(JSON.stringify(value));
43
+ for (let version = startVersion; version < targetVersion; version += 1) {
44
+ const step = steps[version];
45
+ if (!step) throw new require_errors.DocumentEngineError("MIGRATION_FAILED", `There is no migration from schema version ${version}`, { migrationFrom: version });
46
+ try {
47
+ document = step(document, context);
48
+ } catch (error) {
49
+ const reason = error instanceof Error ? error.message : String(error);
50
+ throw new require_errors.DocumentEngineError("MIGRATION_FAILED", `Could not migrate the document from schema version ${version} to ${version + 1}: ${reason}`, {
51
+ cause: error,
52
+ migrationFrom: version
53
+ });
54
+ }
55
+ if (document.schemaVersion !== version + 1) throw new require_errors.DocumentEngineError("MIGRATION_FAILED", `The migration from schema version ${version} produced version ${String(document.schemaVersion)} instead of ${version + 1}`, { migrationFrom: version });
56
+ }
57
+ return {
58
+ document,
59
+ migratedFrom: startVersion
60
+ };
61
+ }
62
+ //#endregion
63
+ exports.detectSchemaVersion = detectSchemaVersion;
64
+ exports.migrateDocument = migrateDocument;
65
+ exports.migrations = migrations;
@@ -0,0 +1,15 @@
1
+ //#region src/migrations/migrate-document.d.ts
2
+ export interface MigrationContext {
3
+ canvasWidth: number;
4
+ canvasHeight: number;
5
+ id?: string;
6
+ metadata?: Record<string, unknown>;
7
+ }
8
+ export type Migration = (document: Record<string, unknown>, context: MigrationContext) => Record<string, unknown>;
9
+ export interface MigrationResult {
10
+ document: unknown;
11
+ migratedFrom: number | undefined;
12
+ }
13
+ export declare function detectSchemaVersion(value: unknown): number | undefined;
14
+ export declare function migrateDocument(value: unknown, context: MigrationContext, steps?: Readonly<Record<number, Migration>>, targetVersion?: number): MigrationResult;
15
+ //#endregion
@@ -0,0 +1,15 @@
1
+ //#region src/migrations/migrate-document.d.ts
2
+ export interface MigrationContext {
3
+ canvasWidth: number;
4
+ canvasHeight: number;
5
+ id?: string;
6
+ metadata?: Record<string, unknown>;
7
+ }
8
+ export type Migration = (document: Record<string, unknown>, context: MigrationContext) => Record<string, unknown>;
9
+ export interface MigrationResult {
10
+ document: unknown;
11
+ migratedFrom: number | undefined;
12
+ }
13
+ export declare function detectSchemaVersion(value: unknown): number | undefined;
14
+ export declare function migrateDocument(value: unknown, context: MigrationContext, steps?: Readonly<Record<number, Migration>>, targetVersion?: number): MigrationResult;
15
+ //#endregion
@@ -0,0 +1,63 @@
1
+ import { DocumentEngineError } from "../engine/errors.js";
2
+ import "../document/document-format.js";
3
+ import { createId } from "../document/ids.js";
4
+ //#region src/migrations/migrate-document.ts
5
+ function isPlainObject(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ function positiveNumberOr(value, fallback) {
9
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
10
+ }
11
+ function fromFabricJson(json, context) {
12
+ const now = (/* @__PURE__ */ new Date()).toISOString();
13
+ const document = {
14
+ schemaVersion: 1,
15
+ id: context.id ?? (typeof json.id === "string" && json.id.length > 0 ? json.id : createId()),
16
+ createdAt: now,
17
+ updatedAt: now,
18
+ revision: 0,
19
+ canvas: {
20
+ width: positiveNumberOr(json.width, context.canvasWidth),
21
+ height: positiveNumberOr(json.height, context.canvasHeight),
22
+ ...json.background === void 0 ? {} : { background: json.background }
23
+ },
24
+ objects: json.objects,
25
+ metadata: { ...context.metadata }
26
+ };
27
+ if (typeof json.version === "string") document.fabricVersion = json.version;
28
+ return document;
29
+ }
30
+ const migrations = { 0: fromFabricJson };
31
+ function detectSchemaVersion(value) {
32
+ if (!isPlainObject(value)) return void 0;
33
+ if (typeof value.schemaVersion === "number" && Number.isInteger(value.schemaVersion)) return value.schemaVersion;
34
+ if (value.schemaVersion === void 0 && Array.isArray(value.objects)) return 0;
35
+ }
36
+ function migrateDocument(value, context, steps = migrations, targetVersion = 1) {
37
+ const startVersion = detectSchemaVersion(value);
38
+ if (startVersion === void 0 || startVersion >= targetVersion || startVersion < 0) return {
39
+ document: value,
40
+ migratedFrom: void 0
41
+ };
42
+ let document = JSON.parse(JSON.stringify(value));
43
+ for (let version = startVersion; version < targetVersion; version += 1) {
44
+ const step = steps[version];
45
+ if (!step) throw new DocumentEngineError("MIGRATION_FAILED", `There is no migration from schema version ${version}`, { migrationFrom: version });
46
+ try {
47
+ document = step(document, context);
48
+ } catch (error) {
49
+ const reason = error instanceof Error ? error.message : String(error);
50
+ throw new DocumentEngineError("MIGRATION_FAILED", `Could not migrate the document from schema version ${version} to ${version + 1}: ${reason}`, {
51
+ cause: error,
52
+ migrationFrom: version
53
+ });
54
+ }
55
+ if (document.schemaVersion !== version + 1) throw new DocumentEngineError("MIGRATION_FAILED", `The migration from schema version ${version} produced version ${String(document.schemaVersion)} instead of ${version + 1}`, { migrationFrom: version });
56
+ }
57
+ return {
58
+ document,
59
+ migratedFrom: startVersion
60
+ };
61
+ }
62
+ //#endregion
63
+ export { detectSchemaVersion, migrateDocument, migrations };
@@ -0,0 +1,12 @@
1
+ let react = require("react");
2
+ //#region src/react/engine-context.ts
3
+ const EngineContext = (0, react.createContext)(null);
4
+ function DocumentEngineProvider({ engine, children }) {
5
+ return (0, react.createElement)(EngineContext.Provider, { value: engine }, children);
6
+ }
7
+ function useEngine() {
8
+ return (0, react.useContext)(EngineContext);
9
+ }
10
+ //#endregion
11
+ exports.DocumentEngineProvider = DocumentEngineProvider;
12
+ exports.useEngine = useEngine;
@@ -0,0 +1,10 @@
1
+ import { DocumentEngine } from "../engine/create-document-engine.cjs";
2
+ import { ReactNode } from "react";
3
+ //#region src/react/engine-context.d.ts
4
+ export interface DocumentEngineProviderProps {
5
+ engine: DocumentEngine | null;
6
+ children?: ReactNode;
7
+ }
8
+ export declare function DocumentEngineProvider({ engine, children }: DocumentEngineProviderProps): ReactNode;
9
+ export declare function useEngine(): DocumentEngine | null;
10
+ //#endregion
@@ -0,0 +1,10 @@
1
+ import { DocumentEngine } from "../engine/create-document-engine.js";
2
+ import { ReactNode } from "react";
3
+ //#region src/react/engine-context.d.ts
4
+ export interface DocumentEngineProviderProps {
5
+ engine: DocumentEngine | null;
6
+ children?: ReactNode;
7
+ }
8
+ export declare function DocumentEngineProvider({ engine, children }: DocumentEngineProviderProps): ReactNode;
9
+ export declare function useEngine(): DocumentEngine | null;
10
+ //#endregion
@@ -0,0 +1,11 @@
1
+ import { createContext, createElement, useContext } from "react";
2
+ //#region src/react/engine-context.ts
3
+ const EngineContext = createContext(null);
4
+ function DocumentEngineProvider({ engine, children }) {
5
+ return createElement(EngineContext.Provider, { value: engine }, children);
6
+ }
7
+ function useEngine() {
8
+ return useContext(EngineContext);
9
+ }
10
+ //#endregion
11
+ export { DocumentEngineProvider, useEngine };
@@ -0,0 +1,23 @@
1
+ const require_create_document_engine = require("../engine/create-document-engine.cjs");
2
+ let react = require("react");
3
+ //#region src/react/use-document-engine.ts
4
+ function useDocumentEngine(canvas, options = {}) {
5
+ const latestOptions = (0, react.useRef)(options);
6
+ latestOptions.current = options;
7
+ const [engine, setEngine] = (0, react.useState)(null);
8
+ (0, react.useEffect)(() => {
9
+ if (!canvas) return void 0;
10
+ const created = require_create_document_engine.createDocumentEngine({
11
+ ...latestOptions.current,
12
+ canvas
13
+ });
14
+ setEngine(created);
15
+ return () => {
16
+ created.destroy();
17
+ setEngine((current) => current === created ? null : current);
18
+ };
19
+ }, [canvas]);
20
+ return engine;
21
+ }
22
+ //#endregion
23
+ exports.useDocumentEngine = useDocumentEngine;
@@ -0,0 +1,6 @@
1
+ import { DocumentEngine, DocumentEngineOptions } from "../engine/create-document-engine.cjs";
2
+ import { StaticCanvas } from "fabric";
3
+ //#region src/react/use-document-engine.d.ts
4
+ export type ReactEngineOptions = Omit<DocumentEngineOptions, "canvas">;
5
+ export declare function useDocumentEngine(canvas: StaticCanvas | null | undefined, options?: ReactEngineOptions): DocumentEngine | null;
6
+ //#endregion
@@ -0,0 +1,6 @@
1
+ import { DocumentEngine, DocumentEngineOptions } from "../engine/create-document-engine.js";
2
+ import { StaticCanvas } from "fabric";
3
+ //#region src/react/use-document-engine.d.ts
4
+ export type ReactEngineOptions = Omit<DocumentEngineOptions, "canvas">;
5
+ export declare function useDocumentEngine(canvas: StaticCanvas | null | undefined, options?: ReactEngineOptions): DocumentEngine | null;
6
+ //#endregion
@@ -0,0 +1,23 @@
1
+ import { createDocumentEngine } from "../engine/create-document-engine.js";
2
+ import { useEffect, useRef, useState } from "react";
3
+ //#region src/react/use-document-engine.ts
4
+ function useDocumentEngine(canvas, options = {}) {
5
+ const latestOptions = useRef(options);
6
+ latestOptions.current = options;
7
+ const [engine, setEngine] = useState(null);
8
+ useEffect(() => {
9
+ if (!canvas) return void 0;
10
+ const created = createDocumentEngine({
11
+ ...latestOptions.current,
12
+ canvas
13
+ });
14
+ setEngine(created);
15
+ return () => {
16
+ created.destroy();
17
+ setEngine((current) => current === created ? null : current);
18
+ };
19
+ }, [canvas]);
20
+ return engine;
21
+ }
22
+ //#endregion
23
+ export { useDocumentEngine };
@@ -0,0 +1,12 @@
1
+ let react = require("react");
2
+ //#region src/react/use-document-event.ts
3
+ function useDocumentEvent(engine, name, handler) {
4
+ const latestHandler = (0, react.useRef)(handler);
5
+ latestHandler.current = handler;
6
+ (0, react.useEffect)(() => {
7
+ if (!engine) return void 0;
8
+ return engine.on(name, (payload) => latestHandler.current(payload));
9
+ }, [engine, name]);
10
+ }
11
+ //#endregion
12
+ exports.useDocumentEvent = useDocumentEvent;
@@ -0,0 +1,4 @@
1
+ import { DocumentEngine, DocumentEngineEvents } from "../engine/create-document-engine.cjs";
2
+ //#region src/react/use-document-event.d.ts
3
+ export declare function useDocumentEvent<Name extends keyof DocumentEngineEvents>(engine: DocumentEngine | null | undefined, name: Name, handler: (payload: DocumentEngineEvents[Name]) => void): void;
4
+ //#endregion
@@ -0,0 +1,4 @@
1
+ import { DocumentEngine, DocumentEngineEvents } from "../engine/create-document-engine.js";
2
+ //#region src/react/use-document-event.d.ts
3
+ export declare function useDocumentEvent<Name extends keyof DocumentEngineEvents>(engine: DocumentEngine | null | undefined, name: Name, handler: (payload: DocumentEngineEvents[Name]) => void): void;
4
+ //#endregion
@@ -0,0 +1,12 @@
1
+ import { useEffect, useRef } from "react";
2
+ //#region src/react/use-document-event.ts
3
+ function useDocumentEvent(engine, name, handler) {
4
+ const latestHandler = useRef(handler);
5
+ latestHandler.current = handler;
6
+ useEffect(() => {
7
+ if (!engine) return void 0;
8
+ return engine.on(name, (payload) => latestHandler.current(payload));
9
+ }, [engine, name]);
10
+ }
11
+ //#endregion
12
+ export { useDocumentEvent };
@@ -0,0 +1,11 @@
1
+ const require_document_state_store = require("../state/document-state-store.cjs");
2
+ let react = require("react");
3
+ //#region src/react/use-document-state.ts
4
+ const subscribeToNothing = () => () => void 0;
5
+ const nothing = () => null;
6
+ function useDocumentState(engine) {
7
+ const store = (0, react.useMemo)(() => engine ? require_document_state_store.createDocumentStateStore(engine) : null, [engine]);
8
+ return (0, react.useSyncExternalStore)(store ? store.subscribe : subscribeToNothing, store ? store.getSnapshot : nothing, nothing);
9
+ }
10
+ //#endregion
11
+ exports.useDocumentState = useDocumentState;
@@ -0,0 +1,5 @@
1
+ import { DocumentEngine } from "../engine/create-document-engine.cjs";
2
+ import { DocumentState } from "../state/document-state-store.cjs";
3
+ //#region src/react/use-document-state.d.ts
4
+ export declare function useDocumentState(engine: DocumentEngine | null | undefined): DocumentState | null;
5
+ //#endregion
@@ -0,0 +1,5 @@
1
+ import { DocumentEngine } from "../engine/create-document-engine.js";
2
+ import { DocumentState } from "../state/document-state-store.js";
3
+ //#region src/react/use-document-state.d.ts
4
+ export declare function useDocumentState(engine: DocumentEngine | null | undefined): DocumentState | null;
5
+ //#endregion
@@ -0,0 +1,11 @@
1
+ import { createDocumentStateStore } from "../state/document-state-store.js";
2
+ import { useMemo, useSyncExternalStore } from "react";
3
+ //#region src/react/use-document-state.ts
4
+ const subscribeToNothing = () => () => void 0;
5
+ const nothing = () => null;
6
+ function useDocumentState(engine) {
7
+ const store = useMemo(() => engine ? createDocumentStateStore(engine) : null, [engine]);
8
+ return useSyncExternalStore(store ? store.subscribe : subscribeToNothing, store ? store.getSnapshot : nothing, nothing);
9
+ }
10
+ //#endregion
11
+ export { useDocumentState };
package/dist/react.cjs ADDED
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_use_document_engine = require("./react/use-document-engine.cjs");
4
+ const require_use_document_state = require("./react/use-document-state.cjs");
5
+ const require_use_document_event = require("./react/use-document-event.cjs");
6
+ const require_engine_context = require("./react/engine-context.cjs");
7
+ exports.DocumentEngineProvider = require_engine_context.DocumentEngineProvider;
8
+ exports.useDocumentEngine = require_use_document_engine.useDocumentEngine;
9
+ exports.useDocumentEvent = require_use_document_event.useDocumentEvent;
10
+ exports.useDocumentState = require_use_document_state.useDocumentState;
11
+ exports.useEngine = require_engine_context.useEngine;
@@ -0,0 +1,6 @@
1
+ import { DocumentState } from "./state/document-state-store.cjs";
2
+ import { ReactEngineOptions, useDocumentEngine } from "./react/use-document-engine.cjs";
3
+ import { useDocumentState } from "./react/use-document-state.cjs";
4
+ import { useDocumentEvent } from "./react/use-document-event.cjs";
5
+ import { DocumentEngineProvider, DocumentEngineProviderProps, useEngine } from "./react/engine-context.cjs";
6
+ export { DocumentEngineProvider, type DocumentEngineProviderProps, type DocumentState, type ReactEngineOptions, useDocumentEngine, useDocumentEvent, useDocumentState, useEngine };
@@ -0,0 +1,6 @@
1
+ import { DocumentState } from "./state/document-state-store.js";
2
+ import { ReactEngineOptions, useDocumentEngine } from "./react/use-document-engine.js";
3
+ import { useDocumentState } from "./react/use-document-state.js";
4
+ import { useDocumentEvent } from "./react/use-document-event.js";
5
+ import { DocumentEngineProvider, DocumentEngineProviderProps, useEngine } from "./react/engine-context.js";
6
+ export { DocumentEngineProvider, type DocumentEngineProviderProps, type DocumentState, type ReactEngineOptions, useDocumentEngine, useDocumentEvent, useDocumentState, useEngine };
package/dist/react.js ADDED
@@ -0,0 +1,6 @@
1
+ "use client";
2
+ import { useDocumentEngine } from "./react/use-document-engine.js";
3
+ import { useDocumentState } from "./react/use-document-state.js";
4
+ import { useDocumentEvent } from "./react/use-document-event.js";
5
+ import { DocumentEngineProvider, useEngine } from "./react/engine-context.js";
6
+ export { DocumentEngineProvider, useDocumentEngine, useDocumentEvent, useDocumentState, useEngine };
@@ -0,0 +1,114 @@
1
+ //#region src/state/document-state-store.ts
2
+ function initialState(engine) {
3
+ const saveState = engine.getSaveState();
4
+ const history = engine.getHistory();
5
+ return {
6
+ documentId: engine.getDocumentInfo().id,
7
+ isLoading: false,
8
+ loadError: void 0,
9
+ saveStatus: saveState.status,
10
+ isDirty: saveState.isDirty,
11
+ isSaving: saveState.isSaving,
12
+ revision: saveState.revision,
13
+ lastSavedAt: saveState.lastSavedAt,
14
+ saveError: saveState.error,
15
+ canUndo: history.undo.length > 0,
16
+ canRedo: history.redo.length > 0,
17
+ undoLabel: history.undo[0],
18
+ redoLabel: history.redo[0],
19
+ assetWarnings: []
20
+ };
21
+ }
22
+ function withLiveEngineValues(engine, previous) {
23
+ return {
24
+ ...previous,
25
+ ...initialState(engine),
26
+ isLoading: previous.isLoading,
27
+ loadError: previous.loadError,
28
+ assetWarnings: previous.assetWarnings
29
+ };
30
+ }
31
+ function isSameState(first, second) {
32
+ return Object.keys(first).every((key) => first[key] === second[key]);
33
+ }
34
+ function createDocumentStateStore(engine) {
35
+ let state = initialState(engine);
36
+ const listeners = /* @__PURE__ */ new Set();
37
+ let detachFromEngine;
38
+ function update(changes) {
39
+ const next = {
40
+ ...state,
41
+ ...changes
42
+ };
43
+ if (isSameState(state, next)) return;
44
+ state = next;
45
+ for (const listener of [...listeners]) listener();
46
+ }
47
+ function attachToEngine() {
48
+ const unsubscribers = [
49
+ engine.on("load:start", () => update({
50
+ isLoading: true,
51
+ loadError: void 0
52
+ })),
53
+ engine.on("load:success", ({ warnings }) => update({
54
+ isLoading: false,
55
+ assetWarnings: warnings
56
+ })),
57
+ engine.on("load:error", ({ error }) => {
58
+ if (error.code !== "LOAD_ABORTED") update({
59
+ isLoading: false,
60
+ loadError: error
61
+ });
62
+ }),
63
+ engine.on("document:change", ({ documentId }) => update({
64
+ documentId,
65
+ loadError: void 0
66
+ })),
67
+ engine.on("assets:warning", ({ warnings }) => update({ assetWarnings: warnings })),
68
+ engine.on("save:status", (saveState) => update({
69
+ saveStatus: saveState.status,
70
+ isDirty: saveState.isDirty,
71
+ isSaving: saveState.isSaving,
72
+ revision: saveState.revision,
73
+ lastSavedAt: saveState.lastSavedAt,
74
+ saveError: saveState.error
75
+ })),
76
+ engine.on("history:change", (history) => update({
77
+ canUndo: history.canUndo,
78
+ canRedo: history.canRedo,
79
+ undoLabel: history.undoLabel,
80
+ redoLabel: history.redoLabel
81
+ }))
82
+ ];
83
+ return () => unsubscribers.forEach((unsubscribe) => unsubscribe());
84
+ }
85
+ function refreshWhileDetached() {
86
+ if (detachFromEngine) return;
87
+ const fresh = withLiveEngineValues(engine, state);
88
+ if (!isSameState(state, fresh)) state = fresh;
89
+ }
90
+ return {
91
+ getSnapshot() {
92
+ refreshWhileDetached();
93
+ return state;
94
+ },
95
+ subscribe(listener) {
96
+ refreshWhileDetached();
97
+ listeners.add(listener);
98
+ detachFromEngine ?? (detachFromEngine = attachToEngine());
99
+ return () => {
100
+ listeners.delete(listener);
101
+ if (listeners.size > 0) return;
102
+ detachFromEngine?.();
103
+ detachFromEngine = void 0;
104
+ };
105
+ },
106
+ destroy() {
107
+ listeners.clear();
108
+ detachFromEngine?.();
109
+ detachFromEngine = void 0;
110
+ }
111
+ };
112
+ }
113
+ //#endregion
114
+ exports.createDocumentStateStore = createDocumentStateStore;