@hamak/navigation-utils 0.4.7 → 0.4.9
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 +4 -1
- package/src/filesystem/fs-node-abstract.types.ts +0 -34
- package/src/index.ts +0 -11
- package/src/itinerary/hyper-layer-node.ts +0 -25
- package/src/itinerary/itinerary.spec.ts +0 -388
- package/src/itinerary/itinerary.ts +0 -363
- package/src/itinerary/stack.spec.ts +0 -46
- package/src/itinerary/stack.ts +0 -62
- package/src/path/pathway-resolver.ts +0 -36
- package/src/path/pathway.ts +0 -232
- package/tsconfig.es2015.json +0 -23
- package/tsconfig.json +0 -19
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hamak/navigation-utils",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Navigation utilities for path manipulation and data structure navigation",
|
|
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",
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Schema information for file content
|
|
3
|
-
*/
|
|
4
|
-
export type FileContentSchema = string | { schemaObject: string, namespace?: string }
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Base interface for filesystem nodes (without state)
|
|
8
|
-
*/
|
|
9
|
-
export interface AbstractFileSystemNodeBase {
|
|
10
|
-
type: 'directory' | 'file'
|
|
11
|
-
name: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Abstract directory node (without state management)
|
|
16
|
-
*/
|
|
17
|
-
export interface AbstractDirectoryNode<T extends AbstractFileSystemNodeBase = AbstractFileSystemNode> extends AbstractFileSystemNodeBase {
|
|
18
|
-
type: 'directory'
|
|
19
|
-
children: Record<string, T>
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Abstract file node (without state management)
|
|
24
|
-
*/
|
|
25
|
-
export interface AbstractFileNode<T = any> extends AbstractFileSystemNodeBase {
|
|
26
|
-
type: 'file'
|
|
27
|
-
content: T
|
|
28
|
-
schema: string | FileContentSchema
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Union type for abstract filesystem nodes
|
|
33
|
-
*/
|
|
34
|
-
export type AbstractFileSystemNode = AbstractDirectoryNode | AbstractFileNode
|
package/src/index.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// Path utilities
|
|
2
|
-
export * from './path/pathway';
|
|
3
|
-
export * from './path/pathway-resolver';
|
|
4
|
-
|
|
5
|
-
// Itinerary utilities
|
|
6
|
-
export * from './itinerary/itinerary';
|
|
7
|
-
export * from './itinerary/stack';
|
|
8
|
-
export * from './itinerary/hyper-layer-node';
|
|
9
|
-
|
|
10
|
-
// Filesystem abstractions
|
|
11
|
-
export * from './filesystem/fs-node-abstract.types';
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export interface HyperLayerNode<T> {
|
|
2
|
-
keys?: Record<PropertyKey, any> // Keys to use for property based navigation when applicable
|
|
3
|
-
data? : T
|
|
4
|
-
children? : Record<string, HyperLayerNode<T>> | Array<HyperLayerNode<T>>
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function hyper<T>(data : T, children?: Record<string, HyperLayerNode<T>> | Array<HyperLayerNode<T>>) : HyperLayerNode<T> {
|
|
8
|
-
return {data, children}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Utility function that create Overlay which mirror original object
|
|
13
|
-
* @param o
|
|
14
|
-
*/
|
|
15
|
-
export function hyperReflect(o : any) : HyperLayerNode<any> {
|
|
16
|
-
if(o === null || o === undefined){
|
|
17
|
-
return hyper(o)
|
|
18
|
-
} else if (Array.isArray(o)) {
|
|
19
|
-
return hyper(o, o.map(e => hyperReflect(e)))
|
|
20
|
-
} else if(typeof o === "object"){
|
|
21
|
-
return hyper(o, Object.entries(o).reduce((acc, [k, v]) => ({...acc, [k] : hyperReflect(v)}), {}))
|
|
22
|
-
} else {
|
|
23
|
-
return hyper(o)
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
areSameItineraryStep,
|
|
4
|
-
constructiveItinerary,
|
|
5
|
-
Itinerary,
|
|
6
|
-
itineraryOf,
|
|
7
|
-
itineraryOverlay,
|
|
8
|
-
itineraryToStepArray,
|
|
9
|
-
navigate,
|
|
10
|
-
navigationDepth,
|
|
11
|
-
overlayNavigator,
|
|
12
|
-
positionStep,
|
|
13
|
-
propertyStep,
|
|
14
|
-
xpathFromStack
|
|
15
|
-
} from './itinerary';
|
|
16
|
-
import stack from './stack';
|
|
17
|
-
import { hyperReflect } from './hyper-layer-node';
|
|
18
|
-
|
|
19
|
-
interface Address {
|
|
20
|
-
street: string
|
|
21
|
-
lines: string[]
|
|
22
|
-
country: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface Person {
|
|
26
|
-
name: string
|
|
27
|
-
age?: number
|
|
28
|
-
address?: Address
|
|
29
|
-
children?: Person[]
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
describe('Navigate itinerary', () => {
|
|
33
|
-
it('should work', () => {
|
|
34
|
-
const itinerary: Itinerary = {
|
|
35
|
-
"value": {
|
|
36
|
-
"type": "property",
|
|
37
|
-
"propertyName": "schema"
|
|
38
|
-
},
|
|
39
|
-
"parent": {
|
|
40
|
-
"value": {
|
|
41
|
-
"type": "position",
|
|
42
|
-
"position": 0
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const actual = navigate(sampleData(), itinerary);
|
|
48
|
-
const expected = schemaEntry1.schema
|
|
49
|
-
|
|
50
|
-
expect(actual).toBe(expected);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('Conversion to array should work', () => {
|
|
54
|
-
const path = [0, 'schema']
|
|
55
|
-
const itinerary = itineraryOf(...path)
|
|
56
|
-
const actual = itineraryToStepArray(itinerary!);
|
|
57
|
-
const expected = path.map(e => typeof e === 'string' ? propertyStep(e) : positionStep(e))
|
|
58
|
-
|
|
59
|
-
expect(actual).toStrictEqual(expected);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('Ensure path should work', () => {
|
|
63
|
-
const person: Person[] = [{
|
|
64
|
-
name: "Elon MUSK",
|
|
65
|
-
}]
|
|
66
|
-
const path = [0, 'address']
|
|
67
|
-
const itinerary = itineraryOf(...path)
|
|
68
|
-
const proto = { street: "Wall Street" } as Address;
|
|
69
|
-
const result = navigate(person, itinerary, stack.fromArray([proto]));
|
|
70
|
-
|
|
71
|
-
expect(result).toBe(proto);
|
|
72
|
-
expect(person[0].address).toBe(proto);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('Deep navigation with constructiveItinerary should work', () => {
|
|
76
|
-
const person: Person[] = [{
|
|
77
|
-
name: "Guezo",
|
|
78
|
-
children: []
|
|
79
|
-
}]
|
|
80
|
-
|
|
81
|
-
const { itinerary, prototypes } = constructiveItinerary(["children", [], 0, { name: "Glélé" }, "children", [], 0, { name: "Béhanzin" }])
|
|
82
|
-
const result = navigate(person, stack.concat(itineraryOf(0), itinerary), prototypes) as Person;
|
|
83
|
-
|
|
84
|
-
expect(person[0]?.name).toBe("Guezo");
|
|
85
|
-
expect(result.name).toBe("Béhanzin");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("Overlay navigation should work", () => {
|
|
89
|
-
const person: Person = {
|
|
90
|
-
name: "John Doe",
|
|
91
|
-
age: 40,
|
|
92
|
-
address: {
|
|
93
|
-
street: "123 Main Street",
|
|
94
|
-
lines: ["Apt 4B", "2nd Floor"],
|
|
95
|
-
country: "USA"
|
|
96
|
-
},
|
|
97
|
-
children: [
|
|
98
|
-
{
|
|
99
|
-
name: "Jane Doe",
|
|
100
|
-
age: 10,
|
|
101
|
-
address: {
|
|
102
|
-
street: "456 Maple Avenue",
|
|
103
|
-
lines: ["House 12"],
|
|
104
|
-
country: "USA"
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
name: "Jack Doe",
|
|
109
|
-
age: 8
|
|
110
|
-
}
|
|
111
|
-
]
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const personOverlay = hyperReflect(person)
|
|
115
|
-
|
|
116
|
-
const itinerary = itineraryOf("children", 0, "address", "lines", 0)
|
|
117
|
-
|
|
118
|
-
expect(navigate(person, itinerary)).toBe("House 12")
|
|
119
|
-
expect(navigate(personOverlay, itineraryOverlay(itinerary))).toBe("House 12")
|
|
120
|
-
expect(overlayNavigator.navigate(personOverlay, itinerary)?.data).toBe("House 12")
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
it("Overlay itinerary rewrite should work", () => {
|
|
124
|
-
const toOverlay = (...path: (string | number)[]) => {
|
|
125
|
-
return xpathFromStack(itineraryOverlay(itineraryOf(...path)))
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
expect(toOverlay()).toBe("/data")
|
|
129
|
-
expect(toOverlay("name")).toBe("/children/name/data")
|
|
130
|
-
expect(toOverlay("persons", 0)).toBe("/children/persons/children/*[1]/data")
|
|
131
|
-
})
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
const schemaEntry1 = {
|
|
135
|
-
"id": "schema:User",
|
|
136
|
-
"schema": {
|
|
137
|
-
"schemaNodeType": "object",
|
|
138
|
-
"operator": false,
|
|
139
|
-
"facets": {},
|
|
140
|
-
"properties": [
|
|
141
|
-
{
|
|
142
|
-
"uuid": "172302fe-29de-462a-9e27-053463b6d118",
|
|
143
|
-
"schemaNodeType": "property",
|
|
144
|
-
"facets": {},
|
|
145
|
-
"name": "uuid",
|
|
146
|
-
"valueSchema": {
|
|
147
|
-
"schemaNodeType": "scalar",
|
|
148
|
-
"operator": false,
|
|
149
|
-
"facets": {},
|
|
150
|
-
"type": "string"
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
"uuid": "733675e6-543e-4013-a071-a9a45145321b",
|
|
155
|
-
"schemaNodeType": "property",
|
|
156
|
-
"facets": {},
|
|
157
|
-
"name": "name",
|
|
158
|
-
"valueSchema": {
|
|
159
|
-
"schemaNodeType": "scalar",
|
|
160
|
-
"operator": false,
|
|
161
|
-
"facets": {},
|
|
162
|
-
"type": "string"
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
"uuid": "8376b829-459a-4b40-b2f9-71a6aea506e5",
|
|
167
|
-
"schemaNodeType": "property",
|
|
168
|
-
"facets": {},
|
|
169
|
-
"name": "firstName",
|
|
170
|
-
"valueSchema": {
|
|
171
|
-
"schemaNodeType": "scalar",
|
|
172
|
-
"operator": false,
|
|
173
|
-
"facets": {},
|
|
174
|
-
"type": "string"
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
"uuid": "3c39bfc4-90ae-4525-9366-47c7866265f3",
|
|
179
|
-
"schemaNodeType": "property",
|
|
180
|
-
"facets": {},
|
|
181
|
-
"name": "lastName",
|
|
182
|
-
"valueSchema": {
|
|
183
|
-
"schemaNodeType": "scalar",
|
|
184
|
-
"operator": false,
|
|
185
|
-
"facets": {},
|
|
186
|
-
"type": "string"
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
"uuid": "a5f05908-8a9f-4879-8dc9-f5214a6aa693",
|
|
191
|
-
"schemaNodeType": "property",
|
|
192
|
-
"facets": {},
|
|
193
|
-
"name": "age",
|
|
194
|
-
"valueSchema": {
|
|
195
|
-
"schemaNodeType": "scalar",
|
|
196
|
-
"operator": false,
|
|
197
|
-
"facets": {},
|
|
198
|
-
"type": "integer"
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
]
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
function sampleData(): any {
|
|
206
|
-
return [
|
|
207
|
-
schemaEntry1,
|
|
208
|
-
{
|
|
209
|
-
"id": "schema:Car",
|
|
210
|
-
"schema": {
|
|
211
|
-
"schemaNodeType": "object",
|
|
212
|
-
"operator": false,
|
|
213
|
-
"facets": {},
|
|
214
|
-
"properties": [
|
|
215
|
-
{
|
|
216
|
-
"uuid": "50fa33fe-03b4-45f2-b5df-da00e742bb89",
|
|
217
|
-
"schemaNodeType": "property",
|
|
218
|
-
"facets": {},
|
|
219
|
-
"name": "uuid",
|
|
220
|
-
"valueSchema": {
|
|
221
|
-
"schemaNodeType": "scalar",
|
|
222
|
-
"operator": false,
|
|
223
|
-
"facets": {},
|
|
224
|
-
"type": "string"
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
"uuid": "6ff05784-dde5-4a70-a57b-d93cba8afb00",
|
|
229
|
-
"schemaNodeType": "property",
|
|
230
|
-
"facets": {},
|
|
231
|
-
"name": "name",
|
|
232
|
-
"valueSchema": {
|
|
233
|
-
"schemaNodeType": "scalar",
|
|
234
|
-
"operator": false,
|
|
235
|
-
"facets": {},
|
|
236
|
-
"type": "string"
|
|
237
|
-
}
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
"uuid": "a8dae1ce-a82f-46f4-a2a0-4fc177988e3c",
|
|
241
|
-
"schemaNodeType": "property",
|
|
242
|
-
"facets": {},
|
|
243
|
-
"name": "brand",
|
|
244
|
-
"valueSchema": {
|
|
245
|
-
"schemaNodeType": "scalar",
|
|
246
|
-
"operator": false,
|
|
247
|
-
"facets": {},
|
|
248
|
-
"type": "string"
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
"uuid": "85342429-673f-420e-b0d0-7e019e7bea2a",
|
|
253
|
-
"schemaNodeType": "property",
|
|
254
|
-
"facets": {},
|
|
255
|
-
"name": "color",
|
|
256
|
-
"valueSchema": {
|
|
257
|
-
"schemaNodeType": "scalar",
|
|
258
|
-
"operator": false,
|
|
259
|
-
"facets": {},
|
|
260
|
-
"type": "string"
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
]
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
]
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Unit tests for areSameItineraryStep
|
|
270
|
-
describe('areSameItineraryStep', () => {
|
|
271
|
-
it('returns true for two position steps with the same position', () => {
|
|
272
|
-
expect(
|
|
273
|
-
areSameItineraryStep(positionStep(3), positionStep(3))
|
|
274
|
-
).toBe(true);
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it('returns false for two position steps with different positions', () => {
|
|
278
|
-
expect(
|
|
279
|
-
areSameItineraryStep(
|
|
280
|
-
positionStep(2),
|
|
281
|
-
positionStep(5)
|
|
282
|
-
)
|
|
283
|
-
).toBe(false);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it('returns true for two property steps with the same propertyName', () => {
|
|
287
|
-
expect(
|
|
288
|
-
areSameItineraryStep(propertyStep('foo'), propertyStep('foo'))
|
|
289
|
-
).toBe(true);
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
it('returns false for two property steps with different propertyNames', () => {
|
|
293
|
-
expect(
|
|
294
|
-
areSameItineraryStep(
|
|
295
|
-
propertyStep('foo'),
|
|
296
|
-
propertyStep('bar')
|
|
297
|
-
)
|
|
298
|
-
).toBe(false);
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
it('returns false for steps of different types', () => {
|
|
302
|
-
expect(
|
|
303
|
-
areSameItineraryStep(
|
|
304
|
-
positionStep(1),
|
|
305
|
-
propertyStep('1')
|
|
306
|
-
)
|
|
307
|
-
).toBe(false);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it('returns false if one or both steps are null or undefined', () => {
|
|
311
|
-
expect(
|
|
312
|
-
areSameItineraryStep(
|
|
313
|
-
positionStep(1),
|
|
314
|
-
null as any
|
|
315
|
-
)
|
|
316
|
-
).toBe(false);
|
|
317
|
-
expect(
|
|
318
|
-
areSameItineraryStep(
|
|
319
|
-
undefined as any,
|
|
320
|
-
propertyStep('foo')
|
|
321
|
-
)
|
|
322
|
-
).toBe(false);
|
|
323
|
-
expect(
|
|
324
|
-
areSameItineraryStep(
|
|
325
|
-
undefined as any,
|
|
326
|
-
undefined as any
|
|
327
|
-
)
|
|
328
|
-
).toBe(false);
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
describe('navigationDepth with itineraryOf', () => {
|
|
333
|
-
it('should return root when itinerary is undefined', () => {
|
|
334
|
-
const obj = { a: 1 };
|
|
335
|
-
const result = navigationDepth(obj, undefined);
|
|
336
|
-
expect(result.value).toBe(obj);
|
|
337
|
-
expect(result.path).toBe('');
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
it('should navigate fully when path exists', () => {
|
|
341
|
-
const obj = { a: { b: { c: 42 } } };
|
|
342
|
-
const itinerary = itineraryOf('a', 'b', 'c');
|
|
343
|
-
|
|
344
|
-
const result = navigationDepth(obj, itinerary);
|
|
345
|
-
expect(result.value).toBe(42);
|
|
346
|
-
expect(result.path).toBe('/a/b/c');
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it('should stop at last defined property (null)', () => {
|
|
350
|
-
const obj = { a: { b: null } };
|
|
351
|
-
const itinerary = itineraryOf('a', 'b', 'c');
|
|
352
|
-
|
|
353
|
-
const result = navigationDepth(obj, itinerary);
|
|
354
|
-
expect(result.value).toEqual({ b: null });
|
|
355
|
-
expect(result.path).toBe('/a/b');
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
it('should stop when root is undefined', () => {
|
|
359
|
-
const result = navigationDepth(undefined, itineraryOf('a', 'b'));
|
|
360
|
-
expect(result.value).toBeUndefined();
|
|
361
|
-
expect(result.path).toBe('');
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it('should navigate inside array with position step', () => {
|
|
365
|
-
const obj = { list: [{ id: 1 }, { id: 2 }] };
|
|
366
|
-
const itinerary = itineraryOf('list', 1);
|
|
367
|
-
|
|
368
|
-
const result = navigationDepth(obj, itinerary);
|
|
369
|
-
expect(result.value).toEqual({ id: 2 });
|
|
370
|
-
expect(result.path).toBe('/list/*[2]');
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
it('should stop when lookup fails', () => {
|
|
374
|
-
const obj = { list: [{ id: 1 }, { id: 2 }] };
|
|
375
|
-
const lookupStep = {
|
|
376
|
-
type: 'lookup' as const,
|
|
377
|
-
keys: [{ propertyName: 'id', propertyValue: 3 }]
|
|
378
|
-
};
|
|
379
|
-
const itinerary = {
|
|
380
|
-
value: lookupStep,
|
|
381
|
-
parent: itineraryOf('list')
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
const result = navigationDepth(obj, itinerary);
|
|
385
|
-
expect(result.value).toEqual(obj.list);
|
|
386
|
-
expect(result.path).toBe('/list');
|
|
387
|
-
});
|
|
388
|
-
});
|
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
import stack, {StackElement} from "./stack";
|
|
2
|
-
import {HyperLayerNode} from "./hyper-layer-node";
|
|
3
|
-
|
|
4
|
-
export type ObjectId = string | number
|
|
5
|
-
|
|
6
|
-
interface PropertyValuePair<P = string,V = any> {
|
|
7
|
-
propertyName : P
|
|
8
|
-
propertyValue : V
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface ItineraryStepBase {
|
|
12
|
-
type : "property" | "lookup" | "position"
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface PropertyStep extends ItineraryStepBase{
|
|
16
|
-
type : "property"
|
|
17
|
-
propertyName : string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface LookupStep extends ItineraryStepBase{
|
|
21
|
-
type : "lookup",
|
|
22
|
-
keys : PropertyValuePair[]
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface PositionStep extends ItineraryStepBase{
|
|
26
|
-
type : "position",
|
|
27
|
-
position : number
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type ItineraryStep = PropertyStep | LookupStep | PositionStep
|
|
31
|
-
|
|
32
|
-
export type Itinerary = StackElement<ItineraryStep>
|
|
33
|
-
|
|
34
|
-
export interface ConstructiveItinerary {
|
|
35
|
-
itinerary : Itinerary | undefined
|
|
36
|
-
prototypes : StackElement<any> | undefined
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export type StepValueNodeType = Array<any> | Record<string | number, any>
|
|
40
|
-
|
|
41
|
-
export function constructiveItinerary(steps : Array<string | number | StepValueNodeType>) : ConstructiveItinerary {
|
|
42
|
-
const segments : Array<string | number> = []
|
|
43
|
-
const prototypes : Array<any> = []
|
|
44
|
-
|
|
45
|
-
for (let i = 0; i < steps.length; i+=2) {
|
|
46
|
-
const segment = steps[i]
|
|
47
|
-
const prototype = steps[i+1]
|
|
48
|
-
|
|
49
|
-
switch (typeof segment) {
|
|
50
|
-
case "string":
|
|
51
|
-
case "number":{
|
|
52
|
-
const step = typeof segment === "number" ? positionStep(segment) : propertyStep(segment)
|
|
53
|
-
switch (typeof prototype) {
|
|
54
|
-
case "object": { // including Array
|
|
55
|
-
segments.push(segment)
|
|
56
|
-
prototypes.push(prototype)
|
|
57
|
-
} break
|
|
58
|
-
default: {
|
|
59
|
-
throw new Error(`Expecting array or object but got : '${typeof prototype}'`)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
} break
|
|
63
|
-
default: {
|
|
64
|
-
throw new Error(`Expecting string or number but gor : '${typeof segment}'`)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
itinerary: itineraryOf(...segments),
|
|
71
|
-
prototypes: stack.fromArray(prototypes)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function areSameItineraryStep(step1 : ItineraryStep | null | undefined, step2: ItineraryStep | null | undefined) : boolean {
|
|
76
|
-
if(step1 == undefined || step2 == undefined){
|
|
77
|
-
return false
|
|
78
|
-
} else if(step1.type !== step2.type){
|
|
79
|
-
return false
|
|
80
|
-
} else {
|
|
81
|
-
switch (step1.type) {
|
|
82
|
-
case "property": return step2.type === "property" && step1.propertyName === step2.propertyName
|
|
83
|
-
case "position": return step2.type === "position" && step1.position === step2.position
|
|
84
|
-
case "lookup": {
|
|
85
|
-
return step2.type === "lookup"
|
|
86
|
-
&& step1.keys?.length === step2.keys?.length
|
|
87
|
-
&& step1.keys?.every(
|
|
88
|
-
pv1 => step2.keys.some(
|
|
89
|
-
pv2 => areSamePropertyValuePair(pv1, pv2)
|
|
90
|
-
)
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function areSamePropertyValuePair(p1: PropertyValuePair, p2: PropertyValuePair) {
|
|
98
|
-
return p1.propertyName === p2.propertyName
|
|
99
|
-
&& p1.propertyValue === p2.propertyValue
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function itineraryOf(... args : (string | number)[]) : Itinerary | undefined{
|
|
103
|
-
return args.reduce((parent, e) => {
|
|
104
|
-
const value = typeof e === 'string' ? propertyStep(e) : positionStep(e)
|
|
105
|
-
return {parent, value}
|
|
106
|
-
}, undefined as StackElement<ItineraryStep> | undefined)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const dataStep = propertyStep("data")
|
|
110
|
-
const childrenStep = propertyStep("children")
|
|
111
|
-
|
|
112
|
-
export function itineraryOverlay(it : Itinerary | undefined) : Itinerary {
|
|
113
|
-
return {value:dataStep, parent:itineraryOverlayInner(it)}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function itineraryOverlayInner(it : Itinerary | undefined) : Itinerary | undefined{
|
|
117
|
-
if(it === undefined){
|
|
118
|
-
return undefined
|
|
119
|
-
} else {
|
|
120
|
-
const {value, parent} = it
|
|
121
|
-
return {value, parent:{value:childrenStep, parent: itineraryOverlayInner(parent)}}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function itineraryToStepArray(itinerary :Itinerary | undefined) : ItineraryStep[] {
|
|
126
|
-
let itn : Itinerary | undefined = itinerary
|
|
127
|
-
const result : ItineraryStep[] = []
|
|
128
|
-
while (itn !== undefined){
|
|
129
|
-
result.push(itn.value)
|
|
130
|
-
itn = itn.parent
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return result.reverse()
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function propertyStep(propertyName: string) : PropertyStep {
|
|
137
|
-
return {type:"property", propertyName}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function positionStep(position: number) : PositionStep {
|
|
141
|
-
return {type:"position", position}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function lookupStep(criteria: Record<string, any>) : LookupStep {
|
|
145
|
-
return {type:"lookup", keys:Object.entries(criteria).map(([k, v]) => ({propertyName:k, propertyValue:v}))}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function xpathFromStack(path?: StackElement<ItineraryStep>) : string{
|
|
149
|
-
if(path === undefined){
|
|
150
|
-
return '' // TODO may need to return '.' here, to be checked
|
|
151
|
-
}else{
|
|
152
|
-
const {value, parent} = path
|
|
153
|
-
const parentXPath = xpathFromStack(parent)
|
|
154
|
-
|
|
155
|
-
switch(value.type){
|
|
156
|
-
case 'property' : {
|
|
157
|
-
return `${parentXPath}/${value.propertyName}`
|
|
158
|
-
}
|
|
159
|
-
case 'position' : {
|
|
160
|
-
return `${parentXPath}/*[${value.position + 1}]`
|
|
161
|
-
}
|
|
162
|
-
case 'lookup' : {
|
|
163
|
-
const predicate = value.keys.map(({propertyName,propertyValue}) => `${propertyName}=$${propertyName}`).join(' and ')
|
|
164
|
-
return `${parentXPath}/*[${predicate}]`
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export function navigate(from: any, itinerary : Itinerary | undefined, prototype? : StackElement<any>) : any {
|
|
172
|
-
if(from === undefined){
|
|
173
|
-
return undefined
|
|
174
|
-
}
|
|
175
|
-
if(itinerary === undefined){
|
|
176
|
-
return from
|
|
177
|
-
}
|
|
178
|
-
const {parent, value : step} = itinerary
|
|
179
|
-
|
|
180
|
-
const current = navigate(from, parent, prototype?.parent)
|
|
181
|
-
|
|
182
|
-
return navigateStep(current, step, prototype?.value)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export function navigateStep(from: any, step : ItineraryStep, prototype? : any) : any {
|
|
186
|
-
if(from === undefined){
|
|
187
|
-
return undefined
|
|
188
|
-
}
|
|
189
|
-
if(step === undefined){
|
|
190
|
-
return from
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if(Array.isArray(from)){
|
|
194
|
-
if(step.type === 'lookup'){
|
|
195
|
-
const stp = step
|
|
196
|
-
const result = from.find(e => e !== undefined && stp.keys.every(({propertyName, propertyValue}) => e[propertyName] === propertyValue))
|
|
197
|
-
if (result === undefined && prototype !== undefined){
|
|
198
|
-
from.push(prototype)
|
|
199
|
-
return prototype
|
|
200
|
-
} else {
|
|
201
|
-
return result
|
|
202
|
-
}
|
|
203
|
-
}else if(step.type === 'position'){
|
|
204
|
-
const result = from[step.position];
|
|
205
|
-
if (result === undefined && prototype !== undefined){
|
|
206
|
-
from[step.position] = prototype
|
|
207
|
-
return prototype
|
|
208
|
-
} else {
|
|
209
|
-
return result
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}else if(typeof from === 'object'){
|
|
213
|
-
if(step.type === 'property'){
|
|
214
|
-
const result = from === null ? undefined : from[step.propertyName];
|
|
215
|
-
if(result === undefined && prototype !== undefined && from !== null && from !== undefined){
|
|
216
|
-
from[step.propertyName] = prototype
|
|
217
|
-
return prototype
|
|
218
|
-
} else {
|
|
219
|
-
return result
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return undefined
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export function navigationDepth(
|
|
228
|
-
from: any,
|
|
229
|
-
itinerary: Itinerary | undefined,
|
|
230
|
-
originalPath: string = xpathFromStack(itinerary)
|
|
231
|
-
): { value: any; path: string; originalPath: string } {
|
|
232
|
-
if (from === undefined || from === null) {
|
|
233
|
-
return { value: undefined, path: '', originalPath };
|
|
234
|
-
}
|
|
235
|
-
if (itinerary === undefined) {
|
|
236
|
-
return { value: from, path: '', originalPath };
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const { parent, value: step } = itinerary;
|
|
240
|
-
const current = navigationDepth(from, parent, originalPath);
|
|
241
|
-
|
|
242
|
-
if (current.value === undefined || current.value === null) {
|
|
243
|
-
return current;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const next = navigateStep(current.value, step);
|
|
247
|
-
|
|
248
|
-
if (next === undefined || next === null) {
|
|
249
|
-
return { value: current.value, path: xpathFromStack(parent), originalPath };
|
|
250
|
-
} else {
|
|
251
|
-
return { value: next, path: xpathFromStack(itinerary), originalPath };
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
class OverlayNavigator {
|
|
256
|
-
|
|
257
|
-
navigate<T>(from: HyperLayerNode<T>, itinerary : Itinerary | undefined) : HyperLayerNode<T> | undefined{
|
|
258
|
-
if(from === undefined){
|
|
259
|
-
return undefined
|
|
260
|
-
}
|
|
261
|
-
if(itinerary === undefined){
|
|
262
|
-
return from
|
|
263
|
-
}
|
|
264
|
-
const {parent, value : step} = itinerary
|
|
265
|
-
|
|
266
|
-
const current = this.navigate(from, parent)
|
|
267
|
-
|
|
268
|
-
return current === undefined ? undefined : this.navigateStep(current, step)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
protected navigateStep<T>(from: HyperLayerNode<T>, step : ItineraryStep) : HyperLayerNode<T> | undefined {
|
|
272
|
-
if(from === undefined){
|
|
273
|
-
return undefined
|
|
274
|
-
}
|
|
275
|
-
if(step === undefined){
|
|
276
|
-
return from
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if(Array.isArray(from.children)){
|
|
280
|
-
if(step.type === 'lookup'){
|
|
281
|
-
const stp = step
|
|
282
|
-
const result = from.children.find(e => e !== undefined && stp.keys.every(({propertyName, propertyValue}) => e.keys?.[propertyName] === propertyValue))
|
|
283
|
-
return result
|
|
284
|
-
}else if(step.type === 'position'){
|
|
285
|
-
const result = from.children[step.position];
|
|
286
|
-
return result
|
|
287
|
-
}
|
|
288
|
-
}else if(from.children !== null && typeof from.children === 'object'){
|
|
289
|
-
if(step.type === 'property'){
|
|
290
|
-
const result = from === null ? undefined : from.children[step.propertyName];
|
|
291
|
-
return result
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return undefined
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export const overlayNavigator = new OverlayNavigator()
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Same as {navigate} but try to path missing data node using prototype parameter
|
|
304
|
-
* @param from
|
|
305
|
-
* @param itinerary
|
|
306
|
-
* @param prototype
|
|
307
|
-
*/
|
|
308
|
-
export function ensurePath(from: any, itinerary : Itinerary | undefined, prototype? : any) : any {
|
|
309
|
-
return navigate(from, itinerary, prototype)
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
interface PointerBase {
|
|
313
|
-
type : "absolute" | "relative"
|
|
314
|
-
itinerary : ItineraryStep[]
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export interface AbsolutePointer extends PointerBase {
|
|
318
|
-
type : "absolute"
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export interface RelativePointer extends PointerBase {
|
|
322
|
-
type : "relative"
|
|
323
|
-
originUuid : ObjectId
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
abstract class AbstractPointerBuilder {
|
|
327
|
-
readonly itinerary : ItineraryStep[] = []
|
|
328
|
-
public child(propertyName : string){
|
|
329
|
-
this.itinerary.push({type:"property", propertyName})
|
|
330
|
-
return this
|
|
331
|
-
}
|
|
332
|
-
public lookup(...criterion : [string, any]){
|
|
333
|
-
criterion.forEach(([propertyName, propertyValue]) => {
|
|
334
|
-
this.itinerary.push({type:"lookup", keys:[{propertyName, propertyValue}]})
|
|
335
|
-
})
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
class RelativePointerBuilder extends AbstractPointerBuilder implements RelativePointer {
|
|
340
|
-
readonly type = "relative"
|
|
341
|
-
public constructor(public originUuid : ObjectId){
|
|
342
|
-
super()
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
class AbsolutePointerBuilder extends AbstractPointerBuilder implements AbsolutePointer {
|
|
347
|
-
readonly type = "absolute"
|
|
348
|
-
public constructor(){
|
|
349
|
-
super()
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
type Pointer = AbsolutePointer | RelativePointer
|
|
354
|
-
|
|
355
|
-
export function pointer(fromId?: ObjectId) : AbsolutePointerBuilder | RelativePointerBuilder {
|
|
356
|
-
if(fromId === undefined){
|
|
357
|
-
return new AbsolutePointerBuilder()
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
return new RelativePointerBuilder(fromId)
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
export const ROOT : AbsolutePointer = Object.freeze({type:"absolute", itinerary:[]})
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import stack from "./stack";
|
|
2
|
-
import { describe, it, expect } from 'vitest';
|
|
3
|
-
|
|
4
|
-
describe("Core util stack", () => {
|
|
5
|
-
it("fromArray should work", () => {
|
|
6
|
-
const itinerary = stack.fromArray([1, 2, 3, 4])
|
|
7
|
-
|
|
8
|
-
expect(itinerary?.value).toBe(4)
|
|
9
|
-
expect(itinerary?.parent?.parent?.parent?.value).toBe(1)
|
|
10
|
-
|
|
11
|
-
expect(stack.fromArray([])).toBe(undefined)
|
|
12
|
-
expect(stack.fromArray(undefined)).toBe(undefined)
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it("reduce should work", () => {
|
|
16
|
-
const itinerary = stack.fromArray([1, 2, 3, 4])
|
|
17
|
-
|
|
18
|
-
const result = stack.reduce(itinerary, (acc, t) => acc + t, 0);
|
|
19
|
-
expect(result).toBe(10)
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it("reduce order should work", () => {
|
|
23
|
-
const itinerary = stack.fromArray([1, 2, 3, 4])
|
|
24
|
-
|
|
25
|
-
const result = stack.reduce(itinerary, (acc, t) => [...acc, t], [] as number[]);
|
|
26
|
-
expect(result).toStrictEqual([1, 2, 3, 4])
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it("equal should work", () => {
|
|
30
|
-
expect(stack.equal(undefined, undefined)).toBe(false)
|
|
31
|
-
expect(stack.equal(null, null)).toBe(false)
|
|
32
|
-
expect(stack.equal(undefined, null)).toBe(false)
|
|
33
|
-
expect(stack.equal(null, undefined)).toBe(false)
|
|
34
|
-
|
|
35
|
-
expect(stack.equal(stack.fromArray([1, 2, 3, 4]), undefined)).toBe(false)
|
|
36
|
-
expect(stack.equal(stack.fromArray([1, 2, 3, 4]), null)).toBe(false)
|
|
37
|
-
|
|
38
|
-
expect(stack.equal(undefined, stack.fromArray([1, 2, 3, 4]))).toBe(false)
|
|
39
|
-
expect(stack.equal(null, stack.fromArray([1, 2, 3, 4]))).toBe(false)
|
|
40
|
-
|
|
41
|
-
expect(stack.equal(stack.fromArray([1]), stack.fromArray([2]))).toBe(false)
|
|
42
|
-
expect(stack.equal(stack.fromArray([1]), stack.fromArray([1, 2]))).toBe(false)
|
|
43
|
-
|
|
44
|
-
expect(stack.equal(stack.fromArray([1, 2, 3, 4]), stack.fromArray([1, 2, 3, 4]))).toBe(true)
|
|
45
|
-
})
|
|
46
|
-
})
|
package/src/itinerary/stack.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
export interface StackElement<T> {
|
|
2
|
-
value : T
|
|
3
|
-
parent? : StackElement<T>
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export function head<T>(value : T) : StackElement<T> {
|
|
7
|
-
return {value}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function push<T>(head : StackElement<T> | undefined, value : T) : StackElement<T> {
|
|
11
|
-
return {value, parent: head}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function concat<T>(tail : StackElement<T> | undefined, stack : StackElement<T> | undefined) : StackElement<T> | undefined{
|
|
15
|
-
if(tail === undefined) return stack
|
|
16
|
-
if(stack === undefined) return tail
|
|
17
|
-
|
|
18
|
-
const {parent, value} = stack
|
|
19
|
-
|
|
20
|
-
return {value, parent:concat(tail, parent)}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function fromArray<T = any>(t : T[] | undefined) : StackElement<T> | undefined {
|
|
24
|
-
return t?.reduce((acc, t) => push(acc, t), undefined as undefined | StackElement<T>)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function reduce<T, R>(head : StackElement<T> | undefined, reducer : (r: R, t : T) => R, initial : R) : R {
|
|
28
|
-
if(head === undefined){
|
|
29
|
-
return initial
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const {parent, value} = head
|
|
33
|
-
const acc = reduce(parent, reducer, initial)
|
|
34
|
-
return reducer(acc, value)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function equal<T>(a:StackElement<T> | null | undefined, b : StackElement<T> | null | undefined) : boolean {
|
|
38
|
-
// Not equal if both are nullish
|
|
39
|
-
if( a === undefined || a === null || b === undefined || b === null ){
|
|
40
|
-
return false
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
while (a !== undefined && b !== undefined){
|
|
44
|
-
if(a.value !== b.value){
|
|
45
|
-
return false
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
a = a.parent
|
|
49
|
-
b = b.parent
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Equal if traversed all stack at both side
|
|
53
|
-
if((a === undefined || a === null) && (b === undefined || b === null) ){
|
|
54
|
-
return true
|
|
55
|
-
} else {
|
|
56
|
-
return false
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const stack = {push, head, concat, reduce, fromArray, equal}
|
|
61
|
-
|
|
62
|
-
export default stack;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import {Path, Pathway} from "./pathway";
|
|
2
|
-
|
|
3
|
-
export interface PathwayResolver<T>{
|
|
4
|
-
resolve(path : string | string[] | Pathway) : T | undefined
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export class RecordPathwayResolver<T> implements PathwayResolver<T> {
|
|
8
|
-
constructor(readonly record : Record<string, T>) {
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
resolve(path: string | string[] | Pathway): T | undefined {
|
|
12
|
-
const key = Pathway.asString(path)
|
|
13
|
-
return this.record[key]
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class EmptyPathwayResolver<T> implements PathwayResolver<T> {
|
|
18
|
-
resolve(path: string | string[] | Pathway): T | undefined {
|
|
19
|
-
return undefined
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export class RelativePathwayResolver<T> implements PathwayResolver<T> {
|
|
24
|
-
private relativePathway : Pathway
|
|
25
|
-
constructor(readonly parentResolver : PathwayResolver<T>, readonly path : Path) {
|
|
26
|
-
this.relativePathway = Pathway.of(path)
|
|
27
|
-
}
|
|
28
|
-
resolve(path: string | string[] | Pathway): T | undefined {
|
|
29
|
-
const pathway = Pathway.of(path)
|
|
30
|
-
if(pathway.isAbsolute()){
|
|
31
|
-
return this.parentResolver.resolve(path)
|
|
32
|
-
} else {
|
|
33
|
-
return this.parentResolver.resolve(this.relativePathway.resolve(pathway))
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
package/src/path/pathway.ts
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
export type Path = string | string[] | Pathway
|
|
2
|
-
/**
|
|
3
|
-
* Class representing a normalized pathway.
|
|
4
|
-
*/
|
|
5
|
-
export class Pathway {
|
|
6
|
-
private segments: string[];
|
|
7
|
-
private isAbsolutePath: boolean;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Creates a Pathway instance.
|
|
11
|
-
* @param path - The pathway to normalize, either as a string or an array of strings.
|
|
12
|
-
* An empty array or "." represents the current directory.
|
|
13
|
-
*/
|
|
14
|
-
constructor(path: string | string[] | null | undefined) {
|
|
15
|
-
if (path == null || path === "." || (Array.isArray(path) && path.length === 0)) {
|
|
16
|
-
this.segments = [];
|
|
17
|
-
this.isAbsolutePath = false;
|
|
18
|
-
} else {
|
|
19
|
-
this.isAbsolutePath = Array.isArray(path) ? path[0].startsWith('/') : path.startsWith('/');
|
|
20
|
-
this.segments = this.normalizePath(path);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Factory method to create a Pathway instance.
|
|
26
|
-
* @param path - The pathway to normalize, either as a string or an array of strings.
|
|
27
|
-
* @returns A new Pathway instance.
|
|
28
|
-
*/
|
|
29
|
-
public static of(path: string | string[] | null | undefined | Pathway): Pathway {
|
|
30
|
-
if(path !== undefined && path !== null && typeof path === "object" && !Array.isArray(path)) {
|
|
31
|
-
return path
|
|
32
|
-
} else {
|
|
33
|
-
return new Pathway(path);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Normalizes a pathway by converting it into an array of segments.
|
|
39
|
-
* @param path - The pathway to normalize, either as a string or an array of strings.
|
|
40
|
-
* @returns An array of normalized path segments, or an empty array if the input is null or undefined.
|
|
41
|
-
*/
|
|
42
|
-
private normalizePath(path: string | string[] | null | undefined): string[] {
|
|
43
|
-
if (path == null) {
|
|
44
|
-
return [];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const segments = Array.isArray(path) ? path : path.split('/');
|
|
48
|
-
return segments
|
|
49
|
-
.reduce((acc, segment) => {
|
|
50
|
-
if (segment === "." || segment === "") {
|
|
51
|
-
return acc;
|
|
52
|
-
}
|
|
53
|
-
return acc.concat(segment.split('/'));
|
|
54
|
-
}, [] as string[])
|
|
55
|
-
.filter(segment => segment.length > 0);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Returns the normalized path segments.
|
|
60
|
-
* @returns An array of normalized path segments.
|
|
61
|
-
*/
|
|
62
|
-
public getSegments(): string[] {
|
|
63
|
-
return this.segments;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
public isCurrentDir(){
|
|
67
|
-
return this.getSegments().length === 0
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Converts the normalized path segments back to a string.
|
|
72
|
-
* @returns The normalized path as a string.
|
|
73
|
-
*/
|
|
74
|
-
public toString(): string {
|
|
75
|
-
return this.isAbsolute() ? '/' + this.segments.join('/') : this.segments.join('/');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Checks if the pathway is absolute.
|
|
80
|
-
* @returns True if the pathway is absolute, false otherwise.
|
|
81
|
-
*/
|
|
82
|
-
public isAbsolute(): boolean {
|
|
83
|
-
return this.isAbsolutePath;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Checks if the pathway is relative.
|
|
88
|
-
* @returns True if the pathway is relative, false otherwise.
|
|
89
|
-
*/
|
|
90
|
-
public isRelative(): boolean {
|
|
91
|
-
return !this.isAbsolutePath;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Resolves the current pathway with another pathway or string.
|
|
96
|
-
* @param otherPath - The other pathway or string to resolve against.
|
|
97
|
-
* @returns A new Pathway instance representing the resolved path.
|
|
98
|
-
*/
|
|
99
|
-
public resolve(otherPath: Pathway | string | string[]): Pathway {
|
|
100
|
-
const otherSegments = otherPath instanceof Pathway ? otherPath.getSegments() : this.normalizePath(otherPath);
|
|
101
|
-
const resolvedSegments = [...this.segments];
|
|
102
|
-
|
|
103
|
-
if (otherPath instanceof Pathway ? otherPath.isAbsolute() : (Array.isArray(otherPath) ? otherPath[0].startsWith('/') : otherPath.startsWith('/'))) {
|
|
104
|
-
return new Pathway(otherSegments);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
for (const segment of otherSegments) {
|
|
108
|
-
if (segment === '..') {
|
|
109
|
-
if (resolvedSegments.length > 0 && resolvedSegments[resolvedSegments.length - 1] !== '..') {
|
|
110
|
-
resolvedSegments.pop();
|
|
111
|
-
} else {
|
|
112
|
-
resolvedSegments.push(segment);
|
|
113
|
-
}
|
|
114
|
-
} else if (segment !== '.') {
|
|
115
|
-
resolvedSegments.push(segment);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return new Pathway(this.isAbsolute() ? ["/", ...resolvedSegments] : resolvedSegments);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Gets the parent directory of the current pathway.
|
|
124
|
-
* @returns A new Pathway instance representing the parent directory.
|
|
125
|
-
*/
|
|
126
|
-
public getParent(): Pathway {
|
|
127
|
-
if (this.segments.length === 0) {
|
|
128
|
-
return new Pathway(null);
|
|
129
|
-
}
|
|
130
|
-
const parentSegments = this.segments.slice(0, -1);
|
|
131
|
-
return new Pathway(this.isAbsolute() ? ["/", ...parentSegments] : parentSegments);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Gets the current directory of the pathway.
|
|
136
|
-
* @returns The current directory as a string.
|
|
137
|
-
*/
|
|
138
|
-
public getCurrentDirectory(): string {
|
|
139
|
-
if (this.segments.length === 0) {
|
|
140
|
-
return '';
|
|
141
|
-
}
|
|
142
|
-
return this.segments[this.segments.length - 1];
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Static method to get the absolute path of the root.
|
|
147
|
-
* @returns A new Pathway instance representing the root path.
|
|
148
|
-
*/
|
|
149
|
-
public static ofRoot(): Pathway {
|
|
150
|
-
return new Pathway('/');
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Determines the relative path from this pathway to another pathway.
|
|
155
|
-
* @param otherPath - The other pathway to relativize against.
|
|
156
|
-
* @returns A new Pathway instance representing the relative path.
|
|
157
|
-
*/
|
|
158
|
-
public relativize(otherPath: Pathway | string | string[]): Pathway {
|
|
159
|
-
const otherSegments = otherPath instanceof Pathway ? otherPath.getSegments() : this.normalizePath(otherPath);
|
|
160
|
-
|
|
161
|
-
if (this.isAbsolute() !== (otherPath instanceof Pathway ? otherPath.isAbsolute() : (Array.isArray(otherPath) ? otherPath[0].startsWith('/') : otherPath.startsWith('/')))) {
|
|
162
|
-
throw new Error("Cannot relativize between absolute and relative paths");
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
let commonLength = 0;
|
|
166
|
-
const maxLength = Math.min(this.segments.length, otherSegments.length);
|
|
167
|
-
for (let i = 0; i < maxLength; i++) {
|
|
168
|
-
if (this.segments[i] !== otherSegments[i]) {
|
|
169
|
-
break;
|
|
170
|
-
}
|
|
171
|
-
commonLength++;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const upSegments = this.segments.slice(commonLength).map(() => '..');
|
|
175
|
-
const downSegments = otherSegments.slice(commonLength);
|
|
176
|
-
|
|
177
|
-
return new Pathway(upSegments.concat(downSegments));
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Checks if the given path is a descendant of the current pathway.
|
|
182
|
-
* @param otherPath - The other pathway to check.
|
|
183
|
-
* @returns True if the given path is a descendant, false otherwise.
|
|
184
|
-
*/
|
|
185
|
-
public isDescendant(otherPath: Pathway | string | string[]): boolean {
|
|
186
|
-
const otherSegments = otherPath instanceof Pathway ? otherPath.getSegments() : this.normalizePath(otherPath);
|
|
187
|
-
|
|
188
|
-
if (otherSegments.length < this.segments.length) {
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
for (let i = 0; i < this.segments.length; i++) {
|
|
193
|
-
if (this.segments[i] !== otherSegments[i]) {
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return true;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
static isRelativePath(path : Path) : boolean {
|
|
203
|
-
const pathway = typeof path === "object" && !Array.isArray(path) ? path : Pathway.of(path)
|
|
204
|
-
return pathway.isRelative()
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
static asString(path : Path) : string {
|
|
208
|
-
if(typeof path === "string"){
|
|
209
|
-
return path
|
|
210
|
-
} else {
|
|
211
|
-
const pathway = typeof path === "object" && !Array.isArray(path)? path : Pathway.of(path)
|
|
212
|
-
return pathway.toString()
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
static getName(path: string | string[] | Pathway) {
|
|
217
|
-
let name: string
|
|
218
|
-
switch (typeof path) {
|
|
219
|
-
case "string": {
|
|
220
|
-
name = path
|
|
221
|
-
}
|
|
222
|
-
break
|
|
223
|
-
case "object": {
|
|
224
|
-
const segments = Array.isArray(path) ? path : path.getSegments()
|
|
225
|
-
name = segments[segments.length - 1]
|
|
226
|
-
}
|
|
227
|
-
break
|
|
228
|
-
}
|
|
229
|
-
return name;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
}
|
package/tsconfig.es2015.json
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "./tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"target": "ES2015",
|
|
5
|
-
"lib": [
|
|
6
|
-
"ES2015"
|
|
7
|
-
],
|
|
8
|
-
"outDir": "./dist/es2015",
|
|
9
|
-
"declaration": false,
|
|
10
|
-
"declarationMap": false,
|
|
11
|
-
"sourceMap": false,
|
|
12
|
-
"downlevelIteration": true,
|
|
13
|
-
"composite": false
|
|
14
|
-
},
|
|
15
|
-
"include": [
|
|
16
|
-
"src/**/*"
|
|
17
|
-
],
|
|
18
|
-
"exclude": [
|
|
19
|
-
"node_modules",
|
|
20
|
-
"dist",
|
|
21
|
-
"**/*.test.ts"
|
|
22
|
-
]
|
|
23
|
-
}
|
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
|
-
}
|