@api-client/core 0.18.27 → 0.18.29

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.
@@ -42062,19 +42062,19 @@
42062
42062
  "@id": "#209"
42063
42063
  },
42064
42064
  {
42065
- "@id": "#191"
42065
+ "@id": "#200"
42066
42066
  },
42067
42067
  {
42068
- "@id": "#194"
42068
+ "@id": "#203"
42069
42069
  },
42070
42070
  {
42071
- "@id": "#197"
42071
+ "@id": "#191"
42072
42072
  },
42073
42073
  {
42074
- "@id": "#200"
42074
+ "@id": "#197"
42075
42075
  },
42076
42076
  {
42077
- "@id": "#203"
42077
+ "@id": "#194"
42078
42078
  },
42079
42079
  {
42080
42080
  "@id": "#206"
@@ -42813,13 +42813,13 @@
42813
42813
  "@id": "#210"
42814
42814
  },
42815
42815
  {
42816
- "@id": "#213"
42816
+ "@id": "#219"
42817
42817
  },
42818
42818
  {
42819
- "@id": "#216"
42819
+ "@id": "#213"
42820
42820
  },
42821
42821
  {
42822
- "@id": "#219"
42822
+ "@id": "#216"
42823
42823
  }
42824
42824
  ],
42825
42825
  "doc:root": false,
@@ -43436,7 +43436,7 @@
43436
43436
  "doc:ExternalDomainElement",
43437
43437
  "doc:DomainElement"
43438
43438
  ],
43439
- "doc:raw": "countryCode: \"BE\"\ngraydonEnterpriseId: 1057155523\nregistrationId: \"0422319093\"\nvatNumber: \"BE0422319093\"\ngraydonCompanyId: \"0422319093\"\nisBranchOffice: false\n",
43439
+ "doc:raw": "code: '5'\ndescription: 'Limited company'\n",
43440
43440
  "core:mediaType": "application/yaml",
