@hamak/ui-store-impl 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/es2015/fs/commands/fs-commands.js +311 -0
  3. package/dist/es2015/fs/commands/structure-commands.js +21 -0
  4. package/dist/es2015/fs/core/fs-adapter.js +123 -0
  5. package/dist/es2015/fs/core/fs-facade.js +78 -0
  6. package/dist/es2015/fs/index.js +9 -0
  7. package/dist/es2015/fs/utils/data-updater.js +244 -0
  8. package/dist/es2015/fs/utils/deep-equal.js +28 -0
  9. package/dist/es2015/index.js +1 -0
  10. package/dist/fs/commands/fs-commands.d.ts +96 -0
  11. package/dist/fs/commands/fs-commands.d.ts.map +1 -0
  12. package/dist/fs/commands/fs-commands.js +311 -0
  13. package/dist/fs/commands/structure-commands.d.ts +76 -0
  14. package/dist/fs/commands/structure-commands.d.ts.map +1 -0
  15. package/dist/fs/commands/structure-commands.js +21 -0
  16. package/dist/fs/core/fs-adapter.d.ts +57 -0
  17. package/dist/fs/core/fs-adapter.d.ts.map +1 -0
  18. package/dist/fs/core/fs-adapter.js +123 -0
  19. package/dist/fs/core/fs-facade.d.ts +37 -0
  20. package/dist/fs/core/fs-facade.d.ts.map +1 -0
  21. package/dist/fs/core/fs-facade.js +78 -0
  22. package/dist/fs/index.d.ts +7 -0
  23. package/dist/fs/index.d.ts.map +1 -0
  24. package/dist/fs/index.js +9 -0
  25. package/dist/fs/utils/data-updater.d.ts +36 -0
  26. package/dist/fs/utils/data-updater.d.ts.map +1 -0
  27. package/dist/fs/utils/data-updater.js +248 -0
  28. package/dist/fs/utils/deep-equal.d.ts +5 -0
  29. package/dist/fs/utils/deep-equal.d.ts.map +1 -0
  30. package/dist/fs/utils/deep-equal.js +28 -0
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1 -0
  34. package/package.json +8 -5
  35. package/src/fs/commands/fs-commands.ts +406 -0
  36. package/src/fs/commands/structure-commands.ts +105 -0
  37. package/src/fs/core/fs-adapter.ts +180 -0
  38. package/src/fs/core/fs-facade.ts +100 -0
  39. package/src/fs/index.ts +11 -0
  40. package/src/fs/utils/data-updater.ts +273 -0
  41. package/src/fs/utils/deep-equal.ts +35 -0
  42. package/src/index.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## 0.4.0 (2025-11-10)
