@jsreport/jsreport-core 3.0.0 → 3.1.2-test.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +166 -166
  2. package/README.md +298 -284
  3. package/index.js +29 -27
  4. package/lib/main/blobStorage/blobStorage.js +52 -47
  5. package/lib/main/blobStorage/inMemoryProvider.js +27 -27
  6. package/lib/main/blobStorage/mainActions.js +24 -24
  7. package/lib/main/createDefaultLoggerFormat.js +17 -17
  8. package/lib/main/defaults.js +14 -14
  9. package/lib/main/extensions/discover.js +20 -20
  10. package/lib/main/extensions/extensionsManager.js +264 -265
  11. package/lib/main/extensions/fileUtils.js +56 -55
  12. package/lib/main/extensions/findVersion.js +49 -53
  13. package/lib/main/extensions/locationCache.js +103 -97
  14. package/lib/main/extensions/sorter.js +10 -10
  15. package/lib/main/extensions/validateMinimalVersion.js +50 -50
  16. package/lib/main/folders/cascadeFolderRemove.js +25 -25
  17. package/lib/main/folders/getEntitiesInFolder.js +53 -53
  18. package/lib/main/folders/index.js +42 -42
  19. package/lib/main/folders/moveBetweenFolders.js +354 -354
  20. package/lib/main/folders/validateDuplicatedName.js +107 -107
  21. package/lib/main/folders/validateReservedName.js +53 -53
  22. package/lib/main/logger.js +244 -244
  23. package/lib/main/migration/resourcesToAssets.js +230 -210
  24. package/lib/main/migration/xlsxTemplatesToAssets.js +128 -118
  25. package/lib/main/monitoring.js +91 -91
  26. package/lib/main/optionsLoad.js +237 -237
  27. package/lib/main/optionsSchema.js +237 -237
  28. package/lib/main/profiler.js +2 -1
  29. package/lib/main/reporter.js +575 -578
  30. package/lib/main/schemaValidator.js +252 -252
  31. package/lib/main/settings.js +154 -154
  32. package/lib/main/store/checkDuplicatedId.js +27 -27
  33. package/lib/main/store/collection.js +329 -329
  34. package/lib/main/store/documentStore.js +469 -469
  35. package/lib/main/store/mainActions.js +28 -28
  36. package/lib/main/store/memoryStoreProvider.js +99 -99
  37. package/lib/main/store/queue.js +48 -48
  38. package/lib/main/store/referenceUtils.js +251 -251
  39. package/lib/main/store/setupValidateId.js +43 -43
  40. package/lib/main/store/setupValidateShortid.js +71 -71
  41. package/lib/main/store/transaction.js +69 -69
  42. package/lib/main/store/typeUtils.js +180 -180
  43. package/lib/main/templates.js +34 -34
  44. package/lib/main/validateEntityName.js +62 -62
  45. package/lib/shared/createError.js +36 -36
  46. package/lib/shared/encryption.js +114 -114
  47. package/lib/shared/folders/index.js +11 -11
  48. package/lib/shared/folders/normalizeEntityPath.js +15 -15
  49. package/lib/shared/folders/resolveEntityFromPath.js +88 -88
  50. package/lib/shared/folders/resolveEntityPath.js +46 -46
  51. package/lib/shared/folders/resolveFolderFromPath.js +38 -38
  52. package/lib/shared/generateRequestId.js +4 -4
  53. package/lib/shared/listenerCollection.js +169 -0
  54. package/lib/shared/normalizeMetaFromLogs.js +30 -30
  55. package/lib/shared/reporter.js +128 -123
  56. package/lib/shared/request.js +64 -64
  57. package/lib/shared/tempFilesHandler.js +81 -81
  58. package/lib/shared/templates.js +82 -82
  59. package/lib/static/helpers.js +33 -33
  60. package/lib/worker/blobStorage.js +34 -34
  61. package/lib/worker/defaultProxyExtend.js +46 -46
  62. package/lib/worker/documentStore.js +49 -49
  63. package/lib/worker/extensionsManager.js +17 -17
  64. package/lib/worker/logger.js +48 -48
  65. package/lib/worker/render/diff.js +138 -138
  66. package/lib/worker/render/executeEngine.js +227 -190
  67. package/lib/worker/render/htmlRecipe.js +10 -10
  68. package/lib/worker/render/moduleHelper.js +45 -43
  69. package/lib/worker/render/noneEngine.js +12 -12
  70. package/lib/worker/render/profiler.js +158 -158
  71. package/lib/worker/render/render.js +213 -209
  72. package/lib/worker/render/resolveReferences.js +60 -60
  73. package/lib/worker/reporter.js +192 -187
  74. package/lib/worker/sandbox/runInSandbox.js +13 -4
  75. package/lib/worker/sandbox/safeSandbox.js +828 -822
  76. package/lib/worker/templates.js +78 -78
  77. package/lib/worker/workerHandler.js +54 -54
  78. package/package.json +92 -92
  79. package/test/blobStorage/common.js +21 -21
  80. package/test/store/common.js +1449 -1449