43441
43441
  "sourcemaps:sources": [
43442
43442
  {
@@ -43457,7 +43457,7 @@
43457
43457
  "doc:ExternalDomainElement",
43458
43458
  "doc:DomainElement"
43459
43459
  ],
43460
- "doc:raw": "addressType: 'REGISTERED-OFFICE-ADDRESS'\nstreetName: 'UITBREIDINGSTRAAT'\nhouseNumber: '84'\nhouseNumberAddition: '/1'\npostalCode: '2600'\ncity: 'BERCHEM (ANTWERPEN)'\ncountry: 'Belgium'\ncountryCode: 'BE'\nfullFormatedAddress: \"UITBREIDINGSTRAAT 84 /1, 2600 BERCHEM (ANTWERPEN), BELIUM\"\n",
43460
+ "doc:raw": "code: 'J'\ndescription: 'Information and communication'\n",
43461
43461
  "core:mediaType": "application/yaml",
43462
43462
  "sourcemaps:sources": [
43463
43463
  {
@@ -43478,7 +43478,7 @@
43478
43478
  "doc:ExternalDomainElement",
43479
43479
  "doc:DomainElement"
43480
43480
  ],
43481
- "doc:raw": "code: '5'\ndescription: 'Limited company'\n",
43481
+ "doc:raw": "class: '3'\ndescription: '150 - 300'\nnumberOfFte: 5500\nnumberOfEmployees: 5232\n",
43482
43482
  "core:mediaType": "application/yaml",
43483
43483
  "sourcemaps:sources": [
43484
43484
  {
@@ -43499,7 +43499,7 @@
43499
43499
  "doc:ExternalDomainElement",
43500
43500
  "doc:DomainElement"
43501
43501
  ],
43502
- "doc:raw": "class: '3'\ndescription: '150 - 300'\nnumberOfFte: 5500\nnumberOfEmployees: 5232\n",
43502
+ "doc:raw": "countryCode: \"BE\"\ngraydonEnterpriseId: 1057155523\nregistrationId: \"0422319093\"\nvatNumber: \"BE0422319093\"\ngraydonCompanyId: \"0422319093\"\nisBranchOffice: false\n",
43503
43503
  "core:mediaType": "application/yaml",
43504
43504
  "sourcemaps:sources": [
43505
43505
  {
@@ -43520,7 +43520,7 @@
43520
43520
  "doc:ExternalDomainElement",
43521
43521
  "doc:DomainElement"
43522
43522
  ],
43523
- "doc:raw": "code: 'J'\ndescription: 'Information and communication'\n",
43523
+ "doc:raw": "addressType: 'REGISTERED-OFFICE-ADDRESS'\nstreetName: 'UITBREIDINGSTRAAT'\nhouseNumber: '84'\nhouseNumberAddition: '/1'\npostalCode: '2600'\ncity: 'BERCHEM (ANTWERPEN)'\ncountry: 'Belgium'\ncountryCode: 'BE'\nfullFormatedAddress: \"UITBREIDINGSTRAAT 84 /1, 2600 BERCHEM (ANTWERPEN), BELIUM\"\n",
43524
43524
  "core:mediaType": "application/yaml",
43525
43525
  "sourcemaps:sources": [
43526
43526
  {
@@ -44253,7 +44253,7 @@
44253
44253
  "doc:ExternalDomainElement",
44254
44254
  "doc:DomainElement"
44255
44255
  ],
44256
- "doc:raw": "type: 'GENERAL'\ncountryDialCode : '+32'\nareaCode : '21'\nsubscriberNumber: '12.87.00'\nformatted: '+32-(0)21 302099'\n",
44256
+ "doc:raw": "-\n type: 'GENERAL'\n value: 'info@company.be'\n-\n type: 'IT_DEPT'\n value: 'it-service@company.be'\n",
44257
44257
  "core:mediaType": "application/yaml",
44258
44258
  "sourcemaps:sources": [
44259
44259
  {
@@ -44274,7 +44274,7 @@
44274
44274
  "doc:ExternalDomainElement",
44275
44275
  "doc:DomainElement"
44276
44276
  ],
44277
- "doc:raw": "-\n type: 'GENERAL'\n value: 'info@company.be'\n-\n type: 'IT_DEPT'\n value: 'it-service@company.be'\n",
44277
+ "doc:raw": "type: \"GENERAL\"\nvalue: \"www.company.be\"\n",
44278
44278
  "core:mediaType": "application/yaml",
44279
44279
  "sourcemaps:sources": [
44280
44280
  {
@@ -44295,7 +44295,7 @@
44295
44295
  "doc:ExternalDomainElement",
44296
44296
  "doc:DomainElement"
44297
44297
  ],
44298
- "doc:raw": "type: \"GENERAL\"\nvalue: \"www.company.be\"\n",
44298
+ "doc:raw": "type: 'GENERAL'\ncountryDialCode : '+32'\nareaCode : '21'\nsubscriberNumber: '12.87.00'\nformatted: '+32-(0)21 302099'\n",
44299
44299
  "core:mediaType": "application/yaml",
44300
44300
  "sourcemaps:sources": [
44301
44301
  {
@@ -44756,27 +44756,27 @@
44756
44756
  {
44757
44757
  "@id": "#193/source-map/lexical/element_0",
44758
44758
  "sourcemaps:element": "amf://id#193",
44759
- "sourcemaps:value": "[(1,0)-(7,0)]"
44759
+ "sourcemaps:value": "[(1,0)-(3,0)]"
44760
44760
  },
44761
44761
  {
44762
44762
  "@id": "#196/source-map/lexical/element_0",
44763
44763
  "sourcemaps:element": "amf://id#196",
44764
- "sourcemaps:value": "[(1,0)-(10,0)]"
44764
+ "sourcemaps:value": "[(1,0)-(3,0)]"
44765
44765
  },
44766
44766
  {
44767
44767
  "@id": "#199/source-map/lexical/element_0",
44768
44768
  "sourcemaps:element": "amf://id#199",
44769
- "sourcemaps:value": "[(1,0)-(3,0)]"
44769
+ "sourcemaps:value": "[(1,0)-(5,0)]"
44770
44770
  },
44771
44771
  {
44772
44772
  "@id": "#202/source-map/lexical/element_0",
44773
44773
  "sourcemaps:element": "amf://id#202",
44774
- "sourcemaps:value": "[(1,0)-(5,0)]"
44774
+ "sourcemaps:value": "[(1,0)-(7,0)]"
44775
44775
  },
44776
44776
  {
44777
44777
  "@id": "#205/source-map/lexical/element_0",
44778
44778
  "sourcemaps:element": "amf://id#205",
44779
- "sourcemaps:value": "[(1,0)-(3,0)]"
44779
+ "sourcemaps:value": "[(1,0)-(10,0)]"
44780
44780
  },
44781
44781
  {
44782
44782
  "@id": "#208/source-map/lexical/element_0",
@@ -45121,17 +45121,17 @@
45121
45121
  {
45122
45122
  "@id": "#215/source-map/lexical/element_0",
45123
45123
  "sourcemaps:element": "amf://id#215",
45124
- "sourcemaps:value": "[(1,0)-(6,0)]"
45124
+ "sourcemaps:value": "[(1,0)-(7,0)]"
45125
45125
  },
45126
45126
  {
45127
45127
  "@id": "#218/source-map/lexical/element_0",
45128
45128
  "sourcemaps:element": "amf://id#218",
45129
- "sourcemaps:value": "[(1,0)-(7,0)]"
45129
+ "sourcemaps:value": "[(1,0)-(3,0)]"
45130
45130
  },
45131
45131
  {
45132
45132
  "@id": "#221/source-map/lexical/element_0",
45133
45133
  "sourcemaps:element": "amf://id#221",
45134
- "sourcemaps:value": "[(1,0)-(3,0)]"
45134
+ "sourcemaps:value": "[(1,0)-(6,0)]"
45135
45135
  },
45136
45136
  {
45137
45137
  "@id": "#338/source-map/synthesized-field/element_1",
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.27",
4
+ "version": "0.18.29",
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",
@@ -96,7 +97,7 @@
96
97
  "chalk": "^5.4.1",
97
98
  "console-table-printer": "^2.11.2",
98
99
  "dompurify": "^3.2.6",
99
- "jsdom": "^26.1.0",
100
+ "jsdom": "^27.0.0",
100
101
  "nanoid": "^5.1.5",
101
102
  "tslog": "^4.9.3",
102
103
  "ws": "^8.12.0",
@@ -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 data domain schema.
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 data domain schema.
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 entityKey The key of the entity to expose.
382
+ * @param entity The entity key and domain to expose.
380
383
  * @returns The exposed entity.
381
384
  */
382
- exposeEntity(entityKey: string): ExposedEntity {
383
- const existing = this.exposes.find((e) => e.key === entityKey)
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: entityKey,
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
- * Removes an entity from the API model.
398
- * @param entityKey The key of the entity to remove.
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
- removeEntity(entityKey: string): void {
401
- const index = this.exposes.findIndex((e) => e.key === entityKey)
402
- if (index !== -1) {
403
- this.exposes.splice(index, 1)
404
- this.notifyChange()
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(entityKey: string): ExposedEntity | undefined {
413
- return this.exposes.find((e) => e.key === entityKey)
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,5 @@
1
+ export function paramNameFor(entityKeyLocal: string): string {
2
+ const parts = entityKeyLocal.split(':')
3
+ const key = parts[parts.length - 1]
4
+ return `${key}Id`
5
+ }
@@ -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
+ }
@@ -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 data modeling system provides a flexible and powerful way to define complex data domains. It allows you to:
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
- - **Organize**: Group related data into namespaces and models.
130
- - **Structure**: Define entities with properties and relationships.
131
- - **Reuse**: Inherit from other entities and reference foreign namespaces.
132
- - **Translate**: Define bindings to map the model to different formats.
133
- - **Validate**: Validate the data model definition.
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