@hamak/ui-store 0.5.1

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 (155) hide show
  1. package/README.md +143 -0
  2. package/dist/api/api/index.d.ts +7 -0
  3. package/dist/api/api/index.d.ts.map +1 -0
  4. package/dist/api/api/index.js +6 -0
  5. package/dist/api/api/middleware-registry.d.ts +33 -0
  6. package/dist/api/api/middleware-registry.d.ts.map +1 -0
  7. package/dist/api/api/middleware-registry.js +5 -0
  8. package/dist/api/api/reducer-registry.d.ts +41 -0
  9. package/dist/api/api/reducer-registry.d.ts.map +1 -0
  10. package/dist/api/api/reducer-registry.js +5 -0
  11. package/dist/api/api/store-manager.d.ts +55 -0
  12. package/dist/api/api/store-manager.d.ts.map +1 -0
  13. package/dist/api/api/store-manager.js +5 -0
  14. package/dist/api/autosave/autosave-action-factory.d.ts +69 -0
  15. package/dist/api/autosave/autosave-action-factory.d.ts.map +1 -0
  16. package/dist/api/autosave/autosave-action-factory.js +135 -0
  17. package/dist/api/autosave/autosave-action-types.d.ts +134 -0
  18. package/dist/api/autosave/autosave-action-types.d.ts.map +1 -0
  19. package/dist/api/autosave/autosave-action-types.js +44 -0
  20. package/dist/api/autosave/autosave-types.d.ts +86 -0
  21. package/dist/api/autosave/autosave-types.d.ts.map +1 -0
  22. package/dist/api/autosave/autosave-types.js +35 -0
  23. package/dist/api/autosave/index.d.ts +9 -0
  24. package/dist/api/autosave/index.d.ts.map +1 -0
  25. package/dist/api/autosave/index.js +8 -0
  26. package/dist/api/fs/contracts/filesystem-facade.contract.d.ts +57 -0
  27. package/dist/api/fs/contracts/filesystem-facade.contract.d.ts.map +1 -0
  28. package/dist/api/fs/contracts/filesystem-facade.contract.js +1 -0
  29. package/dist/api/fs/contracts/filesystem-manager.contract.d.ts +42 -0
  30. package/dist/api/fs/contracts/filesystem-manager.contract.d.ts.map +1 -0
  31. package/dist/api/fs/contracts/filesystem-manager.contract.js +1 -0
  32. package/dist/api/fs/index.d.ts +5 -0
  33. package/dist/api/fs/index.d.ts.map +1 -0
  34. package/dist/api/fs/index.js +4 -0
  35. package/dist/api/index.d.ts +10 -0
  36. package/dist/api/index.d.ts.map +1 -0
  37. package/dist/api/index.js +9 -0
  38. package/dist/api/tokens/index.d.ts +5 -0
  39. package/dist/api/tokens/index.d.ts.map +1 -0
  40. package/dist/api/tokens/index.js +4 -0
  41. package/dist/api/tokens/service-tokens.d.ts +10 -0
  42. package/dist/api/tokens/service-tokens.d.ts.map +1 -0
  43. package/dist/api/tokens/service-tokens.js +10 -0
  44. package/dist/api/types/extension-types.d.ts +32 -0
  45. package/dist/api/types/extension-types.d.ts.map +1 -0
  46. package/dist/api/types/extension-types.js +4 -0
  47. package/dist/api/types/index.d.ts +8 -0
  48. package/dist/api/types/index.d.ts.map +1 -0
  49. package/dist/api/types/index.js +7 -0
  50. package/dist/api/types/middleware-types.d.ts +31 -0
  51. package/dist/api/types/middleware-types.d.ts.map +1 -0
  52. package/dist/api/types/middleware-types.js +4 -0
  53. package/dist/api/types/reducer-types.d.ts +22 -0
  54. package/dist/api/types/reducer-types.d.ts.map +1 -0
  55. package/dist/api/types/reducer-types.js +4 -0
  56. package/dist/api/types/store-types.d.ts +49 -0
  57. package/dist/api/types/store-types.d.ts.map +1 -0
  58. package/dist/api/types/store-types.js +4 -0
  59. package/dist/impl/autosave/autosave-config-resolver.d.ts +45 -0
  60. package/dist/impl/autosave/autosave-config-resolver.d.ts.map +1 -0
  61. package/dist/impl/autosave/autosave-config-resolver.js +107 -0
  62. package/dist/impl/autosave/autosave-middleware.d.ts +37 -0
  63. package/dist/impl/autosave/autosave-middleware.d.ts.map +1 -0
  64. package/dist/impl/autosave/autosave-middleware.js +297 -0
  65. package/dist/impl/autosave/autosave-registry.d.ts +15 -0
  66. package/dist/impl/autosave/autosave-registry.d.ts.map +1 -0
  67. package/dist/impl/autosave/autosave-registry.js +38 -0
  68. package/dist/impl/autosave/autosave-sync-middleware.d.ts +24 -0
  69. package/dist/impl/autosave/autosave-sync-middleware.d.ts.map +1 -0
  70. package/dist/impl/autosave/autosave-sync-middleware.js +98 -0
  71. package/dist/impl/autosave/index.d.ts +8 -0
  72. package/dist/impl/autosave/index.d.ts.map +1 -0
  73. package/dist/impl/autosave/index.js +7 -0
  74. package/dist/impl/core/index.d.ts +4 -0
  75. package/dist/impl/core/index.d.ts.map +1 -0
  76. package/dist/impl/core/index.js +3 -0
  77. package/dist/impl/core/middleware-registry.d.ts +21 -0
  78. package/dist/impl/core/middleware-registry.d.ts.map +1 -0
  79. package/dist/impl/core/middleware-registry.js +60 -0
  80. package/dist/impl/core/reducer-registry.d.ts +18 -0
  81. package/dist/impl/core/reducer-registry.d.ts.map +1 -0
  82. package/dist/impl/core/reducer-registry.js +65 -0
  83. package/dist/impl/core/store-manager.d.ts +28 -0
  84. package/dist/impl/core/store-manager.d.ts.map +1 -0
  85. package/dist/impl/core/store-manager.js +129 -0
  86. package/dist/impl/extensions/store-extensions.d.ts +23 -0
  87. package/dist/impl/extensions/store-extensions.d.ts.map +1 -0
  88. package/dist/impl/extensions/store-extensions.js +66 -0
  89. package/dist/impl/fs/commands/fs-commands.d.ts +100 -0
  90. package/dist/impl/fs/commands/fs-commands.d.ts.map +1 -0
  91. package/dist/impl/fs/commands/fs-commands.js +338 -0
  92. package/dist/impl/fs/commands/structure-commands.d.ts +76 -0
  93. package/dist/impl/fs/commands/structure-commands.d.ts.map +1 -0
  94. package/dist/impl/fs/commands/structure-commands.js +21 -0
  95. package/dist/impl/fs/core/fs-adapter.d.ts +54 -0
  96. package/dist/impl/fs/core/fs-adapter.d.ts.map +1 -0
  97. package/dist/impl/fs/core/fs-adapter.js +204 -0
  98. package/dist/impl/fs/core/fs-facade.d.ts +37 -0
  99. package/dist/impl/fs/core/fs-facade.d.ts.map +1 -0
  100. package/dist/impl/fs/core/fs-facade.js +98 -0
  101. package/dist/impl/fs/index.d.ts +8 -0
  102. package/dist/impl/fs/index.d.ts.map +1 -0
  103. package/dist/impl/fs/index.js +11 -0
  104. package/dist/impl/fs/utils/data-updater.d.ts +36 -0
  105. package/dist/impl/fs/utils/data-updater.d.ts.map +1 -0
  106. package/dist/impl/fs/utils/data-updater.js +248 -0
  107. package/dist/impl/fs/utils/deep-equal.d.ts +5 -0
  108. package/dist/impl/fs/utils/deep-equal.d.ts.map +1 -0
  109. package/dist/impl/fs/utils/deep-equal.js +28 -0
  110. package/dist/impl/index.d.ts +12 -0
  111. package/dist/impl/index.d.ts.map +1 -0
  112. package/dist/impl/index.js +12 -0
  113. package/dist/impl/middleware/event-bridge-middleware.d.ts +7 -0
  114. package/dist/impl/middleware/event-bridge-middleware.d.ts.map +1 -0
  115. package/dist/impl/middleware/event-bridge-middleware.js +19 -0
  116. package/dist/impl/middleware/index.d.ts +6 -0
  117. package/dist/impl/middleware/index.d.ts.map +1 -0
  118. package/dist/impl/middleware/index.js +5 -0
  119. package/dist/impl/middleware/logger-middleware.d.ts +7 -0
  120. package/dist/impl/middleware/logger-middleware.d.ts.map +1 -0
  121. package/dist/impl/middleware/logger-middleware.js +18 -0
  122. package/dist/impl/plugin/index.d.ts +5 -0
  123. package/dist/impl/plugin/index.d.ts.map +1 -0
  124. package/dist/impl/plugin/index.js +4 -0
  125. package/dist/impl/plugin/store-plugin-factory.d.ts +15 -0
  126. package/dist/impl/plugin/store-plugin-factory.d.ts.map +1 -0
  127. package/dist/impl/plugin/store-plugin-factory.js +121 -0
  128. package/dist/index.d.ts +16 -0
  129. package/dist/index.d.ts.map +1 -0
  130. package/dist/index.js +15 -0
  131. package/dist/spi/autosave/i-autosave-provider.d.ts +98 -0
  132. package/dist/spi/autosave/i-autosave-provider.d.ts.map +1 -0
  133. package/dist/spi/autosave/i-autosave-provider.js +8 -0
  134. package/dist/spi/autosave/i-autosave-registry.d.ts +59 -0
  135. package/dist/spi/autosave/i-autosave-registry.d.ts.map +1 -0
  136. package/dist/spi/autosave/i-autosave-registry.js +8 -0
  137. package/dist/spi/autosave/index.d.ts +8 -0
  138. package/dist/spi/autosave/index.d.ts.map +1 -0
  139. package/dist/spi/autosave/index.js +7 -0
  140. package/dist/spi/index.d.ts +9 -0
  141. package/dist/spi/index.d.ts.map +1 -0
  142. package/dist/spi/index.js +9 -0
  143. package/dist/spi/middleware/index.d.ts +2 -0
  144. package/dist/spi/middleware/index.d.ts.map +1 -0
  145. package/dist/spi/middleware/index.js +1 -0
  146. package/dist/spi/middleware/middleware-provider.d.ts +9 -0
  147. package/dist/spi/middleware/middleware-provider.d.ts.map +1 -0
  148. package/dist/spi/middleware/middleware-provider.js +1 -0
  149. package/dist/spi/persistence/index.d.ts +2 -0
  150. package/dist/spi/persistence/index.d.ts.map +1 -0
  151. package/dist/spi/persistence/index.js +1 -0
  152. package/dist/spi/persistence/persistence-provider.d.ts +8 -0
  153. package/dist/spi/persistence/persistence-provider.d.ts.map +1 -0
  154. package/dist/spi/persistence/persistence-provider.js +1 -0
  155. package/package.json +68 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autosave-config-resolver.d.ts","sourceRoot":"","sources":["../../../src/impl/autosave/autosave-config-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzE,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAG5B,MAAM,WAAW,CAAC;AAEnB;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,cAAc,GAAG,SAAS,GAC/B,sBAAsB,GAAG,SAAS,CAOpC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,cAAc,GAAG,SAAS,GAC/B,cAAc,GAAG,SAAS,CAE5B;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,aAAa,EACnB,IAAI,EAAE,MAAM,EAAE,EACd,aAAa,GAAE,cAAwC,GACtD,cAAc,CAkChB;AAoBD;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,aAAa,EACnB,IAAI,EAAE,MAAM,EAAE,EACd,aAAa,CAAC,EAAE,cAAc,GAC7B,OAAO,CAGT;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,aAAa,EACnB,IAAI,EAAE,MAAM,EAAE,GACb,cAAc,GAAG,SAAS,CAW5B;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAEhD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAE/C"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Autosave Configuration Resolver
