@pcg/dynamic-components 1.0.0-alpha.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 (60) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/index.d.ts +1816 -0
  3. package/dist/index.js +1564 -0
  4. package/dist/index.js.map +1 -0
  5. package/eslint.config.cjs +14 -0
  6. package/package.json +30 -0
  7. package/src/assertions/basic.ts +58 -0
  8. package/src/assertions/containers.ts +76 -0
  9. package/src/assertions/index.ts +6 -0
  10. package/src/assertions/paths.ts +12 -0
  11. package/src/assertions/rich-text.ts +16 -0
  12. package/src/assertions/yjs.ts +25 -0
  13. package/src/data-objects/data-object.ts +34 -0
  14. package/src/data-objects/index.ts +3 -0
  15. package/src/data-objects/rich-text.ts +38 -0
  16. package/src/dynamic-components/fractional-indexing.ts +321 -0
  17. package/src/dynamic-components/index.ts +6 -0
  18. package/src/dynamic-components/paths.ts +194 -0
  19. package/src/dynamic-components/registry/chats.ts +24 -0
  20. package/src/dynamic-components/registry/content.ts +118 -0
  21. package/src/dynamic-components/registry/forms.ts +525 -0
  22. package/src/dynamic-components/registry/index.ts +6 -0
  23. package/src/dynamic-components/registry/layout.ts +86 -0
  24. package/src/dynamic-components/registry/uikit-dynamic-component.ts +84 -0
  25. package/src/dynamic-components/tools.ts +195 -0
  26. package/src/dynamic-components/types.ts +237 -0
  27. package/src/index.ts +7 -0
  28. package/src/paths/array-keys.ts +164 -0
  29. package/src/paths/array-ops.ts +124 -0
  30. package/src/paths/basic-ops.ts +181 -0
  31. package/src/paths/constants.ts +1 -0
  32. package/src/paths/index.ts +7 -0
  33. package/src/paths/tools.ts +42 -0
  34. package/src/paths/types.ts +133 -0
  35. package/src/y-components/index.ts +3 -0
  36. package/src/y-components/tools.ts +234 -0
  37. package/src/y-components/types.ts +19 -0
  38. package/src/y-tools/array-path-ops.ts +240 -0
  39. package/src/y-tools/basic-path-ops.ts +189 -0
  40. package/src/y-tools/index.ts +6 -0
  41. package/src/y-tools/tools.ts +122 -0
  42. package/src/y-tools/types.ts +32 -0
  43. package/src/y-tools/y-array-keys.ts +47 -0
  44. package/tests/assertions/basic-types.test.ts +78 -0
  45. package/tests/assertions/containers.test.ts +72 -0
  46. package/tests/assertions/paths.test.ts +23 -0
  47. package/tests/assertions/yjs.test.ts +33 -0
  48. package/tests/dynamic-components/paths.test.ts +171 -0
  49. package/tests/dynamic-components/tools.test.ts +121 -0
  50. package/tests/paths/array-keys.test.ts +182 -0
  51. package/tests/paths/array-ops.test.ts +164 -0
  52. package/tests/paths/basic-ops.test.ts +263 -0
  53. package/tests/paths/tools.test.ts +55 -0
  54. package/tests/y-components/tools.test.ts +198 -0
  55. package/tests/y-tools/array-base-ops.test.ts +55 -0
  56. package/tests/y-tools/array-path-ops.test.ts +95 -0
  57. package/tsconfig.json +13 -0
  58. package/tsconfig.lib.json +13 -0
  59. package/tsdown.config.ts +18 -0
  60. package/vitest.config.ts +19 -0
