@hamak/ui-remote-fs-impl 0.4.6 → 0.4.16

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.
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@hamak/ui-remote-fs-impl",
3
- "version": "0.4.6",
3
+ "version": "0.4.16",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Remote FS Implementation - HTTP-based remote filesystem",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
9
9
  "sideEffects": false,
10
+ "files": [
11
+ "dist"
12
+ ],
10
13
  "repository": {
11
14
  "type": "git",
12
15
  "url": "https://github.com/amah/app-framework.git",
@@ -34,18 +37,18 @@
34
37
  }
35
38
  },
36
39
  "dependencies": {
37
- "@hamak/ui-remote-fs-api": "0.4.6",
38
- "@hamak/ui-remote-fs-spi": "0.4.6",
39
- "@hamak/ui-store-api": "0.4.1",
40
- "@hamak/ui-store-impl": "0.4.1",
41
- "@hamak/microkernel-api": "0.4.1",
42
- "@hamak/microkernel-spi": "0.4.6",
40
+ "@hamak/ui-remote-fs-api": "*",
41
+ "@hamak/ui-remote-fs-spi": "*",
42
+ "@hamak/ui-store-api": "*",
43
+ "@hamak/ui-store-impl": "*",
44
+ "@hamak/microkernel-api": "*",
45
+ "@hamak/microkernel-spi": "*",
43
46
  "@reduxjs/toolkit": "^2.0.0",
44
47
  "axios": "^1.6.0",
45
48
  "redux": "^5.0.1"
46
49
  },
47
50
  "peerDependencies": {
48
- "@hamak/shared-utils": "^0.4.2"
51
+ "@hamak/shared-utils": "*"
49
52
  },
