@voxgig/apidef 8.13.0 → 8.15.0
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/bin/voxgig-apidef +1 -174
- package/dist/apidef.d.ts +2 -2
- package/dist/apidef.js +2 -2
- package/dist/apidef.js.map +1 -1
- package/dist/builder/entity/entity.d.ts +5 -1
- package/dist/builder/entity/entity.js +17 -1
- package/dist/builder/entity/entity.js.map +1 -1
- package/dist/builder/flow/flowHeuristic01.js +14 -14
- package/dist/builder/flow/flowHeuristic01.js.map +1 -1
- package/dist/cli.d.ts +38 -0
- package/dist/cli.js +279 -0
- package/dist/cli.js.map +1 -0
- package/dist/guide/guide.js +8 -5
- package/dist/guide/guide.js.map +1 -1
- package/dist/model.d.ts +35 -33
- package/dist/resolved.d.ts +10 -6
- package/dist/resolved.js +65 -15
- package/dist/resolved.js.map +1 -1
- package/dist/transform/args.js +19 -19
- package/dist/transform/args.js.map +1 -1
- package/dist/transform/clean.js +4 -0
- package/dist/transform/clean.js.map +1 -1
- package/dist/transform/contract.d.ts +0 -2
- package/dist/transform/contract.js +6 -75
- package/dist/transform/contract.js.map +1 -1
- package/dist/transform/entity.d.ts +2 -1
- package/dist/transform/entity.js +13 -1
- package/dist/transform/entity.js.map +1 -1
- package/dist/transform/field.js +64 -64
- package/dist/transform/field.js.map +1 -1
- package/dist/transform/flowstep.js +42 -42
- package/dist/transform/flowstep.js.map +1 -1
- package/dist/transform/graphql.js +8 -8
- package/dist/transform/graphql.js.map +1 -1
- package/dist/transform/operation.js +9 -9
- package/dist/transform/operation.js.map +1 -1
- package/dist/transform/select.js +13 -13
- package/dist/transform/select.js.map +1 -1
- package/dist/transform/top.js +1 -1
- package/dist/transform/top.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utility.d.ts +2 -1
- package/dist/utility.js +6 -1
- package/dist/utility.js.map +1 -1
- package/model/apidef.aon +128 -157
- package/model/guide.aon +3 -2
- package/package.json +5 -3
- package/src/apidef.ts +4 -3
- package/src/builder/entity/entity.ts +18 -1
- package/src/builder/flow/flowHeuristic01.ts +14 -14
- package/src/cli.ts +341 -0
- package/src/guide/guide.ts +8 -4
- package/src/model.ts +36 -37
- package/src/resolved.ts +62 -17
- package/src/transform/args.ts +20 -20
- package/src/transform/clean.ts +5 -0
- package/src/transform/contract.ts +6 -71
- package/src/transform/entity.ts +13 -1
- package/src/transform/field.ts +66 -69
- package/src/transform/flowstep.ts +42 -42
- package/src/transform/graphql.ts +8 -8
- package/src/transform/operation.ts +9 -9
- package/src/transform/select.ts +13 -13
- package/src/transform/top.ts +1 -1
- package/src/types.ts +1 -1
- package/src/utility.ts +8 -1
package/src/resolved.ts
CHANGED
|
@@ -2,7 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
// See docs/design/resolved-spec-capability.md
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// An operation needs its argument types, including recursive input objects.
|
|
6
|
+
// Output types are represented by the field and generated invocation selection;
|
|
7
|
+
// copying the entire connected output graph per operation is quadratic in API size.
|
|
8
|
+
function graphqlInputTypes(field: any, types: any): any {
|
|
9
|
+
const out: any = {}
|
|
10
|
+
function visit(name: string) {
|
|
11
|
+
if (!types[name] || Object.prototype.hasOwnProperty.call(out, name)) return
|
|
12
|
+
const type = types[name]
|
|
13
|
+
out[name] = type
|
|
14
|
+
if (type.kind === 'INPUT_OBJECT') {
|
|
15
|
+
for (const child of Object.values(type.fields || {}) as any[]) visit(child.type)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
for (const arg of field.args || []) visit(arg.type)
|
|
19
|
+
return out
|
|
20
|
+
}
|
|
6
21
|
|
|
7
22
|
|
|
8
23
|
const METHODS = [
|
|
@@ -16,6 +31,8 @@ type OperationFacts = {
|
|
|
16
31
|
}
|
|
17
32
|
|
|
18
33
|
|
|
34
|
+
type OperationSelector = { entity: string, op: string }
|
|
35
|
+
|
|
19
36
|
type ResolvedSpec = {
|
|
20
37
|
version: 1
|
|
21
38
|
kind: string
|
|
@@ -24,16 +41,14 @@ type ResolvedSpec = {
|
|
|
24
41
|
// during parse, so a consumer reading the file itself would miss lookups.
|
|
25
42
|
def: any
|
|
26
43
|
|
|
27
|
-
operation(method: string, path: string): OperationFacts | undefined
|
|
44
|
+
operation(method: string, path: string, selector?: OperationSelector): OperationFacts | undefined
|
|
28
45
|
}
|
|
29
46
|
|
|
30
47
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const method = path?.[String(point.method).toLowerCase()]
|
|
36
|
-
const graphql = def?.query?.[point.orig] || def?.mutation?.[point.orig]
|
|
48
|
+
function operationFacts(def: any, point: { m: string, o: string }): OperationFacts | undefined {
|
|
49
|
+
const path = def?.paths?.[point.o]
|
|
50
|
+
const method = path?.[String(point.m).toLowerCase()]
|
|
51
|
+
const graphql = def?.query?.[point.o] || def?.mutation?.[point.o]
|
|
37
52
|
|
|
38
53
|
if (!method && !graphql) return undefined
|
|
39
54
|
|
|
@@ -67,21 +82,21 @@ function operationFacts(def: any, point: { method: string, orig: string }): Oper
|
|
|
67
82
|
}
|
|
68
83
|
|
|
69
84
|
|
|
70
|
-
// Every described operation, keyed 'METHOD path' as `point.
|
|
85
|
+
// Every described operation, keyed 'METHOD path' as `point.co.id` is.
|
|
71
86
|
function operationIndex(def: any): { [id: string]: OperationFacts } {
|
|
72
87
|
const out: { [id: string]: OperationFacts } = {}
|
|
73
88
|
|
|
74
89
|
for (const path of Object.keys(def?.paths || {})) {
|
|
75
90
|
for (const method of METHODS) {
|
|
76
91
|
if (null == def.paths[path]?.[method]) continue
|
|
77
|
-
const facts = operationFacts(def, { method,
|
|
92
|
+
const facts = operationFacts(def, { m: method, o: path })
|
|
78
93
|
if (facts) out[method.toUpperCase() + ' ' + path] = facts
|
|
79
94
|
}
|
|
80
95
|
}
|
|
81
96
|
|
|
82
97
|
for (const kind of ['query', 'mutation']) {
|
|
83
98
|
for (const field of Object.keys(def?.[kind] || {})) {
|
|
84
|
-
const facts = operationFacts(def, {
|
|
99
|
+
const facts = operationFacts(def, { m: 'POST', o: field })
|
|
85
100
|
if (facts) out['POST ' + field] = facts
|
|
86
101
|
}
|
|
87
102
|
}
|
|
@@ -90,20 +105,49 @@ function operationIndex(def: any): { [id: string]: OperationFacts } {
|
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
|
|
93
|
-
function
|
|
108
|
+
function operationGuide(guide: any, method: string, path: string, graphql: boolean,
|
|
109
|
+
selector?: OperationSelector): any {
|
|
110
|
+
const matches: any[] = []
|
|
111
|
+
for (const [entityName, entity] of Object.entries(guide?.entity || {}) as any) {
|
|
112
|
+
if (selector && selector.entity !== entityName) continue
|
|
113
|
+
const ops = entity[graphql ? 'field' : 'path']?.[path]?.op || {}
|
|
114
|
+
for (const [opName, op] of Object.entries(ops) as any) {
|
|
115
|
+
if (selector && selector.op !== opName) continue
|
|
116
|
+
if (op.method && op.method.toUpperCase() !== method.toUpperCase()) continue
|
|
117
|
+
if (op.contract !== undefined || op.live !== undefined) matches.push(op)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (matches.length > 1) {
|
|
121
|
+
throw new Error('Ambiguous operation guide for ' + method + ' ' + path + '; select entity and op')
|
|
122
|
+
}
|
|
123
|
+
return matches[0]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function makeResolved(kind: string, def: any, guide: () => any = () => undefined): ResolvedSpec {
|
|
94
127
|
return {
|
|
95
128
|
version: 1,
|
|
96
129
|
kind,
|
|
97
130
|
def,
|
|
98
|
-
operation: (method: string, path: string) =>
|
|
99
|
-
operationFacts(def, { method,
|
|
131
|
+
operation: (method: string, path: string, selector?: OperationSelector) => {
|
|
132
|
+
const facts = operationFacts(def, { m: method, o: path })
|
|
133
|
+
if (!facts) return undefined
|
|
134
|
+
const op = operationGuide(guide(), method, path, facts.protocol === 'graphql', selector)
|
|
135
|
+
for (const key of ['requestBody', 'responses', 'parameters', 'security']) {
|
|
136
|
+
if (op?.contract?.[key] !== undefined) {
|
|
137
|
+
facts[key] = op.contract[key]
|
|
138
|
+
;(facts.factSources ??= {})[key] = 'guide'
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (op?.live !== undefined) facts.live = op.live
|
|
142
|
+
return facts
|
|
143
|
+
},
|
|
100
144
|
}
|
|
101
145
|
}
|
|
102
146
|
|
|
103
147
|
|
|
104
148
|
// Tolerates a missing context: apidef also runs outside a model build.
|
|
105
|
-
function publishResolved(ctx: any, kind: string, def: any): ResolvedSpec {
|
|
106
|
-
const resolved = makeResolved(kind, def)
|
|
149
|
+
function publishResolved(ctx: any, kind: string, def: any, guide?: () => any): ResolvedSpec {
|
|
150
|
+
const resolved = makeResolved(kind, def, guide)
|
|
107
151
|
if (null != ctx && 'object' === typeof ctx) {
|
|
108
152
|
ctx.state = ctx.state || {}
|
|
109
153
|
ctx.state.apidef = { ...(ctx.state.apidef || {}), resolved }
|
|
@@ -114,7 +158,7 @@ function publishResolved(ctx: any, kind: string, def: any): ResolvedSpec {
|
|
|
114
158
|
|
|
115
159
|
function resolvedSpec(carrier: any): ResolvedSpec | undefined {
|
|
116
160
|
if (null == carrier || 'object' !== typeof carrier) return undefined
|
|
117
|
-
return carrier.state?.apidef?.resolved ??
|
|
161
|
+
return carrier.resolved ?? carrier.ctx?.resolved ?? carrier.state?.apidef?.resolved ??
|
|
118
162
|
carrier.ctx?.state?.apidef?.resolved ??
|
|
119
163
|
carrier.apidef?.resolved ??
|
|
120
164
|
undefined
|
|
@@ -122,6 +166,7 @@ function resolvedSpec(carrier: any): ResolvedSpec | undefined {
|
|
|
122
166
|
|
|
123
167
|
|
|
124
168
|
export type {
|
|
169
|
+
OperationSelector,
|
|
125
170
|
OperationFacts,
|
|
126
171
|
ResolvedSpec,
|
|
127
172
|
}
|
package/src/transform/args.ts
CHANGED
|
@@ -41,7 +41,7 @@ const argsTransform: Transform = async function(
|
|
|
41
41
|
each(mop.points, (mpoint: ModelPoint) => {
|
|
42
42
|
const argdefs: ParameterDef[] = []
|
|
43
43
|
|
|
44
|
-
if ('graphql' === mpoint.
|
|
44
|
+
if ('graphql' === mpoint.k) {
|
|
45
45
|
// GraphQL root-field arguments become 'param' args, so the existing
|
|
46
46
|
// arg machinery (select.exist matching, request typing, test
|
|
47
47
|
// generation) works on them unchanged. Input-object arguments are
|
|
@@ -64,10 +64,10 @@ const argsTransform: Transform = async function(
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
else {
|
|
67
|
-
const pathdef: PathDef = def.paths[mpoint.
|
|
67
|
+
const pathdef: PathDef = def.paths[mpoint.o]
|
|
68
68
|
argdefs.push(...((pathdef as any)?.parameters ?? []))
|
|
69
69
|
|
|
70
|
-
const opdef: MethodDef = (pathdef as any)?.[mpoint.
|
|
70
|
+
const opdef: MethodDef = (pathdef as any)?.[mpoint.m.toLowerCase()]
|
|
71
71
|
argdefs.push(...(opdef?.parameters ?? []))
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -85,8 +85,8 @@ const argsTransform: Transform = async function(
|
|
|
85
85
|
|
|
86
86
|
// Locate the normalised root-field descriptor a GraphQL point came from.
|
|
87
87
|
function graphqlFieldDef(def: any, mpoint: ModelPoint): any {
|
|
88
|
-
const field = mpoint.
|
|
89
|
-
return 'mutation' === mpoint.
|
|
88
|
+
const field = mpoint.gq?.field ?? mpoint.o
|
|
89
|
+
return 'mutation' === mpoint.gq?.optype ?
|
|
90
90
|
def.mutation?.[field] : def.query?.[field]
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -100,7 +100,7 @@ function gqlScalarType(typeName: string): string | undefined {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
const ARG_KIND: Record<string, ModelArg["
|
|
103
|
+
const ARG_KIND: Record<string, ModelArg["k"]> = {
|
|
104
104
|
'query': 'query',
|
|
105
105
|
'header': 'header',
|
|
106
106
|
'path': 'param',
|
|
@@ -122,11 +122,11 @@ function resolveArgs(
|
|
|
122
122
|
const ref = (argdef as any)?.$ref
|
|
123
123
|
ctx?.warn?.({
|
|
124
124
|
note: `Parameter with no name on entity=${ment.name} op=${mop.name}` +
|
|
125
|
-
` path=${mpoint.
|
|
125
|
+
` path=${mpoint.o} is dropped` +
|
|
126
126
|
(null == ref ? '.' : `: \`$ref\` "${ref}" resolves to nothing.`) +
|
|
127
127
|
' A parameter needs a `name`, or a reference that resolves to one.',
|
|
128
128
|
entity: ment.name,
|
|
129
|
-
path: mpoint.
|
|
129
|
+
path: mpoint.o,
|
|
130
130
|
op: mop.name,
|
|
131
131
|
})
|
|
132
132
|
return
|
|
@@ -136,35 +136,35 @@ function resolveArgs(
|
|
|
136
136
|
// Rename map can be keyed by either the spec original (camelCase) or by
|
|
137
137
|
// the snakified form depending on which path went through heuristic01.
|
|
138
138
|
// Try both before falling through to `orig`.
|
|
139
|
-
const renameMap = mpoint.
|
|
139
|
+
const renameMap = mpoint.r[kind]
|
|
140
140
|
const name = renameMap?.[specName] ?? renameMap?.[orig] ?? orig
|
|
141
141
|
const marg: ModelArg = {
|
|
142
|
-
name,
|
|
143
|
-
orig,
|
|
144
|
-
|
|
145
|
-
kind,
|
|
146
|
-
|
|
142
|
+
n: name,
|
|
143
|
+
or: orig,
|
|
144
|
+
t: inferFieldType(name, validator(argdef.schema?.type)),
|
|
145
|
+
k: kind,
|
|
146
|
+
r: !!argdef.required
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
const example = resolveArgExample(argdef)
|
|
150
150
|
if (undefined !== example) {
|
|
151
|
-
marg.
|
|
151
|
+
marg.ex = example
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
if (argdef.nullable) {
|
|
155
|
-
marg.
|
|
155
|
+
marg.t = ['`$ONE`', '`$NULL`', marg.t]
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
const argsKey = (marg.
|
|
159
|
-
let kindargs = (mpoint.
|
|
158
|
+
const argsKey = (marg.k === 'param' ? 'params' : marg.k) as keyof typeof mpoint.g
|
|
159
|
+
let kindargs = (mpoint.g[argsKey] = mpoint.g[argsKey] ?? [])
|
|
160
160
|
kindargs.push(marg)
|
|
161
161
|
touchedKeys.add(argsKey)
|
|
162
162
|
})
|
|
163
163
|
|
|
164
164
|
// Sort once after all args are collected
|
|
165
|
-
const cmp = (a: ModelArg, b: ModelArg) => a.
|
|
165
|
+
const cmp = (a: ModelArg, b: ModelArg) => a.n < b.n ? -1 : a.n > b.n ? 1 : 0
|
|
166
166
|
for (const key of touchedKeys) {
|
|
167
|
-
mpoint.
|
|
167
|
+
mpoint.g[key as keyof typeof mpoint.g]?.sort(cmp)
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
|
package/src/transform/clean.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import type { TransformResult, Transform } from '../transform'
|
|
3
|
+
import { KIT } from '../types'
|
|
3
4
|
|
|
4
5
|
import { walk, isempty, isnode, ismap, islist } from '@voxgig/struct'
|
|
5
6
|
|
|
@@ -54,6 +55,10 @@ const cleanTransform: Transform = async function(
|
|
|
54
55
|
|
|
55
56
|
ctx.apimodel = cur[0]
|
|
56
57
|
|
|
58
|
+
for (const entity of Object.values(ctx.apimodel.main?.[KIT]?.entity ?? {}) as any[]) {
|
|
59
|
+
entity.fields ??= {}
|
|
60
|
+
}
|
|
61
|
+
|
|
57
62
|
return { ok: true, msg: 'clean' }
|
|
58
63
|
}
|
|
59
64
|
|
|
@@ -1,89 +1,24 @@
|
|
|
1
1
|
// Point contracts. See docs/design/resolved-spec-capability.md
|
|
2
2
|
import type { Transform } from '../transform'
|
|
3
3
|
|
|
4
|
-
import { operationFacts } from '../resolved'
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export function contractJSON(value: any): string {
|
|
8
|
-
function walk(root: any, base: string): any {
|
|
9
|
-
// One memo per fact, so refs stay local to it.
|
|
10
|
-
const seen = new Map<any, string>()
|
|
11
|
-
function copy(v: any, path: string): any {
|
|
12
|
-
if (v === null || typeof v !== 'object') return v
|
|
13
|
-
if (seen.has(v)) return { $ref: seen.get(v) }
|
|
14
|
-
seen.set(v, path)
|
|
15
|
-
const out: any = Array.isArray(v) ? v.map((item, i) => copy(item, path + '/' + i)) : {}
|
|
16
|
-
if (!Array.isArray(v)) for (const k of Object.keys(v).sort()) {
|
|
17
|
-
if (!k.endsWith('$') && !k.startsWith('x-') && undefined !== v[k]) out[k] = copy(v[k], path + '/' + k.replace(/~/g, '~0').replace(/\//g, '~1'))
|
|
18
|
-
}
|
|
19
|
-
return out
|
|
20
|
-
}
|
|
21
|
-
return copy(root, base)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (null === value || 'object' !== typeof value || Array.isArray(value)) {
|
|
25
|
-
return JSON.stringify(walk(value, '#'))
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const out: any = {}
|
|
29
|
-
for (const k of Object.keys(value).sort()) {
|
|
30
|
-
if (k.endsWith('$') || k.startsWith('x-') || undefined === value[k]) continue
|
|
31
|
-
out[k] = walk(value[k], '#/' + k.replace(/~/g, '~0').replace(/\//g, '~1'))
|
|
32
|
-
}
|
|
33
|
-
return JSON.stringify(out)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// An operation needs its argument types, including recursive input objects.
|
|
37
|
-
// Output types are represented by the field and generated invocation selection;
|
|
38
|
-
// copying the entire connected output graph per operation is quadratic in API size.
|
|
39
|
-
export function graphqlInputTypes(field: any, types: any): any {
|
|
40
|
-
const out: any = {}
|
|
41
|
-
function visit(name: string) {
|
|
42
|
-
if (!types[name] || Object.prototype.hasOwnProperty.call(out, name)) return
|
|
43
|
-
const type = types[name]
|
|
44
|
-
out[name] = type
|
|
45
|
-
if (type.kind === 'INPUT_OBJECT') {
|
|
46
|
-
for (const child of Object.values(type.fields || {}) as any[]) visit(child.type)
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
for (const arg of field.args || []) visit(arg.type)
|
|
50
|
-
return out
|
|
51
|
-
}
|
|
52
|
-
|
|
53
4
|
export const contractTransform: Transform = async (ctx: any) => {
|
|
54
5
|
const def = ctx.def || {}
|
|
55
6
|
for (const entity of Object.values(ctx.apimodel.main.kit.entity || {}) as any[]) {
|
|
56
7
|
for (const op of Object.values(entity.op || {}) as any[]) {
|
|
57
8
|
for (const point of op?.points || []) {
|
|
58
|
-
const path = def.paths?.[point.
|
|
59
|
-
const method = path?.[point.
|
|
60
|
-
const graphql = def.query?.[point.
|
|
9
|
+
const path = def.paths?.[point.o]
|
|
10
|
+
const method = path?.[point.m.toLowerCase()]
|
|
11
|
+
const graphql = def.query?.[point.o] || def.mutation?.[point.o]
|
|
61
12
|
if (!method && !graphql) continue
|
|
62
|
-
const
|
|
63
|
-
if (null == facts) continue
|
|
64
|
-
|
|
65
|
-
// A property of the point, not of the definition.
|
|
66
|
-
if (graphql) facts.invocation = point.graphql
|
|
67
|
-
const guideOp = ctx.guide?.entity?.[entity.name]?.[graphql ? 'field' : 'path']?.[point.orig]?.op?.[op.name]
|
|
13
|
+
const guideOp = ctx.guide?.entity?.[entity.name]?.[graphql ? 'field' : 'path']?.[point.o]?.op?.[op.name]
|
|
68
14
|
const hint = guideOp?.live
|
|
69
|
-
for (const key of ['requestBody', 'responses', 'parameters', 'security']) {
|
|
70
|
-
if (guideOp?.contract?.[key] !== undefined) {
|
|
71
|
-
facts[key] = guideOp.contract[key]
|
|
72
|
-
;(facts.factSources ??= {})[key] = 'guide'
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
15
|
if (hint !== undefined) {
|
|
76
|
-
|
|
77
|
-
point.live = hint
|
|
16
|
+
point.li = hint
|
|
78
17
|
}
|
|
79
18
|
// Identity only; facts come from the capability. See
|
|
80
19
|
// docs/design/resolved-spec-capability.md
|
|
81
|
-
point.
|
|
20
|
+
point.co = { version: 2, id: point.m + ' ' + point.o,
|
|
82
21
|
source: graphql ? 'graphql' : def.swagger ? 'swagger2' : 'openapi3' }
|
|
83
|
-
|
|
84
|
-
if (ctx.opts?.contractJson) {
|
|
85
|
-
point.contract.json = contractJSON(facts)
|
|
86
|
-
}
|
|
87
22
|
}
|
|
88
23
|
}
|
|
89
24
|
}
|
package/src/transform/entity.ts
CHANGED
|
@@ -59,7 +59,7 @@ const entityTransform: Transform = async function(
|
|
|
59
59
|
const modelent: ModelEntity = {
|
|
60
60
|
name: entname,
|
|
61
61
|
op: {},
|
|
62
|
-
fields:
|
|
62
|
+
fields: {},
|
|
63
63
|
relations,
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -68,9 +68,20 @@ const entityTransform: Transform = async function(
|
|
|
68
68
|
msg += guideEntity.name + ' '
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
+
filterEntityAncestors(kit.entity)
|
|
71
72
|
return { ok: true, msg }
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
function filterEntityAncestors(entities: Record<string, any>) {
|
|
76
|
+
for (const [name, entity] of Object.entries(entities)) {
|
|
77
|
+
if (null == entity.relations) continue
|
|
78
|
+
entity.relations.ancestors = (entity.relations.ancestors ?? [])
|
|
79
|
+
.map((chain: string[]) => chain.filter(ancestor => ancestor !== name &&
|
|
80
|
+
Object.prototype.hasOwnProperty.call(entities, ancestor)))
|
|
81
|
+
.filter((chain: string[]) => 0 < chain.length)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
74
85
|
|
|
75
86
|
// Move "/X" paths onto the entity that owns "/X/{id}" or "/X/{id}/sub".
|
|
76
87
|
// Only acts when the path "/X" sits on a different entity than the
|
|
@@ -301,6 +312,7 @@ function suffix(p: string[], c: string[]): boolean {
|
|
|
301
312
|
|
|
302
313
|
|
|
303
314
|
export {
|
|
315
|
+
filterEntityAncestors,
|
|
304
316
|
resolvePathList,
|
|
305
317
|
buildRelations,
|
|
306
318
|
entityTransform,
|