@@ -0,0 +1,234 @@
1
+ import { JSONContent } from '@tiptap/core';
2
+ import { yXmlFragmentToProsemirrorJSON } from 'y-prosemirror';
3
+ import * as Y from 'yjs';
4
+
5
+ import {
6
+ isObject, isString, isYArray, isYMap,
7
+ } from '@/assertions/index.js';
8
+ import { RichText } from '@/data-objects/index.js';
9
+ import {
10
+ DynamicComponent, DynamicComponentWithPosition,
11
+ sortDynamicComponents,
12
+ } from '@/dynamic-components/index.js';
13
+ import {
14
+ arrayToYArray,
15
+ objectToYMap,
16
+ } from '@/y-tools/index.js';
17
+
18
+ import { YComponent } from './types.js';
19
+
20
+ /**
21
+ * This function converts dynamic component properties to Y.Map representation.
22
+ */
23
+ export const dynamicComponentPropsToYComponentProps = (props: Record<string, unknown>) => {
24
+ const entries: [string, unknown][] = Object.entries(props).map(([key, value]) => {
25
+ if (key === 'content' && isObject(value) && (value.content as JSONContent)?.type === 'doc') {
26
+ // sae value content as is
27
+ return [key, value.content];
28
+ } else if (Array.isArray(value)) {
29
+ return [key, arrayToYArray(value)];
30
+ } else if (isObject(value)) {
31
+ return [key, objectToYMap(value)];
32
+ } else {
33
+ return [key, value];
34
+ }
35
+ });
36
+
37
+ return new Y.Map(entries);
38
+ };
39
+
40
+ /**
41
+ * This function converts a dynamic component to Y.Map representation.
42
+ */
43
+ export const dynamicComponentToYMap = (component: DynamicComponent, yMap = new Y.Map()): YComponent => {
44
+ const {
45
+ props: propsWithComponents,
46
+ ...fields
47
+ } = component;
48
+
49
+ const {
50
+ components,
51
+ ...props
52
+ } = propsWithComponents ?? {
53
+ };
54
+
55
+ for (const [key, value] of Object.entries(fields)) {
56
+ yMap.set(key, value);
57
+ }
58
+
59
+ const yProps = props ? objectToYMap(props) : new Y.Map();
60
+
61
+ if (Array.isArray(components)) {
62
+ yProps.set('components', dynamicComponentsToYArray(components));
63
+ }
64
+
65
+ yMap.set('props', yProps);
66
+
67
+ return yMap;
68
+ };
69
+
70
+ /**
71
+ * This function converts an array of dynamic components to Y.Array representation.
72
+ */
73
+ export const dynamicComponentsToYArray = (components: DynamicComponent[], yArray = new Y.Array()) => {
74
+ yArray.push(sortDynamicComponents(components).map((cmp) => dynamicComponentToYMap(cmp)));
75
+
76
+ return yArray;
77
+ };
78
+
79
+ /**
80
+ * This function checks if a given Y.Map object is a YComponent.
81
+ */
82
+ export const isYComponent = (yComponent: unknown): yComponent is YComponent => {
83
+ return isYMap(yComponent) && isString(yComponent.get('component')) && isString(yComponent.get('id'));
84
+ };
85
+
86
+ /**
87
+ * This function converts a YComponent to a dynamic component.
88
+ */
89
+ export const yComponentToDynamicComponent = (yComponent: YComponent, extractContent = false): DynamicComponentWithPosition => {
90
+ const component: Record<string, unknown> = {
91
+ props: {
92
+ },
93
+ };
94
+
95
+ for (const [key, value] of yComponent.entries()) {
96
+ if (key === 'props') {
97
+ if (!isYMap(value)) {
98
+ throw new Error('Component props must be YMap');
99
+ }
100
+
101
+ const yComponentProps = value;
102
+
103
+ const props: Record<string, unknown> = {
104
+ };
105
+
106
+ for (const [prop, value] of yComponentProps.entries()) {
107
+ if (prop === 'components') {
108
+ if (!isYArray<YComponent>(value)) {
109
+ throw new Error('Component props.components array must be YArray');
110
+ }
111
+
112
+ props.components = value.toArray().map((it) => yComponentToDynamicComponent(it));
113
+ } else if (
114
+ extractContent &&
115
+ yComponent.doc &&
116
+ isYMap(value) &&
117
+ value.get('type') === 'RichText' &&
118
+ typeof value.get('id') === 'string'
119
+ ) {
120
+ const id = value.get('id') as string;
121
+ const rawRichText = value.toJSON() as RichText;
122
+
123
+ const richText: RichText = {
124
+ ...rawRichText,
125
+ tiptap: yXmlFragmentToProsemirrorJSON(yComponent.doc.getXmlFragment(`RichText:${id}`)),
126
+ };
127
+
128
+ props[prop] = richText;
129
+ } else if (value instanceof Y.AbstractType) {
130
+ props[prop] = value.toJSON();
131
+ } else {
132
+ props[prop] = value;
133
+ }
134
+ }
135
+
136
+ component.props = props;
137
+ } else if (isYArray(value)) {
138
+ component[key] = value.toArray();
139
+ } else if (isYMap(value)) {
140
+ component[key] = value.toJSON();
141
+ } else {
142
+ component[key] = value;
143
+ }
144
+ }
145
+
146
+ return component as unknown as DynamicComponentWithPosition;
147
+ };
148
+
149
+ /**
150
+ * This function converts a dynamic component to YComponent.
151
+ */
152
+ export const dynamicComponentToYComponent = (component: DynamicComponent): YComponent => {
153
+ // return yComponent.toJSON() as DynamicComponentWithPosition;
154
+ return dynamicComponentToYMap(component) as YComponent;
155
+ };
156
+
157
+ /**
158
+ * This function retrieves the properties of a YComponent.
159
+ */
160
+ export const getYComponentProps = (yComponent: YComponent): Y.Map<unknown> => {
161
+ const props = yComponent.get('props');
162
+ if (!isYMap(props)) {
163
+ throw new Error(`Component hasn't "props" property. It must have to be defined as Y.Map (component: ${yComponent.get('id')})`);
164
+ }
165
+
166
+ return props;
167
+ };
168
+
169
+ /**
170
+ * This function retrieves the array of components of a YComponent.
171
+ */
172
+ export const getYComponentComponentsArray = (yComponent: YComponent): Y.Array<YComponent> => {
173
+ const props = getYComponentProps(yComponent);
174
+
175
+ const yComponents = props.get('components');
176
+
177
+ if (!isYArray<YComponent>(yComponents)) {
178
+ throw new Error(`Component hasn't "props.components" property. It must be defined as Y.Array (component: ${yComponent.get('id')})`);
179
+ }
180
+
181
+ return yComponents;
182
+ };
183
+
184
+ /**
185
+ * This function checks if a YComponent has a components array.
186
+ * @example
187
+ * // component
188
+ * {
189
+ * id: "column",
190
+ * component: "column",
191
+ * props: {
192
+ * components: [...] <= has components array
193
+ * }
194
+ * }
195
+ */
196
+ export const yComponentHasComponentsYArray = (yComponent: YComponent): boolean => {
197
+ const props = getYComponentProps(yComponent);
198
+
199
+ const yComponents = props.get('components');
200
+
201
+ return isYArray<YComponent>(yComponents);
202
+ };
203
+
204
+ /**
205
+ * This function retrieves the parent component of a YArray of YComponents.
206
+ * @example
207
+ * // parent component
208
+ * {
209
+ * id: "column",
210
+ * component: "column",
211
+ * props: {
212
+ * components: [...] <= components array
213
+ * }
214
+ * }
215
+ */
216
+ export const getYComponentsArrayParentComponent = (yArray: Y.Array<YComponent>): YComponent | null => {
217
+ // yarray => parent (props) => parent (YIndexedDynamicComponents)
218
+
219
+ return yArray.parent?.parent as YComponent ?? null;
220
+ };
221
+
222
+ /**
223
+ * This function finds a Y.Component in a Y.Array by its ID.
224
+ */
225
+ export const findYComponentInArray = (yArray: Y.Array<YComponent>, componentId: string) => {
226
+ const yComponent = yArray.toArray().find((it) => it.get('id') === componentId);
227
+
228
+ if (!yComponent) {
229
+ throw new Error(`Component with id ${componentId} not found in yArray`);
230
+ }
231
+
232
+ return yComponent;
233
+ };
234
+
@@ -0,0 +1,19 @@
1
+ import * as Y from 'yjs';
2
+
3
+ /**
4
+ * Represents a dynamic component in Y.js.
5
+ * Each component is represented as a Y.Map object where each key-value pair corresponds to a property of the component.
6
+ */
7
+ export type YComponent = Y.Map<unknown>
8
+
9
+ /**
10
+ * Represents an array of dynamic components in Y.js.
11
+ * Each component in the array is represented as a YComponent.
12
+ */
13
+ export type YComponentsArray = Y.Array<YComponent>
14
+
15
+ /**
16
+ * Represents the properties of a dynamic component in Y.js.
17
+ * Each property is represented as a key-value pair in a Y.Map object.
18
+ */
19
+ export type YComponentProps = Y.Map<unknown>
@@ -0,0 +1,240 @@
1
+ import * as Y from 'yjs';
2
+
3
+ import {
4
+ isIndex, isYArray, isYMap,
5
+ } from '@/assertions/index.js';
6
+ import { generateKeyBetween } from '@/dynamic-components/index.js';
7
+
8
+ import { getFromYPath, setInYPath } from './basic-path-ops.js';
9
+ import { getSortedYArray, getYArrayRefsByPosition } from './tools.js';
10
+ import { getYArrayIndexByArrayKey } from './y-array-keys.js';
11
+
12
+ /**
13
+ * Pushes a value to the end of the Y.Array at the specified path.
14
+ *
15
+ * @example
16
+ * let yMap = new Y.Map();
17
+ * yMap.set('names', new Y.Array());
18
+ * pushInYPath(yMap, ['names'], 'John');
19
+ * // yMap now looks like: { names: ['John'] }
20
+ */
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ export const pushInYPath = (entry: Y.Array<any> | Y.Map<any>, path: (string | number)[], value: any) => {
23
+ const yArray = getFromYPath(entry, path);
24
+ if (!isYArray(yArray)) {
25
+ setInYPath(entry, path, Y.Array.from([value]));
26
+ } else {
27
+ yArray.push([value]);
28
+ }
29
+ };
30
+
31
+ const printPosition = (array: unknown[]) => array.map((it) => isYMap(it) ? it.get('position') ?? 'no-position' : 'not-ymap');
32
+
33
+ /**
34
+ * Moves an item to a new position relative to another item in the Y.Array or Y.Map at the specified path.
35
+ *
36
+ * @example
37
+ * moveInYPath(yMap, ['components'], 'a0', 'b1', 'after');
38
+ */
39
+ export const moveInYPath = (
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+ entry: Y.Array<any> | Y.Map<any>,
42
+ path: (string | number)[],
43
+ oldPosition: string,
44
+ relativePosition: string,
45
+ insert: 'before' | 'after',
46
+ ) => {
47
+ const yArray = getFromYPath(entry, path);
48
+ if (!isYArray(yArray)) {
49
+ throw new Error(`Can't move. Target item is not yArray in path ${JSON.stringify(path)}`);
50
+ }
51
+
52
+ const array = yArray.toArray();
53
+
54
+ const item = array.find((it) => isYMap(it) && it.get('position') === oldPosition);
55
+ const relativeItemIndex = array.findIndex((it) => isYMap(it) && it.get('position') === relativePosition);
56
+
57
+ if (!isYMap(item)) {
58
+ throw new Error(`Can't move. Item in position ${oldPosition} not found. ${printPosition(array)}`);
59
+ }
60
+
61
+ if (relativeItemIndex === -1) {
62
+ throw new Error(`Can't move. Relative item in position ${relativePosition} not found. ${printPosition(array)}`);
63
+ }
64
+
65
+ const relativeItem = array[relativeItemIndex];
66
+ if (!isYMap(relativeItem)) {
67
+ throw new Error(`Can't move. Relative item in position ${relativePosition} must be YMap. ${printPosition(array)}`);
68
+ }
69
+
70
+ if (insert === 'before') {
71
+ const left = array[relativeItemIndex - 1];
72
+ const leftPosition = isYMap(left) ? left.get('position') as string ?? null : null;
73
+
74
+ const newPosition = generateKeyBetween(leftPosition, relativeItem.get('position') as string);
75
+ item.set('position', newPosition);
76
+
77
+ return newPosition;
78
+ }
79
+
80
+ const right = array[relativeItemIndex + 1];
81
+ const rightPosition = isYMap(right) ? right.get('position') as string ?? null : null;
82
+
83
+ const newPosition = generateKeyBetween(relativeItem.get('position') as string, rightPosition);
84
+ item.set('position', newPosition);
85
+
86
+ return newPosition;
87
+ };
88
+
89
+ /**
90
+ * Removes an item from the Y.Array or Y.Map at the specified path.
91
+ *
92
+ * @example
93
+ * let yArray = new Y.Array();
94
+ * yArray.push(['item1', 'item2', 'item3']);
95
+ * pullFromYPath(yArray, [1]);
96
+ * // yArray now looks like: ['item1', 'item3']
97
+ */
98
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
+ export const pullFromYPath = (entry: Y.Array<any> | Y.Map<any>, path: (string | number)[], key: string | number) => {
100
+ const yArray = getFromYPath(entry, path) as unknown[];
101
+ if (isYArray(yArray)) {
102
+ if (isIndex(key)) {
103
+ yArray.delete(key);
104
+
105
+ return;
106
+ }
107
+
108
+ const index = getYArrayIndexByArrayKey(yArray, key);
109
+ yArray.delete(index);
110
+ }
111
+ };
112
+
113
+ export interface MoveYMapToStartOptions {
114
+ yArray: Y.Array<Y.Map<unknown>>;
115
+ yMap: Y.Map<unknown>;
116
+ }
117
+
118
+ /**
119
+ * Moves a Y.Map to the start of a Y.Array.
120
+ * */
121
+ export const moveYMapToStart = ({ yArray, yMap }: MoveYMapToEndOptions) => {
122
+ const sortedArray = getSortedYArray(yArray);
123
+ const first = sortedArray[0];
124
+
125
+ const rightIndex = first ? (first.get('position') as string) : null;
126
+
127
+ yMap.set('position', generateKeyBetween(null, rightIndex));
128
+ };
129
+
130
+ export interface MoveYMapToEndOptions {
131
+ yArray: Y.Array<Y.Map<unknown>>;
132
+ yMap: Y.Map<unknown>;
133
+ }
134
+
135
+ /**
136
+ * Moves a Y.Map to the end of a Y.Array.
137
+ * */
138
+ export const moveYMapToEnd = ({ yArray, yMap }: MoveYMapToEndOptions) => {
139
+ const sortedArray = getSortedYArray(yArray);
140
+ const last = sortedArray[sortedArray.length - 1];
141
+
142
+ const leftIndex = last ? (last.get('position') as string) : null;
143
+
144
+ yMap.set('position', generateKeyBetween(leftIndex, null));
145
+ };
146
+
147
+ export interface MoveYMapBeforeOptions {
148
+ yArray: Y.Array<Y.Map<unknown>>;
149
+ yMap: Y.Map<unknown>;
150
+ relativePosition: string;
151
+ }
152
+
153
+ /**
154
+ * Moves a Y.Map before another Y.Map in a Y.Array.
155
+ * */
156
+ export const moveYMapBefore = ({
157
+ yArray,
158
+ yMap,
159
+ relativePosition,
160
+ }: MoveYMapBeforeOptions) => {
161
+ const { ref: relative, left } = getYArrayRefsByPosition(yArray, relativePosition);
162
+
163
+ const right = relative; // left - x - relative
164
+
165
+ const rightIndex = right ? (right.get('position') as string) : null;
166
+ const leftIndex = left ? (left.get('position') as string) : null;
167
+
168
+ yMap.set('position', generateKeyBetween(leftIndex, rightIndex));
169
+ };
170
+
171
+ export interface MoveYMapAfterOptions {
172
+ yArray: Y.Array<Y.Map<unknown>>;
173
+ yMap: Y.Map<unknown>;
174
+ relativePosition: string;
175
+ }
176
+
177
+ /**
178
+ * Moves a Y.Map after another Y.Map in a Y.Array.
179
+ * */
180
+ export const moveYMapAfter = ({
181
+ yArray,
182
+ yMap,
183
+ relativePosition,
184
+ }: MoveYMapAfterOptions) => {
185
+ const { ref: relative, right } = getYArrayRefsByPosition(yArray, relativePosition);
186
+
187
+ const left = relative; // relative - x - right
188
+
189
+ const rightIndex = right ? (right.get('position') as string) : null;
190
+ const leftIndex = left ? (left.get('position') as string) : null;
191
+
192
+ yMap.set('position', generateKeyBetween(leftIndex, rightIndex));
193
+ };
194
+
195
+ export interface InsertYMapToEndOptions {
196
+ yArray: Y.Array<Y.Map<unknown>>;
197
+ yMap: Y.Map<unknown>;
198
+ }
199
+
200
+ /**
201
+ * Inserts a Y.Map to the end of a Y.Array.
202
+ * */
203
+ export const insertYMapToEnd = (opts: InsertYMapToEndOptions) => {
204
+ moveYMapToEnd(opts);
205
+
206
+ // always appended, since sort order is not reliant on array order
207
+ opts.yArray.push([opts.yMap]);
208
+ };
209
+
210
+ export interface InsertYMapBeforeOptions {
211
+ yArray: Y.Array<Y.Map<unknown>>;
212
+ yMap: Y.Map<unknown>;
213
+ relativePosition: string;
214
+ }
215
+
216
+ /**
217
+ * Inserts a Y.Map before another Y.Map in a Y.Array.
218
+ * */
219
+ export const insertYMapBefore = (opts: InsertYMapBeforeOptions) => {
220
+ moveYMapBefore(opts);
221
+
222
+ // always appended, since sort order is not reliant on array order
223
+ opts.yArray.push([opts.yMap]);
224
+ };
225
+
226
+ export interface InsertYMapAfterOptions {
227
+ yArray: Y.Array<Y.Map<unknown>>;
228
+ yMap: Y.Map<unknown>;
229
+ relativePosition: string;
230
+ }
231
+
232
+ /**
233
+ * Inserts a Y.Map before another Y.Map in a Y.Array.
234
+ * */
235
+ export const insertYMapAfter = (opts: InsertYMapAfterOptions) => {
236
+ moveYMapAfter(opts);
237
+
238
+ // always appended, since sort order is not reliant on array order
239
+ opts.yArray.push([opts.yMap]);
240
+ };
@@ -0,0 +1,189 @@
1
+ import * as Y from 'yjs';
2
+
3
+ import {
4
+ isIndex, isNullOrUndefined, isString, isYArray, isYMap,
5
+ } from '@/assertions/index.js';
6
+ import { isArrayKey } from '@/paths/index.js';
7
+
8
+ import { getYArrayIndexByArrayKey } from './y-array-keys.js';
9
+
10
+ /**
11
+ * Sets a value at a given path within a Y.Map or Y.Array.
12
+ *
13
+ * @example
14
+ * let ymap = new Y.Map();
15
+ * setInYPath(ymap, ['key'], 'value');
16
+ * // ymap now looks like: { key: 'value' }
17
+ */
18
+ export const setInYPath = (ymap: Y.Map<unknown> | Y.Array<unknown>, path: (string | number)[], value: unknown): void => {
19
+ let acc: Y.Map<unknown> | Y.Array<unknown> = ymap;
20
+
21
+ for (const [i, rawKey] of path.entries()) {
22
+ const key = isArrayKey(rawKey) ? getYArrayIndexByArrayKey(acc, rawKey) : rawKey;
23
+
24
+ if (isYArray(acc) && isIndex(key)) {
25
+ const index = key;
26
+
27
+ // Last key, set it
28
+ if (i === path.length - 1) {
29
+ acc.delete(index);
30
+ acc.push([value]);
31
+
32
+ return;
33
+ }
34
+
35
+ // Key does not exist, create a container for it
36
+ if (isNullOrUndefined(acc.get(index))) {
37
+ // container can be either an object or an array depending on the next key if it exists
38
+ const nextRawKey = path[i + 1];
39
+ const nextKey = isArrayKey(nextRawKey) ? getYArrayIndexByArrayKey(acc, nextRawKey) : nextRawKey;
40
+
41
+ // if (isArrayKey(nextKey)) {
42
+ // throw Error(`Can't create empty array in "${key}" with next position key (path: ${JSON.stringify(path)})`);
43
+ // }
44
+
45
+ acc.delete(index);
46
+ acc.insert(index, [isIndex(nextKey) ? new Y.Array() : new Y.Map()]);
47
+ }
48
+
49
+ acc = acc.get(index) as Y.Map<unknown> | Y.Array<unknown>;
50
+ } else if (isYMap(acc) && isString(key)) {
51
+ // Last key, set it
52
+ if (i === path.length - 1) {
53
+ acc.set(key, value);
54
+
55
+ return;
56
+ }
57
+
58
+ // Key does not exist, create a container for it
59
+ if (!acc.has(key) || isNullOrUndefined(acc.get(key))) {
60
+ // container can be either an object or an array depending on the next key if it exists
61
+ const nextRawKey = path[i + 1];
62
+ const nextKey = isArrayKey(nextRawKey) ? getYArrayIndexByArrayKey(acc, nextRawKey) : nextRawKey;
63
+
64
+ // if (isArrayKey(nextKey)) {
65
+ // throw Error(`Can't create empty array in "${key}" with next position key "${nextKey}" (path: ${JSON.stringify(path)})`);
66
+ // }
67
+
68
+ acc.set(key, isIndex(nextKey) ? new Y.Array() : new Y.Map());
69
+ }
70
+
71
+ acc = acc.get(key) as Y.Map<unknown> | Y.Array<unknown>;
72
+ }
73
+ }
74
+ };
75
+
76
+ const yUnset = (yType: Y.Map<unknown> | Y.Array<unknown>, key: string | number) => {
77
+ if (yType instanceof Y.Array && isIndex(key)) {
78
+ yType.delete(Number(key));
79
+ } else if (yType instanceof Y.Map && isString(key)) {
80
+ yType.delete(key);
81
+ }
82
+ };
83
+
84
+ /**
85
+ * Removes a nested property from a Y.Map or Y.Array.
86
+ *
87
+ * @example
88
+ * let ymap = new Y.Map();
89
+ * ymap.set('key', 'value');
90
+ * unsetYPath(ymap, ['key']);
91
+ * // ymap now looks like: {}
92
+ */
93
+ export const unsetYPath = (yType: Y.Map<unknown> | Y.Array<unknown>, path: (string | number)[]): void => {
94
+ let acc: Y.Map<unknown> | Y.Array<unknown> = yType;
95
+ for (const [i, rawKey] of path.entries()) {
96
+ const key = isArrayKey(rawKey) ? getYArrayIndexByArrayKey(acc, rawKey) : rawKey;
97
+
98
+ // Last key, unset it
99
+ if (i === path.length - 1) {
100
+ yUnset(acc, key);
101
+ break;
102
+ }
103
+
104
+ if (acc instanceof Y.Array && isIndex(key)) {
105
+ const index = Number(key);
106
+
107
+ // Key does not exist, exit
108
+ if (isNullOrUndefined(acc.get(index))) {
109
+ break;
110
+ }
111
+
112
+ acc = acc.get(index) as Y.Map<unknown> | Y.Array<unknown>;
113
+ } else if (acc instanceof Y.Map && isString(key)) {
114
+ // Key does not exist, exit
115
+ if (!acc.has(key) || isNullOrUndefined(acc.get(key))) {
116
+ break;
117
+ }
118
+
119
+ acc = acc.get(key) as Y.Map<unknown> | Y.Array<unknown>;
120
+ }
121
+ }
122
+
123
+ // const pathValues: (unknown | Record<string, unknown>)[] = keys.map((_, idx) => {
124
+ // return getFromPath(object, keys.slice(0, idx).join('.'));
125
+ // });
126
+
127
+ // for (let i = pathValues.length - 1; i >= 0; i--) {
128
+ // if (!isEmptyContainer(pathValues[i])) {
129
+ // continue;
130
+ // }
131
+
132
+ // if (i === 0) {
133
+ // unset(object, keys[0]);
134
+ // continue;
135
+ // }
136
+
137
+ // unset(pathValues[i - 1] as Record<string, unknown>, keys[i - 1]);
138
+ // }
139
+ };
140
+
141
+ /**
142
+ * Retrieves a nested property value from a Y.Map or Y.Array.
143
+ *
144
+ * @example
145
+ * let ymap = new Y.Map();
146
+ * ymap.set('key', 'value');
147
+ * const value = getFromYPath(ymap, ['key']);
148
+ * // value is now: 'value'
149
+ */
150
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
151
+ export const getFromYPath = (entry: Y.Array<any> | Y.Map<any>, path: (string | number)[]): unknown | null => {
152
+ if (!entry) {
153
+ return;
154
+ }
155
+
156
+ if (path.length === 0) {
157
+ return entry;
158
+ }
159
+
160
+ let ref = entry as unknown;
161
+ for (const rawKey of path) {
162
+ const key = isArrayKey(rawKey) ? getYArrayIndexByArrayKey(ref, rawKey) : rawKey;
163
+
164
+ if (isYArray(ref)) {
165
+ if (!isIndex(key)) {
166
+ console.warn(`Can't use non index key in YArray ${key}: ${JSON.stringify(path)}`);
167
+
168
+ return null;
169
+ }
170
+
171
+ ref = ref.get(key);
172
+ continue;
173
+ } else if (isYMap(ref)) {
174
+ if (!isString(key)) {
175
+ console.warn(`Can't use non string key in YMap ${key}: ${JSON.stringify(path)}`);
176
+
177
+ return null;
178
+ }
179
+
180
+ ref = ref.get(key);
181
+ } else {
182
+ console.warn(`Non supported YType on ${key}: ${JSON.stringify(path)}`);
183
+
184
+ return null;
185
+ }
186
+ }
187
+
188
+ return ref;
189
+ };
@@ -0,0 +1,6 @@
1
+ export * from './array-path-ops.js';
2
+ export * from './basic-path-ops.js';
3
+ export * from './tools.js';
4
+ export * from './types.js';
5
+ export * from './y-array-keys.js';
6
+