@api-client/core 0.18.27 → 0.18.28
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/modeling/ApiModel.d.ts +18 -9
- package/build/src/modeling/ApiModel.d.ts.map +1 -1
- package/build/src/modeling/ApiModel.js +141 -13
- package/build/src/modeling/ApiModel.js.map +1 -1
- package/build/src/modeling/helpers/endpointHelpers.d.ts +2 -0
- package/build/src/modeling/helpers/endpointHelpers.d.ts.map +1 -0
- package/build/src/modeling/helpers/endpointHelpers.js +6 -0
- package/build/src/modeling/helpers/endpointHelpers.js.map +1 -0
- package/build/src/modeling/helpers/keying.d.ts +9 -0
- package/build/src/modeling/helpers/keying.d.ts.map +1 -0
- package/build/src/modeling/helpers/keying.js +10 -0
- package/build/src/modeling/helpers/keying.js.map +1 -0
- package/build/src/modeling/types.d.ts +77 -8
- package/build/src/modeling/types.d.ts.map +1 -1
- package/build/src/modeling/types.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -1
- package/src/modeling/ApiModel.ts +159 -15
- package/src/modeling/helpers/endpointHelpers.ts +5 -0
- package/src/modeling/helpers/keying.ts +11 -0
- package/src/modeling/readme.md +153 -7
- package/src/modeling/types.ts +84 -8
- package/tests/unit/modeling/api_model.spec.ts +25 -137
- package/tests/unit/modeling/api_model_expose_entity.spec.ts +190 -0
- package/tests/unit/modeling/api_model_remove_entity.spec.ts +82 -0
- package/tests/unit/modeling/helpers/endpointHelpers.spec.ts +10 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@api-client/core",
|
|
3
3
|
"description": "The API Client's core client library. Works in NodeJS and in a ES enabled browser.",
|
|
4
|
-
"version": "0.18.
|
|
4
|
+
"version": "0.18.28",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"exports": {
|
|
7
7
|
"./browser.js": {
|
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
"@api-client/graph": "^0.3.5",
|
|
88
88
|
"@api-client/json": "^0.2.0",
|
|
89
89
|
"@esm-bundle/chai": "^4.3.4-fix.0",
|
|
90
|
+
"@jarrodek/pluralize": "^1.0.2",
|
|
90
91
|
"@metrichor/jmespath": "^0.3.1",
|
|
91
92
|
"@pawel-up/csv": "^0.2.0",
|
|
92
93
|
"@pawel-up/data-mock": "^0.4.0",
|
package/src/modeling/ApiModel.ts
CHANGED
|
@@ -11,10 +11,13 @@ import type {
|
|
|
11
11
|
RolesBasedAccessControl,
|
|
12
12
|
SessionConfiguration,
|
|
13
13
|
UsernamePasswordConfiguration,
|
|
14
|
+
ExposeOptions,
|
|
14
15
|
} from './types.js'
|
|
15
16
|
import { DataDomain } from './DataDomain.js'
|
|
16
17
|
import { DependentModel, type DependentModelSchema, type DomainDependency } from './DependentModel.js'
|
|
17
18
|
import { observed, toRaw } from '../decorators/observed.js'
|
|
19
|
+
import pluralize from '@jarrodek/pluralize'
|
|
20
|
+
import { createDomainKey } from './helpers/keying.js'
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
23
|
* Contact information for the exposed API.
|
|
@@ -54,12 +57,12 @@ export interface ApiModelSchema extends DependentModelSchema {
|
|
|
54
57
|
*/
|
|
55
58
|
kind: typeof ApiModelKind
|
|
56
59
|
/**
|
|
57
|
-
* The unique key of the
|
|
60
|
+
* The unique key of the API model schema.
|
|
58
61
|
* This is a stable identifier that does not change across versions.
|
|
59
62
|
*/
|
|
60
63
|
key: string
|
|
61
64
|
/**
|
|
62
|
-
* Contains the name, display name, description, and the version of the
|
|
65
|
+
* Contains the name, display name, description, and the version of the API model schema.
|
|
63
66
|
*/
|
|
64
67
|
info: IThing
|
|
65
68
|
|
|
@@ -376,41 +379,182 @@ export class ApiModel extends DependentModel {
|
|
|
376
379
|
/**
|
|
377
380
|
* Exposes a new entity in the API model.
|
|
378
381
|
* If the entity already exists, it returns the existing one.
|
|
379
|
-
* @param
|
|
382
|
+
* @param entity The entity key and domain to expose.
|
|
380
383
|
* @returns The exposed entity.
|
|
381
384
|
*/
|
|
382
|
-
exposeEntity(
|
|
383
|
-
const
|
|
385
|
+
exposeEntity(entity: AssociationTarget, options?: ExposeOptions): ExposedEntity {
|
|
386
|
+
const domain = this.domain
|
|
387
|
+
if (!domain) {
|
|
388
|
+
throw new Error(`No domain attached to API model`)
|
|
389
|
+
}
|
|
390
|
+
// checks whether the entity is already exposed as a root exposure.
|
|
391
|
+
const existing = this.exposes.find(
|
|
392
|
+
(e) => e.isRoot && e.entity.key === entity.key && e.entity.domain === entity.domain
|
|
393
|
+
)
|
|
384
394
|
if (existing) {
|
|
395
|
+
// quietly return the existing exposure
|
|
396
|
+
// TBD: should we throw an error here?
|
|
385
397
|
return existing
|
|
386
398
|
}
|
|
399
|
+
const domainEntity = domain.findEntity(entity.key, entity.domain)
|
|
400
|
+
if (!domainEntity) {
|
|
401
|
+
throw new Error(`Entity not found in domain: ${entity.key}`)
|
|
402
|
+
}
|
|
403
|
+
const name = domainEntity.info.name || ''
|
|
387
404
|
const newEntity: ExposedEntity = {
|
|
388
|
-
key:
|
|
405
|
+
key: nanoid(),
|
|
406
|
+
entity: { ...entity },
|
|
389
407
|
actions: [],
|
|
408
|
+
isRoot: true,
|
|
409
|
+
path: pluralize(name.toLocaleLowerCase()),
|
|
410
|
+
}
|
|
411
|
+
if (options) {
|
|
412
|
+
newEntity.exposeOptions = { ...options }
|
|
390
413
|
}
|
|
391
414
|
this.exposes.push(newEntity)
|
|
415
|
+
|
|
416
|
+
// Follow associations if requested
|
|
417
|
+
if (options?.followAssociations) {
|
|
418
|
+
if (options?.maxDepth === undefined || options.maxDepth > 0) {
|
|
419
|
+
this.followEntityAssociations(newEntity, options)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
392
422
|
this.notifyChange()
|
|
393
423
|
return newEntity
|
|
394
424
|
}
|
|
395
425
|
|
|
396
426
|
/**
|
|
397
|
-
*
|
|
398
|
-
*
|
|
427
|
+
* Follows associations for a newly exposed entity if configured to do so.
|
|
428
|
+
* This creates nested exposures based on the entity's associations.
|
|
429
|
+
*
|
|
430
|
+
* @param parentExposure The root exposure to follow associations from
|
|
431
|
+
* @param options The expose options containing follow configuration
|
|
399
432
|
*/
|
|
400
|
-
|
|
401
|
-
const
|
|
402
|
-
if (
|
|
403
|
-
|
|
404
|
-
|
|
433
|
+
private followEntityAssociations(parentExposure: ExposedEntity, options: ExposeOptions): void {
|
|
434
|
+
const domain = this.domain
|
|
435
|
+
if (!domain) {
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
const maxDepth = options.maxDepth ?? 6
|
|
439
|
+
const visited = new Set<string>()
|
|
440
|
+
// Add parent entity's key to the visited set so we won't skip it when traversing
|
|
441
|
+
// associations.
|
|
442
|
+
visited.add(createDomainKey(parentExposure.entity))
|
|
443
|
+
|
|
444
|
+
const follow = (currentEntity: AssociationTarget, parentKey: string, depth: number) => {
|
|
445
|
+
// Find the domain entity
|
|
446
|
+
const domainEntity = domain.findEntity(currentEntity.key, currentEntity.domain)
|
|
447
|
+
if (!domainEntity) return
|
|
448
|
+
|
|
449
|
+
// Iterate through associations
|
|
450
|
+
for (const association of domainEntity.listAssociations()) {
|
|
451
|
+
for (const target of association.targets) {
|
|
452
|
+
// Skip self-referencing associations
|
|
453
|
+
if (target.key === currentEntity.key && target.domain === currentEntity.domain) {
|
|
454
|
+
continue
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Create unique identifier for circular detection
|
|
458
|
+
const visitKey = createDomainKey(target)
|
|
459
|
+
if (visited.has(visitKey)) {
|
|
460
|
+
continue // Skip circular references
|
|
461
|
+
}
|
|
462
|
+
visited.add(visitKey)
|
|
463
|
+
|
|
464
|
+
// Check if this nested exposure already exists
|
|
465
|
+
const existingNested = this.exposes.find(
|
|
466
|
+
(e) =>
|
|
467
|
+
!e.isRoot &&
|
|
468
|
+
e.entity.key === target.key &&
|
|
469
|
+
e.entity.domain === target.domain &&
|
|
470
|
+
e.parent?.key === parentKey
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
if (existingNested) {
|
|
474
|
+
continue // Already exposed under this parent
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Find the target domain entity for path generation
|
|
478
|
+
const targetDomainEntity = domain.findEntity(target.key, target.domain)
|
|
479
|
+
if (!targetDomainEntity) continue
|
|
480
|
+
|
|
481
|
+
const name = association.info.name || ''
|
|
482
|
+
// Create nested exposure
|
|
483
|
+
const nestedExposure: ExposedEntity = {
|
|
484
|
+
key: nanoid(),
|
|
485
|
+
entity: { ...target },
|
|
486
|
+
actions: [],
|
|
487
|
+
isRoot: false,
|
|
488
|
+
path: pluralize(name.toLocaleLowerCase()),
|
|
489
|
+
parent: {
|
|
490
|
+
key: parentKey,
|
|
491
|
+
association: {
|
|
492
|
+
key: association.key,
|
|
493
|
+
domain: currentEntity.domain,
|
|
494
|
+
},
|
|
495
|
+
depth: depth + 1,
|
|
496
|
+
},
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
this.exposes.push(nestedExposure)
|
|
500
|
+
if (depth + 1 >= maxDepth) {
|
|
501
|
+
nestedExposure.truncated = true
|
|
502
|
+
} else {
|
|
503
|
+
// Recursively follow associations
|
|
504
|
+
follow(target, nestedExposure.key, depth + 1)
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Start following from the root exposure
|
|
511
|
+
follow(parentExposure.entity, parentExposure.key, 0)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Removes an exposed entity from the API model.
|
|
516
|
+
* @param entity The entity to remove.
|
|
517
|
+
*/
|
|
518
|
+
removeEntity(entity: AssociationTarget): void {
|
|
519
|
+
const current = this.exposes.find((e) => e.entity.key === entity.key && e.entity.domain === entity.domain)
|
|
520
|
+
if (!current) {
|
|
521
|
+
return
|
|
522
|
+
}
|
|
523
|
+
this.removeWithChildren(current.key)
|
|
524
|
+
this.notifyChange()
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
private removeWithChildren(key: string): void {
|
|
528
|
+
const index = this.exposes.findIndex((e) => e.key === key)
|
|
529
|
+
if (index < 0) {
|
|
530
|
+
return
|
|
405
531
|
}
|
|
532
|
+
// Remove the parent itself
|
|
533
|
+
this.exposes.splice(index, 1)
|
|
534
|
+
// Remove all children recursively
|
|
535
|
+
const removeChildren = (parentKey: string) => {
|
|
536
|
+
// Find all exposures whose parent.key matches parentKey
|
|
537
|
+
const children = this.exposes.filter((e) => e.parent?.key === parentKey)
|
|
538
|
+
for (const child of children) {
|
|
539
|
+
removeChildren(child.key)
|
|
540
|
+
const childIndex = this.exposes.findIndex((e) => e.key === child.key)
|
|
541
|
+
if (childIndex >= 0) {
|
|
542
|
+
this.exposes.splice(childIndex, 1)
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// Then also remove children
|
|
547
|
+
removeChildren(key)
|
|
548
|
+
this.notifyChange()
|
|
406
549
|
}
|
|
550
|
+
|
|
407
551
|
/**
|
|
408
552
|
* Returns the exposed entity by its key.
|
|
409
553
|
* @param entityKey The key of the entity to find.
|
|
410
554
|
* @returns The exposed entity or undefined if not found.
|
|
411
555
|
*/
|
|
412
|
-
getExposedEntity(
|
|
413
|
-
return this.exposes.find((e) => e.key ===
|
|
556
|
+
getExposedEntity(entity: AssociationTarget): ExposedEntity | undefined {
|
|
557
|
+
return this.exposes.find((e) => e.entity.key === entity.key && e.entity.domain === entity.domain)
|
|
414
558
|
}
|
|
415
559
|
|
|
416
560
|
/**
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AssociationTarget } from '../types.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a consistent key for a given association target within its domain.
|
|
5
|
+
*
|
|
6
|
+
* @param target The association target to create a key for.
|
|
7
|
+
* @returns A unique key for the association target.
|
|
8
|
+
*/
|
|
9
|
+
export function createDomainKey(target: AssociationTarget): string {
|
|
10
|
+
return target.domain ? `${target.domain}:${target.key}` : target.key
|
|
11
|
+
}
|
package/src/modeling/readme.md
CHANGED
|
@@ -122,13 +122,159 @@ Let's imagine a simple e-commerce domain:
|
|
|
122
122
|
- **Entity-Parent**: A `DomainEntity` can have `DomainEntity` instances as parents.
|
|
123
123
|
- **Namespace-Foreign**: A `DomainNamespace` can have references to `DomainNamespace` instances.
|
|
124
124
|
|
|
125
|
+
## API Modeling
|
|
126
|
+
|
|
127
|
+
Beyond data modeling, the system also provides **API Modeling** capabilities that allow you to define how your data domain is exposed as a secure, production-ready API.
|
|
128
|
+
|
|
129
|
+
### ApiModel
|
|
130
|
+
|
|
131
|
+
The `ApiModel` class extends the data modeling system to define how Data Entities from a `DataDomain` are exposed via HTTP APIs. It provides configuration for:
|
|
132
|
+
|
|
133
|
+
**Core API Configuration:**
|
|
134
|
+
|
|
135
|
+
- **Exposed Entities**: Select which Data Entities from your Data Domain should be accessible via the API
|
|
136
|
+
- **API Actions**: Define what operations (List, Read, Create, Update, Delete, Search) are available for each entity
|
|
137
|
+
- **User Entity**: Designate which Data Entity represents a "User" for authentication purposes
|
|
138
|
+
|
|
139
|
+
**Security & Authentication:**
|
|
140
|
+
|
|
141
|
+
- **Authentication**: Configure how users prove their identity (e.g., username/password)
|
|
142
|
+
- **Authorization**: Define what authenticated users are allowed to do (e.g., Role-Based Access Control)
|
|
143
|
+
- **Session Management**: Configure transport and payload for user sessions (JWT, cookies)
|
|
144
|
+
- **Access Rules**: Define fine-grained access control policies for entities and actions
|
|
145
|
+
|
|
146
|
+
**API Metadata:**
|
|
147
|
+
|
|
148
|
+
- **Contact Information**: API maintainer details
|
|
149
|
+
- **License Information**: Legal information about API usage
|
|
150
|
+
- **Terms of Service**: Link to API usage terms
|
|
151
|
+
- **Rate Limiting**: Protect the API from overuse and abuse
|
|
152
|
+
|
|
153
|
+
### How API Modeling Works with Data Modeling
|
|
154
|
+
|
|
155
|
+
The API modeling system builds on top of the data modeling foundation:
|
|
156
|
+
|
|
157
|
+
1. **Start with a Data Domain**: Create your `DataDomain` with entities, properties, and associations
|
|
158
|
+
2. **Create an API Model**: Instantiate an `ApiModel` and attach your `DataDomain`
|
|
159
|
+
3. **Expose Entities**: Select which entities should be available via the API using `exposeEntity()`
|
|
160
|
+
4. **Configure Security**: Set up authentication, authorization, and session management
|
|
161
|
+
5. **Define Actions**: Configure what operations are available for each exposed entity
|
|
162
|
+
6. **Set API Metadata**: Add contact info, license, terms of service
|
|
163
|
+
|
|
164
|
+
### Example: E-Commerce API
|
|
165
|
+
|
|
166
|
+
Building on the e-commerce data domain example:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// 1. Create the data domain (as shown in previous example)
|
|
170
|
+
const domain = new DataDomain({ key: 'ecommerce', info: { name: 'E-Commerce Platform' } })
|
|
171
|
+
const userModel = domain.addModel({ info: { name: 'User Management' } })
|
|
172
|
+
const userEntity = userModel.addEntity({
|
|
173
|
+
info: { name: 'user' },
|
|
174
|
+
semantics: [{ type: SemanticType.User }] // Mark as User entity
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// 2. Create an API model
|
|
178
|
+
const apiModel = new ApiModel({
|
|
179
|
+
info: { name: 'E-Commerce API', version: '1.0.0' },
|
|
180
|
+
termsOfService: 'https://example.com/terms',
|
|
181
|
+
contact: { name: 'API Team', email: 'api@example.com' },
|
|
182
|
+
license: { name: 'MIT', url: 'https://opensource.org/licenses/MIT' }
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// 3. Attach the data domain
|
|
186
|
+
apiModel.attachDataDomain(domain)
|
|
187
|
+
|
|
188
|
+
// 4. Configure security
|
|
189
|
+
apiModel.user = { key: userEntity.key }
|
|
190
|
+
apiModel.authentication = { strategy: 'UsernamePassword', passwordKey: 'password' }
|
|
191
|
+
apiModel.authorization = { strategy: 'RBAC', roleKey: 'role' }
|
|
192
|
+
apiModel.session = {
|
|
193
|
+
secret: 'your-secure-secret',
|
|
194
|
+
properties: ['email', 'role'],
|
|
195
|
+
cookie: { enabled: true, kind: 'cookie', lifetime: '7d' }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 5. Expose entities and configure actions
|
|
199
|
+
const exposedUser = apiModel.exposeEntity(userEntity.key)
|
|
200
|
+
exposedUser.actions = [
|
|
201
|
+
{ type: 'Read', enabled: true },
|
|
202
|
+
{ type: 'Update', enabled: true },
|
|
203
|
+
{ type: 'Create', enabled: true }
|
|
204
|
+
]
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Key API Modeling Concepts
|
|
208
|
+
|
|
209
|
+
#### ExposedEntity
|
|
210
|
+
|
|
211
|
+
Represents a Data Entity that is exposed via the API. Contains:
|
|
212
|
+
|
|
213
|
+
- **Key**: Reference to the Data Entity
|
|
214
|
+
- **Actions**: List of available API operations (CRUD, Search)
|
|
215
|
+
- **Access Rules**: Entity-specific access control
|
|
216
|
+
- **Rate Limiting**: Entity-specific rate limiting rules
|
|
217
|
+
|
|
218
|
+
#### API Actions
|
|
219
|
+
|
|
220
|
+
Standard RESTful operations that can be enabled for each entity:
|
|
221
|
+
|
|
222
|
+
- **List**: Retrieve collections of entities with filtering and pagination
|
|
223
|
+
- **Read**: Retrieve a single entity by ID
|
|
224
|
+
- **Create**: Create new entities
|
|
225
|
+
- **Update**: Modify existing entities
|
|
226
|
+
- **Delete**: Remove entities
|
|
227
|
+
- **Search**: Full-text or advanced search across entities
|
|
228
|
+
|
|
229
|
+
#### Authentication Configuration
|
|
230
|
+
|
|
231
|
+
Defines how users prove their identity:
|
|
232
|
+
|
|
233
|
+
- **UsernamePassword**: Traditional email/password authentication
|
|
234
|
+
- Extensible for future strategies (SSO, OAuth, etc.)
|
|
235
|
+
|
|
236
|
+
#### Authorization Configuration
|
|
237
|
+
|
|
238
|
+
Defines what authenticated users can do:
|
|
239
|
+
|
|
240
|
+
- **RBAC (Role-Based Access Control)**: Users have roles, permissions granted to roles
|
|
241
|
+
- Extensible for future strategies (PBAC - Permission-Based Access Control)
|
|
242
|
+
|
|
243
|
+
#### Session Configuration
|
|
244
|
+
|
|
245
|
+
Manages user session data:
|
|
246
|
+
|
|
247
|
+
- **Properties**: Which User entity properties to include in session
|
|
248
|
+
- **Transport**: How session data is transmitted (JWT tokens, cookies)
|
|
249
|
+
- **Security**: Encryption, lifetime, and security settings
|
|
250
|
+
|
|
251
|
+
### Business-First API Design
|
|
252
|
+
|
|
253
|
+
The API modeling system follows the same business-first philosophy as data modeling:
|
|
254
|
+
|
|
255
|
+
- **Semantic Awareness**: Uses semantic types to automatically configure security (e.g., Password properties are write-only)
|
|
256
|
+
- **Standard Compliance**: Generates OpenAPI-compliant specifications
|
|
257
|
+
- **Security by Default**: Requires explicit configuration for authentication, authorization, and sessions
|
|
258
|
+
- **User-Friendly**: Clear error messages and validation feedback
|
|
259
|
+
- **Production Ready**: Built-in support for rate limiting, access control, and monitoring
|
|
260
|
+
|
|
125
261
|
## Summary
|
|
126
262
|
|
|
127
|
-
This
|
|
263
|
+
This comprehensive modeling system provides both **Data Modeling** and **API Modeling** capabilities:
|
|
264
|
+
|
|
265
|
+
**Data Modeling** allows you to:
|
|
266
|
+
|
|
267
|
+
- **Organize**: Group related data into namespaces and models
|
|
268
|
+
- **Structure**: Define entities with properties and relationships
|
|
269
|
+
- **Reuse**: Inherit from other entities and reference foreign namespaces
|
|
270
|
+
- **Translate**: Define bindings to map the model to different formats
|
|
271
|
+
- **Validate**: Validate the data model definition
|
|
272
|
+
- **Generate**: Generate AMF shapes and examples
|
|
273
|
+
|
|
274
|
+
**API Modeling** extends this to:
|
|
128
275
|
|
|
129
|
-
- **
|
|
130
|
-
- **
|
|
131
|
-
- **
|
|
132
|
-
- **
|
|
133
|
-
- **
|
|
134
|
-
- **Generate**: Generate AMF shapes and examples.
|
|
276
|
+
- **Expose**: Selectively expose Data Entities as API resources
|
|
277
|
+
- **Secure**: Configure authentication, authorization, and session management
|
|
278
|
+
- **Control**: Define fine-grained access rules and rate limiting
|
|
279
|
+
- **Document**: Generate complete API documentation with business context
|
|
280
|
+
- **Deploy**: Create production-ready APIs with proper security and monitoring
|
package/src/modeling/types.ts
CHANGED
|
@@ -428,25 +428,101 @@ export interface UsernamePasswordConfiguration extends AuthenticationConfigurati
|
|
|
428
428
|
*/
|
|
429
429
|
export interface ExposedEntity {
|
|
430
430
|
/**
|
|
431
|
-
* The
|
|
431
|
+
* The unique identifier for this exposure instance.
|
|
432
|
+
* In the exposure model, we need to uniquely identify each exposure instance, because
|
|
433
|
+
* an entity can be exposed multiple times in different contexts. Consider the following structure:
|
|
434
|
+
*
|
|
435
|
+
* ```
|
|
436
|
+
* /categories/{categoryId}
|
|
437
|
+
* /products/{productId}/categories
|
|
438
|
+
* /products/{productId}/categories/{categoryId}
|
|
439
|
+
* /promotions/{promotionId}/categories
|
|
440
|
+
* /promotions/{promotionId}/categories/{categoryId}
|
|
441
|
+
* ```
|
|
442
|
+
*
|
|
443
|
+
* The `category` entity would be exposed multiple times (as root and nested under products and promotions).
|
|
444
|
+
* We need a way to distinguish between these different exposure instances.
|
|
432
445
|
*/
|
|
433
446
|
key: string
|
|
447
|
+
/**
|
|
448
|
+
* A pointer to a Data Entity from the Data Domain.
|
|
449
|
+
*/
|
|
450
|
+
entity: AssociationTarget
|
|
451
|
+
/**
|
|
452
|
+
* The path segment for this exposure.
|
|
453
|
+
*/
|
|
454
|
+
path: string
|
|
434
455
|
|
|
435
456
|
/**
|
|
436
|
-
*
|
|
437
|
-
*
|
|
457
|
+
* Whether this exposure is a root exposure (top-level collection).
|
|
458
|
+
* If this is set then the `parent` reference must be populated.
|
|
438
459
|
*/
|
|
439
|
-
|
|
460
|
+
isRoot?: boolean
|
|
461
|
+
|
|
440
462
|
/**
|
|
441
|
-
*
|
|
442
|
-
* It override the top-level access rules defined in the API model.
|
|
463
|
+
* Parent reference when this exposure was created via following an association.
|
|
443
464
|
*/
|
|
444
|
-
|
|
465
|
+
parent?: ExposeParentRef
|
|
445
466
|
|
|
446
467
|
/**
|
|
447
|
-
*
|
|
468
|
+
* Expose-time config used to create this exposure (persisted for auditing/UI).
|
|
469
|
+
* This is only populated for the root exposure. All children exposures inherit this config.
|
|
470
|
+
*/
|
|
471
|
+
exposeOptions?: ExposeOptions
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* The list of enabled API actions for this exposure (List/Read/Create/etc.)
|
|
448
475
|
*/
|
|
449
476
|
actions: ApiAction[]
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Optional array of access rules that define the access control policies for this exposure.
|
|
480
|
+
*/
|
|
481
|
+
accessRule?: AccessRule[]
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Optional configuration for rate limiting for this exposure.
|
|
485
|
+
*/
|
|
486
|
+
rateLimiting?: RateLimitingConfiguration
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* When true, generation for this exposure hit configured limits
|
|
490
|
+
*/
|
|
491
|
+
truncated?: boolean
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Parent reference stored on a nested exposure
|
|
496
|
+
*/
|
|
497
|
+
export interface ExposeParentRef {
|
|
498
|
+
/**
|
|
499
|
+
* The key of the parent exposed entity. This references the `ExposedEntity.key` property.
|
|
500
|
+
*/
|
|
501
|
+
key: string
|
|
502
|
+
/**
|
|
503
|
+
* The association from the parent that produced this exposure.
|
|
504
|
+
* A sub-entity must always have a parent association.
|
|
505
|
+
*/
|
|
506
|
+
association: AssociationTarget
|
|
507
|
+
/**
|
|
508
|
+
* The numeric depth from the root exposure (root = 0)
|
|
509
|
+
*/
|
|
510
|
+
depth?: number
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Options passed when creating a new exposure
|
|
515
|
+
*/
|
|
516
|
+
export interface ExposeOptions {
|
|
517
|
+
/**
|
|
518
|
+
* Whether to follow associations when creating the exposure.
|
|
519
|
+
* When not set, it only exposes the passed entity.
|
|
520
|
+
*/
|
|
521
|
+
followAssociations?: boolean
|
|
522
|
+
/**
|
|
523
|
+
* The maximum depth to follow associations when creating the exposure.
|
|
524
|
+
*/
|
|
525
|
+
maxDepth?: number
|
|
450
526
|
}
|
|
451
527
|
|
|
452
528
|
/**
|