2
+
3
+ ### 🚀 Features
4
+
5
+ - implement notification plugin with UI and backend components ([c19ffcf](https://github.com/amah/app-framework/commit/c19ffcf))
6
+ - add ES2015 build support and fix TypeScript config for logging packages ([be5e45e](https://github.com/amah/app-framework/commit/be5e45e))
7
+ - complete logging system build and add optional console interception ([f390bc6](https://github.com/amah/app-framework/commit/f390bc6))
8
+ - implement core pluggable logging system (Phase 1) ([2abdc1a](https://github.com/amah/app-framework/commit/2abdc1a))
9
+
10
+ ### 🩹 Fixes
11
+
12
+ - add notification packages to workspaces ([97a234d](https://github.com/amah/app-framework/commit/97a234d))
13
+
14
+ ### ❤️ Thank You
15
+
16
+ - Amah
17
+ - Claude
18
+
1
19
  ## 0.3.0 (2025-11-06)
2
20
 
3
21
  ### 🚀 Features
@@ -0,0 +1,311 @@
1
+ import { fileSystemNodeInitialState } from '@hamak/ui-store-api';
2
+ import { DataUpdater } from '../utils/data-updater';
3
+ import { produce, current, isDraft, original } from 'immer';
4
+ import { deepEqual } from '../utils/deep-equal';
5
+ /**
6
+ * Convert path to steps array
7
+ */
8
+ export function pathSteps(path) {
9
+ if (Array.isArray(path)) {
10
+ return path;
11
+ }
12
+ return path.split('/').filter(s => s.trim().length > 0);
13
+ }
14
+ /**
15
+ * Get parent path steps
16
+ */
17
+ export function parentPathSteps(path) {
18
+ const steps = pathSteps(path);
19
+ return steps.length > 0 ? steps.slice(0, steps.length - 1) : steps;
20
+ }
21
+ /**
22
+ * Get filesystem node at path
23
+ */
24
+ export function getFileSystemNode(fsNode, path) {
25
+ const steps = pathSteps(path);
26
+ let result = fsNode;
27
+ for (let i = 0; i < steps.length; i++) {
28
+ const step = steps[i];
29
+ if (result === undefined) {
30
+ return undefined;
31
+ }
32
+ else if (result.type === 'directory') {
33
+ result = result.children[step];
34
+ }
35
+ else {
36
+ return undefined;
37
+ }
38
+ }
39
+ return result;
40
+ }
41
+ /**
42
+ * Create a directory node
43
+ */
44
+ function createDirectoryNode(step) {
45
+ return {
46
+ type: 'directory',
47
+ state: fileSystemNodeInitialState(),
48
+ name: step,
49
+ children: {}
50
+ };
51
+ }
52
+ /**
53
+ * Create a file node
54
+ */
55
+ function createFileNode(name, content, schema, state) {
56
+ return { type: 'file', name, content, schema, state };
57
+ }
58
+ /**
59
+ * FileSystem command handler - executes filesystem operations
60
+ */
61
+ export class FileSystemCommandHandler {
62
+ constructor() {
63
+ this.contentCommandHandler = new FileContentCommandHandler();
64
+ }
65
+ execute(state, command) {
66
+ switch (command.name) {
67
+ case 'mkdir': return this.executeMkdir(state, command);
68
+ case 'set-file': return this.executeSetFile(state, command);
69
+ case 'update-file-content': return this.executeContentCommand(state, command);
70
+ case 'set-file-content': return this.executeSetContentCommand(state, command);
71
+ case 'remove': return this.executeRemove(state, command);
72
+ }
73
+ }
74
+ executeMkdir(state, command) {
75
+ const { path, parents } = command;
76
+ const steps = pathSteps(path);
77
+ return produce(state, draft => {
78
+ const { root } = draft;
79
+ let dir = root;
80
+ for (let i = 0; i < steps.length; i++) {
81
+ const step = steps[i];
82
+ if (i + 1 === steps.length) {
83
+ // Last step is the element to create
84
+ if (dir.children[step] === undefined) {
85
+ dir.children[step] = createDirectoryNode(step);
86
+ }
87
+ }
88
+ else {
89
+ let child = dir.children[step];
90
+ if (child === undefined) {
91
+ if (parents === true) {
92
+ child = createDirectoryNode(step);
93
+ dir.children[step] = child;
94
+ }
95
+ else {
96
+ return;
97
+ }
98
+ }
99
+ else if (child.type === 'directory') {
100
+ dir = child;
101
+ }
102
+ else {
103
+ return;
104
+ }
105
+ dir = child;
106
+ }
107
+ }
108
+ });
109
+ }
110
+ executeRemove(state, command) {
111
+ const { path, recursive } = command;
112
+ const steps = pathSteps(path);
113
+ return produce(state, draft => {
114
+ const { root } = draft;
115
+ const parentDir = getFileSystemNode(root, steps.slice(0, steps.length - 1));
116
+ if (parentDir === undefined) {
117
+ return;
118
+ }
119
+ if (parentDir.type === 'directory') {
120
+ const last = steps[steps.length - 1];
121
+ const child = parentDir.children[last];
122
+ if ((child === null || child === void 0 ? void 0 : child.type) === 'directory'
123
+ && Object.keys(child.children).length > 0
124
+ && !(recursive === true)) {
125
+ return;
126
+ }
127
+ delete parentDir.children[last];
128
+ }
129
+ });
130
+ }
131
+ executeSetFile(state, command) {
132
+ const { path, content, schema, override, contentIsPresent } = command;
133
+ const steps = pathSteps(path);
134
+ return produce(state, draft => {
135
+ const { root } = draft;
136
+ const parentPath = steps.slice(0, steps.length - 1);
137
+ const parentDir = getFileSystemNode(root, parentPath);
138
+ if (parentDir === undefined) {
139
+ return;
140
+ }
141
+ if (parentDir.type === 'directory') {
142
+ const last = steps[steps.length - 1];
143
+ const child = parentDir.children[last];
144
+ if (child === undefined || override === true) {
145
+ parentDir.children[last] = createFileNode(last, content, schema, fileSystemNodeInitialState(contentIsPresent));
146
+ }
147
+ else {
148
+ console.warn(`File already exists at location`, path);
149
+ }
150
+ }
151
+ else {
152
+ console.warn(`Parent file is not directory`, parentPath);
153
+ }
154
+ });
155
+ }
156
+ executeContentCommand(state, command) {
157
+ const { path, contentCommand } = command;
158
+ return produce(state, draft => {
159
+ const { root } = draft;
160
+ const fileNode = getFileSystemNode(root, path);
161
+ if (fileNode !== undefined) {
162
+ if (fileNode.type === 'file') {
163
+ this.contentCommandHandler.execute(fileNode, contentCommand);
164
+ const previousFile = getFileSystemNode(state.root, path);
165
+ const previousContent = (previousFile === null || previousFile === void 0 ? void 0 : previousFile.type) === "file" ? previousFile.content : undefined;
166
+ this.contentMayChange(fileNode);
167
+ }
168
+ else {
169
+ console.warn(`Not a file at location`, path);
170
+ }
171
+ }
172
+ else {
173
+ console.warn(`No file found at location`, path);
174
+ }
175
+ });
176
+ }
177
+ contentMayChange(fileNode) {
178
+ const changed = !deepEqual(this.original(fileNode.content), this.current(fileNode.content));
179
+ if (changed) {
180
+ if (fileNode.state === undefined) {
181
+ fileNode.state = fileSystemNodeInitialState();
182
+ }
183
+ fileNode.state.contentHistory.push(this.original(fileNode.content));
184
+ // check compared to memo modification status
185
+ if (fileNode.state.memo !== undefined) {
186
+ const memo = fileNode.state.memo;
187
+ memo.modified = !deepEqual(this.current(memo.originalContent), this.current(fileNode.content));
188
+ }
189
+ }
190
+ }
191
+ executeSetContentCommand(state, command) {
192
+ const { path, content, fromRemote } = command;
193
+ return produce(state, draft => {
194
+ const { root } = draft;
195
+ const fileNode = getFileSystemNode(root, path);
196
+ if (fileNode !== undefined) {
197
+ if (fileNode.type === 'file') {
198
+ fileNode.content = content;
199
+ if (fileNode.state === undefined) {
200
+ fileNode.state = fileSystemNodeInitialState(true);
201
+ }
202
+ if (fromRemote) {
203
+ fileNode.state.contentLoaded = true;
204
+ if (fileNode.state.memo === undefined) {
205
+ fileNode.state.memo = { originalContent: { value: content }, modified: false };
206
+ }
207
+ else {
208
+ const memo = fileNode.state.memo;
209
+ memo.originalContent = { value: content };
210
+ memo.modified = false;
211
+ }
212
+ }
213
+ else {
214
+ this.contentMayChange(fileNode);
215
+ }
216
+ }
217
+ else {
218
+ console.warn(`Not a file at location`, path);
219
+ }
220
+ }
221
+ else {
222
+ console.warn(`No file found at location`, path);
223
+ }
224
+ });
225
+ }
226
+ current(o) {
227
+ if (o === null || o === undefined) {
228
+ return o;
229
+ }
230
+ return isDraft(o) ? current(o) : o;
231
+ }
232
+ original(o) {
233
+ if (o === null || o === undefined) {
234
+ return o;
235
+ }
236
+ return isDraft(o) ? original(o) : o;
237
+ }
238
+ }
239
+ /**
240
+ * File content command handler - handles structure commands on file content
241
+ */
242
+ export class FileContentCommandHandler {
243
+ constructor() {
244
+ this.updater = new DataUpdater();
245
+ }
246
+ execute(file, command) {
247
+ switch (command.name) {
248
+ case 'add-at':
249
+ {
250
+ this.executeAdd(file, command);
251
+ }
252
+ break;
253
+ case 'insert-at':
254
+ {
255
+ this.executeInsert(file, command);
256
+ }
257
+ break;
258
+ case 'set-at':
259
+ {
260
+ this.executeSet(file, command);
261
+ }
262
+ break;
263
+ case 'delete-at':
264
+ {
265
+ this.executeDelete(file, command);
266
+ }
267
+ break;
268
+ case 'batch-update':
269
+ {
270
+ this.executeBatch(file, command);
271
+ }
272
+ break;
273
+ }
274
+ }
275
+ executeDelete(file, command) {
276
+ const { itinerary } = command;
277
+ if (itinerary === undefined) {
278
+ file.content = undefined;
279
+ }
280
+ const { content } = file;
281
+ this.updater.executeDelete(content, itinerary);
282
+ }
283
+ executeSet(file, command) {
284
+ const { itinerary, value } = command;
285
+ if (itinerary === undefined) {
286
+ file.content = value;
287
+ }
288
+ const { content } = file;
289
+ this.updater.executeSet(content, itinerary, value);
290
+ }
291
+ executeAdd(file, command) {
292
+ const { itinerary, value } = command;
293
+ const { content } = file;
294
+ this.updater.executeAdd(content, itinerary, value);
295
+ }
296
+ executeInsert(file, command) {
297
+ const { itinerary, value } = command;
298
+ if (itinerary === undefined) {
299
+ return;
300
+ }
301
+ const { content } = file;
302
+ this.updater.executeInsert(content, itinerary, value);
303
+ }
304
+ /**
305
+ * Executes a batch of commands, rebasing each on previous mutations.
306
+ */
307
+ executeBatch(file, command) {
308
+ const { content } = file;
309
+ this.updater.executeBatch(content, command);
310
+ }
311
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Helper class for creating structure node commands
3
+ */
4
+ export class StructureNodeCommandHelper {
5
+ addNode(itinerary, value, prototypes) {
6
+ return { name: 'add-at', itinerary, value, prototypes };
7
+ }
8
+ insertNode(itinerary, value, prototypes) {
9
+ return { name: 'insert-at', itinerary, value, prototypes };
10
+ }
11
+ setNode(itinerary, value, prototypes) {
12
+ return { name: 'set-at', itinerary, value, prototypes };
13
+ }
14
+ deleteNode(itinerary) {
15
+ return { name: 'delete-at', itinerary };
16
+ }
17
+ batchUpdate(commands, autoRebase = false) {
18
+ return { name: 'batch-update', commands, autoRebase };
19
+ }
20
+ }
21
+ export const structs = new StructureNodeCommandHelper();
@@ -0,0 +1,123 @@
1
+ import { createSelector } from '@reduxjs/toolkit';
2
+ import { FileSystemCommandHandler, pathSteps } from '../commands/fs-commands';
3
+ import { fileSystemNodeInitialState } from '@hamak/ui-store-api';
4
+ /**
5
+ * Factory function to create a FileSystemAdapter
6
+ */
7
+ export function createFileSystemAdapter(sliceName) {
8
+ return new FileSystemAdapter(sliceName);
9
+ }
10
+ /**
11
+ * FileSystemAdapter - Manages Redux state for virtual filesystem
12
+ */
13
+ export class FileSystemAdapter {
14
+ constructor(sliceName) {
15
+ this.sliceName = sliceName;
16
+ this.actions = new FileSystemNodeActions(sliceName);
17
+ this.commandHandler = new FileSystemCommandHandler();
18
+ this.reducer = (state = this.getInitialState(), action) => {
19
+ if (this.actions.isFileSystemNodeAction(action)) {
20
+ return this.commandHandler.execute(state, action.command);
21
+ }
22
+ return state;
23
+ };
24
+ }
25
+ getInitialState() {
26
+ return { root: { type: 'directory', name: '', children: {}, state: fileSystemNodeInitialState() } };
27
+ }
28
+ getActions() {
29
+ return this.actions;
30
+ }
31
+ getReducer(extraReducers) {
32
+ if (extraReducers === undefined) {
33
+ return this.reducer;
34
+ }
35
+ return (state, action) => this.reducer(extraReducers(state, action), action);
36
+ }
37
+ createSelector(fileSystemSelector, path) {
38
+ const rootSelector = createSelector(fileSystemSelector, fs => fs === null || fs === void 0 ? void 0 : fs.root);
39
+ const steps = pathSteps(path);
40
+ if (steps === undefined || steps.length === 0) {
41
+ return rootSelector;
42
+ }
43
+ return steps.reduce((acc, step) => {
44
+ return createSelector(acc, o => {
45
+ if (o === undefined || o.type === 'file') {
46
+ return o;
47
+ }
48
+ else {
49
+ return o.children[step];
50
+ }
51
+ });
52
+ }, rootSelector);
53
+ }
54
+ }
55
+ /**
56
+ * FileSystemNodeActions - Action creators for filesystem operations
57
+ */
58
+ export class FileSystemNodeActions {
59
+ constructor(sliceName) {
60
+ this.sliceName = sliceName;
61
+ this._mkdirType = `${this.sliceName}/createDirectoryNode`;
62
+ this._setFileType = `${this.sliceName}/createFileNode`;
63
+ this._removeType = `${this.sliceName}/removeNode`;
64
+ this._updateFileContentType = `${this.sliceName}/updateFileContent`;
65
+ this._setFileContentType = `${this.sliceName}/setFileContent`;
66
+ this.actionTypeSet = new Set([
67
+ this._mkdirType,
68
+ this._setFileType,
69
+ this._removeType,
70
+ this._updateFileContentType,
71
+ this._setFileContentType
72
+ ]);
73
+ }
74
+ get mkdirType() {
75
+ return this._mkdirType;
76
+ }
77
+ get setFileType() {
78
+ return this._setFileType;
79
+ }
80
+ get removeType() {
81
+ return this._removeType;
82
+ }
83
+ get updateFileContentType() {
84
+ return this._updateFileContentType;
85
+ }
86
+ get setFileContentType() {
87
+ return this._setFileContentType;
88
+ }
89
+ isFileSystemNodeAction(action) {
90
+ return this.actionTypeSet.has(action.type);
91
+ }
92
+ mkdir(path, parents) {
93
+ return {
94
+ type: this.mkdirType,
95
+ command: { name: 'mkdir', path, parents }
96
+ };
97
+ }
98
+ setFile(path, content, schema, params = { override: true, contentIsPresent: true }) {
99
+ const { override, contentIsPresent } = params;
100
+ return {
101
+ type: this.setFileType,
102
+ command: { name: 'set-file', path, content, schema, override, contentIsPresent }
103
+ };
104
+ }
105
+ updateFileContent(path, contentCommand) {
106
+ return {
107
+ type: this.updateFileContentType,
108
+ command: { name: 'update-file-content', path, contentCommand }
109
+ };
110
+ }
111
+ setFileContent(path, content, fromRemote = false) {
112
+ return {
113
+ type: this.setFileContentType,
114
+ command: { name: 'set-file-content', path, content, fromRemote }
115
+ };
116
+ }
117
+ removeNode(path, recursive) {
118
+ return {
119
+ type: this.removeType,
120
+ command: { name: 'remove', path, recursive }
121
+ };
122
+ }
123
+ }
@@ -0,0 +1,78 @@
1
+ import { createSelector } from '@reduxjs/toolkit';
2
+ import { Pathway, RelativePathwayResolver } from '@hamak/navigation-utils';
3
+ /**
4
+ * StoreFileSystemFacade - High-level API for filesystem operations
5
+ */
6
+ export class StoreFileSystemFacade {
7
+ constructor(fileSystemSelector, adapter) {
8
+ this.fileSystemSelector = fileSystemSelector;
9
+ this.adapter = adapter;
10
+ }
11
+ getActions() {
12
+ return this.adapter.getActions();
13
+ }
14
+ createSelector(path) {
15
+ return this.adapter.createSelector(this.fileSystemSelector, path);
16
+ }
17
+ createFileSelector(path) {
18
+ const nodeSelector = this.adapter.createSelector(this.fileSystemSelector, path);
19
+ return createSelector([nodeSelector], (f) => {
20
+ if ((f === null || f === void 0 ? void 0 : f.type) === 'file') {
21
+ return f;
22
+ }
23
+ else {
24
+ return undefined;
25
+ }
26
+ });
27
+ }
28
+ selectFromRoot(state, path = []) {
29
+ const fileSystemState = this.fileSystemSelector(state);
30
+ if (fileSystemState === undefined || fileSystemState === null) {
31
+ return undefined;
32
+ }
33
+ else {
34
+ const root = fileSystemState.root;
35
+ return (path !== null && path !== void 0 ? path : []).reduce((acc, step) => {
36
+ if (acc === undefined || acc === null) {
37
+ return undefined;
38
+ }
39
+ else {
40
+ switch (acc.type) {
41
+ case "directory": return acc.children[step];
42
+ case "file": return undefined;
43
+ }
44
+ }
45
+ }, root);
46
+ }
47
+ }
48
+ selectFileFromRoot(state, path = []) {
49
+ const fsNode = this.selectFromRoot(state, path);
50
+ if (fsNode === undefined) {
51
+ return undefined;
52
+ }
53
+ else {
54
+ if (fsNode.type === "file") {
55
+ return fsNode;
56
+ }
57
+ else {
58
+ throw new Error(`Expecting file at path '${Pathway.of(path).toString()}' but got '${fsNode.type}'`);
59
+ }
60
+ }
61
+ }
62
+ }
63
+ /**
64
+ * StoreFileSystemPathwayResolver - Pathway resolver for filesystem state
65
+ */
66
+ export class StoreFileSystemPathwayResolver {
67
+ constructor(state, fs) {
68
+ this.state = state;
69
+ this.fs = fs;
70
+ }
71
+ resolve(path) {
72
+ const segments = Pathway.of(path).getSegments();
73
+ return this.fs.selectFromRoot(this.state, segments);
74
+ }
75
+ relativeTo(path) {
76
+ return new RelativePathwayResolver(this, path);
77
+ }
78
+ }
@@ -0,0 +1,9 @@
1
+ // Core filesystem functionality
2
+ export * from './core/fs-adapter';
3
+ export * from './core/fs-facade';
4
+ // Commands
5
+ export * from './commands/fs-commands';
6
+ export * from './commands/structure-commands';
7
+ // Utilities
8
+ export * from './utils/data-updater';
9
+ export * from './utils/deep-equal';