@api-client/core 0.14.1 → 0.14.3
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 +1 -1
- package/build/src/browser.d.ts.map +1 -1
- package/build/src/browser.js.map +1 -1
- package/build/src/index.d.ts +2 -1
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +1 -0
- package/build/src/index.js.map +1 -1
- package/build/src/modeling/ApiFile.d.ts +23 -0
- package/build/src/modeling/ApiFile.d.ts.map +1 -0
- package/build/src/modeling/ApiFile.js +44 -0
- package/build/src/modeling/ApiFile.js.map +1 -0
- package/build/src/modeling/ApiModel.d.ts +159 -0
- package/build/src/modeling/ApiModel.d.ts.map +1 -0
- package/build/src/modeling/ApiModel.js +237 -0
- package/build/src/modeling/ApiModel.js.map +1 -0
- package/build/src/modeling/DataDomain.d.ts +1 -1
- package/build/src/modeling/DataDomain.d.ts.map +1 -1
- package/build/src/modeling/DataDomain.js +1 -3
- package/build/src/modeling/DataDomain.js.map +1 -1
- package/build/src/modeling/DomainAssociation.d.ts +35 -0
- package/build/src/modeling/DomainAssociation.d.ts.map +1 -1
- package/build/src/modeling/DomainAssociation.js +42 -5
- package/build/src/modeling/DomainAssociation.js.map +1 -1
- package/build/src/modeling/DomainEntity.js +1 -1
- package/build/src/modeling/DomainEntity.js.map +1 -1
- package/build/src/modeling/DomainFile.d.ts +1 -2
- package/build/src/modeling/DomainFile.d.ts.map +1 -1
- package/build/src/modeling/DomainFile.js +3 -41
- package/build/src/modeling/DomainFile.js.map +1 -1
- package/build/src/modeling/Semantics.d.ts +62 -7
- package/build/src/modeling/Semantics.d.ts.map +1 -1
- package/build/src/modeling/Semantics.js +76 -7
- package/build/src/modeling/Semantics.js.map +1 -1
- package/build/src/modeling/amf/ShapeGenerator.d.ts.map +1 -1
- package/build/src/modeling/amf/ShapeGenerator.js.map +1 -1
- package/build/src/modeling/types.d.ts +491 -0
- 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 +2 -0
- package/build/src/models/kinds.d.ts.map +1 -1
- package/build/src/models/kinds.js +2 -0
- package/build/src/models/kinds.js.map +1 -1
- package/build/src/models/store/File.d.ts +19 -2
- package/build/src/models/store/File.d.ts.map +1 -1
- package/build/src/models/store/File.js +100 -13
- package/build/src/models/store/File.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/data/models/APIC-187.json +3 -3
- package/data/models/APIC-188.json +3 -3
- package/data/models/APIC-233.json +1 -1
- package/data/models/APIC-391.json +2 -2
- package/data/models/APIC-483.json +1 -1
- package/data/models/APIC-487.json +1 -1
- package/data/models/APIC-655.json +1 -1
- package/data/models/APIC-689.json +1 -1
- package/data/models/APIC-690.json +5 -5
- package/data/models/SE-10469.json +1 -1
- package/data/models/SE-13092.json +5 -5
- package/data/models/SE-22063.json +12 -2
- package/data/models/amf-helper-api.json +154 -14
- package/data/models/arc-demo-api.json +95 -15
- package/data/models/async-api.json +1 -1
- package/data/models/example-generator-api.json +361 -21
- package/data/models/expanded-api.json +1 -1
- package/data/models/flattened-api.json +1 -1
- package/data/models/multiple-servers.json +1 -1
- package/data/models/oas-3-api.json +1 -1
- package/data/models/oas-date.json +1 -1
- package/data/models/oas-types.json +1 -1
- package/data/models/oas-unions.json +1 -1
- package/data/models/petstore.json +1 -1
- package/data/models/raml-date.json +1 -1
- package/data/models/recursive.json +1 -1
- package/data/models/schema-api.json +62 -2
- package/data/models/secured-api.json +16 -16
- package/data/models/tracked-to-linked.json +4 -4
- package/package.json +3 -4
- package/src/modeling/ApiFile.ts +53 -0
- package/src/modeling/ApiModel.ts +327 -0
- package/src/modeling/DataDomain.ts +1 -1
- package/src/modeling/DomainAssociation.ts +56 -0
- package/src/modeling/DomainEntity.ts +1 -1
- package/src/modeling/DomainFile.ts +3 -40
- package/src/modeling/Semantics.ts +79 -7
- package/src/modeling/amf/ShapeGenerator.ts +1 -1
- package/src/modeling/types.ts +545 -0
- package/src/models/kinds.ts +2 -0
- package/src/models/store/File.ts +100 -13
- package/tests/unit/modeling/api_model.spec.ts +291 -0
- package/tests/unit/modeling/domain_asociation.spec.ts +92 -2
- package/tests/unit/modeling/domain_entity.spec.ts +15 -15
- package/tests/unit/modeling/domain_file.spec.ts +1 -11
- package/tests/unit/modeling/domain_model_entities.spec.ts +2 -2
- package/tests/unit/modeling/semantics.spec.ts +146 -0
- package/tests/unit/models/File/constructor.spec.ts +3 -2
- package/tests/unit/models/File/shortcutTo.spec.ts +1 -1
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { nanoid } from '../nanoid.js'
|
|
2
|
+
import { ApiModelKind } from '../models/kinds.js'
|
|
3
|
+
import { type IThing, Thing } from '../models/Thing.js'
|
|
4
|
+
import type {
|
|
5
|
+
AccessRule,
|
|
6
|
+
AuthenticationConfiguration,
|
|
7
|
+
AuthorizationConfiguration,
|
|
8
|
+
ExposedEntity,
|
|
9
|
+
ForeignDomainDependency,
|
|
10
|
+
RateLimitingConfiguration,
|
|
11
|
+
SessionConfiguration,
|
|
12
|
+
} from './types.js'
|
|
13
|
+
import { DataDomain, type DataDomainSchema } from './DataDomain.js'
|
|
14
|
+
|
|
15
|
+
export interface ApiModelSchema {
|
|
16
|
+
/**
|
|
17
|
+
* The data domain kind recognizable by the ecosystem.
|
|
18
|
+
*/
|
|
19
|
+
kind: typeof ApiModelKind
|
|
20
|
+
/**
|
|
21
|
+
* The unique key of the data domain schema.
|
|
22
|
+
* This is a stable identifier that does not change across versions.
|
|
23
|
+
*/
|
|
24
|
+
key: string
|
|
25
|
+
/**
|
|
26
|
+
* Contains the name, display name, description, and the version of the data domain schema.
|
|
27
|
+
*/
|
|
28
|
+
info: IThing
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The designated Data Entity that represents a "User".
|
|
32
|
+
* This entity must be marked with the "User" semantic in the Data Modeler.
|
|
33
|
+
*
|
|
34
|
+
* This property is required to publish the API.
|
|
35
|
+
*/
|
|
36
|
+
userKey?: string
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Reference to the stable, version-controlled data definition from the
|
|
40
|
+
* Data Catalog. When not set, the model cannot be published.
|
|
41
|
+
*/
|
|
42
|
+
domain?: ForeignDomainDependency
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Configuration for how users prove their identity.
|
|
46
|
+
* The API model is invalid if this is not set.
|
|
47
|
+
*/
|
|
48
|
+
authentication?: AuthenticationConfiguration
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Configuration for what authenticated users are allowed to do.
|
|
52
|
+
* The API model is invalid if this is not set.
|
|
53
|
+
*/
|
|
54
|
+
authorization?: AuthorizationConfiguration
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Configuration for the transport and payload of the user session.
|
|
58
|
+
* The API model is invalid if this is not set.
|
|
59
|
+
*/
|
|
60
|
+
session?: SessionConfiguration
|
|
61
|
+
/**
|
|
62
|
+
* The specific subset of Data Entities to be exposed by this API.
|
|
63
|
+
* These are the entities that are included in the data domain schema.
|
|
64
|
+
*/
|
|
65
|
+
exposes: ExposedEntity[]
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Optional array of access rules that define the access control policies
|
|
69
|
+
* for the API. These rules are used to enforce security and permissions
|
|
70
|
+
* on the exposed entities.
|
|
71
|
+
*
|
|
72
|
+
* These rules apply to all exposed entities and actions. An API action
|
|
73
|
+
* can declare its own access rules, which will override these.
|
|
74
|
+
*/
|
|
75
|
+
accessRule?: AccessRule[]
|
|
76
|
+
/**
|
|
77
|
+
* Optional configuration for API-wide rate limiting and throttling.
|
|
78
|
+
* Defines rules to protect the API from overuse.
|
|
79
|
+
*/
|
|
80
|
+
rateLimiting?: RateLimitingConfiguration
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export class ApiModel extends EventTarget {
|
|
84
|
+
/**
|
|
85
|
+
* The data domain kind recognizable by the ecosystem.
|
|
86
|
+
*/
|
|
87
|
+
kind: typeof ApiModelKind
|
|
88
|
+
/**
|
|
89
|
+
* The unique key of the data domain schema.
|
|
90
|
+
* This is a stable identifier that does not change across versions.
|
|
91
|
+
*/
|
|
92
|
+
key: string
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The description of the domain property.
|
|
96
|
+
*/
|
|
97
|
+
info: Thing
|
|
98
|
+
/**
|
|
99
|
+
* The designated Data Entity that represents a "User".
|
|
100
|
+
* This entity must be marked with the "User" semantic in the Data Modeler.
|
|
101
|
+
*
|
|
102
|
+
* This property is required to publish the API.
|
|
103
|
+
*/
|
|
104
|
+
userKey?: string
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Reference to the stable, version-controlled data definition from the
|
|
108
|
+
* Data Catalog. When not set, the model cannot be published.
|
|
109
|
+
*/
|
|
110
|
+
domain?: ForeignDomainDependency
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Configuration for how users prove their identity.
|
|
114
|
+
* The API model is invalid if this is not set.
|
|
115
|
+
*/
|
|
116
|
+
authentication?: AuthenticationConfiguration
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Configuration for what authenticated users are allowed to do.
|
|
120
|
+
* The API model is invalid if this is not set.
|
|
121
|
+
*/
|
|
122
|
+
authorization?: AuthorizationConfiguration
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Configuration for the transport and payload of the user session.
|
|
126
|
+
* The API model is invalid if this is not set.
|
|
127
|
+
*/
|
|
128
|
+
session?: SessionConfiguration
|
|
129
|
+
/**
|
|
130
|
+
* The specific subset of Data Entities to be exposed by this API.
|
|
131
|
+
* These are the entities that are included in the data domain schema.
|
|
132
|
+
*/
|
|
133
|
+
exposes: ExposedEntity[]
|
|
134
|
+
/**
|
|
135
|
+
* Optional array of access rules that define the access control policies
|
|
136
|
+
* for the API. These rules are used to enforce security and permissions
|
|
137
|
+
* on the exposed entities.
|
|
138
|
+
*
|
|
139
|
+
* These rules apply to all exposed entities and actions. An API action
|
|
140
|
+
* can declare its own access rules, which will override these.
|
|
141
|
+
*/
|
|
142
|
+
accessRule?: AccessRule[]
|
|
143
|
+
/**
|
|
144
|
+
* Optional configuration for API-wide rate limiting and throttling.
|
|
145
|
+
* Defines rules to protect the API from overuse.
|
|
146
|
+
*/
|
|
147
|
+
rateLimiting?: RateLimitingConfiguration
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* When the initializing flag is set to true,
|
|
151
|
+
* the domain is not notified of changes.
|
|
152
|
+
*/
|
|
153
|
+
#initializing = true
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* When the notifying flag is set to true,
|
|
157
|
+
* the domain is pending a notification.
|
|
158
|
+
* No other notifications will be sent until
|
|
159
|
+
* the current notification is sent.
|
|
160
|
+
*/
|
|
161
|
+
#notifying = false
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* A reference to the published data domain.
|
|
165
|
+
*/
|
|
166
|
+
dataDomain?: DataDomain
|
|
167
|
+
|
|
168
|
+
static createSchema(input: Partial<ApiModelSchema> = {}): ApiModelSchema {
|
|
169
|
+
const { key = nanoid(), exposes = [] } = input
|
|
170
|
+
const info = Thing.fromJSON(input.info, { name: 'Unnamed API' }).toJSON()
|
|
171
|
+
const result: ApiModelSchema = {
|
|
172
|
+
kind: ApiModelKind,
|
|
173
|
+
key,
|
|
174
|
+
info,
|
|
175
|
+
exposes,
|
|
176
|
+
}
|
|
177
|
+
if (input.userKey) {
|
|
178
|
+
result.userKey = input.userKey
|
|
179
|
+
}
|
|
180
|
+
if (input.domain) {
|
|
181
|
+
result.domain = input.domain
|
|
182
|
+
}
|
|
183
|
+
if (input.authentication) {
|
|
184
|
+
result.authentication = input.authentication
|
|
185
|
+
}
|
|
186
|
+
if (input.authorization) {
|
|
187
|
+
result.authorization = input.authorization
|
|
188
|
+
}
|
|
189
|
+
if (input.session) {
|
|
190
|
+
result.session = input.session
|
|
191
|
+
}
|
|
192
|
+
if (input.accessRule) {
|
|
193
|
+
result.accessRule = input.accessRule
|
|
194
|
+
}
|
|
195
|
+
if (input.rateLimiting) {
|
|
196
|
+
result.rateLimiting = input.rateLimiting
|
|
197
|
+
}
|
|
198
|
+
return result
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
constructor(state?: Partial<ApiModelSchema>, domain?: DataDomainSchema) {
|
|
202
|
+
super()
|
|
203
|
+
const init = ApiModel.createSchema(state)
|
|
204
|
+
this.kind = init.kind
|
|
205
|
+
this.key = init.key
|
|
206
|
+
this.info = new Thing(init.info)
|
|
207
|
+
this.userKey = init.userKey
|
|
208
|
+
if (init.domain) {
|
|
209
|
+
this.domain = structuredClone(init.domain)
|
|
210
|
+
}
|
|
211
|
+
if (init.authentication) {
|
|
212
|
+
this.authentication = structuredClone(init.authentication)
|
|
213
|
+
}
|
|
214
|
+
if (init.authorization) {
|
|
215
|
+
this.authorization = structuredClone(init.authorization)
|
|
216
|
+
}
|
|
217
|
+
if (init.session) {
|
|
218
|
+
this.session = structuredClone(init.session)
|
|
219
|
+
}
|
|
220
|
+
if (Array.isArray(init.exposes)) {
|
|
221
|
+
this.exposes = structuredClone(init.exposes)
|
|
222
|
+
} else {
|
|
223
|
+
this.exposes = []
|
|
224
|
+
}
|
|
225
|
+
if (init.accessRule) {
|
|
226
|
+
this.accessRule = structuredClone(init.accessRule)
|
|
227
|
+
}
|
|
228
|
+
if (init.rateLimiting) {
|
|
229
|
+
this.rateLimiting = structuredClone(init.rateLimiting)
|
|
230
|
+
}
|
|
231
|
+
if (domain) {
|
|
232
|
+
this.dataDomain = new DataDomain(domain)
|
|
233
|
+
}
|
|
234
|
+
this.#initializing = false
|
|
235
|
+
this.info.addEventListener('change', () => {
|
|
236
|
+
this.notifyChange()
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
toJSON(): ApiModelSchema {
|
|
241
|
+
const result: ApiModelSchema = {
|
|
242
|
+
kind: this.kind,
|
|
243
|
+
key: this.key,
|
|
244
|
+
info: this.info.toJSON(),
|
|
245
|
+
exposes: structuredClone(this.exposes),
|
|
246
|
+
}
|
|
247
|
+
if (this.userKey) {
|
|
248
|
+
result.userKey = this.userKey
|
|
249
|
+
}
|
|
250
|
+
if (this.domain) {
|
|
251
|
+
result.domain = structuredClone(this.domain)
|
|
252
|
+
}
|
|
253
|
+
if (this.authentication) {
|
|
254
|
+
result.authentication = structuredClone(this.authentication)
|
|
255
|
+
}
|
|
256
|
+
if (this.authorization) {
|
|
257
|
+
result.authorization = structuredClone(this.authorization)
|
|
258
|
+
}
|
|
259
|
+
if (this.session) {
|
|
260
|
+
result.session = structuredClone(this.session)
|
|
261
|
+
}
|
|
262
|
+
if (this.accessRule) {
|
|
263
|
+
result.accessRule = structuredClone(this.accessRule)
|
|
264
|
+
}
|
|
265
|
+
if (this.rateLimiting) {
|
|
266
|
+
result.rateLimiting = structuredClone(this.rateLimiting)
|
|
267
|
+
}
|
|
268
|
+
return result
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* This function is used internally by all domain elements to notify that something has changed.
|
|
273
|
+
* Since we want to notify listeners after the operation commits, we use microtask
|
|
274
|
+
* to ensure that the event is dispatched after the current operation.
|
|
275
|
+
*/
|
|
276
|
+
notifyChange() {
|
|
277
|
+
if (this.#notifying || this.#initializing) {
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
this.#notifying = true
|
|
281
|
+
queueMicrotask(() => {
|
|
282
|
+
this.#notifying = false
|
|
283
|
+
const event = new Event('change')
|
|
284
|
+
this.dispatchEvent(event)
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Exposes a new entity in the API model.
|
|
290
|
+
* If the entity already exists, it returns the existing one.
|
|
291
|
+
* @param entityKey The key of the entity to expose.
|
|
292
|
+
* @returns The exposed entity.
|
|
293
|
+
*/
|
|
294
|
+
exposeEntity(entityKey: string): ExposedEntity {
|
|
295
|
+
const existing = this.exposes.find((e) => e.key === entityKey)
|
|
296
|
+
if (existing) {
|
|
297
|
+
return existing
|
|
298
|
+
}
|
|
299
|
+
const newEntity: ExposedEntity = {
|
|
300
|
+
key: entityKey,
|
|
301
|
+
actions: [],
|
|
302
|
+
}
|
|
303
|
+
this.exposes.push(newEntity)
|
|
304
|
+
this.notifyChange()
|
|
305
|
+
return newEntity
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Removes an entity from the API model.
|
|
310
|
+
* @param entityKey The key of the entity to remove.
|
|
311
|
+
*/
|
|
312
|
+
removeEntity(entityKey: string): void {
|
|
313
|
+
const index = this.exposes.findIndex((e) => e.key === entityKey)
|
|
314
|
+
if (index !== -1) {
|
|
315
|
+
this.exposes.splice(index, 1)
|
|
316
|
+
this.notifyChange()
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Returns the exposed entity by its key.
|
|
321
|
+
* @param entityKey The key of the entity to find.
|
|
322
|
+
* @returns The exposed entity or undefined if not found.
|
|
323
|
+
*/
|
|
324
|
+
getExposedEntity(entityKey: string): ExposedEntity | undefined {
|
|
325
|
+
return this.exposes.find((e) => e.key === entityKey)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
@@ -12,6 +12,26 @@ import type { AssociationTarget, DomainGraphEdge } from './types.js'
|
|
|
12
12
|
import { ShapeGenerator } from './amf/ShapeGenerator.js'
|
|
13
13
|
import { DataSemantics, isAssociationSemantic, type SemanticType, type AppliedDataSemantic } from './Semantics.js'
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Defines the behavior when a parent entity in an association is deleted.
|
|
17
|
+
*
|
|
18
|
+
* - `restrict`: Prevents the deletion of a parent entity if it has any associated child entities.
|
|
19
|
+
* The generated API should return a clear and specific error message (e.g., 409 Conflict).
|
|
20
|
+
* - _Example_: Do not allow a Department to be deleted if it still has Employees.
|
|
21
|
+
* - `cascade`: Automatically deletes all associated child entities when the parent entity is deleted.
|
|
22
|
+
* - _Example_: Deleting a User will also delete all their associated Posts and Comments.
|
|
23
|
+
* - `setNull`: Sets the foreign key of the associated child entities to NULL. This is only valid if the
|
|
24
|
+
* association property on the child entity is nullable.
|
|
25
|
+
* - _Example_: When a `Project` is deleted, the `project_id` on associated `Tasks` is set to NULL,
|
|
26
|
+
* making them unassigned but not deleting them.
|
|
27
|
+
* - `doNothing`: No action is taken on the associated child entities when the parent entity is deleted.
|
|
28
|
+
* - _Example_: Deleting a `Category` does not affect associated `Products`, which remain in the database
|
|
29
|
+
* but may become orphaned.
|
|
30
|
+
* This is useful when the association is optional or when child entities should not be deleted
|
|
31
|
+
* or modified upon the deletion of a parent entity.
|
|
32
|
+
*/
|
|
33
|
+
export type OnDeleteRule = 'restrict' | 'cascade' | 'setNull' | 'doNothing'
|
|
34
|
+
|
|
15
35
|
export interface DomainAssociationSchema extends DomainElementSchema {
|
|
16
36
|
kind: typeof DomainAssociationKind
|
|
17
37
|
/**
|
|
@@ -54,6 +74,14 @@ export interface DomainAssociationSchema extends DomainElementSchema {
|
|
|
54
74
|
* describe the association in more detail.
|
|
55
75
|
*/
|
|
56
76
|
semantics?: AppliedDataSemantic[]
|
|
77
|
+
/**
|
|
78
|
+
* Defines the behavior when a parent entity in an association is deleted.
|
|
79
|
+
*/
|
|
80
|
+
onDelete?: OnDeleteRule
|
|
81
|
+
/**
|
|
82
|
+
* Whether the association is read-only.
|
|
83
|
+
*/
|
|
84
|
+
readOnly?: boolean
|
|
57
85
|
}
|
|
58
86
|
|
|
59
87
|
/**
|
|
@@ -153,6 +181,16 @@ export class DomainAssociation extends DomainElement {
|
|
|
153
181
|
*/
|
|
154
182
|
@observed({ deep: true }) accessor semantics: AppliedDataSemantic[] = []
|
|
155
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Defines the behavior when a parent entity in an association is deleted.
|
|
186
|
+
*/
|
|
187
|
+
@observed() accessor onDelete: OnDeleteRule | undefined
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Whether the association is read-only.
|
|
191
|
+
*/
|
|
192
|
+
@observed() accessor readOnly: boolean | undefined
|
|
193
|
+
|
|
156
194
|
/**
|
|
157
195
|
* Creates a full data association schema with defaults.
|
|
158
196
|
*
|
|
@@ -170,6 +208,12 @@ export class DomainAssociation extends DomainElement {
|
|
|
170
208
|
if (Array.isArray(semantics)) {
|
|
171
209
|
result.semantics = [...semantics]
|
|
172
210
|
}
|
|
211
|
+
if (input.onDelete) {
|
|
212
|
+
result.onDelete = input.onDelete
|
|
213
|
+
}
|
|
214
|
+
if (typeof input.readOnly === 'boolean') {
|
|
215
|
+
result.readOnly = input.readOnly
|
|
216
|
+
}
|
|
173
217
|
if (input.schema) {
|
|
174
218
|
result.schema = structuredClone(input.schema)
|
|
175
219
|
}
|
|
@@ -227,6 +271,12 @@ export class DomainAssociation extends DomainElement {
|
|
|
227
271
|
} else {
|
|
228
272
|
this.semantics = []
|
|
229
273
|
}
|
|
274
|
+
if (init.onDelete) {
|
|
275
|
+
this.onDelete = init.onDelete
|
|
276
|
+
}
|
|
277
|
+
if (typeof init.readOnly === 'boolean') {
|
|
278
|
+
this.readOnly = init.readOnly
|
|
279
|
+
}
|
|
230
280
|
}
|
|
231
281
|
|
|
232
282
|
/**
|
|
@@ -258,6 +308,12 @@ export class DomainAssociation extends DomainElement {
|
|
|
258
308
|
if (Array.isArray(this.semantics) && this.semantics.length) {
|
|
259
309
|
result.semantics = toRaw(this, this.semantics)?.map((i) => structuredClone(i))
|
|
260
310
|
}
|
|
311
|
+
if (this.onDelete) {
|
|
312
|
+
result.onDelete = this.onDelete
|
|
313
|
+
}
|
|
314
|
+
if (typeof this.readOnly === 'boolean' && this.readOnly) {
|
|
315
|
+
result.readOnly = this.readOnly
|
|
316
|
+
}
|
|
261
317
|
return result
|
|
262
318
|
}
|
|
263
319
|
|
|
@@ -165,7 +165,7 @@ export class DomainEntity extends DomainElement {
|
|
|
165
165
|
*/
|
|
166
166
|
static createSchema(input: Partial<DomainEntitySchema> = {}): DomainEntitySchema {
|
|
167
167
|
const { key = nanoid(), tags, semantics, fields, deprecated } = input
|
|
168
|
-
const info = Thing.fromJSON(input.info, { name: '
|
|
168
|
+
const info = Thing.fromJSON(input.info, { name: 'new_entity' }).toJSON()
|
|
169
169
|
const result: DomainEntitySchema = {
|
|
170
170
|
kind: DomainEntityKind,
|
|
171
171
|
key,
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { nanoid } from '../nanoid.js'
|
|
2
1
|
import { File, type IFile } from '../models/store/File.js'
|
|
3
|
-
import { Thing } from '../models/Thing.js'
|
|
4
2
|
import type { DataDomain, DataDomainSchema } from './DataDomain.js'
|
|
5
3
|
import { DomainFileKind } from '../models/kinds.js'
|
|
6
4
|
|
|
@@ -30,46 +28,11 @@ export class DomainFile extends File {
|
|
|
30
28
|
} else {
|
|
31
29
|
final = input as DataDomainSchema
|
|
32
30
|
}
|
|
33
|
-
|
|
34
|
-
kind: DomainFileKind,
|
|
35
|
-
key: final.key,
|
|
36
|
-
info: { ...final.info },
|
|
37
|
-
lastModified: { user: '', time: 0, byMe: false },
|
|
38
|
-
parents: [],
|
|
39
|
-
permissionIds: [],
|
|
40
|
-
permissions: [],
|
|
41
|
-
}
|
|
42
|
-
return new DomainFile(init)
|
|
31
|
+
return new DomainFile({ key: final.key, info: final.info })
|
|
43
32
|
}
|
|
44
33
|
|
|
45
|
-
constructor(
|
|
46
|
-
super()
|
|
47
|
-
let init: IDomainFile
|
|
48
|
-
if (typeof input === 'string') {
|
|
49
|
-
init = JSON.parse(input)
|
|
50
|
-
} else if (typeof input === 'object') {
|
|
51
|
-
init = input
|
|
52
|
-
} else {
|
|
53
|
-
init = {
|
|
54
|
-
kind: DomainFileKind,
|
|
55
|
-
key: nanoid(),
|
|
56
|
-
info: Thing.fromName('').toJSON(),
|
|
57
|
-
parents: [],
|
|
58
|
-
permissionIds: [],
|
|
59
|
-
permissions: [],
|
|
60
|
-
lastModified: { user: '', time: 0, byMe: false },
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
this.new(init)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
override new(init: IDomainFile): this {
|
|
67
|
-
if (!DomainFile.isDomainFile(init)) {
|
|
68
|
-
throw new Error(`Not a data file.`)
|
|
69
|
-
}
|
|
70
|
-
super.new(init)
|
|
71
|
-
this.kind = DomainFileKind
|
|
72
|
-
return this
|
|
34
|
+
constructor(state: Partial<IDomainFile> = {}) {
|
|
35
|
+
super({ ...state, kind: DomainFileKind })
|
|
73
36
|
}
|
|
74
37
|
|
|
75
38
|
static isDomainFile(input: unknown): boolean {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
1
2
|
import type { DomainPropertyType } from './DataFormat.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -9,24 +10,79 @@ export enum SemanticType {
|
|
|
9
10
|
/**
|
|
10
11
|
* Designates a Data Entity that represents users of the system.
|
|
11
12
|
*/
|
|
12
|
-
User = '
|
|
13
|
+
User = 'Semantic#User',
|
|
13
14
|
|
|
14
15
|
// Property-Level Semantics
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Annotates the field as the user password.
|
|
18
|
+
* The runtime should treat this field with special care,
|
|
19
|
+
* ensuring it is encrypted and not exposed in API responses.
|
|
20
|
+
*/
|
|
21
|
+
Password = 'Semantic#Password',
|
|
22
|
+
/**
|
|
23
|
+
* Designates a Data Property as the `createdAt` timestamp of an entity.
|
|
24
|
+
* This is used to track when the entity was first created.
|
|
25
|
+
*/
|
|
26
|
+
CreatedTimestamp = 'Semantic#CreatedTimestamp',
|
|
27
|
+
/**
|
|
28
|
+
* Designates a Data Property as the `updatedAt` timestamp of an entity.
|
|
29
|
+
* This is used to track when the entity was last modified.
|
|
30
|
+
*/
|
|
31
|
+
UpdatedTimestamp = 'Semantic#UpdatedTimestamp',
|
|
32
|
+
/**
|
|
33
|
+
* Designates a Data Property as the `deletedAt` timestamp of an entity.
|
|
34
|
+
* This is used to track when the entity was soft-deleted.
|
|
35
|
+
* Soft-deletion means the entity is not physically removed from the database,
|
|
36
|
+
* but marked as deleted for logical deletion purposes.
|
|
37
|
+
*/
|
|
38
|
+
DeletedTimestamp = 'Semantic#DeletedTimestamp',
|
|
39
|
+
/**
|
|
40
|
+
* Designates a Data Property as a boolean flag indicating whether the entity is deleted.
|
|
41
|
+
* This is used for soft-deletion, where the entity is not physically removed from the database,
|
|
42
|
+
* but marked as deleted.
|
|
43
|
+
*/
|
|
44
|
+
DeletedFlag = 'Semantic#DeletedFlag',
|
|
45
|
+
/**
|
|
46
|
+
* Designates a Data Property as a unique public identifier for a resource.
|
|
47
|
+
* This is often used in URLs to provide a user-friendly way to access the resource.
|
|
48
|
+
* For example, a blog post might have a public unique name like "my-first-post".
|
|
49
|
+
*/
|
|
50
|
+
PublicUniqueName = 'Semantic#PublicUniqueName',
|
|
51
|
+
/**
|
|
52
|
+
* Designates a Data Property as the `role` of a user within the system.
|
|
53
|
+
* This is used to define the user's permissions and access levels.
|
|
54
|
+
* For example, a user with the role of "admin" would have elevated permissions
|
|
55
|
+
* compared to a user with the role of "guest".
|
|
56
|
+
* Roles are defined on the entity as enums, or as a string property with a controlled vocabulary.
|
|
57
|
+
*/
|
|
58
|
+
UserRole = 'Semantic#UserRole',
|
|
20
59
|
// Association-Level Semantics
|
|
21
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Designates an association that links a resource to a "User" entity instance.
|
|
62
|
+
* This is used to indicate ownership of the resource for access control purposes.
|
|
63
|
+
* For example, a blog post might have a resource owner identifier that points to the user who created it.
|
|
64
|
+
*/
|
|
65
|
+
ResourceOwnerIdentifier = 'Semantic#ResourceOwnerIdentifier',
|
|
22
66
|
}
|
|
23
67
|
|
|
24
68
|
/**
|
|
25
69
|
* Defines the scope at which a semantic can be applied.
|
|
26
70
|
*/
|
|
27
71
|
export enum SemanticScope {
|
|
72
|
+
/**
|
|
73
|
+
* The semantic applies to an entire Data Entity.
|
|
74
|
+
* This is used for semantics that provide context or constraints at the entity level.
|
|
75
|
+
*/
|
|
28
76
|
Entity = 'Entity',
|
|
77
|
+
/**
|
|
78
|
+
* The semantic applies to a single Data Property.
|
|
79
|
+
* This is used for semantics that provide context or constraints at the property level.
|
|
80
|
+
*/
|
|
29
81
|
Property = 'Property',
|
|
82
|
+
/**
|
|
83
|
+
* The semantic applies to an Association between Data Entities.
|
|
84
|
+
* This is used for semantics that provide context or constraints at the association level.
|
|
85
|
+
*/
|
|
30
86
|
Association = 'Association',
|
|
31
87
|
}
|
|
32
88
|
|
|
@@ -118,6 +174,14 @@ export const DataSemantics: Record<SemanticType, DataSemantic> = {
|
|
|
118
174
|
},
|
|
119
175
|
|
|
120
176
|
// Property-Level Definitions
|
|
177
|
+
[SemanticType.Password]: {
|
|
178
|
+
id: SemanticType.Password,
|
|
179
|
+
displayName: 'User Password',
|
|
180
|
+
scope: SemanticScope.Property,
|
|
181
|
+
description:
|
|
182
|
+
'Annotates the field as the user password. The runtime should treat this field with special care, ensuring it is encrypted and not exposed in API responses.',
|
|
183
|
+
applicableDataTypes: ['string'],
|
|
184
|
+
},
|
|
121
185
|
[SemanticType.CreatedTimestamp]: {
|
|
122
186
|
id: SemanticType.CreatedTimestamp,
|
|
123
187
|
displayName: 'Creation Timestamp',
|
|
@@ -159,6 +223,14 @@ export const DataSemantics: Record<SemanticType, DataSemantic> = {
|
|
|
159
223
|
description: 'A user-friendly, unique public identifier for a resource, often used in URLs.',
|
|
160
224
|
applicableDataTypes: ['string'],
|
|
161
225
|
},
|
|
226
|
+
[SemanticType.UserRole]: {
|
|
227
|
+
id: SemanticType.UserRole,
|
|
228
|
+
displayName: 'User Role Field',
|
|
229
|
+
scope: SemanticScope.Property,
|
|
230
|
+
description:
|
|
231
|
+
'A text field that is recognized by the runtime as a role of a user in the API. Used with the role-based authorization strategy.',
|
|
232
|
+
applicableDataTypes: ['string'],
|
|
233
|
+
},
|
|
162
234
|
}
|
|
163
235
|
|
|
164
236
|
/**
|
|
@@ -107,7 +107,7 @@ export class ShapeGenerator {
|
|
|
107
107
|
* const amfShape = generator.entity(myDomainEntity);
|
|
108
108
|
* ```
|
|
109
109
|
*/
|
|
110
|
-
entity(input: DomainEntity, visited
|
|
110
|
+
entity(input: DomainEntity, visited = new Set<string>()): IApiNodeShape | IApiRecursiveShape {
|
|
111
111
|
if (visited.has(input.key)) {
|
|
112
112
|
// create a recursive shape.
|
|
113
113
|
return this.createRecursiveShape(input)
|