@01-edu/shared 2.0.5 → 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.
- package/dist/attrs-defs.js +1 -0
- package/dist/attrs.js +1 -0
- package/dist/bin/check-definitions.js +3 -0
- package/dist/chunk-BLXMKMNK.js +1 -0
- package/dist/chunk-FQWECXED.js +1 -0
- package/dist/chunk-IF63VBJO.js +1 -0
- package/dist/chunk-K5Z4W5GV.js +1 -0
- package/dist/chunk-MV3DQ3PZ.js +1 -0
- package/dist/chunk-NR2KVFFU.js +1 -0
- package/dist/chunk-T2KIY67K.js +1 -0
- package/dist/chunk-YJMXNRLI.js +1 -0
- package/dist/chunk-ZI7IBRHE.js +1 -0
- package/dist/definitions-checker.js +1 -0
- package/dist/event-utils.js +1 -0
- package/dist/games-utils.js +1 -0
- package/dist/graph.js +1 -0
- package/dist/hasura-core.js +1 -0
- package/dist/hasura-model.js +34 -0
- package/dist/hasura-prepare.js +1 -0
- package/dist/modular-steps-utils.js +1 -0
- package/dist/object-structure.js +1 -0
- package/dist/onboarding.js +1 -0
- package/dist/path.js +1 -0
- package/dist/qa-utils.js +1 -0
- package/dist/score.js +1 -0
- package/dist/skill-definitions.js +1 -0
- package/dist/toolbox.js +1 -0
- package/package.json +17 -6
- package/attrs-defs.js +0 -4273
- package/attrs.js +0 -423
- package/bin/check-definitions.js +0 -74
- package/definitions-checker.js +0 -233
- package/event-utils.js +0 -58
- package/graph.js +0 -96
- package/hasura-core.js +0 -217
- package/hasura-model.js +0 -138
- package/hasura-prepare.js +0 -44
- package/languages.js +0 -147
- package/onboarding.js +0 -25
- package/path.js +0 -73
- package/programming-languages.js +0 -21
- package/qa-utils.js +0 -13
- package/score.js +0 -80
- package/skill-definitions.js +0 -359
- package/toolbox.js +0 -532
package/definitions-checker.js
DELETED
|
@@ -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/hasura-core.js
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
export class HasuraError extends Error {
|
|
2
|
-
constructor({ extensions, message, ...props }, cause) {
|
|
3
|
-
super(message, cause)
|
|
4
|
-
Object.assign(this, props)
|
|
5
|
-
Object.assign(this, extensions)
|
|
6
|
-
Error.captureStackTrace?.(this, HasuraError)
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const initClient = ({ debug, address, log, ...params }) => {
|
|
11
|
-
log || (log = debug ? console.debug : () => {})
|
|
12
|
-
const handlers = new Map()
|
|
13
|
-
const subscribers = new Map()
|
|
14
|
-
|
|
15
|
-
const getId = () => {
|
|
16
|
-
const id = Math.random().toString(36).slice(2)
|
|
17
|
-
return handlers.has(id) ? getId() : id
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const rejectAllPending = err => {
|
|
21
|
-
subscribers.clear() // TODO: store subscribers query and re-trigger them
|
|
22
|
-
for (const [id, { reject, noCleanup }] of handlers) {
|
|
23
|
-
noCleanup || activeQueries.delete(id)
|
|
24
|
-
handlers.delete(id)
|
|
25
|
-
reject(err)
|
|
26
|
-
}
|
|
27
|
-
return err
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const end = (handler, props = {}) => {
|
|
31
|
-
props.duration = Date.now() - handler.start
|
|
32
|
-
props.size = handler.size
|
|
33
|
-
props.name = handler.query
|
|
34
|
-
props.id = handler.id
|
|
35
|
-
log('query', props)
|
|
36
|
-
handlers.delete(handler.id)
|
|
37
|
-
handler.noCleanup || activeQueries.delete(handler.id)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const messageFail = (handler, payload, id) => {
|
|
41
|
-
if (!handler) return log('missing-handler', { id, type: 'error' })
|
|
42
|
-
|
|
43
|
-
end(handler, { payload, type: 'error' })
|
|
44
|
-
handlers.delete(id)
|
|
45
|
-
return handler.reject(
|
|
46
|
-
new HasuraError(payload.errors[0], debug && { cause: handler.cause }),
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const handleMessage = (data, resolve, reject) => {
|
|
51
|
-
if (data === '{"type":"ka"}') return // ignore keep alive
|
|
52
|
-
|
|
53
|
-
const { type, payload, id } = JSON.parse(data)
|
|
54
|
-
const handler = handlers.get(id)
|
|
55
|
-
handler && (handler.size += data.length)
|
|
56
|
-
|
|
57
|
-
log('raw', data)
|
|
58
|
-
|
|
59
|
-
switch (type) {
|
|
60
|
-
case 'connection_ack': {
|
|
61
|
-
return resolve(payload)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
case 'connection_error': {
|
|
65
|
-
const err = rejectAllPending(new HasuraError({ errors: [payload] }))
|
|
66
|
-
return reject(err)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
case 'data': {
|
|
70
|
-
if (payload.errors) return messageFail(handler, payload, id)
|
|
71
|
-
|
|
72
|
-
const sub = subscribers.get(id)
|
|
73
|
-
if (!sub) {
|
|
74
|
-
return handler
|
|
75
|
-
? (handler.payload = payload)
|
|
76
|
-
: log('missing-handler', { id, type: 'error' })
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
sub(payload.data)
|
|
80
|
-
if (handler) {
|
|
81
|
-
end(handler, { type, payload })
|
|
82
|
-
handler.resolve()
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
case 'error': {
|
|
89
|
-
return messageFail(handler, payload, id)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
case 'complete': {
|
|
93
|
-
if (!handler) return
|
|
94
|
-
end(handler, { type, payload })
|
|
95
|
-
return handler.resolve(handler.payload?.data)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const handleFail = (event, type) =>
|
|
101
|
-
rejectAllPending(
|
|
102
|
-
new HasuraError({ message: `WebSocket connection ${type}`, event }),
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
let ws = new WebSocket(address, 'graphql-ws')
|
|
106
|
-
let activeQueries = new Map()
|
|
107
|
-
const exec = async (id, payload, name, noCleanup) => {
|
|
108
|
-
await connection
|
|
109
|
-
const handler = {
|
|
110
|
-
id,
|
|
111
|
-
size: 0,
|
|
112
|
-
query: name,
|
|
113
|
-
start: Date.now(),
|
|
114
|
-
noCleanup,
|
|
115
|
-
}
|
|
116
|
-
const result = new Promise((resolve, reject) => {
|
|
117
|
-
handler.resolve = resolve
|
|
118
|
-
handler.reject = reject
|
|
119
|
-
})
|
|
120
|
-
debug && (handler.cause = Error('hasuraClient.exec'))
|
|
121
|
-
handlers.set(id, handler)
|
|
122
|
-
activeQueries.set(id, { payload, name })
|
|
123
|
-
log('start', { id, payload })
|
|
124
|
-
ws.send(`{"type":"start","id":"${id}","payload":${payload}}`)
|
|
125
|
-
return result
|
|
126
|
-
}
|
|
127
|
-
const runFromString = (payload, name) => exec(getId(), payload, name)
|
|
128
|
-
|
|
129
|
-
const subscribeFromString = (sub, payload, name) => {
|
|
130
|
-
const id = getId()
|
|
131
|
-
subscribers.set(id, sub)
|
|
132
|
-
|
|
133
|
-
return {
|
|
134
|
-
execution: exec(id, payload, name, true),
|
|
135
|
-
unsubscribe: () => {
|
|
136
|
-
subscribers.delete(id)
|
|
137
|
-
activeQueries.delete(id)
|
|
138
|
-
log('stop', { id })
|
|
139
|
-
ws.send(`{"type":"stop","id":"${id}"}`)
|
|
140
|
-
},
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
let connected = false
|
|
145
|
-
const getConnection = async () => {
|
|
146
|
-
connected = false
|
|
147
|
-
const { promise, resolve, reject } = Promise.withResolvers()
|
|
148
|
-
const onError = () => reject(handleFail(undefined, 'failed'))
|
|
149
|
-
const onClose = event =>
|
|
150
|
-
reject(handleFail({ code: event.code, reason: event.reason }, 'close'))
|
|
151
|
-
ws.addEventListener('error', onError, { once: true })
|
|
152
|
-
ws.addEventListener('close', onClose, { once: true })
|
|
153
|
-
ws.addEventListener('message', event =>
|
|
154
|
-
handleMessage(event.data, resolve, reject),
|
|
155
|
-
)
|
|
156
|
-
try {
|
|
157
|
-
await promise
|
|
158
|
-
return (connected = true)
|
|
159
|
-
} finally {
|
|
160
|
-
ws.removeEventListener('error', onError)
|
|
161
|
-
ws.removeEventListener('close', onClose)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
let connection = getConnection()
|
|
166
|
-
|
|
167
|
-
const connect = async ({ adminSecret, token, role, headers }) => {
|
|
168
|
-
const previousActiveQueries = activeQueries
|
|
169
|
-
const reload = connected
|
|
170
|
-
if (reload) {
|
|
171
|
-
ws.close()
|
|
172
|
-
ws = new WebSocket(address, 'graphql-ws')
|
|
173
|
-
connection = getConnection()
|
|
174
|
-
activeQueries = new Map()
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (!ws.readyState) {
|
|
178
|
-
await new Promise(s => ws.addEventListener('open', s, { once: true }))
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const payload = {
|
|
182
|
-
headers: adminSecret
|
|
183
|
-
? { 'x-hasura-admin-secret': adminSecret, ...headers }
|
|
184
|
-
: { Authorization: `Bearer ${token}`, ...headers },
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
role && (payload.headers['x-hasura-role'] = role)
|
|
188
|
-
|
|
189
|
-
ws.send(JSON.stringify({ type: 'connection_init', payload }))
|
|
190
|
-
|
|
191
|
-
reload &&
|
|
192
|
-
connection.then(() => {
|
|
193
|
-
// re exec all previous active queries
|
|
194
|
-
for (const [id, { payload, name }] of previousActiveQueries) {
|
|
195
|
-
exec(id, payload, name)
|
|
196
|
-
}
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
return connection
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (params.adminSecret || params.token) {
|
|
203
|
-
connect(params)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return {
|
|
207
|
-
ws,
|
|
208
|
-
connect,
|
|
209
|
-
connection,
|
|
210
|
-
runFromString,
|
|
211
|
-
subscribeFromString,
|
|
212
|
-
run: (query, variables) =>
|
|
213
|
-
runFromString(JSON.stringify({ query, variables })),
|
|
214
|
-
subscribe: (sub, query, variables) =>
|
|
215
|
-
subscribeFromString(sub, JSON.stringify({ query, variables })),
|
|
216
|
-
}
|
|
217
|
-
}
|