@01-edu/shared 2.0.4 → 2.0.6

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.
@@ -1,233 +0,0 @@
1
- import { attributes, relationAttributes } from './attrs.js'
2
- import { childTypes, objectTypes } from './object-structure.ts'
3
-
4
- const normalize = str =>
5
- str
6
- .toLowerCase()
7
- .replaceAll(/[^a-z0-9]+/g, ' ')
8
- .trim()
9
- .replaceAll(' ', '-')
10
-
11
- const assertDef = def => {
12
- const {
13
- type,
14
- name,
15
- attrs,
16
- children: _children,
17
- childrenAttrs,
18
- referencePath: _referencePath,
19
- ...rest
20
- } = def
21
- const [extra] = Object.keys(rest)
22
- if (extra) {
23
- throw Error(
24
- `Unexpected property ${extra}, did you mean to define an attribute ?`,
25
- )
26
- }
27
- if (type === 'campus') throw Error(`Campuses can't be defined in definitions`)
28
- if (!objectTypes.has(type)) throw Error(`Invalid type property`)
29
- if (!name || typeof name !== 'string') throw Error(`Invalid name property`)
30
- if (!attrs || typeof attrs !== 'object' || Array.isArray(attrs)) {
31
- throw Error(`Invalid attrs property`)
32
- }
33
- if (childrenAttrs) throw Error(`childrenAttrs is no longer supported`)
34
-
35
- for (const key of Object.keys(attrs || {})) {
36
- if (relationAttributes[key]) {
37
- throw Error(
38
- `Attr ${key} should be defined in the relation with its parent, not on the object itself.`,
39
- )
40
- }
41
-
42
- const matches = attributes[key]
43
- if (!matches) throw Error(`Undefined attr ${key}`)
44
- const attrDefs = matches[type]
45
- if (!attrDefs) {
46
- const types = Object.keys(matches).join(', ')
47
- throw Error(`${type} does not match any of ${types} for attr ${key}`)
48
- }
49
-
50
- if (attrDefs.deprecated) {
51
- throw Error(`attr ${key} deprecated: ${attrDefs.deprecated}`)
52
- }
53
- }
54
- }
55
-
56
- const buildTree = (
57
- { name, type, attrs, children, referencePath },
58
- parent,
59
- depth,
60
- ) => {
61
- // As we check for the parent / child type structure, this should never happen
62
- // so we did not bother to make a more descriptive error for this case
63
- if (depth > 100) throw Error('Unexpected very deep tree, maybe circular ?')
64
- const object = { name, type, attrs, children: {}, referencePath, parent }
65
- if (!children) return object
66
- let prev
67
- for (const [key, { ref, ...rest }] of Object.entries(children)) {
68
- const child = buildTree(ref, object, (depth || 0) + 1)
69
- child.attrs = { ...child.attrs, ...rest }
70
- prev && (prev.next = child)
71
- child.prev = prev
72
- object.children[key] = child
73
- }
74
- return object
75
- }
76
-
77
- const assertRelation = (parent, key, relation) => {
78
- if (!/^[a-z0-9-]*$/.test(key)) {
79
- throw Error(
80
- `Invalid key for child ${key} Kebab-case key suggestion: ${normalize(
81
- key,
82
- )}`,
83
- )
84
- }
85
-
86
- const child = relation.ref
87
- const { type } = child
88
- const allowedTypes = childTypes[parent.type]
89
- if (!allowedTypes.includes(type)) {
90
- throw Error(`Type ${type} ${key} is not a possible child`)
91
- }
92
-
93
- // Should never happen
94
- if (parent.referencePath === child.referencePath) {
95
- throw Error(`Self reference in child ${key}`)
96
- }
97
-
98
- for (const key of Object.keys(relation)) {
99
- if (key === 'ref') continue
100
- const matches = attributes[key]
101
- if (matches?.[type]?.restrictive) {
102
- throw Error(
103
- `Attr ${key} should be defined on the object itself, not in the relation with its parent.`,
104
- )
105
- }
106
- const relMatches = relationAttributes[key]
107
- if (!matches && !relMatches) throw Error(`Undefined attr ${key}`)
108
- const attrDefs = (relMatches?.[parent.type] || matches)[type]
109
- if (!attrDefs) {
110
- const types = Object.keys(matches).join(', ')
111
- throw Error(`${type} does not match any of ${types} for attr ${key}`)
112
- }
113
-
114
- if (attrDefs.deprecated) {
115
- throw Error(`attr ${key} deprecated: ${attrDefs.deprecated}`)
116
- }
117
- }
118
- }
119
-
120
- const checkAttrs = object => {
121
- for (const [key, value] of Object.entries(object.attrs)) {
122
- try {
123
- const attrDefs =
124
- relationAttributes[key]?.[object.parent?.type]?.[object.type] ||
125
- attributes[key][object.type]
126
-
127
- if (value?.type === 'function') {
128
- const fn = attrDefs.functionsByName?.[value.name]
129
- if (!fn) throw Error(`function ${value.name} not found for attr ${key}`)
130
- } else if (!attrDefs.check(value, object)) {
131
- throw Error(`wrong type for attr ${key}`)
132
- }
133
- } catch (err) {
134
- err.parentRef = object.parent?.referencePath
135
- err.childRef = object.referencePath
136
- err.value = value
137
- err.key = key
138
- throw err
139
- }
140
- }
141
-
142
- for (const child of Object.values(object.children)) {
143
- checkAttrs(child)
144
- }
145
- }
146
-
147
- const isExam = def => def.type === 'exam'
148
- const assertExams = definitions => {
149
- const groups = {}
150
- for (const exam of definitions.filter(isExam)) {
151
- let prevExamGroup = 0
152
- for (const [key, exercise] of Object.entries(exam.children)) {
153
- try {
154
- const { group } = exercise
155
- if (!group) throw Error('missing exam group')
156
-
157
- const groupDiff = group - prevExamGroup
158
- prevExamGroup = group
159
- if (groupDiff > 1) throw Error('exam must not have gaps in groups')
160
- if (groupDiff < 0) {
161
- throw Error('exercise must be sorted by group from lower to greater')
162
- }
163
-
164
- // exam exercise group should stay consistent across all exams
165
- const prevGroup = groups[key]
166
- if (prevGroup == null) {
167
- groups[key] = group
168
- } else if (prevGroup !== group) {
169
- throw Error('Exercise used in different exam groups')
170
- }
171
- } catch (err) {
172
- err.exam = exam.referencePath
173
- err.exercise = exercise.ref.referencePath
174
- throw err
175
- }
176
- }
177
- }
178
- }
179
-
180
- export const checkAndBuildDefinitions = async readDef => {
181
- const cache = {}
182
- const getDefs = async ([key, relation]) => {
183
- let def
184
- try {
185
- const cached = cache[key]
186
- def = await (cached || (cache[key] = readDef(key)))
187
- relation && (relation.ref = def)
188
- if (cached) return [] // skip assert and children checks if already cached
189
- } catch (err) {
190
- err.name = key
191
- throw err
192
- }
193
- try {
194
- assertDef(def)
195
- } catch (err) {
196
- err.type = def.type
197
- err.referencePath = def.referencePath
198
- throw err
199
- }
200
- const relations = Object.entries(def.children || {}).map(getDefs)
201
- try {
202
- return [def, ...(await Promise.all(relations)).flat()]
203
- } catch (err) {
204
- // Some content may be used at multiple place (ex: exam exercises)
205
- // so we may have multiple parents
206
- key && (err.parents || (err.parents = [])).push(key)
207
- throw err
208
- }
209
- }
210
-
211
- const definitions = await getDefs([])
212
-
213
- // Assert all relations looks ok, possible child / parent, allowed attributes
214
- for (const def of definitions) {
215
- if (!def.children) continue
216
- try {
217
- for (const entry of Object.entries(def.children)) {
218
- assertRelation(def, entry[0], entry[1])
219
- }
220
- } catch (err) {
221
- err.type = def.type
222
- err.referencePath = def.referencePath
223
- throw err
224
- }
225
- }
226
-
227
- // Check all attributes in context
228
- const root = buildTree(definitions[0])
229
- checkAttrs(root)
230
- assertExams(definitions)
231
-
232
- return { definitions, root }
233
- }
package/event-utils.js DELETED
@@ -1,58 +0,0 @@
1
- export const SEC = 1000
2
- export const MIN = 60 * SEC
3
- export const HOUR = 60 * MIN
4
- export const DAY = 24 * HOUR
5
- export const WEEK = 7 * DAY
6
-
7
- export const numTime = date => new Date(date).getTime()
8
- export const numToISOString = number =>
9
- number ? new Date(number).toISOString() : Number.NaN
10
- export const getChildRegistrationStartAt = (parentEventStartAt, child) => {
11
- const {
12
- startAfter = 0,
13
- eventStartDelay = 0,
14
- registrationDuration,
15
- } = child.attrs
16
- return (
17
- numTime(parentEventStartAt) +
18
- (startAfter - (eventStartDelay + registrationDuration)) * MIN
19
- )
20
- }
21
- export const getChildRegistrationEndAt = (parentEventStartAt, child) => {
22
- const { startAfter = 0, eventStartDelay = 0 } = child.attrs
23
- return numTime(parentEventStartAt) + (startAfter - eventStartDelay) * MIN
24
- }
25
- export const getChildEventStartAt = (parentEventStartAt, { attrs }) =>
26
- numTime(parentEventStartAt) + (attrs.startAfter || 0) * MIN
27
-
28
- export const getChildEventEndAt = (parentEventStartAt, child) => {
29
- const { startAfter = 0, eventDuration } = child.attrs
30
- return numTime(parentEventStartAt) + (startAfter + eventDuration) * MIN
31
- }
32
-
33
- export const getChildEventInfo = (parentEventStartAt, child) => ({
34
- startAt: numToISOString(
35
- getChildRegistrationStartAt(parentEventStartAt, child),
36
- ),
37
- endAt: numToISOString(getChildRegistrationEndAt(parentEventStartAt, child)),
38
- eventJoinedAt: numToISOString(
39
- getChildEventStartAt(parentEventStartAt, child),
40
- ),
41
- eventEndAt: numToISOString(getChildEventEndAt(parentEventStartAt, child)),
42
- })
43
-
44
- // NB: useful when creating event for first time (add event)
45
- export const getEndOfEvent = ({ eventJoinedAt }, object) =>
46
- numTime(eventJoinedAt) + object.attrs.eventDuration * MIN
47
-
48
- // TODO: rename this fn - also used for project
49
- // the best would be to find a word to label the child items that are not events
50
- export const getQuestStartAt = (parentEventStartAt, quest) =>
51
- numTime(parentEventStartAt) + quest.attrs.delay
52
- export const getQuestExtraEndAt = (questStartAt, quest) => {
53
- const { scopeExtraDuration, duration } = quest.attrs
54
- return questStartAt + ((scopeExtraDuration || 0) + (duration || 1)) * DAY
55
- }
56
-
57
- // TODO: rename isPassed
58
- export const isFinished = date => new Date(date).getTime() < Date.now()
package/graph.js DELETED
@@ -1,96 +0,0 @@
1
- const isCoreObj = (contentShape, coreKey) =>
2
- typeof contentShape === 'object' && Object.keys(contentShape)[0] === coreKey
3
- const isSatteliteObj = (contentShape, satteliteKey) =>
4
- typeof contentShape === 'object' &&
5
- Object.values(contentShape)[0].some(sat => sat === satteliteKey)
6
-
7
- export const getCoreSattelites = (coreObject, objectsList) => {
8
- const { key, parent } = coreObject
9
- const objects = objectsList || Object.values(parent.children)
10
- const sattelitesSection = parent.attrs.graph.innerCircle.find(
11
- s => s.type === 'slice' && s.innerArc.contents.find(c => isCoreObj(c, key)),
12
- )
13
- if (!sattelitesSection) return []
14
- const sattelitesObject = sattelitesSection.innerArc.contents.find(c =>
15
- isCoreObj(c, key),
16
- )
17
- const sattelitesList = new Set(...Object.values(sattelitesObject))
18
-
19
- return objects.filter(o => sattelitesList.has(o.key))
20
- }
21
-
22
- export const getCoreOfSattelite = (satteliteObject, objectsList) => {
23
- const { key, parent } = satteliteObject
24
- const objects = objectsList || Object.values(parent.children)
25
- const sattelitesSection = parent.attrs.graph?.innerCircle?.find(
26
- s =>
27
- s.type === 'slice' &&
28
- s.innerArc?.contents.find(c => isSatteliteObj(c, key)),
29
- )
30
- if (!sattelitesSection) return undefined
31
- const coreObject = sattelitesSection.innerArc.contents.find(c =>
32
- isSatteliteObj(c, key),
33
- )
34
-
35
- return objects.filter(o => o.key === Object.keys(coreObject)[0])
36
- }
37
-
38
- export const flatGraphContents = graph => [
39
- ...(graph.centralPoint ? [graph.centralPoint] : []),
40
- ...(graph.innerCircle || []).flatMap(section =>
41
- section.type === 'slice'
42
- ? [
43
- ...(section.entryPoint ? [section.entryPoint] : []),
44
- ...(section.innerArc?.contents || []).flatMap(c =>
45
- typeof c === 'string' ? c : Object.entries(c)[0].flat(),
46
- ),
47
- ...(section.outerArcs || []).flatMap(arc => arc.contents || []),
48
- ]
49
- : section.contents,
50
- ),
51
- ...(graph.middleCircle || []).flatMap(arc => arc.contents || []),
52
- ...(graph.outerCircle || []).flatMap(arc => arc.contents || []),
53
- ]
54
-
55
- // WARNING: without central point (for linear graph, already destructured)
56
- export const flatAtomeAndCoreContents = graph => [
57
- ...(graph.innerCircle || []).flatMap(section =>
58
- section.type === 'slice'
59
- ? [
60
- ...(section.entryPoint ? [section.entryPoint] : []),
61
- ...(section.innerArc?.contents || []).flatMap(c =>
62
- typeof c === 'string' ? c : Object.keys(c)[0],
63
- ),
64
- ...(section.outerArcs || []).flatMap(arc => arc.contents || []),
65
- ]
66
- : section.contents,
67
- ),
68
- ...(graph.middleCircle || []).flatMap(arc => arc.contents || []),
69
- ...(graph.outerCircle || []).flatMap(arc => arc.contents || []),
70
- ]
71
-
72
- export const limitations = {
73
- LINE: {
74
- maxLinesCount: 6,
75
- maxContentsCount: 7, // 7 contents max split along a single line
76
- },
77
- SLICE: {
78
- maxSlicesCount: 4,
79
- innerCircle: {
80
- maxSubContentsCount: 5, // 5 sub-contents max by content
81
- maxContentsCount: 30, // 30 contents max spread evenly over the number of inner cirlces of slices
82
- },
83
- outerArc: {
84
- maxArcsCount: 2,
85
- maxContentsCount: 10, // 10 contents max spread evenly over the 1 or 2 outer arcs
86
- },
87
- },
88
- MIDDLE_CIRCLE: {
89
- maxArcsCount: 8,
90
- maxContentsCount: 70, // 70 contents max spread evenly over the number of arcs
91
- },
92
- OUTER_CIRCLE: {
93
- maxArcsCount: 9,
94
- maxContentsCount: 90, // 90 contents max spread evenly over the number of arcs
95
- },
96
- }
package/languages.js DELETED
@@ -1,147 +0,0 @@
1
- export const languages = {
2
- ab: 'Abkhazian',
3
- aa: 'Afar',
4
- af: 'Afrikaans',
5
- sq: 'Albanian',
6
- am: 'Amharic',
7
- ar: 'Arabic',
8
- hy: 'Armenian',
9
- as: 'Assamese',
10
- ay: 'Aymara',
11
- az: 'Azerbaijani',
12
- ba: 'Bashkir',
13
- eu: 'Basque',
14
- bn: 'Bengali (Bangla)',
15
- dz: 'Bhutani',
16
- bh: 'Bihari',
17
- bi: 'Bislama',
18
- br: 'Breton',
19
- bg: 'Bulgarian',
20
- my: 'Burmese',
21
- be: 'Byelorussian (Belarusian)',
22
- km: 'Cambodian',
23
- ca: 'Catalan',
24
- zh: 'Chinese (Simplified)',
25
- // zh: 'Chinese (Traditional)',
26
- co: 'Corsican',
27
- hr: 'Croatian',
28
- cs: 'Czech',
29
- da: 'Danish',
30
- nl: 'Dutch',
31
- en: 'English',
32
- eo: 'Esperanto',
33
- et: 'Estonian',
34
- fo: 'Faeroese',
35
- fa: 'Farsi',
36
- fj: 'Fiji',
37
- fi: 'Finnish',
38
- fr: 'French',
39
- fy: 'Frisian',
40
- gl: 'Galician',
41
- gd: 'Gaelic (Scottish)',
42
- gv: 'Gaelic (Manx)',
43
- ka: 'Georgian',
44
- de: 'German',
45
- el: 'Greek',
46
- kl: 'Greenlandic',
47
- gn: 'Guarani',
48
- gu: 'Gujarati',
49
- ha: 'Hausa',
50
- he: 'Hebrew',
51
- hi: 'Hindi',
52
- hu: 'Hungarian',
53
- is: 'Icelandic',
54
- id: 'Indonesian',
55
- ia: 'Interlingua',
56
- ie: 'Interlingue',
57
- iu: 'Inuktitut',
58
- ik: 'Inupiak',
59
- ga: 'Irish',
60
- it: 'Italian',
61
- ja: 'Japanese',
62
- // jv: 'Javanese',
63
- kn: 'Kannada',
64
- ks: 'Kashmiri',
65
- kk: 'Kazakh',
66
- rw: 'Kinyarwanda (Ruanda)',
67
- ky: 'Kirghiz',
68
- rn: 'Kirundi (Rundi)',
69
- ko: 'Korean',
70
- ku: 'Kurdish',
71
- lo: 'Laothian',
72
- la: 'Latin',
73
- lv: 'Latvian (Lettish)',
74
- li: 'Limburgish ( Limburger)',
75
- ln: 'Lingala',
76
- lt: 'Lithuanian',
77
- mk: 'Macedonian',
78
- mg: 'Malagasy',
79
- ms: 'Malay',
80
- ml: 'Malayalam',
81
- mt: 'Maltese',
82
- mi: 'Maori',
83
- mr: 'Marathi',
84
- mo: 'Moldavian',
85
- mn: 'Mongolian',
86
- na: 'Nauru',
87
- ne: 'Nepali',
88
- no: 'Norwegian',
89
- oc: 'Occitan',
90
- or: 'Oriya',
91
- om: 'Oromo (Afan, Galla)',
92
- ps: 'Pashto (Pushto)',
93
- pl: 'Polish',
94
- pt: 'Portuguese',
95
- pa: 'Punjabi',
96
- qu: 'Quechua',
97
- rm: 'Rhaeto-Romance',
98
- ro: 'Romanian',
99
- ru: 'Russian',
100
- sm: 'Samoan',
101
- sg: 'Sangro',
102
- sa: 'Sanskrit',
103
- sr: 'Serbian',
104
- sh: 'Serbo-Croatian',
105
- st: 'Sesotho',
106
- tn: 'Setswana',
107
- sn: 'Shona',
108
- sd: 'Sindhi',
109
- si: 'Sinhalese',
110
- ss: 'Siswati',
111
- sk: 'Slovak',
112
- sl: 'Slovenian',
113
- so: 'Somali',
114
- es: 'Spanish',
115
- su: 'Sundanese',
116
- sw: 'Swahili (Kiswahili)',
117
- sv: 'Swedish',
118
- tl: 'Tagalog',
119
- tg: 'Tajik',
120
- ta: 'Tamil',
121
- tt: 'Tatar',
122
- te: 'Telugu',
123
- th: 'Thai',
124
- bo: 'Tibetan',
125
- ti: 'Tigrinya',
126
- to: 'Tonga',
127
- ts: 'Tsonga',
128
- tr: 'Turkish',
129
- tk: 'Turkmen',
130
- tw: 'Twi',
131
- ug: 'Uighur',
132
- uk: 'Ukrainian',
133
- ur: 'Urdu',
134
- uz: 'Uzbek',
135
- vi: 'Vietnamese',
136
- vo: 'Volapük',
137
- cy: 'Welsh',
138
- wo: 'Wolof',
139
- xh: 'Xhosa',
140
- yi: 'Yiddish',
141
- yo: 'Yoruba',
142
- zu: 'Zulu',
143
- }
144
-
145
- export const languagesByName = Object.fromEntries(
146
- Object.entries(languages).map(([code, language]) => [language, code]),
147
- )
@@ -1,60 +0,0 @@
1
- export const onboardingTypes = new Set([
2
- 'onboarding',
3
- 'piscine-registration',
4
- 'interview',
5
- 'games',
6
- 'administration',
7
- 'module-registration',
8
- 'form-step',
9
- 'sign-step',
10
- 'upload-step',
11
- 'contact-validation-step',
12
- 'avatar-step',
13
- ] as const)
14
-
15
- type Extract<T> = [T] extends [Set<infer V>] ? V : never
16
-
17
- export const objectTypes = new Set([
18
- 'module',
19
- 'piscine',
20
- 'exam',
21
- 'raid',
22
- 'quest',
23
- 'exercise',
24
- 'project',
25
- 'signup',
26
- 'campus',
27
- ...onboardingTypes,
28
- ] as const)
29
-
30
- export type ObjectType = Extract<typeof objectTypes>
31
- export const childTypes = {
32
- campus: ['signup', 'onboarding', 'piscine', 'module'],
33
- signup: [
34
- 'form-step',
35
- 'sign-step',
36
- 'upload-step',
37
- 'contact-validation-step',
38
- 'avatar-step',
39
- ],
40
- onboarding: [
41
- 'games',
42
- 'administration',
43
- 'interview',
44
- 'piscine-registration',
45
- 'module-registration',
46
- ],
47
- administration: [
48
- 'form-step',
49
- 'sign-step',
50
- 'upload-step',
51
- 'contact-validation-step',
52
- 'avatar-step',
53
- ],
54
- piscine: ['quest', 'exam', 'raid', 'project'],
55
- exam: ['exercise'],
56
- quest: ['exercise'],
57
- module: ['project', 'piscine', 'exam'],
58
- } as const satisfies Partial<Record<ObjectType, ObjectType[]>>
59
-
60
- export type ChildTypes = typeof childTypes
package/onboarding.js DELETED
@@ -1,25 +0,0 @@
1
- export const prevValidated = (key, object) => {
2
- const { prev } = (
3
- object.parent.type !== 'onboarding' ? object : object.parent
4
- ).children[key]
5
- if (!prev) return true
6
- return prev.attrs.status === 'succeeded'
7
- }
8
-
9
- export const nextStep = (key, object) => {
10
- const { next } = (object.type === 'onboarding' ? object : object.parent)
11
- .children[key]
12
-
13
- return next ? next.path : undefined
14
- }
15
-
16
- export const getOnboardingStep = onboarding => {
17
- if (!onboarding) return undefined
18
- const children = Object.values(onboarding.children)
19
-
20
- return (
21
- children.find(
22
- ({ attrs }) => attrs.status === 'available' || attrs.status === 'failed',
23
- ) || children[children.length - 1]
24
- )
25
- }
package/path.js DELETED
@@ -1,73 +0,0 @@
1
- /**
2
- * @returns {string} the relative path converted to absolute
3
- * @argument relativePath {string} a relative path
4
- * @argument currentPath {string} the current absolute path
5
- */
6
- export const getAbsolutePath = (relativePath, currentPath) => {
7
- if (isAbsolutePath(relativePath)) {
8
- throw new Error("'relativePath' must be a relative path")
9
- }
10
- // Ignore trailing `/` in the relative path
11
- if (relativePath[relativePath.length - 1] === '/') {
12
- relativePath = relativePath.slice(0, -1)
13
- }
14
- if (!isAbsolutePath(currentPath)) {
15
- throw Error('the currentPath has to be an absolute path')
16
- }
17
- const absSegments = currentPath.split('/')
18
- const pathSegments = relativePath.split('/')
19
- for (const segment of pathSegments) {
20
- if (segment === '..') {
21
- if (absSegments.length > 0) {
22
- absSegments.pop()
23
- } else {
24
- throw Error(
25
- `Incorrect relative path = ${relativePath}, current path = ${currentPath}`,
26
- )
27
- }
28
- } else if (segment !== '.') {
29
- absSegments.push(segment)
30
- }
31
- }
32
- return absSegments.join('/')
33
- }
34
-
35
- const walk = (object, segment) => {
36
- if (!object) return
37
- if (segment === '.') return object
38
- return segment === '..' ? object.parent : object.children[segment]
39
- }
40
-
41
- /**
42
- * @param {string} relativePath - a relative path
43
- * @param {Object} currentObj - the object from where the relative path starts
44
- * @param {Object} [opts = { throwError: true } ] - options object with boolean property `throwError` to throw an error in case the path is invalid or no object is found
45
- * @returns {Object} - the object in the relative path
46
- */
47
- export const getObjectFromRelativePath = (
48
- relativePath,
49
- currentObj,
50
- opts = { throwError: true },
51
- ) => {
52
- const currentPath = currentObj.path
53
- if (isAbsolutePath(relativePath)) {
54
- if (opts.throwError) throw Error("'relativePath' must be a relative path")
55
- console.error("'relativePath' must be a relative path")
56
- return undefined
57
- }
58
-
59
- // Ignore trailing `/` in the relative path
60
- if (relativePath[relativePath.length - 1] === '/') {
61
- relativePath = relativePath.slice(0, -1)
62
- }
63
-
64
- const matchingObject = relativePath.split('/').reduce(walk, currentObj)
65
- if (matchingObject) return matchingObject
66
-
67
- const errorMessage = `Incorrect relative path '${relativePath}': no object found — current path = '${currentPath}'`
68
- if (opts.throwError) throw Error(errorMessage)
69
- console.error(errorMessage)
70
- return undefined
71
- }
72
-
73
- const isAbsolutePath = path => path.startsWith('/')