@hamak/ui-remote-resource 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 (91) hide show
  1. package/dist/api/actions/entity-action-factory.d.ts +28 -0
  2. package/dist/api/actions/entity-action-factory.d.ts.map +1 -0
  3. package/dist/api/actions/entity-action-factory.js +62 -0
  4. package/dist/api/actions/entity-action-types.d.ts +104 -0
  5. package/dist/api/actions/entity-action-types.d.ts.map +1 -0
  6. package/dist/api/actions/entity-action-types.js +16 -0
  7. package/dist/api/actions/resource-action-factory.d.ts +40 -0
  8. package/dist/api/actions/resource-action-factory.d.ts.map +1 -0
  9. package/dist/api/actions/resource-action-factory.js +93 -0
  10. package/dist/api/actions/resource-action-types.d.ts +105 -0
  11. package/dist/api/actions/resource-action-types.d.ts.map +1 -0
  12. package/dist/api/actions/resource-action-types.js +13 -0
  13. package/dist/api/constants.d.ts +6 -0
  14. package/dist/api/constants.d.ts.map +1 -0
  15. package/dist/api/constants.js +5 -0
  16. package/dist/api/contracts/i-entity-registry.d.ts +19 -0
  17. package/dist/api/contracts/i-entity-registry.d.ts.map +1 -0
  18. package/dist/api/contracts/i-entity-registry.js +1 -0
  19. package/dist/api/contracts/i-resource-registry.d.ts +19 -0
  20. package/dist/api/contracts/i-resource-registry.d.ts.map +1 -0
  21. package/dist/api/contracts/i-resource-registry.js +1 -0
  22. package/dist/api/index.d.ts +12 -0
  23. package/dist/api/index.d.ts.map +1 -0
  24. package/dist/api/index.js +15 -0
  25. package/dist/api/types/action-types.d.ts +34 -0
  26. package/dist/api/types/action-types.d.ts.map +1 -0
  27. package/dist/api/types/action-types.js +1 -0
  28. package/dist/api/types/resource-attributes.d.ts +43 -0
  29. package/dist/api/types/resource-attributes.d.ts.map +1 -0
  30. package/dist/api/types/resource-attributes.js +1 -0
  31. package/dist/api/types/resource-types.d.ts +20 -0
  32. package/dist/api/types/resource-types.d.ts.map +1 -0
  33. package/dist/api/types/resource-types.js +1 -0
  34. package/dist/api/utils/resource-attributes-util.d.ts +183 -0
  35. package/dist/api/utils/resource-attributes-util.d.ts.map +1 -0
  36. package/dist/api/utils/resource-attributes-util.js +280 -0
  37. package/dist/impl/autosave/index.d.ts +5 -0
  38. package/dist/impl/autosave/index.d.ts.map +1 -0
  39. package/dist/impl/autosave/index.js +4 -0
  40. package/dist/impl/autosave/resource-autosave-provider.d.ts +49 -0
  41. package/dist/impl/autosave/resource-autosave-provider.d.ts.map +1 -0
  42. package/dist/impl/autosave/resource-autosave-provider.js +112 -0
  43. package/dist/impl/index.d.ts +20 -0
  44. package/dist/impl/index.d.ts.map +1 -0
  45. package/dist/impl/index.js +20 -0
  46. package/dist/impl/middleware/entity-middleware.d.ts +13 -0
  47. package/dist/impl/middleware/entity-middleware.d.ts.map +1 -0
  48. package/dist/impl/middleware/entity-middleware.js +221 -0
  49. package/dist/impl/middleware/entity-sync-middleware.d.ts +7 -0
  50. package/dist/impl/middleware/entity-sync-middleware.d.ts.map +1 -0
  51. package/dist/impl/middleware/entity-sync-middleware.js +32 -0
  52. package/dist/impl/middleware/resource-middleware.d.ts +13 -0
  53. package/dist/impl/middleware/resource-middleware.d.ts.map +1 -0
  54. package/dist/impl/middleware/resource-middleware.js +97 -0
  55. package/dist/impl/middleware/sync-middleware.d.ts +12 -0
  56. package/dist/impl/middleware/sync-middleware.d.ts.map +1 -0
  57. package/dist/impl/middleware/sync-middleware.js +80 -0
  58. package/dist/impl/plugin/resource-plugin-factory.d.ts +31 -0
  59. package/dist/impl/plugin/resource-plugin-factory.d.ts.map +1 -0
  60. package/dist/impl/plugin/resource-plugin-factory.js +131 -0
  61. package/dist/impl/providers/mock-resource-provider.d.ts +147 -0
  62. package/dist/impl/providers/mock-resource-provider.d.ts.map +1 -0
  63. package/dist/impl/providers/mock-resource-provider.js +242 -0
  64. package/dist/impl/providers/rest-resource-provider.d.ts +51 -0
  65. package/dist/impl/providers/rest-resource-provider.d.ts.map +1 -0
  66. package/dist/impl/providers/rest-resource-provider.js +128 -0
  67. package/dist/impl/registry/entity-registry.d.ts +54 -0
  68. package/dist/impl/registry/entity-registry.d.ts.map +1 -0
  69. package/dist/impl/registry/entity-registry.js +51 -0
  70. package/dist/impl/registry/resource-registry.d.ts +80 -0
  71. package/dist/impl/registry/resource-registry.d.ts.map +1 -0
  72. package/dist/impl/registry/resource-registry.js +74 -0
  73. package/dist/index.d.ts +6 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +5 -0
  76. package/dist/spi/config/endpoint-definition.d.ts +46 -0
  77. package/dist/spi/config/endpoint-definition.d.ts.map +1 -0
  78. package/dist/spi/config/endpoint-definition.js +1 -0
  79. package/dist/spi/config/entity-definition.d.ts +50 -0
  80. package/dist/spi/config/entity-definition.d.ts.map +1 -0
  81. package/dist/spi/config/entity-definition.js +1 -0
  82. package/dist/spi/config/plugin-config.d.ts +21 -0
  83. package/dist/spi/config/plugin-config.d.ts.map +1 -0
  84. package/dist/spi/config/plugin-config.js +1 -0
  85. package/dist/spi/index.d.ts +6 -0
  86. package/dist/spi/index.d.ts.map +1 -0
  87. package/dist/spi/index.js +7 -0
  88. package/dist/spi/providers/i-resource-provider.d.ts +42 -0
  89. package/dist/spi/providers/i-resource-provider.d.ts.map +1 -0
  90. package/dist/spi/providers/i-resource-provider.js +1 -0
  91. package/package.json +71 -0
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Resource Autosave Provider
3
+ *
4
+ * Implements autosave for files managed by the remote resource plugin.
5
+ * Uses UPDATE_ENTITY or RESOURCE_CALL_REQUEST actions to save files.
6
+ */
7
+ import { REMOTE_RESOURCE_EXTENSION_KEY, } from '../../api';
8
+ import { EntityActionFactory } from '../../api';
9
+ import { ResourceActionFactory } from '../../api';
10
+ /**
11
+ * Autosave provider for remote resources
12
+ *
13
+ * This provider handles autosave for files that are managed via the
14
+ * remote resource plugin. It checks for the presence of resource/entity
15
+ * extension state and uses appropriate actions to save changes.
16
+ */
17
+ export class ResourceAutosaveProvider {
18
+ constructor(config = {}) {
19
+ Object.defineProperty(this, "id", {
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true,
23
+ value: 'remote-resource'
24
+ });
25
+ Object.defineProperty(this, "priority", {
26
+ enumerable: true,
27
+ configurable: true,
28
+ writable: true,
29
+ value: 20
30
+ }); // Higher priority than remote-fs
31
+ Object.defineProperty(this, "entityActionFactory", {
32
+ enumerable: true,
33
+ configurable: true,
34
+ writable: true,
35
+ value: void 0
36
+ });
37
+ Object.defineProperty(this, "resourceActionFactory", {
38
+ enumerable: true,
39
+ configurable: true,
40
+ writable: true,
41
+ value: void 0
42
+ });
43
+ this.entityActionFactory = config.entityActionFactory ?? new EntityActionFactory();
44
+ this.resourceActionFactory = config.resourceActionFactory ?? new ResourceActionFactory();
45
+ }
46
+ /**
47
+ * Check if this provider supports a given path.
48
+ * Returns true if the node has remote resource extension state.
49
+ */
50
+ supports(_path, node) {
51
+ const extensionState = node.state?.extensionStates?.[REMOTE_RESOURCE_EXTENSION_KEY];
52
+ return extensionState !== undefined;
53
+ }
54
+ /**
55
+ * Save file content to remote server.
56
+ * Dispatches UPDATE_ENTITY (if entity) or RESOURCE_CALL_REQUEST (if resource).
57
+ */
58
+ async save(path, content, node, dispatch) {
59
+ try {
60
+ const resourceAttrs = node.state?.extensionStates?.[REMOTE_RESOURCE_EXTENSION_KEY];
61
+ if (!resourceAttrs) {
62
+ return {
63
+ success: false,
64
+ error: {
65
+ code: 'NO_RESOURCE_STATE',
66
+ message: 'Node has no remote resource extension state',
67
+ },
68
+ };
69
+ }
70
+ // Check if this is an entity
71
+ if (resourceAttrs.entity?.entityId) {
72
+ // Use entity update action
73
+ dispatch(this.entityActionFactory.update(resourceAttrs.entity.entityId, path, content));
74
+ }
75
+ else if (resourceAttrs.endpointId) {
76
+ // Use resource call request with update operation
77
+ dispatch(this.resourceActionFactory.callRequest(resourceAttrs.endpointId, 'update', { body: content }, path));
78
+ }
79
+ else {
80
+ return {
81
+ success: false,
82
+ error: {
83
+ code: 'NO_ENDPOINT',
84
+ message: 'Node has no entity or endpoint information',
85
+ },
86
+ };
87
+ }
88
+ // Note: The actual HTTP request is async and handled by resource middleware.
89
+ // Success/failure will be tracked by the middleware dispatching
90
+ // RESOURCE_CALL_SUCCESS or RESOURCE_CALL_FAILURE actions.
91
+ return {
92
+ success: true,
93
+ timestamp: Date.now(),
94
+ };
95
+ }
96
+ catch (error) {
97
+ return {
98
+ success: false,
99
+ error: {
100
+ code: error.code ?? 'SAVE_ERROR',
101
+ message: error.message ?? 'Failed to dispatch save action',
102
+ },
103
+ };
104
+ }
105
+ }
106
+ /**
107
+ * Cancel is a no-op for resources since we don't track in-flight requests here.
108
+ */
109
+ cancel(_path) {
110
+ // No-op: resource middleware handles request lifecycle
111
+ }
112
+ }
@@ -0,0 +1,20 @@
1
+ export * from '../api';
2
+ export * from '../spi';
3
+ export { createResourcePlugin } from './plugin/resource-plugin-factory';
4
+ export type { PluginModule } from './plugin/resource-plugin-factory';
5
+ export { RESOURCE_REGISTRY_TOKEN, ENTITY_REGISTRY_TOKEN } from './plugin/resource-plugin-factory';
6
+ export { STORE_MANAGER_TOKEN, MIDDLEWARE_REGISTRY_TOKEN, STORE_EXTENSIONS_TOKEN } from '@hamak/ui-store-api';
7
+ export { RestResourceProvider } from './providers/rest-resource-provider';
8
+ export type { RestProviderConfig } from './providers/rest-resource-provider';
9
+ export { MockResourceProvider } from './providers/mock-resource-provider';
10
+ export type { MockProviderConfig, EndpointMockConfig, MockConfigMap, MockCallHistoryEntry } from './providers/mock-resource-provider';
11
+ export { ResourceRegistry } from './registry/resource-registry';
12
+ export { EntityRegistry } from './registry/entity-registry';
13
+ export { createResourceMiddleware } from './middleware/resource-middleware';
14
+ export { createSyncMiddleware } from './middleware/sync-middleware';
15
+ export { createEntityMiddleware } from './middleware/entity-middleware';
16
+ export { createEntitySyncMiddleware } from './middleware/entity-sync-middleware';
17
+ export type { FileSystemNodeActions } from '@hamak/ui-store-impl';
18
+ export { ResourceAutosaveProvider } from './autosave/resource-autosave-provider';
19
+ export type { ResourceAutosaveProviderConfig } from './autosave/resource-autosave-provider';
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/impl/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AAGvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,YAAY,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACtB,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,sBAAsB,EACvB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC1E,YAAY,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC1E,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,oBAAoB,EACrB,MAAM,oCAAoC,CAAC;AAG5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AAGjF,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAGlE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AACjF,YAAY,EAAE,8BAA8B,EAAE,MAAM,uCAAuC,CAAC"}
@@ -0,0 +1,20 @@
1
+ export * from '../api';
2
+ export * from '../spi';
3
+ // Plugin factory (main export)
4
+ export { createResourcePlugin } from './plugin/resource-plugin-factory';
5
+ export { RESOURCE_REGISTRY_TOKEN, ENTITY_REGISTRY_TOKEN } from './plugin/resource-plugin-factory';
6
+ // Re-export store tokens from ui-store-api for convenience
7
+ export { STORE_MANAGER_TOKEN, MIDDLEWARE_REGISTRY_TOKEN, STORE_EXTENSIONS_TOKEN } from '@hamak/ui-store-api';
8
+ // Built-in providers (exported for convenience and testing)
9
+ export { RestResourceProvider } from './providers/rest-resource-provider';
10
+ export { MockResourceProvider } from './providers/mock-resource-provider';
11
+ // Registry implementations (internal, but exported for testing)
12
+ export { ResourceRegistry } from './registry/resource-registry';
13
+ export { EntityRegistry } from './registry/entity-registry';
14
+ // Middleware factories (internal, but exported for advanced use cases)
15
+ export { createResourceMiddleware } from './middleware/resource-middleware';
16
+ export { createSyncMiddleware } from './middleware/sync-middleware';
17
+ export { createEntityMiddleware } from './middleware/entity-middleware';
18
+ export { createEntitySyncMiddleware } from './middleware/entity-sync-middleware';
19
+ // Autosave provider
20
+ export { ResourceAutosaveProvider } from './autosave/resource-autosave-provider';
@@ -0,0 +1,13 @@
1
+ import type { Middleware } from 'redux';
2
+ import { type EntityAction } from '../../api';
3
+ export interface EntityMiddlewareConfig {
4
+ entityRegistry: any;
5
+ fsSliceName: string;
6
+ onError?: (error: any, action: EntityAction) => void;
7
+ }
8
+ /**
9
+ * Entity middleware
10
+ * Translates entity actions to resource actions
11
+ */
12
+ export declare function createEntityMiddleware<S = any>(config: EntityMiddlewareConfig): Middleware<{}, S>;
13
+ //# sourceMappingURL=entity-middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-middleware.d.ts","sourceRoot":"","sources":["../../../src/impl/middleware/entity-middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,EAGL,KAAK,YAAY,EAMlB,MAAM,WAAW,CAAC;AAGnB,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,GAAG,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;CACtD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,GAAG,GAAG,EAC5C,MAAM,EAAE,sBAAsB,GAC7B,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,CAgCnB"}
@@ -0,0 +1,221 @@
1
+ import { EntityActionTypes, ResourceActionFactory, resourceAttributesUtil } from '../../api';
2
+ /**
3
+ * Entity middleware
4
+ * Translates entity actions to resource actions
5
+ */
6
+ export function createEntityMiddleware(config) {
7
+ const { entityRegistry, fsSliceName, onError } = config;
8
+ const resourceActionFactory = new ResourceActionFactory();
9
+ return (store) => (next) => (action) => {
10
+ // Only handle entity actions
11
+ if (!isEntityAction(action)) {
12
+ return next(action);
13
+ }
14
+ // Pass through for tracking
15
+ next(action);
16
+ const entityAction = action;
17
+ try {
18
+ // Translate entity action to resource action
19
+ const resourceAction = translateEntityAction(entityAction, entityRegistry, resourceActionFactory, store.getState(), fsSliceName);
20
+ // Dispatch resource action
21
+ store.dispatch(resourceAction);
22
+ }
23
+ catch (error) {
24
+ onError?.(error, entityAction);
25
+ console.error('Entity middleware error:', error);
26
+ }
27
+ };
28
+ }
29
+ function isEntityAction(action) {
30
+ return [
31
+ EntityActionTypes.FETCH_ENTITY,
32
+ EntityActionTypes.UPDATE_ENTITY,
33
+ EntityActionTypes.DELETE_ENTITY,
34
+ EntityActionTypes.CREATE_ENTITY
35
+ ].includes(action.type);
36
+ }
37
+ function translateEntityAction(entityAction, entityRegistry, resourceActionFactory, state, fsSliceName) {
38
+ const { entityId } = entityAction.payload;
39
+ // Get entity definition
40
+ const entityDef = entityRegistry.getEntity(entityId);
41
+ if (!entityDef) {
42
+ throw new Error(`Entity not found: ${entityId}`);
43
+ }
44
+ switch (entityAction.type) {
45
+ case EntityActionTypes.FETCH_ENTITY:
46
+ return handleFetchEntity(entityAction, entityDef, resourceActionFactory);
47
+ case EntityActionTypes.UPDATE_ENTITY:
48
+ return handleUpdateEntity(entityAction, entityDef, resourceActionFactory, state, fsSliceName);
49
+ case EntityActionTypes.DELETE_ENTITY:
50
+ return handleDeleteEntity(entityAction, entityDef, resourceActionFactory, state, fsSliceName);
51
+ case EntityActionTypes.CREATE_ENTITY:
52
+ return handleCreateEntity(entityAction, entityDef, resourceActionFactory);
53
+ default:
54
+ throw new Error(`Unknown entity action type: ${entityAction.type}`);
55
+ }
56
+ }
57
+ /**
58
+ * Handle FETCH_ENTITY: keys provided, generate path and params
59
+ */
60
+ function handleFetchEntity(action, entityDef, resourceActionFactory) {
61
+ const { keys, params, path } = action.payload;
62
+ // Validate keys
63
+ validateKeys(keys, entityDef.keySchema);
64
+ // Generate path from keys (if not explicitly provided)
65
+ const targetPath = path || generatePath(keys, entityDef);
66
+ // Map keys to resource params
67
+ const resourceParams = entityDef.keyMapper(keys, 'fetch');
68
+ // Merge with additional params
69
+ const mergedParams = {
70
+ ...resourceParams.params,
71
+ ...params,
72
+ // Store entity context for entity sync middleware
73
+ _entityContext: {
74
+ entityId: entityDef.id,
75
+ keys,
76
+ keyFields: entityDef.keySchema.fields
77
+ }
78
+ };
79
+ return resourceActionFactory.callRequest(entityDef.resourceEndpointId, 'fetch', mergedParams, targetPath, action.id);
80
+ }
81
+ /**
82
+ * Handle UPDATE_ENTITY: path provided, read keys from file attributes
83
+ */
84
+ function handleUpdateEntity(action, entityDef, resourceActionFactory, state, fsSliceName) {
85
+ const { path, data, params } = action.payload;
86
+ // Read file node
87
+ const fileNode = selectFileNode(state, path, fsSliceName);
88
+ if (!fileNode) {
89
+ throw new Error(`Entity not found at path: ${JSON.stringify(path)}`);
90
+ }
91
+ // Get entity keys from extension state
92
+ const keys = resourceAttributesUtil.getEntityKeys(fileNode);
93
+ if (!keys) {
94
+ throw new Error(`Entity keys not found in file attributes at: ${JSON.stringify(path)}`);
95
+ }
96
+ // Validate keys
97
+ validateKeys(keys, entityDef.keySchema);
98
+ // Map keys to resource params
99
+ const resourceParams = entityDef.keyMapper(keys, 'update');
100
+ // Merge with body data and additional params
101
+ const mergedParams = {
102
+ ...resourceParams.params,
103
+ ...params,
104
+ body: data,
105
+ _entityContext: {
106
+ entityId: entityDef.id,
107
+ keys,
108
+ keyFields: entityDef.keySchema.fields
109
+ }
110
+ };
111
+ return resourceActionFactory.callRequest(entityDef.resourceEndpointId, 'update', mergedParams, path, action.id);
112
+ }
113
+ /**
114
+ * Handle DELETE_ENTITY: path provided, read keys from file attributes
115
+ */
116
+ function handleDeleteEntity(action, entityDef, resourceActionFactory, state, fsSliceName) {
117
+ const { path, params } = action.payload;
118
+ // Read file node
119
+ const fileNode = selectFileNode(state, path, fsSliceName);
120
+ if (!fileNode) {
121
+ throw new Error(`Entity not found at path: ${JSON.stringify(path)}`);
122
+ }
123
+ // Get entity keys from extension state
124
+ const keys = resourceAttributesUtil.getEntityKeys(fileNode);
125
+ if (!keys) {
126
+ throw new Error(`Entity keys not found in file attributes at: ${JSON.stringify(path)}`);
127
+ }
128
+ // Validate keys
129
+ validateKeys(keys, entityDef.keySchema);
130
+ // Map keys to resource params
131
+ const resourceParams = entityDef.keyMapper(keys, 'delete');
132
+ // Merge with additional params
133
+ const mergedParams = {
134
+ ...resourceParams.params,
135
+ ...params,
136
+ _entityContext: {
137
+ entityId: entityDef.id,
138
+ keys,
139
+ keyFields: entityDef.keySchema.fields
140
+ }
141
+ };
142
+ return resourceActionFactory.callRequest(entityDef.resourceEndpointId, 'delete', mergedParams, path, action.id);
143
+ }
144
+ /**
145
+ * Handle CREATE_ENTITY: keys optional (may be generated by server)
146
+ */
147
+ function handleCreateEntity(action, entityDef, resourceActionFactory) {
148
+ const { keys, data, path, params } = action.payload;
149
+ // Keys are optional for create (server may generate ID)
150
+ let targetPath = path;
151
+ let resourceParams;
152
+ if (keys) {
153
+ validateKeys(keys, entityDef.keySchema, true); // Partial validation
154
+ targetPath = path || generatePath(keys, entityDef);
155
+ resourceParams = entityDef.keyMapper(keys, 'create');
156
+ }
157
+ else {
158
+ resourceParams = { params: {} };
159
+ }
160
+ // Merge with body data and additional params
161
+ const mergedParams = {
162
+ ...resourceParams.params,
163
+ ...params,
164
+ body: data,
165
+ _entityContext: keys
166
+ ? {
167
+ entityId: entityDef.id,
168
+ keys,
169
+ keyFields: entityDef.keySchema.fields
170
+ }
171
+ : undefined
172
+ };
173
+ return resourceActionFactory.callRequest(entityDef.resourceEndpointId, 'create', mergedParams, targetPath, action.id);
174
+ }
175
+ /**
176
+ * Validate entity keys against schema
177
+ */
178
+ function validateKeys(keys, schema, partial = false) {
179
+ if (!partial) {
180
+ for (const field of schema.fields) {
181
+ if (!(field in keys) || keys[field] === undefined || keys[field] === null) {
182
+ throw new Error(`Missing required key field: ${field}`);
183
+ }
184
+ }
185
+ }
186
+ if (schema.validate) {
187
+ const result = schema.validate(keys);
188
+ if (result !== true) {
189
+ throw new Error(typeof result === 'string' ? result : 'Invalid entity keys');
190
+ }
191
+ }
192
+ }
193
+ /**
194
+ * Generate file path from entity keys
195
+ */
196
+ function generatePath(keys, entityDef) {
197
+ if (entityDef.pathGenerator) {
198
+ const result = entityDef.pathGenerator(keys, entityDef.id);
199
+ return Array.isArray(result) ? result : [result];
200
+ }
201
+ // Default path generator: ['entities', entityId, ...keyValues]
202
+ const keyValues = entityDef.keySchema.fields.map((field) => String(keys[field]));
203
+ return ['entities', entityDef.id, ...keyValues];
204
+ }
205
+ /**
206
+ * Select file node from state
207
+ */
208
+ function selectFileNode(state, path, fsSliceName) {
209
+ const fsState = state[fsSliceName];
210
+ if (!fsState?.root)
211
+ return undefined;
212
+ const pathArray = Array.isArray(path) ? path : [path];
213
+ let currentNode = fsState.root;
214
+ for (const segment of pathArray) {
215
+ if (!currentNode || currentNode.type !== 'directory') {
216
+ return undefined;
217
+ }
218
+ currentNode = currentNode.children?.[segment];
219
+ }
220
+ return currentNode;
221
+ }
@@ -0,0 +1,7 @@
1
+ import type { Middleware } from 'redux';
2
+ /**
3
+ * Entity sync middleware
4
+ * Stores entity keys in extension state when resource calls succeed
5
+ */
6
+ export declare function createEntitySyncMiddleware<S = any>(): Middleware<{}, S>;
7
+ //# sourceMappingURL=entity-sync-middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-sync-middleware.d.ts","sourceRoot":"","sources":["../../../src/impl/middleware/entity-sync-middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAOxC;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,GAAG,GAAG,KAAK,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,CAWvE"}
@@ -0,0 +1,32 @@
1
+ import { ResourceActionTypes, resourceAttributesUtil } from '../../api';
2
+ /**
3
+ * Entity sync middleware
4
+ * Stores entity keys in extension state when resource calls succeed
5
+ */
6
+ export function createEntitySyncMiddleware() {
7
+ return (_store) => (next) => (action) => {
8
+ const result = next(action);
9
+ // Listen for resource success actions that originated from entity actions
10
+ if (action.type === ResourceActionTypes.RESOURCE_CALL_SUCCESS) {
11
+ handleEntityResourceSuccess(action);
12
+ }
13
+ return result;
14
+ };
15
+ }
16
+ function handleEntityResourceSuccess(action) {
17
+ const { request } = action;
18
+ // Check if this resource action originated from an entity action
19
+ const entityContext = request.payload.params?._entityContext;
20
+ if (!entityContext) {
21
+ return; // Not an entity-originated action
22
+ }
23
+ const { entityId, keys, keyFields } = entityContext;
24
+ const { path: _path } = action.payload;
25
+ // Create entity attributes
26
+ // Entity attributes - reserved for future use when metadata support is added
27
+ void resourceAttributesUtil.createEntityAttributes(entityId, keys, keyFields);
28
+ // TODO: Entity attributes are no longer stored in file extension state
29
+ // The FileSystemAdapter doesn't support updating extension states directly
30
+ // Entity information is now tracked through the file content itself
31
+ // Future: Consider adding entity metadata support to FileSystemAdapter
32
+ }
@@ -0,0 +1,13 @@
1
+ import type { Middleware } from 'redux';
2
+ import { type ResourceAction } from '../../api';
3
+ export interface ResourceMiddlewareConfig {
4
+ registry: any;
5
+ onError?: (error: any, action: ResourceAction) => void;
6
+ onSuccess?: (result: any, action: ResourceAction) => void;
7
+ }
8
+ /**
9
+ * Resource middleware
10
+ * Intercepts RESOURCE_CALL_REQUEST actions and executes API calls via providers
11
+ */
12
+ export declare function createResourceMiddleware<S = any>(config: ResourceMiddlewareConfig): Middleware<{}, S>;
13
+ //# sourceMappingURL=resource-middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-middleware.d.ts","sourceRoot":"","sources":["../../../src/impl/middleware/resource-middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,WAAW,CAAC;AAGnB,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,GAAG,CAAC;IACd,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;IACvD,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;CAC3D;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,GAAG,GAAG,EAC9C,MAAM,EAAE,wBAAwB,GAC/B,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,CA6EnB"}
@@ -0,0 +1,97 @@
1
+ import { ResourceActionTypes, ResourceActionFactory } from '../../api';
2
+ /**
3
+ * Resource middleware
4
+ * Intercepts RESOURCE_CALL_REQUEST actions and executes API calls via providers
5
+ */
6
+ export function createResourceMiddleware(config) {
7
+ const { registry, onError, onSuccess } = config;
8
+ const actionFactory = new ResourceActionFactory();
9
+ return (store) => (next) => async (action) => {
10
+ // Only handle RESOURCE_CALL_REQUEST actions
11
+ if (action.type !== ResourceActionTypes.RESOURCE_CALL_REQUEST) {
12
+ return next(action);
13
+ }
14
+ // Continue action flow (for sync middleware to set loading state)
15
+ next(action);
16
+ const requestAction = action;
17
+ const { endpointId, operation, params, path } = requestAction.payload;
18
+ try {
19
+ // Lookup endpoint
20
+ const endpointDef = registry.getEndpoint(endpointId);
21
+ if (!endpointDef) {
22
+ throw new Error(`Endpoint not found: ${endpointId}`);
23
+ }
24
+ // Get provider
25
+ const provider = registry.getProvider(endpointDef.providerType);
26
+ if (!provider) {
27
+ throw new Error(`Provider not found: ${endpointDef.providerType}`);
28
+ }
29
+ // Map action to call params (mapper receives entire action)
30
+ const callParams = endpointDef.payloadMapper
31
+ ? endpointDef.payloadMapper(requestAction)
32
+ : { params }; // Default: just use params
33
+ // Execute provider call
34
+ const result = await provider.call(endpointDef.endpoint, callParams, operation);
35
+ // Transform response (transformer receives entire action)
36
+ const transformedData = endpointDef.responseTransformer
37
+ ? endpointDef.responseTransformer(result.data, result.metadata, requestAction)
38
+ : result.data;
39
+ // Determine storage path
40
+ const storagePath = determinePath(path, endpointDef, params, operation);
41
+ // Dispatch success action
42
+ const successAction = actionFactory.callSuccess(endpointId, storagePath, transformedData, result.metadata, requestAction);
43
+ store.dispatch(successAction);
44
+ onSuccess?.(result, requestAction);
45
+ }
46
+ catch (error) {
47
+ // Determine storage path for error state
48
+ const endpointDef = registry.getEndpoint(endpointId);
49
+ const storagePath = determinePath(path, endpointDef, params, operation);
50
+ // Dispatch failure action
51
+ const failureAction = actionFactory.callFailure(endpointId, storagePath, normalizeError(error), requestAction);
52
+ store.dispatch(failureAction);
53
+ onError?.(error, requestAction);
54
+ }
55
+ };
56
+ }
57
+ /**
58
+ * Determine the file path where resource should be stored
59
+ */
60
+ function determinePath(explicitPath, endpointDef, params, operation) {
61
+ // 1. Use explicit path from action if provided
62
+ if (explicitPath) {
63
+ return explicitPath;
64
+ }
65
+ // 2. Use endpoint's default folder + generate filename
66
+ if (endpointDef?.defaultFolder) {
67
+ const folder = Array.isArray(endpointDef.defaultFolder)
68
+ ? endpointDef.defaultFolder
69
+ : [endpointDef.defaultFolder];
70
+ // Generate filename from params or operation
71
+ const filename = generateFilename(params, operation);
72
+ return [...folder, filename];
73
+ }
74
+ // 3. Fallback to root with generated filename
75
+ return [generateFilename(params, operation)];
76
+ }
77
+ /**
78
+ * Generate filename from params or operation
79
+ */
80
+ function generateFilename(params, operation) {
81
+ // If params has an id field, use it
82
+ if (params?.id) {
83
+ return String(params.id);
84
+ }
85
+ // Otherwise generate based on operation + timestamp
86
+ return `${operation}-${Date.now()}`;
87
+ }
88
+ /**
89
+ * Normalize error to consistent format
90
+ */
91
+ function normalizeError(error) {
92
+ return {
93
+ message: error.message || 'Unknown error',
94
+ code: error.code || error.response?.status?.toString(),
95
+ details: error.response?.data || error
96
+ };
97
+ }
@@ -0,0 +1,12 @@
1
+ import type { Middleware } from 'redux';
2
+ import type { FileSystemNodeActions } from '@hamak/ui-store-impl';
3
+ export interface SyncMiddlewareConfig {
4
+ /** FileSystem actions from FileSystemAdapter */
5
+ fsActions: FileSystemNodeActions;
6
+ }
7
+ /**
8
+ * Sync middleware
9
+ * Updates file system state when resource actions complete
10
+ */
11
+ export declare function createSyncMiddleware<S = any>(config: SyncMiddlewareConfig): Middleware<{}, S>;
12
+ //# sourceMappingURL=sync-middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-middleware.d.ts","sourceRoot":"","sources":["../../../src/impl/middleware/sync-middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAOxC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAGlE,MAAM,WAAW,oBAAoB;IACnC,gDAAgD;IAChD,SAAS,EAAE,qBAAqB,CAAC;CAClC;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,GAAG,GAAG,EAC1C,MAAM,EAAE,oBAAoB,GAC3B,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,CAwBnB"}
@@ -0,0 +1,80 @@
1
+ import { ResourceActionTypes } from '../../api';
2
+ import { Pathway } from '@hamak/navigation-utils';
3
+ /**
4
+ * Sync middleware
5
+ * Updates file system state when resource actions complete
6
+ */
7
+ export function createSyncMiddleware(config) {
8
+ const { fsActions } = config;
9
+ return (store) => (next) => (action) => {
10
+ // Pass through all actions first
11
+ const result = next(action);
12
+ // Handle request action (set loading state)
13
+ if (action.type === ResourceActionTypes.RESOURCE_CALL_REQUEST) {
14
+ handleRequestAction(store, action, fsActions);
15
+ }
16
+ // Handle success action (update content)
17
+ if (action.type === ResourceActionTypes.RESOURCE_CALL_SUCCESS) {
18
+ handleSuccessAction(store, action, fsActions);
19
+ }
20
+ // Handle failure action (set error state)
21
+ if (action.type === ResourceActionTypes.RESOURCE_CALL_FAILURE) {
22
+ handleFailureAction(store, action, fsActions);
23
+ }
24
+ return result;
25
+ };
26
+ }
27
+ function handleRequestAction(store, action, fsActions) {
28
+ const { path } = action.payload;
29
+ if (!path) {
30
+ // Loading state will be set when success/failure with resolved path
31
+ return;
32
+ }
33
+ // Normalize path using Pathway utility
34
+ const pathway = Pathway.of(path);
35
+ const parentPathway = pathway.getParent();
36
+ const parentSegments = parentPathway.getSegments();
37
+ // Ensure parent directory exists
38
+ if (parentSegments.length > 0) {
39
+ store.dispatch(fsActions.mkdir(parentSegments, true));
40
+ }
41
+ // Set loading state by creating file node with contentIsPresent=false
42
+ store.dispatch(fsActions.setFile(path, null, 'xs:any', { override: false, contentIsPresent: false }));
43
+ }
44
+ function handleSuccessAction(store, action, fsActions) {
45
+ const { path, data } = action.payload;
46
+ const { request } = action;
47
+ // Normalize path using Pathway utility
48
+ const pathway = Pathway.of(path);
49
+ const parentPathway = pathway.getParent();
50
+ const parentSegments = parentPathway.getSegments();
51
+ // Ensure parent directory exists
52
+ if (parentSegments.length > 0) {
53
+ store.dispatch(fsActions.mkdir(parentSegments, true));
54
+ }
55
+ // Set file content from remote (automatically sets contentLoaded=true and clears loading)
56
+ // Schema should come from endpoint definition if available
57
+ const schema = request.meta?.schema || 'xs:any';
58
+ // First ensure file exists with proper schema
59
+ store.dispatch(fsActions.setFile(path, data, schema, { override: true, contentIsPresent: true }));
60
+ // Then set content from remote to mark it as loaded from remote source
61
+ store.dispatch(fsActions.setFileContent(path, data, true));
62
+ }
63
+ function handleFailureAction(store, action, fsActions) {
64
+ const { path, error } = action.payload;
65
+ if (!path) {
66
+ return;
67
+ }
68
+ // Normalize path using Pathway utility
69
+ const pathway = Pathway.of(path);
70
+ const parentPathway = pathway.getParent();
71
+ const parentSegments = parentPathway.getSegments();
72
+ // Ensure parent directory exists
73
+ if (parentSegments.length > 0) {
74
+ store.dispatch(fsActions.mkdir(parentSegments, true));
75
+ }
76
+ // For now, just ensure the file exists and set content to null/error placeholder
77
+ // TODO: FileSystemAdapter doesn't currently support setting error state directly
78
+ // We might need to extend it or handle errors differently
79
+ store.dispatch(fsActions.setFile(path, { error }, 'xs:any', { override: true, contentIsPresent: true }));
80
+ }