@api-client/core 0.19.23 → 0.19.25
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/build/src/browser.d.ts +3 -0
- package/build/src/browser.d.ts.map +1 -1
- package/build/src/browser.js +2 -0
- package/build/src/browser.js.map +1 -1
- package/build/src/index.d.ts +5 -0
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +4 -0
- package/build/src/index.js.map +1 -1
- package/build/src/mocking/ModelingMock.d.ts +2 -0
- package/build/src/mocking/ModelingMock.d.ts.map +1 -1
- package/build/src/mocking/ModelingMock.js +2 -0
- package/build/src/mocking/ModelingMock.js.map +1 -1
- package/build/src/mocking/lib/Deployment.d.ts +16 -0
- package/build/src/mocking/lib/Deployment.d.ts.map +1 -0
- package/build/src/mocking/lib/Deployment.js +76 -0
- package/build/src/mocking/lib/Deployment.js.map +1 -0
- package/build/src/modeling/Bindings.d.ts +4 -0
- package/build/src/modeling/Bindings.d.ts.map +1 -1
- package/build/src/modeling/Bindings.js.map +1 -1
- package/build/src/modeling/DataFormat.d.ts +1 -1
- package/build/src/modeling/DataFormat.d.ts.map +1 -1
- package/build/src/modeling/DataFormat.js +2 -0
- package/build/src/modeling/DataFormat.js.map +1 -1
- package/build/src/modeling/DomainAssociation.d.ts +7 -0
- package/build/src/modeling/DomainAssociation.d.ts.map +1 -1
- package/build/src/modeling/DomainAssociation.js +10 -0
- package/build/src/modeling/DomainAssociation.js.map +1 -1
- package/build/src/modeling/DomainEntity.d.ts +9 -1
- package/build/src/modeling/DomainEntity.d.ts.map +1 -1
- package/build/src/modeling/DomainEntity.js +26 -1
- package/build/src/modeling/DomainEntity.js.map +1 -1
- package/build/src/modeling/ExposedEntity.d.ts +12 -1
- package/build/src/modeling/ExposedEntity.d.ts.map +1 -1
- package/build/src/modeling/ExposedEntity.js +24 -1
- package/build/src/modeling/ExposedEntity.js.map +1 -1
- package/build/src/modeling/RuntimeApiModel.d.ts +52 -0
- package/build/src/modeling/RuntimeApiModel.d.ts.map +1 -0
- package/build/src/modeling/RuntimeApiModel.js +85 -0
- package/build/src/modeling/RuntimeApiModel.js.map +1 -0
- package/build/src/modeling/actions/index.d.ts +10 -0
- package/build/src/modeling/actions/index.d.ts.map +1 -1
- package/build/src/modeling/actions/index.js +30 -0
- package/build/src/modeling/actions/index.js.map +1 -1
- package/build/src/modeling/index.d.ts.map +1 -1
- package/build/src/modeling/index.js +1 -0
- package/build/src/modeling/index.js.map +1 -1
- package/build/src/modeling/types.d.ts +25 -1
- package/build/src/modeling/types.d.ts.map +1 -1
- package/build/src/modeling/types.js.map +1 -1
- package/build/src/models/kinds.d.ts +1 -0
- package/build/src/models/kinds.d.ts.map +1 -1
- package/build/src/models/kinds.js +1 -0
- package/build/src/models/kinds.js.map +1 -1
- package/build/src/models/store/CustomDomain.d.ts +50 -0
- package/build/src/models/store/CustomDomain.d.ts.map +1 -0
- package/build/src/models/store/CustomDomain.js +79 -0
- package/build/src/models/store/CustomDomain.js.map +1 -0
- package/build/src/models/store/Deployment.d.ts +118 -0
- package/build/src/models/store/Deployment.d.ts.map +1 -0
- package/build/src/models/store/Deployment.js +182 -0
- package/build/src/models/store/Deployment.js.map +1 -0
- package/build/src/models/store/DeploymentCustomDomain.d.ts +52 -0
- package/build/src/models/store/DeploymentCustomDomain.d.ts.map +1 -0
- package/build/src/models/store/DeploymentCustomDomain.js +84 -0
- package/build/src/models/store/DeploymentCustomDomain.js.map +1 -0
- package/build/src/sdk/DataCatalogSdk.d.ts.map +1 -1
- package/build/src/sdk/DataCatalogSdk.js +22 -179
- package/build/src/sdk/DataCatalogSdk.js.map +1 -1
- package/build/src/sdk/DeploymentsSdk.d.ts +48 -0
- package/build/src/sdk/DeploymentsSdk.d.ts.map +1 -0
- package/build/src/sdk/DeploymentsSdk.js +94 -0
- package/build/src/sdk/DeploymentsSdk.js.map +1 -0
- package/build/src/sdk/RouteBuilder.d.ts +2 -0
- package/build/src/sdk/RouteBuilder.d.ts.map +1 -1
- package/build/src/sdk/RouteBuilder.js +6 -0
- package/build/src/sdk/RouteBuilder.js.map +1 -1
- package/build/src/sdk/Sdk.d.ts +2 -0
- package/build/src/sdk/Sdk.d.ts.map +1 -1
- package/build/src/sdk/Sdk.js +2 -0
- package/build/src/sdk/Sdk.js.map +1 -1
- package/build/src/sdk/SdkBase.d.ts +19 -1
- package/build/src/sdk/SdkBase.d.ts.map +1 -1
- package/build/src/sdk/SdkBase.js +31 -1
- package/build/src/sdk/SdkBase.js.map +1 -1
- package/build/src/sdk/SdkMock.d.ts +9 -0
- package/build/src/sdk/SdkMock.d.ts.map +1 -1
- package/build/src/sdk/SdkMock.js +73 -0
- package/build/src/sdk/SdkMock.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -1
- package/src/matchit.d.ts +19 -0
- package/src/mocking/ModelingMock.ts +2 -0
- package/src/mocking/lib/Deployment.ts +88 -0
- package/src/modeling/Bindings.ts +4 -0
- package/src/modeling/DataFormat.ts +4 -0
- package/src/modeling/DomainAssociation.ts +11 -0
- package/src/modeling/DomainEntity.ts +30 -1
- package/src/modeling/ExposedEntity.ts +26 -1
- package/src/modeling/RuntimeApiModel.ts +137 -0
- package/src/modeling/types.ts +26 -1
- package/src/models/kinds.ts +1 -0
- package/src/models/store/CustomDomain.ts +119 -0
- package/src/models/store/Deployment.ts +250 -0
- package/src/models/store/DeploymentCustomDomain.ts +120 -0
- package/src/sdk/DataCatalogSdk.ts +22 -176
- package/src/sdk/DeploymentsSdk.ts +123 -0
- package/src/sdk/RouteBuilder.ts +8 -0
- package/src/sdk/Sdk.ts +3 -0
- package/src/sdk/SdkBase.ts +35 -3
- package/src/sdk/SdkMock.ts +103 -0
- package/tests/unit/modeling/RuntimeApiModel.spec.ts +122 -0
- package/tests/unit/modeling/actions/index.spec.ts +113 -0
- package/tests/unit/modeling/domain_asociation.spec.ts +28 -0
- package/tests/unit/modeling/domain_entity_parents.spec.ts +49 -0
- package/tests/unit/modeling/exposed_entity_actions.spec.ts +47 -0
- package/tests/unit/models/store/CustomDomain.spec.ts +111 -0
- package/tests/unit/models/store/Deployment.spec.ts +198 -0
- package/tests/unit/models/store/DeploymentCustomDomain.spec.ts +122 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { SdkBase, type SdkOptions, E_RESPONSE_STATUS } from './SdkBase.js'
|
|
2
|
+
import { RouteBuilder } from './RouteBuilder.js'
|
|
3
|
+
import { DeploymentEnvironment, DeploymentKind, DeploymentSchema } from '../models/store/Deployment.js'
|
|
4
|
+
import { Exception } from '../exceptions/exception.js'
|
|
5
|
+
import type { ContextListOptions, ContextListResult } from '../events/BaseEvents.js'
|
|
6
|
+
|
|
7
|
+
export interface CreateDeploymentPayload {
|
|
8
|
+
/**
|
|
9
|
+
* API file ID
|
|
10
|
+
*/
|
|
11
|
+
fid: string
|
|
12
|
+
/**
|
|
13
|
+
* API deployment version (e.g. "v1", "v2")
|
|
14
|
+
* It is optional for non production environments (e.g. 'dev').
|
|
15
|
+
*/
|
|
16
|
+
version?: string
|
|
17
|
+
/**
|
|
18
|
+
* API model version (e.g. "3.1.2", "4.0.0")
|
|
19
|
+
*/
|
|
20
|
+
modelVersion: string
|
|
21
|
+
/**
|
|
22
|
+
* Target environment tag (e.g. "prod", "staging", "dev")
|
|
23
|
+
*/
|
|
24
|
+
env: DeploymentEnvironment
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class DeploymentsSdk extends SdkBase {
|
|
28
|
+
async list(
|
|
29
|
+
oid: string,
|
|
30
|
+
options: ContextListOptions = {},
|
|
31
|
+
request: SdkOptions = {}
|
|
32
|
+
): Promise<ContextListResult<DeploymentSchema>> {
|
|
33
|
+
const { token = this.sdk.token } = request
|
|
34
|
+
const url = this.sdk.getUrl(RouteBuilder.deployments(oid))
|
|
35
|
+
this.sdk.appendListOptions(url, options)
|
|
36
|
+
const result = await this.sdk.http.get(url.toString(), { token })
|
|
37
|
+
return this.processListResponse<DeploymentSchema>(result, 'Unable to list deployments. ')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Triggers the creation of a new deployment.
|
|
42
|
+
*
|
|
43
|
+
* @param oid The parent organization key
|
|
44
|
+
* @param payload The deployment payload
|
|
45
|
+
* @param request Optional SDK options
|
|
46
|
+
*/
|
|
47
|
+
async create(oid: string, payload: CreateDeploymentPayload, request: SdkOptions = {}): Promise<DeploymentSchema> {
|
|
48
|
+
// Let's do some basic validation before hitting the API endpoint.
|
|
49
|
+
if (payload.env === DeploymentEnvironment.PROD && !payload.version) {
|
|
50
|
+
throw new Exception(`Version must not be empty for production environment: ${payload.env}`, {
|
|
51
|
+
code: 'INVALID_DEPLOYMENT_VERSION',
|
|
52
|
+
help: 'Please provide a version for production environment',
|
|
53
|
+
status: 400,
|
|
54
|
+
})
|
|
55
|
+
} else if (payload.env !== DeploymentEnvironment.PROD && payload.version) {
|
|
56
|
+
throw new Exception(`Version must not be present for non-production environment: ${payload.env}`, {
|
|
57
|
+
code: 'INVALID_DEPLOYMENT_VERSION',
|
|
58
|
+
help: 'Please remove the version for non-production environment',
|
|
59
|
+
status: 400,
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
const url = this.sdk.getUrl(RouteBuilder.deployments(oid))
|
|
63
|
+
const { token = this.sdk.token } = request
|
|
64
|
+
const result = await this.sdk.http.post(url.toString(), {
|
|
65
|
+
token,
|
|
66
|
+
body: JSON.stringify(payload),
|
|
67
|
+
headers: {
|
|
68
|
+
'content-type': 'application/json',
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
this.inspectCommonStatusCodes(result)
|
|
73
|
+
const E_PREFIX = 'Unable to create a deployment. '
|
|
74
|
+
// It is accepted for async processing.
|
|
75
|
+
if (result.status !== 202) {
|
|
76
|
+
this.logInvalidResponse(result)
|
|
77
|
+
throw this.createApiError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.body)
|
|
78
|
+
}
|
|
79
|
+
const data = this.readResponseJSON<DeploymentSchema>(result, E_PREFIX)
|
|
80
|
+
this.assertObjectKind(E_PREFIX, DeploymentKind, data.kind)
|
|
81
|
+
return data
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Reads the status of an API deployment.
|
|
86
|
+
*
|
|
87
|
+
* @param oid The parent organization key
|
|
88
|
+
* @param did The deployment ID
|
|
89
|
+
* @param request Optional SDK options
|
|
90
|
+
*/
|
|
91
|
+
async read(oid: string, did: string, request: SdkOptions = {}): Promise<DeploymentSchema> {
|
|
92
|
+
const url = this.sdk.getUrl(RouteBuilder.deployment(oid, did))
|
|
93
|
+
const { token = this.sdk.token } = request
|
|
94
|
+
const result = await this.sdk.http.get(url.toString(), { token })
|
|
95
|
+
|
|
96
|
+
this.inspectCommonStatusCodes(result)
|
|
97
|
+
const E_PREFIX = 'Unable to read a deployment. '
|
|
98
|
+
if (result.status !== 200) {
|
|
99
|
+
this.logInvalidResponse(result)
|
|
100
|
+
throw this.createApiError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.body)
|
|
101
|
+
}
|
|
102
|
+
const data = this.readResponseJSON<DeploymentSchema>(result, E_PREFIX)
|
|
103
|
+
this.assertObjectKind(E_PREFIX, DeploymentKind, data.kind)
|
|
104
|
+
return data
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Deactivates a deployment. Essentially it disables the version of an API.
|
|
109
|
+
* @param oid The organization ID
|
|
110
|
+
* @param did The deployment ID
|
|
111
|
+
*/
|
|
112
|
+
async deactivate(oid: string, did: string, request: SdkOptions = {}): Promise<void> {
|
|
113
|
+
const url = this.sdk.getUrl(RouteBuilder.deployment(oid, did))
|
|
114
|
+
const { token = this.sdk.token } = request
|
|
115
|
+
const result = await this.sdk.http.delete(url.toString(), { token })
|
|
116
|
+
this.inspectCommonStatusCodes(result)
|
|
117
|
+
const E_PREFIX = 'Unable to deactivate a deployment. '
|
|
118
|
+
if (result.status !== 204) {
|
|
119
|
+
this.logInvalidResponse(result)
|
|
120
|
+
throw this.createApiError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.body)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
package/src/sdk/RouteBuilder.ts
CHANGED
|
@@ -334,4 +334,12 @@ export class RouteBuilder {
|
|
|
334
334
|
static orgSlugValidate(): string {
|
|
335
335
|
return `/v1/orgs/slugs/validate`
|
|
336
336
|
}
|
|
337
|
+
|
|
338
|
+
static deployments(oid: string): string {
|
|
339
|
+
return `/v1/orgs/${oid}/deployments`
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
static deployment(oid: string, deploymentId: string): string {
|
|
343
|
+
return `${RouteBuilder.deployments(oid)}/${deploymentId}`
|
|
344
|
+
}
|
|
337
345
|
}
|
package/src/sdk/Sdk.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { OrganizationsSdk } from './OrganizationsSdk.js'
|
|
|
13
13
|
import { DataCatalogSdk } from './DataCatalogSdk.js'
|
|
14
14
|
import { GroupsSdk } from './GroupsSdk.js'
|
|
15
15
|
import { AiSdk } from './AiSdk.js'
|
|
16
|
+
import { DeploymentsSdk } from './DeploymentsSdk.js'
|
|
16
17
|
|
|
17
18
|
const baseUriSymbol = Symbol('baseUri')
|
|
18
19
|
|
|
@@ -84,6 +85,8 @@ export abstract class Sdk {
|
|
|
84
85
|
groups = new GroupsSdk(this)
|
|
85
86
|
|
|
86
87
|
ai = new AiSdk(this)
|
|
88
|
+
|
|
89
|
+
deployments = new DeploymentsSdk(this)
|
|
87
90
|
/**
|
|
88
91
|
* When set it limits log output to minimum.
|
|
89
92
|
*/
|
package/src/sdk/SdkBase.ts
CHANGED
|
@@ -208,7 +208,7 @@ export class SdkBase {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
protected readResponseBody(response: IStoreResponse, errorPrefix: string): string {
|
|
211
|
-
if (response.status
|
|
211
|
+
if (![200, 202].includes(response.status)) {
|
|
212
212
|
this.logInvalidResponse(response)
|
|
213
213
|
throw this.createApiError(`${errorPrefix}${E_RESPONSE_STATUS}${response.status}`, response.body)
|
|
214
214
|
}
|
|
@@ -221,9 +221,15 @@ export class SdkBase {
|
|
|
221
221
|
return response.body
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
|
|
224
|
+
/**
|
|
225
|
+
* A helper function that asserts the `body` is set and it is a valid JSON.
|
|
226
|
+
* @param response The response that is expected to have a valid JSON on the `body`
|
|
227
|
+
* @param errorPrefix The error prefix for the errors thrown
|
|
228
|
+
* @returns The valid JSON response object
|
|
229
|
+
*/
|
|
230
|
+
protected readResponseJSON<T = unknown>(response: IStoreResponse, errorPrefix: string): T {
|
|
225
231
|
const body = this.readResponseBody(response, errorPrefix)
|
|
226
|
-
let data:
|
|
232
|
+
let data: T
|
|
227
233
|
try {
|
|
228
234
|
data = JSON.parse(body)
|
|
229
235
|
} catch {
|
|
@@ -231,4 +237,30 @@ export class SdkBase {
|
|
|
231
237
|
}
|
|
232
238
|
return data
|
|
233
239
|
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* A helper function to process List response.
|
|
243
|
+
* The list response is standardised response containing the `items` and `cursor` properties.
|
|
244
|
+
* The list response also returns 200 status code. The logic validates the JSON response,
|
|
245
|
+
* 200 status code, and whether the response the `items` array (can be empty).
|
|
246
|
+
*
|
|
247
|
+
* @param result The read results of a standard List request
|
|
248
|
+
* @param prefix The error prefix to use with the error codes.
|
|
249
|
+
* @returns The valid list results.
|
|
250
|
+
*/
|
|
251
|
+
protected processListResponse<T = unknown>(result: IStoreResponse, prefix: string): ContextListResult<T> {
|
|
252
|
+
this.inspectCommonStatusCodes(result)
|
|
253
|
+
// Status check happens in the readResponseBody method.
|
|
254
|
+
const data = this.readResponseJSON<ContextListResult<T>>(result, prefix)
|
|
255
|
+
if (!Array.isArray(data.items)) {
|
|
256
|
+
throw new Exception(`${prefix}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
|
|
257
|
+
}
|
|
258
|
+
return data
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
protected assertObjectKind(prefix: string, expected: string, actual?: unknown): void {
|
|
262
|
+
if (expected !== actual) {
|
|
263
|
+
throw new Exception(`${prefix}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: 500 })
|
|
264
|
+
}
|
|
265
|
+
}
|
|
234
266
|
}
|
package/src/sdk/SdkMock.ts
CHANGED
|
@@ -27,6 +27,9 @@ import type { ForeignDomainDependency } from '../modeling/types.js'
|
|
|
27
27
|
import { nanoid } from '../nanoid.js'
|
|
28
28
|
import type { AiSessionSchema, AiSessionApp } from '../models/AiSession.js'
|
|
29
29
|
import type { AiMessageSchema } from '../models/AiMessage.js'
|
|
30
|
+
import type { DeploymentSchema } from '../models/store/Deployment.js'
|
|
31
|
+
import { DeploymentEnvironment } from '../models/store/Deployment.js'
|
|
32
|
+
import type { CreateDeploymentPayload } from './DeploymentsSdk.js'
|
|
30
33
|
|
|
31
34
|
export interface MockResult {
|
|
32
35
|
/**
|
|
@@ -1614,6 +1617,106 @@ export class SdkMock {
|
|
|
1614
1617
|
},
|
|
1615
1618
|
}
|
|
1616
1619
|
|
|
1620
|
+
/**
|
|
1621
|
+
* Deployments API mocks.
|
|
1622
|
+
*/
|
|
1623
|
+
deployments = {
|
|
1624
|
+
list: async (init?: MockListResult, options?: InterceptOptions): Promise<void> => {
|
|
1625
|
+
const { mock } = this
|
|
1626
|
+
const respond = this.createDefaultResponse(
|
|
1627
|
+
200,
|
|
1628
|
+
{ 'content-type': 'application/json' },
|
|
1629
|
+
() => {
|
|
1630
|
+
const obj: ContextListResult<DeploymentSchema> = {
|
|
1631
|
+
items: this.gen.deployments.deployments(init?.size ?? 5),
|
|
1632
|
+
cursor: this.createCursorOption(init),
|
|
1633
|
+
}
|
|
1634
|
+
return JSON.stringify(obj)
|
|
1635
|
+
},
|
|
1636
|
+
init
|
|
1637
|
+
)
|
|
1638
|
+
await mock.add(
|
|
1639
|
+
{
|
|
1640
|
+
match: {
|
|
1641
|
+
uri: RouteBuilder.deployments(':oid'),
|
|
1642
|
+
methods: ['GET'],
|
|
1643
|
+
},
|
|
1644
|
+
respond,
|
|
1645
|
+
},
|
|
1646
|
+
options
|
|
1647
|
+
)
|
|
1648
|
+
},
|
|
1649
|
+
create: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
|
|
1650
|
+
const { mock } = this
|
|
1651
|
+
const respond = this.createDefaultResponse(
|
|
1652
|
+
202,
|
|
1653
|
+
{ 'content-type': 'application/json' },
|
|
1654
|
+
(req: SerializedRequest) => {
|
|
1655
|
+
const raw = req.body as string
|
|
1656
|
+
const payload = JSON.parse(raw) as CreateDeploymentPayload
|
|
1657
|
+
const obj = this.gen.deployments.deployment({
|
|
1658
|
+
orgId: req.params.oid,
|
|
1659
|
+
apiId: payload.fid,
|
|
1660
|
+
modelVersion: payload.modelVersion,
|
|
1661
|
+
env: payload.env,
|
|
1662
|
+
...(payload.env === DeploymentEnvironment.PROD ? { version: payload.version } : {}),
|
|
1663
|
+
})
|
|
1664
|
+
return JSON.stringify(obj)
|
|
1665
|
+
},
|
|
1666
|
+
init
|
|
1667
|
+
)
|
|
1668
|
+
await mock.add(
|
|
1669
|
+
{
|
|
1670
|
+
match: {
|
|
1671
|
+
uri: RouteBuilder.deployments(':oid'),
|
|
1672
|
+
methods: ['POST'],
|
|
1673
|
+
},
|
|
1674
|
+
respond,
|
|
1675
|
+
},
|
|
1676
|
+
options
|
|
1677
|
+
)
|
|
1678
|
+
},
|
|
1679
|
+
read: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
|
|
1680
|
+
const { mock } = this
|
|
1681
|
+
const respond = this.createDefaultResponse(
|
|
1682
|
+
200,
|
|
1683
|
+
{ 'content-type': 'application/json' },
|
|
1684
|
+
(req: SerializedRequest) => {
|
|
1685
|
+
const obj = this.gen.deployments.deployment({
|
|
1686
|
+
key: req.params.id,
|
|
1687
|
+
orgId: req.params.oid,
|
|
1688
|
+
})
|
|
1689
|
+
return JSON.stringify(obj)
|
|
1690
|
+
},
|
|
1691
|
+
init
|
|
1692
|
+
)
|
|
1693
|
+
await mock.add(
|
|
1694
|
+
{
|
|
1695
|
+
match: {
|
|
1696
|
+
uri: RouteBuilder.deployment(':oid', ':id'),
|
|
1697
|
+
methods: ['GET'],
|
|
1698
|
+
},
|
|
1699
|
+
respond,
|
|
1700
|
+
},
|
|
1701
|
+
options
|
|
1702
|
+
)
|
|
1703
|
+
},
|
|
1704
|
+
deactivate: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
|
|
1705
|
+
const { mock } = this
|
|
1706
|
+
const respond = this.createDefaultResponse(204, undefined, undefined, init)
|
|
1707
|
+
await mock.add(
|
|
1708
|
+
{
|
|
1709
|
+
match: {
|
|
1710
|
+
uri: RouteBuilder.deployment(':oid', ':id'),
|
|
1711
|
+
methods: ['DELETE'],
|
|
1712
|
+
},
|
|
1713
|
+
respond,
|
|
1714
|
+
},
|
|
1715
|
+
options
|
|
1716
|
+
)
|
|
1717
|
+
},
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1617
1720
|
/**
|
|
1618
1721
|
* Trash Data Catalog mocks.
|
|
1619
1722
|
*/
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import { ApiModel } from '../../../src/modeling/ApiModel.js'
|
|
3
|
+
import { RuntimeApiModel, type RuntimeApiModelSchema } from '../../../src/modeling/RuntimeApiModel.js'
|
|
4
|
+
import { DataDomain } from '../../../src/modeling/DataDomain.js'
|
|
5
|
+
|
|
6
|
+
test.group('RuntimeApiModel', () => {
|
|
7
|
+
test('initializes from schema', ({ assert }) => {
|
|
8
|
+
const domain = new DataDomain({ info: { version: '1.0.0' } })
|
|
9
|
+
const model = domain.addModel({ info: { name: 'Test Model' } })
|
|
10
|
+
const entity = model.addEntity({ key: 'ent-1', info: { name: 'User' } })
|
|
11
|
+
|
|
12
|
+
const baseModel = new ApiModel(
|
|
13
|
+
{
|
|
14
|
+
key: 'api-1',
|
|
15
|
+
info: { name: 'Test API' },
|
|
16
|
+
},
|
|
17
|
+
domain
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
const expose = baseModel.exposeEntity({ key: entity.key })
|
|
21
|
+
expose.setResourcePath('/users/{id}')
|
|
22
|
+
expose.addActionFromKind('read')
|
|
23
|
+
|
|
24
|
+
const schema: RuntimeApiModelSchema = {
|
|
25
|
+
...baseModel.toJSON(),
|
|
26
|
+
routingMap: {
|
|
27
|
+
GET: [{ path: '/users/{id}', lookup: { exposedEntityKey: expose.key, actionKind: 'read' } }],
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const runtimeModel = new RuntimeApiModel(schema, domain.toJSON())
|
|
32
|
+
assert.deepEqual(runtimeModel.toJSON().routingMap, schema.routingMap)
|
|
33
|
+
}).tags(['@modeling', '@runtime'])
|
|
34
|
+
|
|
35
|
+
test('resolves path with matchit', ({ assert }) => {
|
|
36
|
+
const domain = new DataDomain({ info: { version: '1.0.0' } })
|
|
37
|
+
const schema = {
|
|
38
|
+
key: 'api-1',
|
|
39
|
+
info: { name: 'Test API' },
|
|
40
|
+
exposes: [
|
|
41
|
+
{
|
|
42
|
+
key: 'expose-1',
|
|
43
|
+
entity: { key: 'ent-1', type: 'association' },
|
|
44
|
+
actions: [
|
|
45
|
+
{ kind: 'read', type: 'crud' },
|
|
46
|
+
{ kind: 'list', type: 'crud' },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
routingMap: {
|
|
51
|
+
GET: [
|
|
52
|
+
{ path: '/users/{id}', lookup: { exposedEntityKey: 'expose-1', actionKind: 'read' } },
|
|
53
|
+
{ path: '/users', lookup: { exposedEntityKey: 'expose-1', actionKind: 'list' } },
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const runtimeModel = new RuntimeApiModel(schema as any, domain.toJSON())
|
|
59
|
+
|
|
60
|
+
const listResult = runtimeModel.lookupAction('GET', '/users')
|
|
61
|
+
assert.isOk(listResult)
|
|
62
|
+
assert.equal(listResult!.action.kind, 'list')
|
|
63
|
+
assert.deepEqual(listResult!.params, {})
|
|
64
|
+
|
|
65
|
+
const readResult = runtimeModel.lookupAction('get', '/users/123') // Case insensitivity test
|
|
66
|
+
assert.isOk(readResult)
|
|
67
|
+
assert.equal(readResult!.action.kind, 'read')
|
|
68
|
+
assert.deepEqual(readResult!.params, { id: '123' })
|
|
69
|
+
}).tags(['@modeling', '@runtime'])
|
|
70
|
+
|
|
71
|
+
test('returns undefined for unknown routes', ({ assert }) => {
|
|
72
|
+
const domain = new DataDomain({ info: { version: '1.0.0' } })
|
|
73
|
+
const schema = {
|
|
74
|
+
key: 'api-1',
|
|
75
|
+
info: { name: 'Test API' },
|
|
76
|
+
routingMap: {
|
|
77
|
+
GET: [{ path: '/users', lookup: { exposedEntityKey: 'expose-1', actionKind: 'list' } }],
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const runtimeModel = new RuntimeApiModel(schema as any, domain.toJSON())
|
|
82
|
+
|
|
83
|
+
// Method not found
|
|
84
|
+
const postResult = runtimeModel.lookupAction('POST', '/users')
|
|
85
|
+
assert.isUndefined(postResult)
|
|
86
|
+
|
|
87
|
+
// Method found, path not found
|
|
88
|
+
const getResult = runtimeModel.lookupAction('GET', '/unknown')
|
|
89
|
+
assert.isUndefined(getResult)
|
|
90
|
+
}).tags(['@modeling', '@runtime'])
|
|
91
|
+
|
|
92
|
+
test('returns undefined when entity or action is missing', ({ assert }) => {
|
|
93
|
+
const domain = new DataDomain({ info: { version: '1.0.0' } })
|
|
94
|
+
const schema = {
|
|
95
|
+
key: 'api-1',
|
|
96
|
+
info: { name: 'Test API' },
|
|
97
|
+
exposes: [
|
|
98
|
+
{
|
|
99
|
+
key: 'expose-1',
|
|
100
|
+
entity: { key: 'ent-1', type: 'association' },
|
|
101
|
+
actions: [{ kind: 'list', type: 'crud' }],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
routingMap: {
|
|
105
|
+
GET: [
|
|
106
|
+
{ path: '/missing-entity', lookup: { exposedEntityKey: 'missing-expose', actionKind: 'list' } },
|
|
107
|
+
{ path: '/missing-action', lookup: { exposedEntityKey: 'expose-1', actionKind: 'read' } },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const runtimeModel = new RuntimeApiModel(schema as any, domain.toJSON())
|
|
113
|
+
|
|
114
|
+
// Exposed entity not found
|
|
115
|
+
const missingEntityResult = runtimeModel.lookupAction('GET', '/missing-entity')
|
|
116
|
+
assert.isUndefined(missingEntityResult)
|
|
117
|
+
|
|
118
|
+
// Action not found on entity
|
|
119
|
+
const missingActionResult = runtimeModel.lookupAction('GET', '/missing-action')
|
|
120
|
+
assert.isUndefined(missingActionResult)
|
|
121
|
+
}).tags(['@modeling', '@runtime'])
|
|
122
|
+
})
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import { createActionFromKind, restoreAction, type ActionKind } from '../../../../src/modeling/actions/index.js'
|
|
3
|
+
import { ListAction } from '../../../../src/modeling/actions/ListAction.js'
|
|
4
|
+
import { ReadAction } from '../../../../src/modeling/actions/ReadAction.js'
|
|
5
|
+
import { CreateAction } from '../../../../src/modeling/actions/CreateAction.js'
|
|
6
|
+
import { UpdateAction } from '../../../../src/modeling/actions/UpdateAction.js'
|
|
7
|
+
import { DeleteAction } from '../../../../src/modeling/actions/DeleteAction.js'
|
|
8
|
+
import { SearchAction } from '../../../../src/modeling/actions/SearchAction.js'
|
|
9
|
+
import { DataDomain } from '../../../../src/modeling/DataDomain.js'
|
|
10
|
+
import { ApiModel } from '../../../../src/modeling/ApiModel.js'
|
|
11
|
+
import type { ExposedEntity } from '../../../../src/modeling/ExposedEntity.js'
|
|
12
|
+
|
|
13
|
+
test.group('modeling/actions/index', (group) => {
|
|
14
|
+
let domain: DataDomain
|
|
15
|
+
let baseModel: ApiModel
|
|
16
|
+
let expose: ExposedEntity
|
|
17
|
+
|
|
18
|
+
group.each.setup(() => {
|
|
19
|
+
domain = new DataDomain({ info: { version: '1.0.0' } })
|
|
20
|
+
const model = domain.addModel({ info: { name: 'Test Model' } })
|
|
21
|
+
const entity = model.addEntity({ key: 'ent-1', info: { name: 'User' } })
|
|
22
|
+
|
|
23
|
+
baseModel = new ApiModel(
|
|
24
|
+
{
|
|
25
|
+
key: 'api-1',
|
|
26
|
+
info: { name: 'Test API' },
|
|
27
|
+
},
|
|
28
|
+
domain
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
expose = baseModel.exposeEntity({ key: entity.key })
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('createActionFromKind: creates ListAction', ({ assert }) => {
|
|
35
|
+
const action = createActionFromKind(expose, 'list')
|
|
36
|
+
assert.instanceOf(action, ListAction)
|
|
37
|
+
assert.equal(action.kind, 'list')
|
|
38
|
+
}).tags(['@actions'])
|
|
39
|
+
|
|
40
|
+
test('createActionFromKind: creates ReadAction', ({ assert }) => {
|
|
41
|
+
const action = createActionFromKind(expose, 'read')
|
|
42
|
+
assert.instanceOf(action, ReadAction)
|
|
43
|
+
assert.equal(action.kind, 'read')
|
|
44
|
+
}).tags(['@actions'])
|
|
45
|
+
|
|
46
|
+
test('createActionFromKind: creates CreateAction', ({ assert }) => {
|
|
47
|
+
const action = createActionFromKind(expose, 'create')
|
|
48
|
+
assert.instanceOf(action, CreateAction)
|
|
49
|
+
assert.equal(action.kind, 'create')
|
|
50
|
+
}).tags(['@actions'])
|
|
51
|
+
|
|
52
|
+
test('createActionFromKind: creates UpdateAction', ({ assert }) => {
|
|
53
|
+
const action = createActionFromKind(expose, 'update')
|
|
54
|
+
assert.instanceOf(action, UpdateAction)
|
|
55
|
+
assert.equal(action.kind, 'update')
|
|
56
|
+
}).tags(['@actions'])
|
|
57
|
+
|
|
58
|
+
test('createActionFromKind: creates DeleteAction', ({ assert }) => {
|
|
59
|
+
const action = createActionFromKind(expose, 'delete')
|
|
60
|
+
assert.instanceOf(action, DeleteAction)
|
|
61
|
+
assert.equal(action.kind, 'delete')
|
|
62
|
+
}).tags(['@actions'])
|
|
63
|
+
|
|
64
|
+
test('createActionFromKind: creates SearchAction', ({ assert }) => {
|
|
65
|
+
const action = createActionFromKind(expose, 'search')
|
|
66
|
+
assert.instanceOf(action, SearchAction)
|
|
67
|
+
assert.equal(action.kind, 'search')
|
|
68
|
+
}).tags(['@actions'])
|
|
69
|
+
|
|
70
|
+
test('createActionFromKind: throws exception for unknown action kind', ({ assert }) => {
|
|
71
|
+
assert.throws(() => createActionFromKind(expose, 'unknown' as ActionKind), 'Unknown action kind')
|
|
72
|
+
}).tags(['@actions'])
|
|
73
|
+
|
|
74
|
+
test('restoreAction: restores ListAction', ({ assert }) => {
|
|
75
|
+
const action = restoreAction(expose, { kind: 'list' })
|
|
76
|
+
assert.instanceOf(action, ListAction)
|
|
77
|
+
assert.equal(action.kind, 'list')
|
|
78
|
+
}).tags(['@actions'])
|
|
79
|
+
|
|
80
|
+
test('restoreAction: restores ReadAction', ({ assert }) => {
|
|
81
|
+
const action = restoreAction(expose, { kind: 'read' })
|
|
82
|
+
assert.instanceOf(action, ReadAction)
|
|
83
|
+
assert.equal(action.kind, 'read')
|
|
84
|
+
}).tags(['@actions'])
|
|
85
|
+
|
|
86
|
+
test('restoreAction: restores CreateAction', ({ assert }) => {
|
|
87
|
+
const action = restoreAction(expose, { kind: 'create' })
|
|
88
|
+
assert.instanceOf(action, CreateAction)
|
|
89
|
+
assert.equal(action.kind, 'create')
|
|
90
|
+
}).tags(['@actions'])
|
|
91
|
+
|
|
92
|
+
test('restoreAction: restores UpdateAction', ({ assert }) => {
|
|
93
|
+
const action = restoreAction(expose, { kind: 'update' })
|
|
94
|
+
assert.instanceOf(action, UpdateAction)
|
|
95
|
+
assert.equal(action.kind, 'update')
|
|
96
|
+
}).tags(['@actions'])
|
|
97
|
+
|
|
98
|
+
test('restoreAction: restores DeleteAction', ({ assert }) => {
|
|
99
|
+
const action = restoreAction(expose, { kind: 'delete' })
|
|
100
|
+
assert.instanceOf(action, DeleteAction)
|
|
101
|
+
assert.equal(action.kind, 'delete')
|
|
102
|
+
}).tags(['@actions'])
|
|
103
|
+
|
|
104
|
+
test('restoreAction: restores SearchAction', ({ assert }) => {
|
|
105
|
+
const action = restoreAction(expose, { kind: 'search' })
|
|
106
|
+
assert.instanceOf(action, SearchAction)
|
|
107
|
+
assert.equal(action.kind, 'search')
|
|
108
|
+
}).tags(['@actions'])
|
|
109
|
+
|
|
110
|
+
test('restoreAction: throws exception for unknown action kind', ({ assert }) => {
|
|
111
|
+
assert.throws(() => restoreAction(expose, { kind: 'unknown' }), 'Unknown action kind')
|
|
112
|
+
}).tags(['@actions'])
|
|
113
|
+
})
|
|
@@ -794,6 +794,34 @@ test.group('DomainAssociation.readBinding()', () => {
|
|
|
794
794
|
})
|
|
795
795
|
})
|
|
796
796
|
|
|
797
|
+
test.group('DomainAssociation.readWebBinding()', () => {
|
|
798
|
+
test('returns undefined if no web binding exists', ({ assert }) => {
|
|
799
|
+
const dataDomain = new DataDomain()
|
|
800
|
+
const association = new DomainAssociation(dataDomain, 'test-parent')
|
|
801
|
+
const webBindings = association.readWebBinding()
|
|
802
|
+
assert.isUndefined(webBindings)
|
|
803
|
+
}).tags(['@modeling', '@association'])
|
|
804
|
+
|
|
805
|
+
test('returns the web binding schema if it exists', ({ assert }) => {
|
|
806
|
+
const dataDomain = new DataDomain()
|
|
807
|
+
const association = new DomainAssociation(dataDomain, 'test-parent', {
|
|
808
|
+
bindings: [{ type: 'web', schema: { hidden: true } }],
|
|
809
|
+
})
|
|
810
|
+
const webBindings = association.readWebBinding()
|
|
811
|
+
assert.deepEqual(webBindings, { hidden: true })
|
|
812
|
+
}).tags(['@modeling', '@association'])
|
|
813
|
+
|
|
814
|
+
test('returns undefined if the web binding exists but has no schema', ({ assert }) => {
|
|
815
|
+
const dataDomain = new DataDomain()
|
|
816
|
+
const association = new DomainAssociation(dataDomain, 'test-parent', {
|
|
817
|
+
// @ts-expect-error Testing undefined schema
|
|
818
|
+
bindings: [{ type: 'web', schema: undefined }],
|
|
819
|
+
})
|
|
820
|
+
const webBindings = association.readWebBinding()
|
|
821
|
+
assert.isUndefined(webBindings)
|
|
822
|
+
}).tags(['@modeling', '@association'])
|
|
823
|
+
})
|
|
824
|
+
|
|
797
825
|
test.group('DomainAssociation.addSemantic()', () => {
|
|
798
826
|
test('adds a new semantic to the association', ({ assert }) => {
|
|
799
827
|
const dataDomain = new DataDomain()
|
|
@@ -59,6 +59,55 @@ test.group('DomainEntity.listParents()', () => {
|
|
|
59
59
|
})
|
|
60
60
|
})
|
|
61
61
|
|
|
62
|
+
test.group('DomainEntity.listAllParents()', () => {
|
|
63
|
+
test('lists all parent entities recursively upwards (default)', ({ assert }) => {
|
|
64
|
+
const dataDomain = new DataDomain()
|
|
65
|
+
const model = dataDomain.addModel()
|
|
66
|
+
const grandparent = model.addEntity({ key: 'grandparent' })
|
|
67
|
+
const parent = model.addEntity({ key: 'parent' })
|
|
68
|
+
const child = model.addEntity({ key: 'child' })
|
|
69
|
+
parent.addParent(grandparent.key)
|
|
70
|
+
child.addParent(parent.key)
|
|
71
|
+
const parents = child.listAllParents()
|
|
72
|
+
assert.deepEqual(parents, [parent, grandparent])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('lists all parent entities recursively downwards', ({ assert }) => {
|
|
76
|
+
const dataDomain = new DataDomain()
|
|
77
|
+
const model = dataDomain.addModel()
|
|
78
|
+
const grandparent = model.addEntity({ key: 'grandparent' })
|
|
79
|
+
const parent = model.addEntity({ key: 'parent' })
|
|
80
|
+
const child = model.addEntity({ key: 'child' })
|
|
81
|
+
parent.addParent(grandparent.key)
|
|
82
|
+
child.addParent(parent.key)
|
|
83
|
+
const parents = child.listAllParents('down')
|
|
84
|
+
assert.deepEqual(parents, [grandparent, parent])
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('handles multiple inheritance properly', ({ assert }) => {
|
|
88
|
+
const dataDomain = new DataDomain()
|
|
89
|
+
const model = dataDomain.addModel()
|
|
90
|
+
const grandparent1 = model.addEntity({ key: 'grandparent1' })
|
|
91
|
+
const grandparent2 = model.addEntity({ key: 'grandparent2' })
|
|
92
|
+
const parent1 = model.addEntity({ key: 'parent1' })
|
|
93
|
+
const parent2 = model.addEntity({ key: 'parent2' })
|
|
94
|
+
const child = model.addEntity({ key: 'child' })
|
|
95
|
+
|
|
96
|
+
parent1.addParent(grandparent1.key)
|
|
97
|
+
parent2.addParent(grandparent2.key)
|
|
98
|
+
child.addParent(parent1.key)
|
|
99
|
+
child.addParent(parent2.key)
|
|
100
|
+
|
|
101
|
+
const parentsUp = child.listAllParents('up')
|
|
102
|
+
// Order based on BFS: parent1, parent2, grandparent1, grandparent2
|
|
103
|
+
assert.deepEqual(parentsUp, [parent1, parent2, grandparent1, grandparent2])
|
|
104
|
+
|
|
105
|
+
const parentsDown = child.listAllParents('down')
|
|
106
|
+
// Order reversed: grandparent2, grandparent1, parent2, parent1
|
|
107
|
+
assert.deepEqual(parentsDown, [grandparent2, grandparent1, parent2, parent1])
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
62
111
|
test.group('DomainEntity.addParent()', () => {
|
|
63
112
|
test('adds a parent to the entity', ({ assert }) => {
|
|
64
113
|
const dataDomain = new DataDomain()
|