3
+ *
4
+ * Resolves autosave configuration with hierarchical inheritance.
5
+ * Configuration priority (highest to lowest):
6
+ * 1. File node extension state config
7
+ * 2. Parent folder config (recursive, respects inherit flag)
8
+ * 3. Default config
9
+ */
10
+ import { AUTOSAVE_EXTENSION_KEY, DEFAULT_AUTOSAVE_CONFIG, } from '../../api';
11
+ /**
12
+ * Get autosave extension state from a node
13
+ */
14
+ export function getAutosaveExtensionState(node) {
15
+ if (!node?.state?.extensionStates) {
16
+ return undefined;
17
+ }
18
+ return node.state.extensionStates[AUTOSAVE_EXTENSION_KEY];
19
+ }
20
+ /**
21
+ * Get autosave config from a node's extension state
22
+ */
23
+ export function getAutosaveConfig(node) {
24
+ return getAutosaveExtensionState(node)?.config;
25
+ }
26
+ /**
27
+ * Resolve autosave configuration for a path with inheritance
28
+ *
29
+ * @param root - Root directory node
30
+ * @param path - Path segments to the target node
31
+ * @param defaultConfig - Default configuration to use if none found
32
+ * @returns Resolved configuration
33
+ */
34
+ export function resolveAutosaveConfig(root, path, defaultConfig = DEFAULT_AUTOSAVE_CONFIG) {
35
+ const configs = [];
36
+ // Walk down the path, collecting configs
37
+ let currentNode = root;
38
+ for (let i = 0; i <= path.length; i++) {
39
+ if (!currentNode)
40
+ break;
41
+ const config = getAutosaveConfig(currentNode);
42
+ if (config) {
43
+ configs.push(config);
44
+ // If inherit is false, stop here
45
+ if (config.inherit === false) {
46
+ break;
47
+ }
48
+ }
49
+ // Move to next segment
50
+ if (i < path.length && currentNode.type === 'directory') {
51
+ currentNode = currentNode.children[path[i]];
52
+ }
53
+ }
54
+ // Merge configs from root to target (later configs override earlier)
55
+ // Start with default, then apply each config in order
56
+ let result = { ...defaultConfig };
57
+ for (const config of configs) {
58
+ result = mergeConfigs(result, config);
59
+ }
60
+ return result;
61
+ }
62
+ /**
63
+ * Merge two autosave configs (source overrides target for defined values)
64
+ */
65
+ function mergeConfigs(target, source) {
66
+ return {
67
+ enabled: source.enabled,
68
+ debounceMs: source.debounceMs ?? target.debounceMs,
69
+ maxWaitMs: source.maxWaitMs ?? target.maxWaitMs,
70
+ saveOnBlur: source.saveOnBlur ?? target.saveOnBlur,
71
+ retryOnFailure: source.retryOnFailure ?? target.retryOnFailure,
72
+ maxRetries: source.maxRetries ?? target.maxRetries,
73
+ inherit: source.inherit ?? target.inherit,
74
+ };
75
+ }
76
+ /**
77
+ * Check if autosave is effectively enabled for a path
78
+ */
79
+ export function isAutosaveEnabled(root, path, defaultConfig) {
80
+ const config = resolveAutosaveConfig(root, path, defaultConfig);
81
+ return config.enabled;
82
+ }
83
+ /**
84
+ * Get node at path
85
+ */
86
+ export function getNodeAtPath(root, path) {
87
+ let current = root;
88
+ for (const segment of path) {
89
+ if (!current || current.type !== 'directory') {
90
+ return undefined;
91
+ }
92
+ current = current.children[segment];
93
+ }
94
+ return current;
95
+ }
96
+ /**
97
+ * Convert path array to string key
98
+ */
99
+ export function pathToKey(path) {
100
+ return path.join('/');
101
+ }
102
+ /**
103
+ * Convert string key back to path array
104
+ */
105
+ export function keyToPath(key) {
106
+ return key === '' ? [] : key.split('/');
107
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Autosave Middleware
3
+ *
4
+ * Core middleware that orchestrates autosave operations:
5
+ * 1. Detects file content changes
6
+ * 2. Manages debounce timers per file
7
+ * 3. Delegates save operations to registered providers
8
+ * 4. Handles retries on failure
9
+ */
10
+ import type { Middleware } from 'redux';
11
+ import type { IAutosaveProviderRegistry, AutosaveResult } from '../../spi';
12
+ import { type AutosaveConfig } from '../../api';
13
+ /**
14
+ * Autosave middleware configuration
15
+ */
16
+ export interface AutosaveMiddlewareConfig {
17
+ /** Autosave provider registry */
18
+ registry: IAutosaveProviderRegistry;
19
+ /** Redux slice name for filesystem state. Default: 'fileSystem' */
20
+ fsSliceName?: string;
21
+ /** Default autosave configuration */
22
+ defaultConfig?: Partial<AutosaveConfig>;
23
+ /** Callback when save starts */
24
+ onSaveStart?: (path: string[]) => void;
25
+ /** Callback when save succeeds */
26
+ onSaveSuccess?: (path: string[], result: AutosaveResult) => void;
27
+ /** Callback when save fails */
28
+ onSaveError?: (path: string[], error: {
29
+ code: string;
30
+ message: string;
31
+ }) => void;
32
+ }
33
+ /**
34
+ * Create autosave middleware
35
+ */
36
+ export declare function createAutosaveMiddleware(config: AutosaveMiddlewareConfig): Middleware;
37
+ //# sourceMappingURL=autosave-middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autosave-middleware.d.ts","sourceRoot":"","sources":["../../../src/impl/autosave/autosave-middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAA4B,MAAM,OAAO,CAAC;AAElE,OAAO,KAAK,EAAE,yBAAyB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3E,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,WAAW,CAAC;AAgFnB;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,iCAAiC;IACjC,QAAQ,EAAE,yBAAyB,CAAC;IAEpC,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,qCAAqC;IACrC,aAAa,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IAExC,gCAAgC;IAChC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAEvC,kCAAkC;IAClC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;IAEjE,+BAA+B;IAC/B,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAClF;AAcD;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,wBAAwB,GAC/B,UAAU,CA6RZ"}
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Autosave Middleware
3
+ *
4
+ * Core middleware that orchestrates autosave operations:
5
+ * 1. Detects file content changes
6
+ * 2. Manages debounce timers per file
7
+ * 3. Delegates save operations to registered providers
8
+ * 4. Handles retries on failure
9
+ */
10
+ import { AutosaveActionTypes, autosaveActions, DEFAULT_AUTOSAVE_CONFIG, } from '../../api';
11
+ import { resolveAutosaveConfig, getNodeAtPath, pathToKey, } from './autosave-config-resolver';
12
+ /**
13
+ * Content change action types to monitor
14
+ */
15
+ const CONTENT_CHANGE_ACTION_TYPES = [
16
+ 'set-file-content',
17
+ 'update-file-content',
18
+ ];
19
+ /**
20
+ * Check if an action is a content change action
21
+ */
22
+ function isContentChangeAction(action) {
23
+ // Check for filesystem command actions
24
+ if (action.type === '@@fs/COMMAND' && action.command) {
25
+ return CONTENT_CHANGE_ACTION_TYPES.includes(action.command.name);
26
+ }
27
+ // Check for direct content change actions from fs-adapter
28
+ if (typeof action.type === 'string') {
29
+ const type = action.type.toLowerCase();
30
+ return (type.includes('setfilecontent') ||
31
+ type.includes('updatefilecontent') ||
32
+ type.includes('set_file_content') ||
33
+ type.includes('update_file_content'));
34
+ }
35
+ return false;
36
+ }
37
+ /**
38
+ * Check if content change is from remote (should not trigger autosave)
39
+ */
40
+ function isFromRemote(action) {
41
+ if (action.command?.fromRemote === true) {
42
+ return true;
43
+ }
44
+ if (action.payload?.fromRemote === true) {
45
+ return true;
46
+ }
47
+ if (action.fromRemote === true) {
48
+ return true;
49
+ }
50
+ return false;
51
+ }
52
+ /**
53
+ * Get path from action
54
+ */
55
+ function getPathFromAction(action) {
56
+ // Try command path
57
+ if (action.command?.path) {
58
+ return Array.isArray(action.command.path)
59
+ ? action.command.path
60
+ : [action.command.path];
61
+ }
62
+ // Try payload path
63
+ if (action.payload?.path) {
64
+ return Array.isArray(action.payload.path)
65
+ ? action.payload.path
66
+ : [action.payload.path];
67
+ }
68
+ // Try direct path
69
+ if (action.path) {
70
+ return Array.isArray(action.path) ? action.path : [action.path];
71
+ }
72
+ return undefined;
73
+ }
74
+ /**
75
+ * Create autosave middleware
76
+ */
77
+ export function createAutosaveMiddleware(config) {
78
+ const { registry, fsSliceName = 'fileSystem', defaultConfig = {}, onSaveStart, onSaveSuccess, onSaveError, } = config;
79
+ const mergedDefaultConfig = {
80
+ ...DEFAULT_AUTOSAVE_CONFIG,
81
+ ...defaultConfig,
82
+ };
83
+ // Track pending saves by path key
84
+ const pendingSaves = new Map();
85
+ /**
86
+ * Get filesystem root from state
87
+ */
88
+ function getRoot(state) {
89
+ return state[fsSliceName]?.root;
90
+ }
91
+ /**
92
+ * Clear timers for a path
93
+ */
94
+ function clearTimers(pathKey) {
95
+ const pending = pendingSaves.get(pathKey);
96
+ if (pending) {
97
+ clearTimeout(pending.debounceTimer);
98
+ if (pending.maxWaitTimer) {
99
+ clearTimeout(pending.maxWaitTimer);
100
+ }
101
+ }
102
+ }
103
+ /**
104
+ * Cancel pending save
105
+ */
106
+ function cancelPending(pathKey) {
107
+ clearTimers(pathKey);
108
+ pendingSaves.delete(pathKey);
109
+ }
110
+ /**
111
+ * Schedule autosave for a path
112
+ */
113
+ function scheduleAutosave(store, path, providerId, config) {
114
+ const pathKey = pathToKey(path);
115
+ const now = Date.now();
116
+ const debounceMs = config.debounceMs ?? DEFAULT_AUTOSAVE_CONFIG.debounceMs;
117
+ const maxWaitMs = config.maxWaitMs ?? DEFAULT_AUTOSAVE_CONFIG.maxWaitMs;
118
+ // Clear existing debounce timer (but keep max wait if exists)
119
+ const existing = pendingSaves.get(pathKey);
120
+ if (existing) {
121
+ clearTimeout(existing.debounceTimer);
122
+ }
123
+ // Create debounce timer
124
+ const debounceTimer = setTimeout(() => {
125
+ store.dispatch(autosaveActions.debounceExpired(path));
126
+ }, debounceMs);
127
+ // Create max wait timer if not exists
128
+ let maxWaitTimer = existing?.maxWaitTimer;
129
+ if (!maxWaitTimer) {
130
+ maxWaitTimer = setTimeout(() => {
131
+ store.dispatch(autosaveActions.debounceExpired(path));
132
+ }, maxWaitMs);
133
+ }
134
+ // Store pending save
135
+ pendingSaves.set(pathKey, {
136
+ path,
137
+ providerId,
138
+ debounceTimer,
139
+ maxWaitTimer,
140
+ retryCount: existing?.retryCount ?? 0,
141
+ config,
142
+ });
143
+ // Dispatch change detected
144
+ store.dispatch(autosaveActions.changeDetected(path, now, now + debounceMs, providerId));
145
+ }
146
+ /**
147
+ * Execute save operation
148
+ */
149
+ async function executeSave(store, path) {
150
+ const pathKey = pathToKey(path);
151
+ const pending = pendingSaves.get(pathKey);
152
+ if (!pending) {
153
+ return;
154
+ }
155
+ // Clear timers
156
+ clearTimers(pathKey);
157
+ // Get current state
158
+ const state = store.getState();
159
+ const root = getRoot(state);
160
+ if (!root) {
161
+ pendingSaves.delete(pathKey);
162
+ return;
163
+ }
164
+ // Get node
165
+ const node = getNodeAtPath(root, path);
166
+ if (!node || node.type !== 'file') {
167
+ pendingSaves.delete(pathKey);
168
+ return;
169
+ }
170
+ // Get provider
171
+ const provider = registry.get(pending.providerId);
172
+ if (!provider) {
173
+ pendingSaves.delete(pathKey);
174
+ return;
175
+ }
176
+ // Dispatch save started
177
+ store.dispatch(autosaveActions.saveStarted(path, pending.providerId));
178
+ onSaveStart?.(path);
179
+ try {
180
+ // Execute save via provider
181
+ const result = await provider.save(path, node.content, node, store.dispatch);
182
+ if (result.success) {
183
+ // Success
184
+ pendingSaves.delete(pathKey);
185
+ store.dispatch(autosaveActions.saveSucceeded(path, result.timestamp));
186
+ onSaveSuccess?.(path, result);
187
+ }
188
+ else {
189
+ // Provider reported failure
190
+ handleSaveFailure(store, path, result.error ?? { code: 'UNKNOWN', message: 'Save failed' });
191
+ }
192
+ }
193
+ catch (error) {
194
+ // Exception during save
195
+ handleSaveFailure(store, path, {
196
+ code: error.code ?? 'ERROR',
197
+ message: error.message ?? 'Unknown error',
198
+ });
199
+ }
200
+ }
201
+ /**
202
+ * Handle save failure with retry logic
203
+ */
204
+ function handleSaveFailure(store, path, error) {
205
+ const pathKey = pathToKey(path);
206
+ const pending = pendingSaves.get(pathKey);
207
+ if (!pending) {
208
+ return;
209
+ }
210
+ const retryCount = pending.retryCount + 1;
211
+ const maxRetries = pending.config.maxRetries ?? DEFAULT_AUTOSAVE_CONFIG.maxRetries;
212
+ const shouldRetry = pending.config.retryOnFailure !== false && retryCount < maxRetries;
213
+ // Dispatch failure
214
+ store.dispatch(autosaveActions.saveFailed(path, error, retryCount));
215
+ onSaveError?.(path, error);
216
+ if (shouldRetry) {
217
+ // Schedule retry with exponential backoff
218
+ const retryDelay = Math.min(1000 * Math.pow(2, retryCount - 1), 10000);
219
+ const retryAt = Date.now() + retryDelay;
220
+ store.dispatch(autosaveActions.retryScheduled(path, retryAt, retryCount));
221
+ // Update pending save with retry timer
222
+ pending.retryCount = retryCount;
223
+ pending.debounceTimer = setTimeout(() => {
224
+ executeSave(store, path);
225
+ }, retryDelay);
226
+ pendingSaves.set(pathKey, pending);
227
+ }
228
+ else {
229
+ // No more retries
230
+ pendingSaves.delete(pathKey);
231
+ }
232
+ }
233
+ /**
234
+ * The middleware
235
+ */
236
+ const autosaveMiddleware = (store) => (next) => (action) => {
237
+ const result = next(action);
238
+ const typedAction = action;
239
+ // ─────────────────────────────────────────────────────────────────
240
+ // Handle content changes
241
+ // ─────────────────────────────────────────────────────────────────
242
+ if (isContentChangeAction(typedAction) && !isFromRemote(typedAction)) {
243
+ const path = getPathFromAction(typedAction);
244
+ if (path) {
245
+ const state = store.getState();
246
+ const root = getRoot(state);
247
+ if (root) {
248
+ // Resolve config
249
+ const autosaveConfig = resolveAutosaveConfig(root, path, mergedDefaultConfig);
250
+ if (autosaveConfig.enabled) {
251
+ // Get node
252
+ const node = getNodeAtPath(root, path);
253
+ if (node) {
254
+ // Find provider
255
+ const provider = registry.findProvider(path, node);
256
+ if (provider) {
257
+ scheduleAutosave(store, path, provider.id, autosaveConfig);
258
+ }
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
264
+ // ─────────────────────────────────────────────────────────────────
265
+ // Handle autosave actions
266
+ // ─────────────────────────────────────────────────────────────────
267
+ switch (typedAction.type) {
268
+ case AutosaveActionTypes.DEBOUNCE_EXPIRED: {
269
+ const path = typedAction.payload.path;
270
+ void executeSave(store, path);
271
+ break;
272
+ }
273
+ case AutosaveActionTypes.FLUSH_PENDING: {
274
+ const path = typedAction.payload.path;
275
+ const pathKey = pathToKey(path);
276
+ if (pendingSaves.has(pathKey)) {
277
+ void executeSave(store, path);
278
+ }
279
+ break;
280
+ }
281
+ case AutosaveActionTypes.FLUSH_ALL_PENDING: {
282
+ const paths = Array.from(pendingSaves.values()).map((p) => p.path);
283
+ for (const path of paths) {
284
+ void executeSave(store, path);
285
+ }
286
+ break;
287
+ }
288
+ case AutosaveActionTypes.CANCEL_PENDING: {
289
+ const path = typedAction.payload.path;
290
+ cancelPending(pathToKey(path));
291
+ break;
292
+ }
293
+ }
294
+ return result;
295
+ };
296
+ return autosaveMiddleware;
297
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Autosave Provider Registry Implementation
3
+ */
4
+ import type { FileSystemNode } from '@hamak/shared-utils';
5
+ import type { IAutosaveProvider, IAutosaveProviderRegistry } from '../../spi';
6
+ export declare class AutosaveProviderRegistry implements IAutosaveProviderRegistry {
7
+ private providers;
8
+ register(provider: IAutosaveProvider): void;
9
+ unregister(providerId: string): void;
10
+ get(providerId: string): IAutosaveProvider | undefined;
11
+ findProvider(path: string[], node: FileSystemNode): IAutosaveProvider | undefined;
12
+ getAll(): IAutosaveProvider[];
13
+ has(providerId: string): boolean;
14
+ }
15
+ //# sourceMappingURL=autosave-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autosave-registry.d.ts","sourceRoot":"","sources":["../../../src/impl/autosave/autosave-registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAE9E,qBAAa,wBAAyB,YAAW,yBAAyB;IACxE,OAAO,CAAC,SAAS,CAAwC;IAEzD,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI;IAS3C,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIpC,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAItD,YAAY,CACV,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE,cAAc,GACnB,iBAAiB,GAAG,SAAS;IAShC,MAAM,IAAI,iBAAiB,EAAE;IAI7B,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;CAGjC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Autosave Provider Registry Implementation
3
+ */
4
+ export class AutosaveProviderRegistry {
5
+ constructor() {
6
+ Object.defineProperty(this, "providers", {
7
+ enumerable: true,
8
+ configurable: true,
9
+ writable: true,
10
+ value: new Map()
11
+ });
12
+ }
13
+ register(provider) {
14
+ if (this.providers.has(provider.id)) {
15
+ console.warn(`[AutosaveRegistry] Provider "${provider.id}" already registered, overwriting.`);
16
+ }
17
+ this.providers.set(provider.id, provider);
18
+ }
19
+ unregister(providerId) {
20
+ this.providers.delete(providerId);
21
+ }
22
+ get(providerId) {
23
+ return this.providers.get(providerId);
24
+ }
25
+ findProvider(path, node) {
26
+ // Get all providers that support this path, sorted by priority (descending)
27
+ const supportingProviders = Array.from(this.providers.values())
28
+ .filter((provider) => provider.supports(path, node))
29
+ .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
30
+ return supportingProviders[0];
31
+ }
32
+ getAll() {
33
+ return Array.from(this.providers.values());
34
+ }
35
+ has(providerId) {
36
+ return this.providers.has(providerId);
37
+ }
38
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Autosave Sync Middleware
3
+ *
4
+ * Updates FileSystemNode extension states based on autosave lifecycle actions.
5
+ * This middleware runs after the main autosave middleware and updates the
6
+ * state so components can display autosave status.
7
+ */
8
+ import type { Middleware, AnyAction } from 'redux';
9
+ import { type AutosaveExtensionState } from '../../api';
10
+ /**
11
+ * Autosave sync middleware configuration
12
+ */
13
+ export interface AutosaveSyncMiddlewareConfig {
14
+ /**
15
+ * Function to dispatch extension state update.
16
+ * This should update the node's extensionStates[AUTOSAVE_EXTENSION_KEY].
17
+ */
18
+ updateExtensionState: (path: string[], state: Partial<AutosaveExtensionState>) => AnyAction;
19
+ }
20
+ /**
21
+ * Create autosave sync middleware
22
+ */
23
+ export declare function createAutosaveSyncMiddleware(config: AutosaveSyncMiddlewareConfig): Middleware;
24
+ //# sourceMappingURL=autosave-sync-middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autosave-sync-middleware.d.ts","sourceRoot":"","sources":["../../../src/impl/autosave/autosave-sync-middleware.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,EAGL,KAAK,sBAAsB,EAE5B,MAAM,WAAW,CAAC;AAEnB;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;;OAGG;IACH,oBAAoB,EAAE,CACpB,IAAI,EAAE,MAAM,EAAE,EACd,KAAK,EAAE,OAAO,CAAC,sBAAsB,CAAC,KACnC,SAAS,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,4BAA4B,GACnC,UAAU,CAmHZ"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Autosave Sync Middleware
3
+ *
4
+ * Updates FileSystemNode extension states based on autosave lifecycle actions.
5
+ * This middleware runs after the main autosave middleware and updates the
6
+ * state so components can display autosave status.
7
+ */
8
+ import { AutosaveActionTypes, autosaveActions, } from '../../api';
9
+ /**
10
+ * Create autosave sync middleware
11
+ */
12
+ export function createAutosaveSyncMiddleware(config) {
13
+ const { updateExtensionState } = config;
14
+ const autosaveSyncMiddleware = (store) => (next) => (action) => {
15
+ const result = next(action);
16
+ const typedAction = action;
17
+ // Only process autosave actions
18
+ if (!autosaveActions.isAutosaveAction(typedAction)) {
19
+ return result;
20
+ }
21
+ const dispatch = store.dispatch;
22
+ switch (typedAction.type) {
23
+ case AutosaveActionTypes.SET_CONFIG: {
24
+ const { path, config: autosaveConfig } = typedAction.payload;
25
+ dispatch(updateExtensionState(path, {
26
+ config: autosaveConfig,
27
+ effectivelyEnabled: autosaveConfig.enabled,
28
+ status: autosaveConfig.enabled ? 'idle' : 'disabled',
29
+ }));
30
+ break;
31
+ }
32
+ case AutosaveActionTypes.CLEAR_CONFIG: {
33
+ const { path } = typedAction.payload;
34
+ dispatch(updateExtensionState(path, {
35
+ config: undefined,
36
+ // Status will be recomputed based on parent config
37
+ }));
38
+ break;
39
+ }
40
+ case AutosaveActionTypes.CHANGE_DETECTED: {
41
+ const { path, detectedAt, providerId } = typedAction.payload;
42
+ dispatch(updateExtensionState(path, {
43
+ status: 'pending',
44
+ pendingSince: detectedAt,
45
+ providerId,
46
+ }));
47
+ break;
48
+ }
49
+ case AutosaveActionTypes.SAVE_STARTED: {
50
+ const { path, providerId } = typedAction.payload;
51
+ dispatch(updateExtensionState(path, {
52
+ status: 'saving',
53
+ lastSaveAttempt: Date.now(),
54
+ providerId,
55
+ }));
56
+ break;
57
+ }
58
+ case AutosaveActionTypes.SAVE_SUCCEEDED: {
59
+ const { path, timestamp } = typedAction.payload;
60
+ dispatch(updateExtensionState(path, {
61
+ status: 'idle',
62
+ lastSaveSuccess: timestamp,
63
+ lastSaveError: undefined,
64
+ retryCount: 0,
65
+ pendingSince: undefined,
66
+ }));
67
+ break;
68
+ }
69
+ case AutosaveActionTypes.SAVE_FAILED: {
70
+ const { path, error } = typedAction.payload;
71
+ dispatch(updateExtensionState(path, {
72
+ status: 'error',
73
+ lastSaveError: error,
74
+ retryCount: error.attempt,
75
+ }));
76
+ break;
77
+ }
78
+ case AutosaveActionTypes.RETRY_SCHEDULED: {
79
+ const { path, attempt } = typedAction.payload;
80
+ dispatch(updateExtensionState(path, {
81
+ status: 'pending',
82
+ retryCount: attempt,
83
+ }));
84
+ break;
85
+ }
86
+ case AutosaveActionTypes.CANCEL_PENDING: {
87
+ const { path } = typedAction.payload;
88
+ dispatch(updateExtensionState(path, {
89
+ status: 'idle',
90
+ pendingSince: undefined,
91
+ }));
92
+ break;
93
+ }
94
+ }
95
+ return result;
96
+ };
97
+ return autosaveSyncMiddleware;
98
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Autosave Implementation
3
+ */
4
+ export * from './autosave-registry';
5
+ export * from './autosave-config-resolver';
6
+ export * from './autosave-middleware';
7
+ export * from './autosave-sync-middleware';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/impl/autosave/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Autosave Implementation
3
+ */
4
+ export * from './autosave-registry';
5
+ export * from './autosave-config-resolver';
6
+ export * from './autosave-middleware';
7
+ export * from './autosave-sync-middleware';
@@ -0,0 +1,4 @@
1
+ export * from './store-manager';
2
+ export * from './middleware-registry';
3
+ export * from './reducer-registry';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/impl/core/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './store-manager';
2
+ export * from './middleware-registry';
3
+ export * from './reducer-registry';