@@ -1,209 +1,213 @@
1
- /*!
2
- * Copyright(c) 2018 Jan Blaha
3
- *
4
- * Orchestration of the rendering process
5
- */
6
- const { Readable } = require('stream')
7
- const extend = require('node.extend.without.arrays')
8
- const ExecuteEngine = require('./executeEngine')
9
- const Request = require('../../shared/request')
10
- const generateRequestId = require('../../shared/generateRequestId')
11
- const resolveReferences = require('./resolveReferences.js')
12
- const moduleHelper = require('./moduleHelper')
13
- let reportCounter = 0
14
- const fs = require('fs').promises
15
- const path = require('path')
16
-
17
- module.exports = (reporter) => {
18
- let helpersScript
19
- reporter.beforeRenderListeners.add('core-helpers', async (req) => {
20
- if (!helpersScript) {
21
- helpersScript = await fs.readFile(path.join(__dirname, '../../static/helpers.js'), 'utf8')
22
- }
23
- req.context.systemHelpers += helpersScript + '\n'
24
- })
25
- moduleHelper(reporter)
26
- reporter.addRequestContextMetaConfig('systemHelpers', { sandboxHidden: true })
27
-
28
- const executeEngine = ExecuteEngine(reporter)
29
- async function beforeRender (reporter, request, response) {
30
- if (!request.template) {
31
- throw reporter.createError('template property must be defined', {
32
- statusCode: 400
33
- })
34
- }
35
-
36
- await reporter.beforeRenderListeners.fire(request, response)
37
- await reporter.validateRenderListeners.fire(request, response)
38
- }
39
-
40
- async function invokeRender (reporter, request, response) {
41
- if (!request.template.engine) {
42
- throw reporter.createError('Engine must be specified', {
43
- statusCode: 400
44
- })
45
- }
46
-
47
- const engine = reporter.extensionsManager.engines.find((e) => e.name === request.template.engine)
48
-
49
- if (!engine) {
50
- throw reporter.createError(`Engine '${request.template.engine}' not found. If this is a custom engine make sure it's properly installed from npm`, {
51
- statusCode: 400
52
- })
53
- }
54
-
55
- if (
56
- request.data != null &&
57
- typeof request.data === 'object' &&
58
- Array.isArray(request.data)
59
- ) {
60
- throw reporter.createError('Request data can not be an array. you should pass an object in request.data input', {
61
- statusCode: 400
62
- })
63
- }
64
-
65
- const engineProfilerEvent = reporter.profiler.emit({
66
- type: 'operationStart',
67
- subtype: 'engine',
68
- name: request.template.engine
69
- }, request, response)
70
-
71
- reporter.logger.debug(`Rendering engine ${engine.name}`, request)
72
-
73
- const engineRes = await executeEngine(engine, request)
74
-
75
- response.content = Buffer.from(engineRes.content != null ? engineRes.content : '')
76
-
77
- reporter.profiler.emit({
78
- type: 'operationEnd',
79
- operationId: engineProfilerEvent.operationId
80
- }, request, response)
81
-
82
- await reporter.afterTemplatingEnginesExecutedListeners.fire(request, response)
83
-
84
- if (!request.template.recipe) {
85
- throw reporter.createError('Recipe must be specified', {
86
- statusCode: 400
87
- })
88
- }
89
-
90
- const recipe = reporter.extensionsManager.recipes.find((r) => r.name === request.template.recipe)
91
-
92
- if (!recipe) {
93
- throw reporter.createError(`Recipe '${request.template.recipe}' not found. If this is a custom recipe make sure it's properly installed from npm.`, {
94
- statusCode: 400
95
- })
96
- }
97
-
98
- const recipeProfilerEvent = reporter.profiler.emit({
99
- type: 'operationStart',
100
- subtype: 'recipe',
101
- name: request.template.recipe
102
- }, request, response)
103
-
104
- reporter.logger.debug('Executing recipe ' + request.template.recipe, request)
105
-
106
- await recipe.execute(request, response)
107
- reporter.profiler.emit({
108
- type: 'operationEnd',
109
- operationId: recipeProfilerEvent.operationId
110
- }, request, response)
111
- }
112
-
113
- async function afterRender (reporter, request, response) {
114
- await reporter.afterRenderListeners.fire(request, response)
115
-
116
- response.stream = Readable.from(response.content)
117
- response.result = response.stream
118
-
119
- return response
120
- }
121
-
122
- return async (req, parentReq) => {
123
- const request = Request(req, parentReq)
124
- request.context.systemHelpers = ''
125
- const response = { meta: {} }
126
- let renderStartProfilerEvent
127
- try {
128
- if (request.context.id == null) {
129
- request.context.id = generateRequestId()
130
- }
131
-
132
- renderStartProfilerEvent = await reporter.profiler.renderStart(request, parentReq, response)
133
- request.data = resolveReferences(request.data) || {}
134
-
135
- if (request.options.reportName) {
136
- response.meta.reportName = String(request.options.reportName)
137
- } else {
138
- response.meta.reportName = 'report'
139
- }
140
-
141
- request.context.reportCounter = ++reportCounter
142
- request.context.startTimestamp = new Date().getTime()
143
-
144
- reporter.requestModulesCache.set(request.context.id, Object.create(null))
145
-
146
- reporter.logger.info(`Starting rendering request ${request.context.reportCounter} (user: ${(request.context.user ? request.context.user.username : 'null')})`, request)
147
-
148
- // TODO
149
- /* if (reporter.entityTypeValidator.getSchema('TemplateType') != null) {
150
- const templateValidationResult = reporter.entityTypeValidator.validate('TemplateType', request.template, { rootPrefix: 'template' })
151
-
152
- if (!templateValidationResult.valid) {
153
- throw reporter.createError(`template input in request contain values that does not match the defined schema. ${templateValidationResult.fullErrorMessage}`, {
154
- statusCode: 400
155
- })
156
- }
157
- } */
158
-
159
- await beforeRender(reporter, request, response)
160
- await invokeRender(reporter, request, response)
161
- await afterRender(reporter, request, response)
162
-
163
- reporter.logger.info(`Rendering request ${request.context.reportCounter} finished in ${(new Date().getTime() - request.context.startTimestamp)} ms`, request)
164
-
165
- response.meta.logs = request.context.logs
166
-
167
- if (parentReq) {
168
- parentReq.context.logs = parentReq.context.logs.concat(request.context.logs)
169
- parentReq.context.shared = extend(true, parentReq.context.shared, request.context.shared)
170
- }
171
-
172
- await reporter.profiler.renderEnd(renderStartProfilerEvent.operationId, request, response)
173
-
174
- return response
175
- } catch (e) {
176
- await reporter.renderErrorListeners.fire(request, response, e)
177
-
178
- const logFn = e.weak ? reporter.logger.warn : reporter.logger.error
179
-
180
- logFn(`Error when processing render request ${request.context.reportCounter} ${e.message}${e.stack != null ? ' ' + e.stack : ''}`, request)
181
-
182
- logFn(`Rendering request ${request.context.reportCounter} finished with error in ${(new Date().getTime() - request.context.startTimestamp)} ms`, request)
183
-
184
- if (
185
- parentReq &&
186
- parentReq.context &&
187
- parentReq.context.logs &&
188
- request.context &&
189
- request.context.logs
190
- ) {
191
- parentReq.context.logs = parentReq.context.logs.concat(request.context.logs)
192
- }
193
-
194
- if (parentReq) {
195
- parentReq.context.shared = extend(true, parentReq.context.shared, request.context.shared)
196
- }
197
-
198
- e.logged = true
199
-
200
- if (renderStartProfilerEvent) {
201
- await reporter.profiler.renderEnd(renderStartProfilerEvent.operationId, request, response, e)
202
- }
203
-
204
- throw e
205
- } finally {
206
- reporter.requestModulesCache.delete(request.context.id)
207
- }
208
- }
209
- }
1
+ /*!
2
+ * Copyright(c) 2018 Jan Blaha
3
+ *
4
+ * Orchestration of the rendering process
5
+ */
6
+ const { Readable } = require('stream')
7
+ const extend = require('node.extend.without.arrays')
8
+ const ExecuteEngine = require('./executeEngine')
9
+ const Request = require('../../shared/request')
10
+ const generateRequestId = require('../../shared/generateRequestId')
11
+ const resolveReferences = require('./resolveReferences.js')
12
+ const moduleHelper = require('./moduleHelper')
13
+ let reportCounter = 0
14
+
15
+ module.exports = (reporter) => {
16
+ reporter.addRequestContextMetaConfig('systemHelpers', { sandboxHidden: true })
17
+
18
+ moduleHelper(reporter)
19
+
20
+ const executeEngine = ExecuteEngine(reporter)
21
+ async function beforeRender (reporter, request, response) {
22
+ if (!request.template) {
23
+ throw reporter.createError('template property must be defined', {
24
+ statusCode: 400
25
+ })
26
+ }
27
+
28
+ await reporter.beforeRenderListeners.fire(request, response)
29
+ await reporter.validateRenderListeners.fire(request, response)
30
+ }
31
+
32
+ async function invokeRender (reporter, request, response) {
33
+ if (!request.template.engine) {
34
+ throw reporter.createError('Engine must be specified', {
35
+ statusCode: 400
36
+ })
37
+ }
38
+
39
+ const engine = reporter.extensionsManager.engines.find((e) => e.name === request.template.engine)
40
+
41
+ if (!engine) {
42
+ throw reporter.createError(`Engine '${request.template.engine}' not found. If this is a custom engine make sure it's properly installed from npm`, {
43
+ statusCode: 400
44
+ })
45
+ }
46
+
47
+ if (
48
+ request.data != null &&
49
+ typeof request.data === 'object' &&
50
+ Array.isArray(request.data)
51
+ ) {
52
+ throw reporter.createError('Request data can not be an array. you should pass an object in request.data input', {
53
+ statusCode: 400
54
+ })
55
+ }
56
+
57
+ let helpersResults = await reporter.registerHelpersListeners.fire(request)
58
+
59
+ helpersResults = helpersResults.filter((result) => {
60
+ return result != null
61
+ })
62
+
63
+ request.context.systemHelpers = helpersResults.join('\n')
64
+
65
+ const engineProfilerEvent = reporter.profiler.emit({
66
+ type: 'operationStart',
67
+ subtype: 'engine',
68
+ name: request.template.engine
69
+ }, request, response)
70
+
71
+ reporter.logger.debug(`Rendering engine ${engine.name}`, request)
72
+
73
+ const engineRes = await executeEngine(engine, request)
74
+
75
+ response.content = Buffer.from(engineRes.content != null ? engineRes.content : '')
76
+
77
+ reporter.profiler.emit({
78
+ type: 'operationEnd',
79
+ operationId: engineProfilerEvent.operationId
80
+ }, request, response)
81
+
82
+ await reporter.afterTemplatingEnginesExecutedListeners.fire(request, response)
83
+
84
+ if (!request.template.recipe) {
85
+ throw reporter.createError('Recipe must be specified', {
86
+ statusCode: 400
87
+ })
88
+ }
89
+
90
+ const recipe = reporter.extensionsManager.recipes.find((r) => r.name === request.template.recipe)
91
+
92
+ if (!recipe) {
93
+ throw reporter.createError(`Recipe '${request.template.recipe}' not found. If this is a custom recipe make sure it's properly installed from npm.`, {
94
+ statusCode: 400
95
+ })
96
+ }
97
+
98
+ const recipeProfilerEvent = reporter.profiler.emit({
99
+ type: 'operationStart',
100
+ subtype: 'recipe',
101
+ name: request.template.recipe
102
+ }, request, response)
103
+
104
+ reporter.logger.debug('Executing recipe ' + request.template.recipe, request)
105
+
106
+ await recipe.execute(request, response)
107
+ reporter.profiler.emit({
108
+ type: 'operationEnd',
109
+ operationId: recipeProfilerEvent.operationId
110
+ }, request, response)
111
+ }
112
+
113
+ async function afterRender (reporter, request, response) {
114
+ await reporter.afterRenderListeners.fire(request, response)
115
+
116
+ response.stream = Readable.from(response.content)
117
+ response.result = response.stream
118
+
119
+ return response
120
+ }
121
+
122
+ return async (req, parentReq) => {
123
+ const request = Request(req, parentReq)
124
+ const response = { meta: {} }
125
+ let renderStartProfilerEvent
126
+ try {
127
+ if (request.context.id == null) {
128
+ request.context.id = generateRequestId()
129
+ }
130
+
131
+ renderStartProfilerEvent = await reporter.profiler.renderStart(request, parentReq, response)
132
+ request.data = resolveReferences(request.data) || {}
133
+
134
+ if (request.options.reportName) {
135
+ response.meta.reportName = String(request.options.reportName)
136
+ } else {
137
+ response.meta.reportName = 'report'
138
+ }
139
+
140
+ request.context.reportCounter = ++reportCounter
141
+ request.context.startTimestamp = new Date().getTime()
142
+ request.context.systemHelpers = ''
143
+
144
+ if (parentReq == null) {
145
+ reporter.requestModulesCache.set(request.context.rootId, Object.create(null))
146
+ }
147
+
148
+ reporter.logger.info(`Starting rendering request ${request.context.reportCounter} (user: ${(request.context.user ? request.context.user.username : 'null')})`, request)
149
+
150
+ // TODO
151
+ /* if (reporter.entityTypeValidator.getSchema('TemplateType') != null) {
152
+ const templateValidationResult = reporter.entityTypeValidator.validate('TemplateType', request.template, { rootPrefix: 'template' })
153
+
154
+ if (!templateValidationResult.valid) {
155
+ throw reporter.createError(`template input in request contain values that does not match the defined schema. ${templateValidationResult.fullErrorMessage}`, {
156
+ statusCode: 400
157
+ })
158
+ }
159
+ } */
160
+
161
+ await beforeRender(reporter, request, response)
162
+ await invokeRender(reporter, request, response)
163
+ await afterRender(reporter, request, response)
164
+
165
+ reporter.logger.info(`Rendering request ${request.context.reportCounter} finished in ${(new Date().getTime() - request.context.startTimestamp)} ms`, request)
166
+
167
+ response.meta.logs = request.context.logs
168
+
169
+ if (parentReq) {
170
+ parentReq.context.logs = parentReq.context.logs.concat(request.context.logs)
171
+ parentReq.context.shared = extend(true, parentReq.context.shared, request.context.shared)
172
+ }
173
+
174
+ await reporter.profiler.renderEnd(renderStartProfilerEvent.operationId, request, response)
175
+
176
+ return response
177
+ } catch (e) {
178
+ await reporter.renderErrorListeners.fire(request, response, e)
179
+
180
+ const logFn = e.weak ? reporter.logger.warn : reporter.logger.error
181
+
182
+ logFn(`Error when processing render request ${request.context.reportCounter} ${e.message}${e.stack != null ? ' ' + e.stack : ''}`, request)
183
+
184
+ logFn(`Rendering request ${request.context.reportCounter} finished with error in ${(new Date().getTime() - request.context.startTimestamp)} ms`, request)
185
+
186
+ if (
187
+ parentReq &&
188
+ parentReq.context &&
189
+ parentReq.context.logs &&
190
+ request.context &&
191
+ request.context.logs
192
+ ) {
193
+ parentReq.context.logs = parentReq.context.logs.concat(request.context.logs)
194
+ }
195
+
196
+ if (parentReq) {
197
+ parentReq.context.shared = extend(true, parentReq.context.shared, request.context.shared)
198
+ }
199
+
200
+ e.logged = true
201
+
202
+ if (renderStartProfilerEvent) {
203
+ await reporter.profiler.renderEnd(renderStartProfilerEvent.operationId, request, response, e)
204
+ }
205
+
206
+ throw e
207
+ } finally {
208
+ if (parentReq == null) {
209
+ reporter.requestModulesCache.delete(request.context.rootId)
210
+ }
211
+ }
212
+ }
213
+ }
@@ -1,60 +1,60 @@
1
- // resolve references in json specified by $ref and $id attribute, this is handy when user send cycles in json
2
- module.exports = function (json) {
3
- if (typeof json === 'string') {
4
- json = JSON.parse(json)
5
- }
6
-
7
- const byid = {} // all objects by id
8
- const refs = [] // references to objects that could not be resolved
9
- json = (function recurse (obj, prop, parent) {
10
- if (typeof obj !== 'object' || !obj) { // a primitive value
11
- return obj
12
- }
13
- if (Object.prototype.toString.call(obj) === '[object Array]') {
14
- for (let i = 0; i < obj.length; i++) {
15
- if (obj[i] === null) {
16
- continue
17
- }
18
-
19
- if (Object.prototype.hasOwnProperty.call(obj[i], '$ref')) {
20
- obj[i] = recurse(obj[i], i, obj)
21
- } else {
22
- obj[i] = recurse(obj[i], prop, obj)
23
- }
24
- }
25
- return obj
26
- }
27
-
28
- if ('$ref' in obj) { // a reference
29
- const ref = obj.$ref
30
- if (ref in byid) {
31
- return byid[ref]
32
- }
33
- // else we have to make it lazy:
34
- refs.push([parent, prop, ref])
35
- return
36
- } else if ('$id' in obj) {
37
- const id = obj.$id
38
- delete obj.$id
39
- if ('$values' in obj) { // an array
40
- obj = obj.$values.map(recurse)
41
- } else { // a plain object
42
- for (const p in obj) {
43
- if (Object.prototype.hasOwnProperty.call(obj, p)) {
44
- obj[p] = recurse(obj[p], p, obj)
45
- }
46
- }
47
- }
48
- byid[id] = obj
49
- }
50
-
51
- return obj
52
- })(json) // run it!
53
-
54
- for (let i = 0; i < refs.length; i++) { // resolve previously unknown references
55
- const ref = refs[i]
56
- ref[0][ref[1]] = byid[ref[2]]
57
- // Notice that this throws if you put in a reference at top-level
58
- }
59
- return json
60
- }
1
+ // resolve references in json specified by $ref and $id attribute, this is handy when user send cycles in json
2
+ module.exports = function (json) {
3
+ if (typeof json === 'string') {
4
+ json = JSON.parse(json)
5
+ }
6
+
7
+ const byid = {} // all objects by id
8
+ const refs = [] // references to objects that could not be resolved
9
+ json = (function recurse (obj, prop, parent) {
10
+ if (typeof obj !== 'object' || !obj) { // a primitive value
11
+ return obj
12
+ }
13
+ if (Object.prototype.toString.call(obj) === '[object Array]') {
14
+ for (let i = 0; i < obj.length; i++) {
15
+ if (obj[i] === null) {
16
+ continue
17
+ }
18
+
19
+ if (Object.prototype.hasOwnProperty.call(obj[i], '$ref')) {
20
+ obj[i] = recurse(obj[i], i, obj)
21
+ } else {
22
+ obj[i] = recurse(obj[i], prop, obj)
23
+ }
24
+ }
25
+ return obj
26
+ }
27
+
28
+ if ('$ref' in obj) { // a reference
29
+ const ref = obj.$ref
30
+ if (ref in byid) {
31
+ return byid[ref]
32
+ }
33
+ // else we have to make it lazy:
34
+ refs.push([parent, prop, ref])
35
+ return
36
+ } else if ('$id' in obj) {
37
+ const id = obj.$id
38
+ delete obj.$id
39
+ if ('$values' in obj) { // an array
40
+ obj = obj.$values.map(recurse)
41
+ } else { // a plain object
42
+ for (const p in obj) {
43
+ if (Object.prototype.hasOwnProperty.call(obj, p)) {
44
+ obj[p] = recurse(obj[p], p, obj)
45
+ }
46
+ }
47
+ }
48
+ byid[id] = obj
49
+ }
50
+
51
+ return obj
52
+ })(json) // run it!
53
+
54
+ for (let i = 0; i < refs.length; i++) { // resolve previously unknown references
55
+ const ref = refs[i]
56
+ ref[0][ref[1]] = byid[ref[2]]
57
+ // Notice that this throws if you put in a reference at top-level
58
+ }
59
+ return json
60
+ }