-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.ts
285 lines (209 loc) · 11.1 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import { ArbitraryObject, AttributeType, GroupType, LinkType, DatasetType } from '../../src/types/general.types';
import { OptionsType } from './types';
import Classify from './classify';
import InheritanceTree from './classify/InheritanceTree';
import { hasNestedGroups, isTypedGroup, hasTypedChildren } from './utils/globals';
// // HDF5-IO
// import {
// symbols
// } from '../../../hdf5-io/src';
import {
symbols
// objectify,
// isGroup as isGroupType,
// isDataset as isDatasetType,
// isAttribute
} from 'hdf5-io';
// ESConform
// import * as conform from "../../../esmodel/src/index";
import * as conform from 'esconform'
type SpecificationType = { [x: OptionsType['coreName']]: ArbitraryObject } & ArbitraryObject
// Generate an API from included specification
export default class API {
_registry: SpecificationType
_specification: SpecificationType = {}
_options: OptionsType;
_nameToSchema: ArbitraryObject = {};
extensions: ArbitraryObject = {};
_version?: string;
_classify: Classify = new Classify()
_inheritanceTree = new InheritanceTree();
[x: string]: any;
constructor(
specification: SpecificationType, // Fallback to latest schema or empty specification
options:Partial<OptionsType> = {}
) {
// copy options
this._options = options as OptionsType
if (!this._options.name) this._options.name = 'apify'
if (!this._options.overrides) this._options.overrides = {}
if (typeof this._options.getValue !== 'function') this._options.getValue = () => undefined // triggert default
this._classify.baseClass = this._options.baseClass
// copy user-specified specification
this._registry = JSON.parse(JSON.stringify(specification)) // Deep copy
const globalTarget = (globalThis as any)
// assign latest API to global (for create calls...)
if (!globalTarget.apify) globalTarget.apify = {}
globalTarget.apify[this._options.name] = this
}
_define = (name:string, target:any, format:any) => {
Object.defineProperty(target, name, format)
}
// Set schema item
set = (name:string, value:any, key?:string) => {
const path = this._nameToSchema[name]?.path
if (path){
let target = this
if (!key) key = path.pop() // define last key
path.forEach((str:string) => target = target[str] ?? target)
target[key as string] = value
return true
} else return null
}
// Get schema item (constructor function)
get = (name:string, objectShape?: any, target=this._registry): null | Function => {
const key = this._options.classKey
if (key && objectShape && key in objectShape) name = objectShape[key] ?? name
const path = this._nameToSchema[name]?.path
if (path){
path.forEach((str:string) => target = target[str] ?? target)
return target[name]
} else return (objectShape) ? this.getMatchingClass(objectShape) : null
}
getMatchingClass = (input: any, choices?: string[]) => this.get(this._classify.match(input, choices))
_setFromObject = (o: any, aggregator: ArbitraryObject = {}, type?: string, path: string[] = []) => {
const isGroup = type === 'group'
const className = this._options.className.reduce((acc:any, str:string) => acc = (!acc) ? o[str] : acc, null)
let name = className ?? o.name //className ?? o.name // Has name by default
const inheritedType = this._options.inheritsFrom.reduce((acc:any, str:string) => acc = (!acc) ? o[str] : acc, null) // Class that children can inherit from
if(inheritedType && !name) {
if (!aggregator[hasTypedChildren]) Object.defineProperty(aggregator, hasTypedChildren, { value: new Set() })
aggregator[hasTypedChildren].add(inheritedType)
}
const newPath = name ? [...path, name] : path
// const isTypedGroup = inheritedType && !name
// Will throw out (1) top-level specification groups without a name and (2) classes that indicate a typed group
if (name) {
// console.log('Class', className, inheritedType)
let inherit = {
type,
value: inheritedType
}
// Group
if (isGroup) {
if (!(name in aggregator)) aggregator[name] = {} as any // Set aggregator value (if not set)
const value = aggregator[name]
if (inheritedType) {
if (className){} // Is a class
else Object.defineProperty(value, isTypedGroup, { value: inheritedType }) // Is a typed group
}
// Mirror HDF5-IO Symbol Behaviors on the JSON Specification
Object.defineProperty(value, symbols.isGroup, { value: true }) // NOTE: Set as configurable to avoid downstream errors...
if (aggregator[symbols.isGroup] && !aggregator[hasNestedGroups]) Object.defineProperty(aggregator, hasNestedGroups, { value: true })
}
// Dataset
else {
let value = o.value ?? o.default_value // Allow for creating a null object
const objectValue = value = conform.presets.objectify(name, value)
// Mirror HDF5-IO Symbol Behaviors on the JSON Specification
if (type === 'dataset') Object.defineProperty(objectValue, symbols.isDataset, { value: true, configurable: true}) // Setting type on the dataset (set as configurable to avoid downstream errors...)
else if (type === 'attribute') Object.defineProperty(objectValue, symbols.isAttribute, { value: true, configurable: true}) // Setting type on the dataset (set as configurable to avoid downstream errors...)
// else console.error('Failed to handle type', type, path)
// Set the value on the aggregator
Object.defineProperty(aggregator, name, {value: objectValue, enumerable: true, configurable: true})
}
// Add to inheritance tree
if (inherit.value && inherit.type) this._inheritanceTree.add(inherit.value, name, 'classes')
}
// Indicate typed children on the aggregator
else if (inheritedType) {
if (!aggregator[hasTypedChildren]) Object.defineProperty(aggregator, hasTypedChildren, { value: new Set() })
aggregator[hasTypedChildren].add(inheritedType)
}
const aggregated = (name) ? aggregator[name] : aggregator
// Set properties
const set = (o: any, type:string) => this._setFromObject(o, aggregated, type, newPath)
const keys = Object.keys(o)
keys.forEach(key => {
if (key === 'attributes') o.attributes.forEach((attr: AttributeType) => set(attr, 'attribute'))
else if (key === 'groups') o.groups.forEach((group: GroupType) => set(group, 'group'))
else if (key === 'links') o.links.forEach((link: LinkType) => set(link, 'link'))
else if (key === 'datasets') o.datasets.forEach((dataset: DatasetType) => set(dataset, 'dataset'))
if (name) Object.defineProperty(aggregated, key, { value: o[key] }) // NOTE: This change will limit assignments TO AVOID MULTIPLE DECLARATIONS
// if (aggregated) Object.defineProperty(aggregated, key, { value: o[key] })
})
return aggregator
}
_generate(spec: any = this._registry, key?: string) {
if (!this._options.coreName) {
this._options.coreName = 'core'
spec = {core: spec} // nest core in a root specification
}
let keys = (key) ? [key] : Object.keys(spec)
keys.forEach((key: any) => {
const o = JSON.parse(JSON.stringify(spec[key])) // Deep Copy Spec
const isFormatted = !!o.namespace
const version = (!o.namespace) ? o[Object.keys(o)[0]] : o // File OR Specification Format
// Account for File vs Schema Specification Formats
const namespaceInfo = version?.namespace // File OR Specification Format
const namespace = (typeof namespaceInfo === 'string') ? JSON.parse(namespaceInfo) : namespaceInfo
if (namespace) {
namespace.namespaces.forEach((namespace: any) => {
const scopedSpec: ArbitraryObject = {}
const tick = performance.now()
if (namespace.name !== this._options.coreName && this._options.debug) console.log(`[${this._options.name}]: Loading ${namespace.name} extension.`)
namespace.schema.forEach((schema: any) => {
// Grabbing Schema
if (schema.source) {
// Differentiate Non-Core Elements
const extension = namespace.name !== this._options.coreName
if (extension && !this.extensions[namespace.name]) this.extensions[namespace.name] = {}
// Set Schema Information
const label = this._options.getNamespaceLabel ? this._options.getNamespaceLabel(schema.source) : schema.source
const base = (extension) ? this.extensions[namespace.name] : this
base[label] = {}
// Account for File vs Schema Specification Formats
const name = this._options.getNamespaceKey ? this._options.getNamespaceKey(schema.source) : schema.source
const schemaInfo = version[name]
const info = (typeof schemaInfo === 'string') ? JSON.parse(schemaInfo) : schemaInfo
const spec = base[label] = this._setFromObject(info, undefined, undefined, [label])
for (let name in spec) this._options.onSchemaValue ? this._options.onSchemaValue(name, spec[name], namespace.name) : info
const path = [namespace.name, namespace.version, label]
// Track Object Namespaces and Paths
for (let key in spec) this._nameToSchema[key] = { namespace: namespace.name, path }
scopedSpec[label] = spec
}
})
// Generate Specification Registry
if (isFormatted) delete this._registry[key] // Delete Pre-Formatted Specs
this._registry[namespace.name] = {}
this._registry[namespace.name][namespace.version] = scopedSpec
const tock = performance.now() // show Performance
if (this._options.debug) console.log(`[${this._options.name}]: Generated ${namespace.name} in ${tock - tick} ms`)
// setting version
if (namespace.name === this._options.coreName) this._version = namespace.version
})
} else console.warn(`[${this._options.name}]: Unable to be generate API from file specification.`)
})
// ------------------ AFTER GENERATING ALL SCHEMAS ------------------
// Set classify information
this._classify.set(Object.assign({ version: this._version as string }, this._options))
// Decouple specification (while maintaining non-enumerable properties)
this._specification = JSON.parse(JSON.stringify(this._registry))
for (let spec in this._registry) {
for (let version in this._registry[spec]){
const schema = this._registry[spec][version]
for (let namespace in schema) {
this._specification[spec][version][namespace] = Object.assign({}, schema[namespace]) // Shallow copy of the current specification information
}
}
}
// Populate the class registry
this._classify.load(this._registry, {
tree: this._inheritanceTree,
type: 'classes'
}, false) // get classes for namespace (apply to reference)
// Flatten All Schema Classes
for (let clsName in this._classify.flat.classes) this[clsName] = this._classify.flat.classes[clsName]
}
}