50
53
  "devDependencies": {
51
54
  "typescript": "~5.4.0",
package/project.json DELETED
@@ -1,24 +0,0 @@
1
- {
2
- "name": "@hamak/ui-remote-fs-impl",
3
- "$schema": "../../../node_modules/nx/schemas/project-schema.json",
4
- "sourceRoot": "packages/ui-remote-fs/ui-remote-fs-impl/src",
5
- "projectType": "library",
6
- "targets": {
7
- "build": {
8
- "executor": "nx:run-commands",
9
- "outputs": ["{projectRoot}/dist"],
10
- "options": {
11
- "command": "tsc -p tsconfig.json && tsc -p tsconfig.es2015.json",
12
- "cwd": "{projectRoot}"
13
- }
14
- },
15
- "clean": {
16
- "executor": "nx:run-commands",
17
- "options": {
18
- "command": "rm -rf dist",
19
- "cwd": "{projectRoot}"
20
- }
21
- }
22
- },
23
- "tags": ["type:library", "scope:ui-remote-fs"]
24
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * Remote FS Actions
3
- */
4
-
5
- export * from './remote-fs-actions';
@@ -1,302 +0,0 @@
1
- /**
2
- * Remote FS Actions
3
- *
4
- * Action creators for remote filesystem operations.
5
- * Migrated from amk/libs/ui/core/remote-fs/src/lib/remote-fs-actions.ts
6
- */
7
-
8
- import { RemoteFsActionTypes, ErrorObject } from '@hamak/ui-remote-fs-api';
9
- import { FileInfo } from '@hamak/shared-utils';
10
-
11
- // Interfaces for action payloads
12
- export interface LsRequestAction {
13
- type: RemoteFsActionTypes.LS_REQUEST;
14
- payload: { path: string | string[] };
15
- [key: string]: any;
16
- }
17
-
18
- export interface LsCompletedAction {
19
- type: RemoteFsActionTypes.LS_COMPLETED;
20
- payload: { data: FileInfo[]; request: LsRequestAction };
21
- [key: string]: any;
22
- }
23
-
24
- export interface LsFailedAction {
25
- type: RemoteFsActionTypes.LS_FAILED;
26
- payload: { error: ErrorObject; request: LsRequestAction };
27
- [key: string]: any;
28
- }
29
-
30
- export interface MkdirRequestAction {
31
- type: RemoteFsActionTypes.MKDIR_REQUEST;
32
- payload: { path: string | string[] };
33
- [key: string]: any;
34
- }
35
-
36
- export interface MkdirCompletedAction {
37
- type: RemoteFsActionTypes.MKDIR_COMPLETED;
38
- payload: { data: FileInfo; request: MkdirRequestAction };
39
- [key: string]: any;
40
- }
41
-
42
- export interface MkdirFailedAction {
43
- type: RemoteFsActionTypes.MKDIR_FAILED;
44
- payload: { error: ErrorObject; request: MkdirRequestAction };
45
- [key: string]: any;
46
- }
47
-
48
- export interface GetRequestAction {
49
- type: RemoteFsActionTypes.GET_REQUEST;
50
- payload: { path: string | string[] };
51
- [key: string]: any;
52
- }
53
-
54
- export interface GetCompletedAction {
55
- type: RemoteFsActionTypes.GET_COMPLETED;
56
- payload: { data: FileInfo; request: GetRequestAction };
57
- [key: string]: any;
58
- }
59
-
60
- export interface GetFailedAction {
61
- type: RemoteFsActionTypes.GET_FAILED;
62
- payload: { error: ErrorObject; request: GetRequestAction };
63
- [key: string]: any;
64
- }
65
-
66
- export interface PostRequestAction {
67
- type: RemoteFsActionTypes.POST_REQUEST;
68
- payload: { path: string | string[]; content: any };
69
- [key: string]: any;
70
- }
71
-
72
- export interface PostCompletedAction {
73
- type: RemoteFsActionTypes.POST_COMPLETED;
74
- payload: { data: FileInfo; request: PostRequestAction };
75
- [key: string]: any;
76
- }
77
-
78
- export interface PostFailedAction {
79
- type: RemoteFsActionTypes.POST_FAILED;
80
- payload: { error: ErrorObject; request: PostRequestAction };
81
- [key: string]: any;
82
- }
83
-
84
- export interface PutRequestAction {
85
- type: RemoteFsActionTypes.PUT_REQUEST;
86
- payload: { path: string | string[]; content: any };
87
- [key: string]: any;
88
- }
89
-
90
- export interface PutCompletedAction {
91
- type: RemoteFsActionTypes.PUT_COMPLETED;
92
- payload: { data: FileInfo; request: PutRequestAction };
93
- [key: string]: any;
94
- }
95
-
96
- export interface PutFailedAction {
97
- type: RemoteFsActionTypes.PUT_FAILED;
98
- payload: { error: ErrorObject; request: PutRequestAction };
99
- [key: string]: any;
100
- }
101
-
102
- export interface DeleteRequestAction {
103
- type: RemoteFsActionTypes.DELETE_REQUEST;
104
- payload: { path: string | string[] };
105
- [key: string]: any;
106
- }
107
-
108
- export interface DeleteCompletedAction {
109
- type: RemoteFsActionTypes.DELETE_COMPLETED;
110
- payload: { data: FileInfo; request: DeleteRequestAction };
111
- [key: string]: any;
112
- }
113
-
114
- export interface DeleteFailedAction {
115
- type: RemoteFsActionTypes.DELETE_FAILED;
116
- payload: { error: ErrorObject; request: DeleteRequestAction };
117
- [key: string]: any;
118
- }
119
-
120
- /**
121
- * Union type for all remote FS actions
122
- */
123
- export type RemoteFsAction =
124
- | LsRequestAction
125
- | LsCompletedAction
126
- | LsFailedAction
127
- | MkdirRequestAction
128
- | MkdirCompletedAction
129
- | MkdirFailedAction
130
- | GetRequestAction
131
- | GetCompletedAction
132
- | GetFailedAction
133
- | PostRequestAction
134
- | PostCompletedAction
135
- | PostFailedAction
136
- | PutRequestAction
137
- | PutCompletedAction
138
- | PutFailedAction
139
- | DeleteRequestAction
140
- | DeleteCompletedAction
141
- | DeleteFailedAction;
142
-
143
- /**
144
- * Action factory class for creating remote filesystem actions
145
- */
146
- export class RemoteFsActionFactory {
147
- public types = RemoteFsActionTypes;
148
-
149
- // LS (List) operations
150
- ofLsRequest(path: string | string[]): LsRequestAction {
151
- return {
152
- type: this.types.LS_REQUEST,
153
- payload: { path },
154
- };
155
- }
156
-
157
- ofLsCompleted(data: FileInfo[], request: LsRequestAction): LsCompletedAction {
158
- return {
159
- type: this.types.LS_COMPLETED,
160
- payload: { data, request },
161
- };
162
- }
163
-
164
- ofLsFailed(error: ErrorObject, request: LsRequestAction): LsFailedAction {
165
- return {
166
- type: this.types.LS_FAILED,
167
- payload: { error, request },
168
- };
169
- }
170
-
171
- // MKDIR (Make Directory) operations
172
- ofMkdirRequest(path: string | string[]): MkdirRequestAction {
173
- return {
174
- type: this.types.MKDIR_REQUEST,
175
- payload: { path },
176
- };
177
- }
178
-
179
- ofMkdirCompleted(data: FileInfo, request: MkdirRequestAction): MkdirCompletedAction {
180
- return {
181
- type: this.types.MKDIR_COMPLETED,
182
- payload: { data, request },
183
- };
184
- }
185
-
186
- ofMkdirFailed(error: ErrorObject, request: MkdirRequestAction): MkdirFailedAction {
187
- return {
188
- type: this.types.MKDIR_FAILED,
189
- payload: { error, request },
190
- };
191
- }
192
-
193
- // GET (Read File) operations
194
- ofGetRequest(path: string | string[]): GetRequestAction {
195
- return {
196
- type: this.types.GET_REQUEST,
197
- payload: { path },
198
- };
199
- }
200
-
201
- ofGetCompleted(data: FileInfo, request: GetRequestAction): GetCompletedAction {
202
- return {
203
- type: this.types.GET_COMPLETED,
204
- payload: { data, request },
205
- };
206
- }
207
-
208
- ofGetFailed(error: ErrorObject, request: GetRequestAction): GetFailedAction {
209
- return {
210
- type: this.types.GET_FAILED,
211
- payload: { error, request },
212
- };
213
- }
214
-
215
- // POST (Create File) operations
216
- ofPostRequest(path: string | string[], content: any): PostRequestAction {
217
- return {
218
- type: this.types.POST_REQUEST,
219
- payload: { path, content },
220
- };
221
- }
222
-
223
- ofPostCompleted(data: FileInfo, request: PostRequestAction): PostCompletedAction {
224
- return {
225
- type: this.types.POST_COMPLETED,
226
- payload: { data, request },
227
- };
228
- }
229
-
230
- ofPostFailed(error: ErrorObject, request: PostRequestAction): PostFailedAction {
231
- return {
232
- type: this.types.POST_FAILED,
233
- payload: { error, request },
234
- };
235
- }
236
-
237
- // PUT (Update File) operations
238
- ofPutRequest(path: string | string[], content: any): PutRequestAction {
239
- return {
240
- type: this.types.PUT_REQUEST,
241
- payload: { path, content },
242
- };
243
- }
244
-
245
- ofPutCompleted(data: FileInfo, request: PutRequestAction): PutCompletedAction {
246
- return {
247
- type: this.types.PUT_COMPLETED,
248
- payload: { data, request },
249
- };
250
- }
251
-
252
- ofPutFailed(error: ErrorObject, request: PutRequestAction): PutFailedAction {
253
- return {
254
- type: this.types.PUT_FAILED,
255
- payload: { error, request },
256
- };
257
- }
258
-
259
- // DELETE operations
260
- ofDeleteRequest(path: string | string[]): DeleteRequestAction {
261
- return {
262
- type: this.types.DELETE_REQUEST,
263
- payload: { path },
264
- };
265
- }
266
-
267
- ofDeleteCompleted(data: FileInfo, request: DeleteRequestAction): DeleteCompletedAction {
268
- return {
269
- type: this.types.DELETE_COMPLETED,
270
- payload: { data, request },
271
- };
272
- }
273
-
274
- ofDeleteFailed(error: ErrorObject, request: DeleteRequestAction): DeleteFailedAction {
275
- return {
276
- type: this.types.DELETE_FAILED,
277
- payload: { error, request },
278
- };
279
- }
280
-
281
- /**
282
- * Type guard to check if an action is a remote FS action
283
- */
284
- isRemoteFsAction(action: any): action is RemoteFsAction {
285
- return (
286
- action &&
287
- typeof action === 'object' &&
288
- 'type' in action &&
289
- Object.values(RemoteFsActionTypes).includes(action.type)
290
- );
291
- }
292
- }
293
-
294
- /**
295
- * Singleton instance of the action factory
296
- */
297
- export const rfsActions = new RemoteFsActionFactory();
298
-
299
- /**
300
- * Re-export the action types enum for convenience
301
- */
302
- export { RemoteFsActionTypes };
package/src/index.ts DELETED
@@ -1,10 +0,0 @@
1
- /**
2
- * UI Remote FS Implementation
3
- * HTTP-based remote filesystem implementation
4
- */
5
-
6
- export * from './actions';
7
- export * from './middleware';
8
- export * from './providers';
9
- export * from './plugin';
10
- export * from './services';
@@ -1,6 +0,0 @@
1
- /**
2
- * Remote FS Middleware
3
- */
4
-
5
- export * from './remote-fs-middleware';
6
- export * from './store-sync-middleware';
@@ -1,244 +0,0 @@
1
- /**
2
- * Remote FS Middleware
3
- *
4
- * Redux middleware for handling remote filesystem operations via HTTP client.
5
- * Migrated from amk/libs/ui/core/remote-fs/src/lib/remote-fs-middleware.ts
6
- */
7
-
8
- import { AnyAction, Middleware } from '@reduxjs/toolkit';
9
- import { Pathway } from '@hamak/shared-utils';
10
- import { IWorkspaceClient, IPathTranslator } from '@hamak/ui-remote-fs-spi';
11
- import { RemoteFsActionTypes, ErrorObject } from '@hamak/ui-remote-fs-api';
12
- import {
13
- rfsActions,
14
- LsRequestAction,
15
- MkdirRequestAction,
16
- GetRequestAction,
17
- PostRequestAction,
18
- PutRequestAction,
19
- DeleteRequestAction,
20
- } from '../actions/remote-fs-actions';
21
-
22
- /**
23
- * Configuration for remote FS middleware
24
- */
25
- export interface RemoteFsMiddlewareConfig {
26
- /**
27
- * Workspace client for remote operations
28
- */
29
- client: IWorkspaceClient;
30
-
31
- /**
32
- * Path translator for mount point translation
33
- */
34
- pathTranslator: IPathTranslator;
35
-
36
- /**
37
- * Optional error handler
38
- */
39
- onError?: (error: ErrorObject, action: AnyAction) => void;
40
-
41
- /**
42
- * Optional success handler
43
- */
44
- onSuccess?: (result: any, action: AnyAction) => void;
45
- }
46
-
47
- /**
48
- * Create remote filesystem middleware
49
- *
50
- * This middleware intercepts remote FS actions and executes them via the provided
51
- * workspace client. It handles path translation from local mount point to remote paths.
52
- *
53
- * @param config Middleware configuration
54
- * @returns Redux middleware
55
- *
56
- * @example
57
- * ```typescript
58
- * const middleware = createRemoteFsMiddleware({
59
- * client: new HttpWorkspaceClient({ workspaceId: '0' }),
60
- * pathTranslator: new PathTranslator(Pathway.ofRoot().resolve('remote'))
61
- * });
62
- * ```
63
- */
64
- export function createRemoteFsMiddleware<S = any>(
65
- config: RemoteFsMiddlewareConfig
66
- ): Middleware<{}, S> {
67
- const { client, pathTranslator, onError, onSuccess } = config;
68
-
69
- /**
70
- * Translate local path to remote path
71
- */
72
- const toRemotePath = (path: string | string[]): Pathway | undefined => {
73
- const pathway = Array.isArray(path) ? Pathway.of(path) : Pathway.of([path]);
74
- return pathTranslator.toRemotePath(pathway);
75
- };
76
-
77
- /**
78
- * Handle errors and convert to ErrorObject
79
- */
80
- const handleError = (error: any): ErrorObject => {
81
- if (error && typeof error === 'object' && 'code' in error && 'message' in error) {
82
- return error as ErrorObject;
83
- }
84
- return {
85
- code: error?.code || 'UNKNOWN',
86
- message: error?.message || 'Unknown error',
87
- };
88
- };
89
-
90
- const remoteFsMiddleware: Middleware<{}, S> = (store) => (next) => (action) => {
91
- const result = next(action);
92
- const dispatch = store.dispatch;
93
- const anyAction = action as AnyAction;
94
-
95
- switch (anyAction.type) {
96
- case RemoteFsActionTypes.LS_REQUEST: {
97
- const requestAction = anyAction as LsRequestAction;
98
- const remotePath = toRemotePath(requestAction.payload.path);
99
- if (remotePath === undefined) {
100
- break;
101
- }
102
- void (async () => {
103
- try {
104
- const data = await client.listFiles(remotePath.getSegments());
105
- const completedAction = rfsActions.ofLsCompleted(data, requestAction);
106
- dispatch(completedAction);
107
- onSuccess?.(data, completedAction);
108
- } catch (error) {
109
- const errorObject = handleError(error);
110
- const failedAction = rfsActions.ofLsFailed(errorObject, requestAction);
111
- dispatch(failedAction);
112
- onError?.(errorObject, failedAction);
113
- }
114
- })();
115
- break;
116
- }
117
-
118
- case RemoteFsActionTypes.MKDIR_REQUEST: {
119
- const requestAction = anyAction as MkdirRequestAction;
120
- const remotePath = toRemotePath(requestAction.payload.path);
121
- if (remotePath === undefined) {
122
- break;
123
- }
124
- void (async () => {
125
- try {
126
- const data = await client.createDirectory(remotePath.getSegments());
127
- const completedAction = rfsActions.ofMkdirCompleted(data, requestAction);
128
- dispatch(completedAction);
129
- onSuccess?.(data, completedAction);
130
- } catch (error) {
131
- const errorObject = handleError(error);
132
- const failedAction = rfsActions.ofMkdirFailed(errorObject, requestAction);
133
- dispatch(failedAction);
134
- onError?.(errorObject, failedAction);
135
- }
136
- })();
137
- break;
138
- }
139
-
140
- case RemoteFsActionTypes.GET_REQUEST: {
141
- const requestAction = anyAction as GetRequestAction;
142
- const remotePath = toRemotePath(requestAction.payload.path);
143
- if (remotePath === undefined) {
144
- break;
145
- }
146
- void (async () => {
147
- try {
148
- const data = await client.readFile(remotePath.getSegments());
149
- const completedAction = rfsActions.ofGetCompleted(data, requestAction);
150
- dispatch(completedAction);
151
- onSuccess?.(data, completedAction);
152
- } catch (error) {
153
- const errorObject = handleError(error);
154
- const failedAction = rfsActions.ofGetFailed(errorObject, requestAction);
155
- dispatch(failedAction);
156
- onError?.(errorObject, failedAction);
157
- }
158
- })();
159
- break;
160
- }
161
-
162
- case RemoteFsActionTypes.POST_REQUEST: {
163
- const requestAction = anyAction as PostRequestAction;
164
- const remotePath = toRemotePath(requestAction.payload.path);
165
- if (remotePath === undefined) {
166
- break;
167
- }
168
- void (async () => {
169
- try {
170
- const data = await client.putFile(
171
- remotePath.getSegments(),
172
- requestAction.payload.content
173
- );
174
- const completedAction = rfsActions.ofPostCompleted(data, requestAction);
175
- dispatch(completedAction);
176
- onSuccess?.(data, completedAction);
177
- } catch (error) {
178
- const errorObject = handleError(error);
179
- const failedAction = rfsActions.ofPostFailed(errorObject, requestAction);
180
- dispatch(failedAction);
181
- onError?.(errorObject, failedAction);
182
- }
183
- })();
184
- break;
185
- }
186
-
187
- case RemoteFsActionTypes.PUT_REQUEST: {
188
- const requestAction = anyAction as PutRequestAction;
189
- const remotePath = toRemotePath(requestAction.payload.path);
190
- if (remotePath === undefined) {
191
- break;
192
- }
193
- void (async () => {
194
- try {
195
- const data = await client.putFile(
196
- remotePath.getSegments(),
197
- requestAction.payload.content
198
- );
199
- const completedAction = rfsActions.ofPutCompleted(data, requestAction);
200
- dispatch(completedAction);
201
- onSuccess?.(data, completedAction);
202
- } catch (error) {
203
- const errorObject = handleError(error);
204
- const failedAction = rfsActions.ofPutFailed(errorObject, requestAction);
205
- dispatch(failedAction);
206
- onError?.(errorObject, failedAction);
207
- }
208
- })();
209
- break;
210
- }
211
-
212
- case RemoteFsActionTypes.DELETE_REQUEST: {
213
- const requestAction = anyAction as DeleteRequestAction;
214
- const remotePath = toRemotePath(requestAction.payload.path);
215
- if (remotePath === undefined) {
216
- break;
217
- }
218
- void (async () => {
219
- try {
220
- const data = await client.deleteFile(remotePath.getSegments());
221
- const completedAction = rfsActions.ofDeleteCompleted(data, requestAction);
222
- dispatch(completedAction);
223
- onSuccess?.(data, completedAction);
224
- } catch (error) {
225
- const errorObject = handleError(error);
226
- const failedAction = rfsActions.ofDeleteFailed(errorObject, requestAction);
227
- dispatch(failedAction);
228
- onError?.(errorObject, failedAction);
229
- }
230
- })();
231
- break;
232
- }
233
-
234
- default:
235
- break;
236
- }
237
-
238
- return result;
239
- };
240
-
241
- return remoteFsMiddleware;
242
- }
243
-
244
- export default createRemoteFsMiddleware;
@@ -1,175 +0,0 @@
1
- /**
2
- * Store Sync Middleware
3
- *
4
- * Redux middleware for synchronizing remote filesystem operations to Redux Store FS.
5
- * This middleware listens to remote FS completion actions and updates the Store FS accordingly.
6
- * Migrated from amk/libs/ui/core/remote-fs/src/lib/store-fs-adapter-middleware.ts
7
- */
8
-
9
- import { AnyAction, Middleware } from '@reduxjs/toolkit';
10
- import { Pathway } from '@hamak/shared-utils';
11
- import { RemoteFsActionTypes } from '@hamak/ui-remote-fs-api';
12
- import { StoreSyncMiddlewareConfig } from '@hamak/ui-remote-fs-spi';
13
- import {
14
- rfsActions,
15
- LsCompletedAction,
16
- MkdirCompletedAction,
17
- GetCompletedAction,
18
- PostCompletedAction,
19
- PutCompletedAction,
20
- DeleteCompletedAction,
21
- } from '../actions/remote-fs-actions';
22
-
23
- /**
24
- * Create store sync middleware
25
- *
26
- * This middleware intercepts remote FS completion actions and synchronizes them
27
- * to the Redux Store FileSystem. It handles:
28
- * - LS_COMPLETED: Creates directories and file entries in Store FS
29
- * - MKDIR_COMPLETED: Creates directory in Store FS
30
- * - GET_COMPLETED: Creates/updates file content in Store FS
31
- * - POST/PUT_COMPLETED: Reloads file content from remote
32
- * - DELETE_COMPLETED: Removes node from Store FS
33
- *
34
- * @param config Middleware configuration
35
- * @returns Redux middleware
36
- *
37
- * @example
38
- * ```typescript
39
- * const middleware = createStoreSyncMiddleware({
40
- * fileSystemAdapter: storeFs,
41
- * pathTranslator: new PathTranslator(Pathway.ofRoot().resolve('remote')),
42
- * autoReload: true
43
- * });
44
- * ```
45
- */
46
- export function createStoreSyncMiddleware<S = any>(
47
- config: StoreSyncMiddlewareConfig
48
- ): Middleware<{}, S> {
49
- const { fileSystemAdapter, pathTranslator, autoReload = true, transformContent } = config;
50
- const storeFsActions = fileSystemAdapter.getActions();
51
-
52
- const storeSyncMiddleware: Middleware<{}, S> = (store) => (next) => (action) => {
53
- const result = next(action);
54
- const anyAction = action as AnyAction;
55
-
56
- // Only process remote FS actions
57
- if (!rfsActions.isRemoteFsAction(anyAction)) {
58
- return result;
59
- }
60
-
61
- const dispatch = store.dispatch;
62
-
63
- switch (anyAction.type) {
64
- case RemoteFsActionTypes.LS_COMPLETED: {
65
- const completedAction = anyAction as LsCompletedAction;
66
- const { request, data } = completedAction.payload;
67
-
68
- // Iterate through file list and create entries in Store FS
69
- data?.forEach((fileInfo) => {
70
- // Build local path: request path + file name
71
- const localPath = Pathway.of(request.payload.path)
72
- .resolve(fileInfo.name)
73
- .getSegments();
74
-
75
- if (fileInfo.isDirectory) {
76
- // Create directory with parents flag
77
- dispatch(storeFsActions.mkdir(localPath, true));
78
- } else {
79
- // Create file entry without content (contentIsPresent: false)
80
- dispatch(
81
- storeFsActions.setFile(localPath, undefined, 'xs:any', {
82
- override: true,
83
- contentIsPresent: false,
84
- })
85
- );
86
- }
87
- });
88
- break;
89
- }
90
-
91
- case RemoteFsActionTypes.MKDIR_COMPLETED: {
92
- const completedAction = anyAction as MkdirCompletedAction;
93
- const { request } = completedAction.payload;
94
- const localPath = Pathway.of(request.payload.path).getSegments();
95
-
96
- // Create directory in Store FS
97
- dispatch(storeFsActions.mkdir(localPath, true));
98
- break;
99
- }
100
-
101
- case RemoteFsActionTypes.GET_COMPLETED: {
102
- const completedAction = anyAction as GetCompletedAction;
103
- const { request, data } = completedAction.payload;
104
- const localPath = Pathway.of(request.payload.path).getSegments();
105
-
106
- // Parse content
107
- let content =
108
- data.content === undefined || data.content === null
109
- ? undefined
110
- : data.content === ''
111
- ? undefined
112
- : JSON.parse(data.content);
113
-
114
- // Apply content transform if provided
115
- if (content !== undefined && transformContent) {
116
- content = transformContent(content);
117
- }
118
-
119
- // Check if file exists in Store FS
120
- const fileSystemNode = fileSystemAdapter.createSelector(
121
- (state: S) => (state as any).fileSystem,
122
- localPath
123
- )(store.getState());
124
-
125
- if (fileSystemNode === undefined) {
126
- // File doesn't exist, create it with content
127
- dispatch(
128
- storeFsActions.setFile(localPath, content, 'xs:any', {
129
- override: true,
130
- contentIsPresent: true,
131
- })
132
- );
133
- } else {
134
- // File exists, update content only if it's a file
135
- if (fileSystemNode?.type === 'file') {
136
- dispatch(storeFsActions.setFileContent(localPath, content, true));
137
- }
138
- }
139
- break;
140
- }
141
-
142
- case RemoteFsActionTypes.POST_COMPLETED:
143
- case RemoteFsActionTypes.PUT_COMPLETED: {
144
- const completedAction = anyAction as PostCompletedAction | PutCompletedAction;
145
- const { request } = completedAction.payload;
146
- const localPath = Pathway.of(request.payload.path).getSegments();
147
-
148
- // Reload file content from remote if autoReload is enabled
149
- if (autoReload) {
150
- dispatch(rfsActions.ofGetRequest(localPath));
151
- }
152
- break;
153
- }
154
-
155
- case RemoteFsActionTypes.DELETE_COMPLETED: {
156
- const completedAction = anyAction as DeleteCompletedAction;
157
- const { request } = completedAction.payload;
158
- const localPath = Pathway.of(request.payload.path).getSegments();
159
-
160
- // Remove node from Store FS
161
- dispatch(storeFsActions.removeNode(localPath));
162
- break;
163
- }
164
-
165
- default:
166
- break;
167
- }
168
-
169
- return result;
170
- };
171
-
172
- return storeSyncMiddleware;
173
- }
174
-
175
- export default createStoreSyncMiddleware;
@@ -1,5 +0,0 @@
1
- /**
2
- * Remote FS Plugin Factory
3
- */
4
-
5
- export * from './remote-fs-plugin-factory';
@@ -1,238 +0,0 @@
1
- /**
2
- * Remote FS Plugin Factory
3
- *
4
- * Creates a microkernel plugin for remote filesystem operations.
5
- * Integrates HTTP-based remote filesystem with Redux Store FS.
6
- */
7
-
8
- import type { PluginModule } from '@hamak/microkernel-spi';
9
- import type { Pathway } from '@hamak/shared-utils';
10
- import {
11
- WORKSPACE_CLIENT_TOKEN,
12
- PATH_TRANSLATOR_TOKEN,
13
- REMOTE_FS_SERVICE_TOKEN,
14
- } from '@hamak/ui-remote-fs-api';
15
- import { IWorkspaceClient, IPathTranslator, PathTranslator } from '@hamak/ui-remote-fs-spi';
16
- import { HttpWorkspaceClient, HttpWorkspaceClientConfig } from '../providers/http-workspace-client';
17
- import { createRemoteFsMiddleware } from '../middleware/remote-fs-middleware';
18
- import { createStoreSyncMiddleware } from '../middleware/store-sync-middleware';
19
- import {
20
- STORE_MANAGER_TOKEN,
21
- STORE_EXTENSIONS_TOKEN,
22
- type StoreMiddlewareExtension,
23
- } from '@hamak/ui-store-api';
24
-
25
- /**
26
- * Configuration for Remote FS plugin
27
- */
28
- export interface RemoteFsPluginConfig {
29
- /**
30
- * Workspace identifier
31
- */
32
- workspaceId: string;
33
-
34
- /**
35
- * Mount point for remote filesystem
36
- * This is the local path where remote files will be mounted
37
- * @example Pathway.ofRoot().resolve('remote')
38
- */
39
- mountPoint: Pathway;
40
-
41
- /**
42
- * Base URL for the workspace API
43
- * If not provided, defaults to current origin + '/api'
44
- * @example 'http://localhost:3000/api'
45
- */
46
- baseUrl?: string;
47
-
48
- /**
49
- * Request timeout in milliseconds
50
- * Default: 30000 (30 seconds)
51
- */
52
- timeout?: number;
53
-
54
- /**
55
- * Whether to automatically reload file content after PUT/POST
56
- * Default: true
57
- */
58
- autoReload?: boolean;
59
-
60
- /**
61
- * Optional transform function for file content before storing
62
- */
63
- transformContent?: (content: any) => any;
64
-
65
- /**
66
- * Enable development logging
67
- * Default: false
68
- */
69
- debug?: boolean;
70
-
71
- /**
72
- * Custom workspace client (for testing or advanced scenarios)
73
- */
74
- customClient?: IWorkspaceClient;
75
-
76
- /**
77
- * Custom path translator (for advanced scenarios)
78
- */
79
- customPathTranslator?: IPathTranslator;
80
- }
81
-
82
- /**
83
- * Create Remote FS plugin
84
- *
85
- * This plugin provides remote filesystem capabilities through HTTP API.
86
- * It registers two middleware:
87
- * - Remote FS Middleware: Handles remote operations via HTTP client
88
- * - Store Sync Middleware: Synchronizes remote data to Redux Store FS
89
- *
90
- * @param config Plugin configuration
91
- * @returns PluginModule for microkernel integration
92
- *
93
- * @example
94
- * ```typescript
95
- * import { Pathway } from '@hamak/shared-utils';
96
- * import { createRemoteFsPlugin } from '@hamak/ui-remote-fs-impl';
97
- *
98
- * const plugin = createRemoteFsPlugin({
99
- * workspaceId: '0',
100
- * mountPoint: Pathway.ofRoot().resolve('remote'),
101
- * baseUrl: 'http://localhost:3000/api',
102
- * autoReload: true
103
- * });
104
- * ```
105
- */
106
- export function createRemoteFsPlugin(config: RemoteFsPluginConfig): PluginModule {
107
- let workspaceClient: IWorkspaceClient;
108
- let pathTranslator: IPathTranslator;
109
- let storeManager: any;
110
-
111
- const log = (message: string, ...args: any[]) => {
112
- if (config.debug) {
113
- console.log(`[ui-remote-fs] ${message}`, ...args);
114
- }
115
- };
116
-
117
- return {
118
- async initialize(ctx) {
119
- log('Initializing plugin...');
120
-
121
- // Create or use custom workspace client
122
- if (config.customClient) {
123
- workspaceClient = config.customClient;
124
- log('Using custom workspace client');
125
- } else {
126
- const clientConfig: HttpWorkspaceClientConfig = {
127
- workspaceId: config.workspaceId,
128
- baseUrl: config.baseUrl,
129
- timeout: config.timeout,
130
- };
131
- workspaceClient = new HttpWorkspaceClient(clientConfig);
132
- log('Created HTTP workspace client', { baseUrl: config.baseUrl, workspaceId: config.workspaceId });
133
- }
134
-
135
- // Create or use custom path translator
136
- if (config.customPathTranslator) {
137
- pathTranslator = config.customPathTranslator;
138
- log('Using custom path translator');
139
- } else {
140
- pathTranslator = new PathTranslator(config.mountPoint);
141
- log('Created path translator', { mountPoint: config.mountPoint.getSegments() });
142
- }
143
-
144
- // Register services via DI
145
- ctx.provide({ provide: WORKSPACE_CLIENT_TOKEN, useValue: workspaceClient });
146
- ctx.provide({ provide: PATH_TRANSLATOR_TOKEN, useValue: pathTranslator });
147
-
148
- // Note: IRemoteFsService implementation will be added in future phases
149
- // ctx.provide({ provide: REMOTE_FS_SERVICE_TOKEN, useValue: remoteFsService });
150
-
151
- log('Plugin initialized');
152
- },
153
-
154
- async activate(ctx) {
155
- log('Activating plugin...');
156
-
157
- // Resolve store manager from DI
158
- try {
159
- storeManager = ctx.resolve(STORE_MANAGER_TOKEN);
160
- } catch (error) {
161
- throw new Error(
162
- '[ui-remote-fs] Store manager not found. Ensure @hamak/ui-store plugin is loaded before ui-remote-fs.'
163
- );
164
- }
165
-
166
- // Get file system adapter from store
167
- const fileSystemAdapter = storeManager.getFileSystemAdapter();
168
- if (!fileSystemAdapter) {
169
- throw new Error('[ui-remote-fs] File system adapter not found in store manager.');
170
- }
171
-
172
- // Get store extensions collector
173
- const extensionsCollector = ctx.resolve(STORE_EXTENSIONS_TOKEN) as any;
174
-
175
- // Create and register remote FS middleware
176
- const remoteFsMiddleware = createRemoteFsMiddleware({
177
- client: workspaceClient,
178
- pathTranslator,
179
- onError: (error, action) => {
180
- log('Remote FS error:', error, action);
181
- ctx.hooks.emit('ui-remote-fs:error', { error, action });
182
- },
183
- onSuccess: (result, action) => {
184
- log('Remote FS success:', action.type);
185
- ctx.hooks.emit('ui-remote-fs:success', { result, action });
186
- },
187
- });
188
-
189
- // Create and register store sync middleware
190
- const storeSyncMiddleware = createStoreSyncMiddleware({
191
- fileSystemAdapter,
192
- pathTranslator,
193
- autoReload: config.autoReload,
194
- transformContent: config.transformContent,
195
- });
196
-
197
- // Register middleware with store extensions
198
- const middlewareExtensions: StoreMiddlewareExtension[] = [
199
- {
200
- id: 'remote-fs',
201
- middleware: remoteFsMiddleware,
202
- priority: 100,
203
- plugin: 'ui-remote-fs',
204
- description: 'Handles remote filesystem operations via HTTP',
205
- },
206
- {
207
- id: 'store-sync',
208
- middleware: storeSyncMiddleware,
209
- priority: 50,
210
- plugin: 'ui-remote-fs',
211
- description: 'Synchronizes remote FS to Redux Store FS',
212
- },
213
- ];
214
-
215
- extensionsCollector.register('ui-remote-fs:middleware', {
216
- middleware: middlewareExtensions,
217
- });
218
-
219
- log('Middleware registered');
220
-
221
- // Emit ready event
222
- ctx.hooks.emit('ui-remote-fs:ready', {
223
- workspaceClient,
224
- pathTranslator,
225
- mountPoint: config.mountPoint,
226
- });
227
-
228
- log('Plugin activated');
229
- },
230
-
231
- async deactivate() {
232
- log('Plugin deactivated');
233
- // Cleanup if needed
234
- },
235
- };
236
- }
237
-
238
- export default createRemoteFsPlugin;
@@ -1,232 +0,0 @@
1
- /**
2
- * HTTP Workspace Client
3
- *
4
- * HTTP-based implementation of IWorkspaceClient using Axios.
5
- * Migrated from amk/libs/server/ws/ws-client/src/lib/server-ws-ws-client.ts
6
- */
7
-
8
- import axios, { AxiosInstance, AxiosError } from 'axios';
9
- import { IWorkspaceClient } from '@hamak/ui-remote-fs-spi';
10
- import { FileInfo } from '@hamak/shared-utils';
11
- import { ErrorObject } from '@hamak/ui-remote-fs-api';
12
-
13
- /**
14
- * Configuration for HTTP workspace client
15
- */
16
- export interface HttpWorkspaceClientConfig {
17
- /**
18
- * Base URL for the API (e.g., 'http://localhost:3000/api')
19
- * If not provided, defaults to current origin + '/api'
20
- */
21
- baseUrl?: string;
22
-
23
- /**
24
- * Workspace identifier
25
- */
26
- workspaceId: string;
27
-
28
- /**
29
- * Request timeout in milliseconds
30
- * Default: 30000 (30 seconds)
31
- */
32
- timeout?: number;
33
-
34
- /**
35
- * Custom axios instance (for advanced configuration)
36
- */
37
- axiosInstance?: AxiosInstance;
38
- }
39
-
40
- /**
41
- * HTTP-based implementation of IWorkspaceClient
42
- *
43
- * This implementation uses Axios to communicate with a REST API backend.
44
- * Supports configurable base URL, timeout, and custom axios instances.
45
- *
46
- * @example
47
- * ```typescript
48
- * const client = new HttpWorkspaceClient({
49
- * workspaceId: '0',
50
- * baseUrl: 'http://localhost:3000/api'
51
- * });
52
- *
53
- * const files = await client.listFiles(['folder', 'subfolder']);
54
- * ```
55
- */
56
- export class HttpWorkspaceClient implements IWorkspaceClient {
57
- private readonly workspaceId: string;
58
- private readonly baseUrl: string;
59
- private readonly axios: AxiosInstance;
60
-
61
- constructor(config: HttpWorkspaceClientConfig) {
62
- this.workspaceId = config.workspaceId;
63
-
64
- // Default base URL to current origin if not provided
65
- this.baseUrl = config.baseUrl ||
66
- (typeof globalThis !== 'undefined' && 'location' in globalThis
67
- ? `${(globalThis as any).location.protocol}//${(globalThis as any).location.host}/api`
68
- : '/api');
69
-
70
- // Use provided axios instance or create new one
71
- this.axios = config.axiosInstance || axios.create({
72
- baseURL: this.baseUrl,
73
- timeout: config.timeout || 30000,
74
- headers: {
75
- 'Content-Type': 'application/json',
76
- },
77
- });
78
- }
79
-
80
- /**
81
- * Format path segments to URL path string
82
- */
83
- private formatPath(path: string[]): string {
84
- return path.join('/');
85
- }
86
-
87
- /**
88
- * Handle axios errors and convert to ErrorObject
89
- */
90
- private handleError(error: unknown): never {
91
- if (axios.isAxiosError(error)) {
92
- const axiosError = error as AxiosError<{ error?: ErrorObject }>;
93
-
94
- // Try to extract error from response
95
- if (axiosError.response?.data?.error) {
96
- throw axiosError.response.data.error;
97
- }
98
-
99
- // Create error from axios error
100
- const errorObj: ErrorObject = {
101
- code: axiosError.code || 'UNKNOWN',
102
- message: axiosError.message || 'Unknown error occurred',
103
- params: {
104
- status: axiosError.response?.status,
105
- statusText: axiosError.response?.statusText,
106
- },
107
- };
108
- throw errorObj;
109
- }
110
-
111
- // Handle non-axios errors
112
- const errorObj: ErrorObject = {
113
- code: 'UNKNOWN',
114
- message: error instanceof Error ? error.message : 'Unknown error occurred',
115
- };
116
- throw errorObj;
117
- }
118
-
119
- /**
120
- * Serialize content to string for transmission
121
- */
122
- private serializeContent(content: any): string {
123
- if (typeof content === 'string') {
124
- return content;
125
- }
126
- return JSON.stringify(content);
127
- }
128
-
129
- /**
130
- * List files and directories at the specified path
131
- */
132
- async listFiles(path: string[]): Promise<FileInfo[]> {
133
- try {
134
- const formattedPath = this.formatPath(path);
135
- const response = await this.axios.get<FileInfo[]>(
136
- `/workspaces/${this.workspaceId}/files/${formattedPath}`
137
- );
138
- return response.data;
139
- } catch (error) {
140
- this.handleError(error);
141
- }
142
- }
143
-
144
- /**
145
- * Create a directory at the specified path
146
- */
147
- async createDirectory(path: string[]): Promise<FileInfo> {
148
- try {
149
- const formattedPath = this.formatPath(path);
150
- const response = await this.axios.post<FileInfo>(
151
- `/workspaces/${this.workspaceId}/mkdir/${formattedPath}`
152
- );
153
- return response.data;
154
- } catch (error) {
155
- this.handleError(error);
156
- }
157
- }
158
-
159
- /**
160
- * Read a file's metadata and content
161
- */
162
- async readFile(path: string[]): Promise<FileInfo> {
163
- try {
164
- const formattedPath = this.formatPath(path);
165
- const response = await this.axios.get<FileInfo>(
166
- `/workspaces/${this.workspaceId}/read/${formattedPath}`
167
- );
168
- return response.data;
169
- } catch (error) {
170
- this.handleError(error);
171
- }
172
- }
173
-
174
- /**
175
- * Write or update a file's content (uses PUT method)
176
- */
177
- async putFile(path: string[], content: any): Promise<FileInfo> {
178
- try {
179
- const formattedPath = this.formatPath(path);
180
- const serializedContent = this.serializeContent(content);
181
- const response = await this.axios.put<FileInfo>(
182
- `/workspaces/${this.workspaceId}/put/${formattedPath}`,
183
- { content: serializedContent }
184
- );
185
- return response.data;
186
- } catch (error) {
187
- this.handleError(error);
188
- }
189
- }
190
-
191
- /**
192
- * Delete a file or directory
193
- */
194
- async deleteFile(path: string[]): Promise<FileInfo> {
195
- try {
196
- const formattedPath = this.formatPath(path);
197
- const response = await this.axios.delete<FileInfo>(
198
- `/workspaces/${this.workspaceId}/delete/${formattedPath}`
199
- );
200
- return response.data;
201
- } catch (error) {
202
- this.handleError(error);
203
- }
204
- }
205
-
206
- /**
207
- * Get the workspace ID
208
- */
209
- getWorkspaceId(): string {
210
- return this.workspaceId;
211
- }
212
-
213
- /**
214
- * Get the base URL
215
- */
216
- getBaseUrl(): string {
217
- return this.baseUrl;
218
- }
219
- }
220
-
221
- /**
222
- * Factory function to create HTTP workspace client with default configuration
223
- */
224
- export function createHttpWorkspaceClient(
225
- workspaceId: string,
226
- baseUrl?: string
227
- ): HttpWorkspaceClient {
228
- return new HttpWorkspaceClient({
229
- workspaceId,
230
- baseUrl,
231
- });
232
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * Remote FS Providers
3
- */
4
-
5
- export * from './http-workspace-client';
@@ -1,5 +0,0 @@
1
- /**
2
- * Remote FS Services
3
- */
4
-
5
- export * from './remote-fs-service';
@@ -1,13 +0,0 @@
1
- /**
2
- * Remote FS Service
3
- *
4
- * Service implementation for remote filesystem operations.
5
- * Will be implemented in future phases.
6
- */
7
-
8
- // Placeholder - will be implemented in future phases
9
- export class RemoteFsService {
10
- constructor() {
11
- throw new Error('Not yet implemented');
12
- }
13
- }
@@ -1,21 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2015",
4
- "module": "CommonJS",
5
- "lib": ["ES2015"],
6
- "skipLibCheck": true,
7
- "moduleResolution": "node",
8
- "resolveJsonModule": true,
9
- "strict": true,
10
- "outDir": "./dist/es2015",
11
- "rootDir": "./src",
12
- "declaration": false,
13
- "declarationMap": false,
14
- "sourceMap": false,
15
- "downlevelIteration": true,
16
- "esModuleInterop": true,
17
- "allowSyntheticDefaultImports": true
18
- },
19
- "include": ["src/**/*"],
20
- "exclude": ["node_modules", "dist", "**/*.test.ts"]
21
- }
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "ESNext",
5
- "lib": ["ES2020"],
6
- "skipLibCheck": true,
7
- "moduleResolution": "bundler",
8
- "resolveJsonModule": true,
9
- "isolatedModules": true,
10
- "strict": true,
11
- "outDir": "./dist",
12
- "rootDir": "./src",
13
- "declaration": true,
14
- "declarationMap": true,
15
- "allowImportingTsExtensions": false
16
- },
17
- "include": ["src/**/*"],
18
- "exclude": ["node_modules", "dist", "**/*.test.ts"]
19
- }