@api-client/core 0.19.18 → 0.19.20

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.
Files changed (154) hide show
  1. package/build/src/authorization/Utils.js +3 -3
  2. package/build/src/authorization/Utils.js.map +1 -1
  3. package/build/src/browser.d.ts +1 -1
  4. package/build/src/browser.d.ts.map +1 -1
  5. package/build/src/browser.js.map +1 -1
  6. package/build/src/index.d.ts +1 -1
  7. package/build/src/index.d.ts.map +1 -1
  8. package/build/src/index.js.map +1 -1
  9. package/build/src/mocking/lib/Organization.d.ts +5 -1
  10. package/build/src/mocking/lib/Organization.d.ts.map +1 -1
  11. package/build/src/mocking/lib/Organization.js +17 -0
  12. package/build/src/mocking/lib/Organization.js.map +1 -1
  13. package/build/src/modeling/ApiModel.d.ts +16 -5
  14. package/build/src/modeling/ApiModel.d.ts.map +1 -1
  15. package/build/src/modeling/ApiModel.js +17 -2
  16. package/build/src/modeling/ApiModel.js.map +1 -1
  17. package/build/src/modeling/ApiValidation.d.ts.map +1 -1
  18. package/build/src/modeling/ApiValidation.js +2 -1
  19. package/build/src/modeling/ApiValidation.js.map +1 -1
  20. package/build/src/modeling/DomainProperty.d.ts +12 -0
  21. package/build/src/modeling/DomainProperty.d.ts.map +1 -1
  22. package/build/src/modeling/DomainProperty.js +23 -28
  23. package/build/src/modeling/DomainProperty.js.map +1 -1
  24. package/build/src/modeling/DomainSerialization.js +1 -1
  25. package/build/src/modeling/DomainSerialization.js.map +1 -1
  26. package/build/src/modeling/ExposedEntity.d.ts +15 -1
  27. package/build/src/modeling/ExposedEntity.d.ts.map +1 -1
  28. package/build/src/modeling/ExposedEntity.js +42 -4
  29. package/build/src/modeling/ExposedEntity.js.map +1 -1
  30. package/build/src/modeling/actions/Action.d.ts.map +1 -1
  31. package/build/src/modeling/actions/Action.js +1 -0
  32. package/build/src/modeling/actions/Action.js.map +1 -1
  33. package/build/src/modeling/actions/ListAction.d.ts +3 -17
  34. package/build/src/modeling/actions/ListAction.d.ts.map +1 -1
  35. package/build/src/modeling/actions/ListAction.js +18 -38
  36. package/build/src/modeling/actions/ListAction.js.map +1 -1
  37. package/build/src/modeling/actions/SearchAction.d.ts +4 -4
  38. package/build/src/modeling/actions/SearchAction.d.ts.map +1 -1
  39. package/build/src/modeling/actions/SearchAction.js +16 -13
  40. package/build/src/modeling/actions/SearchAction.js.map +1 -1
  41. package/build/src/modeling/generators/oas_312/OasGenerator.d.ts +32 -0
  42. package/build/src/modeling/generators/oas_312/OasGenerator.d.ts.map +1 -0
  43. package/build/src/modeling/generators/oas_312/OasGenerator.js +1452 -0
  44. package/build/src/modeling/generators/oas_312/OasGenerator.js.map +1 -0
  45. package/build/src/modeling/generators/oas_312/OasSchemaGenerator.d.ts +27 -0
  46. package/build/src/modeling/generators/oas_312/OasSchemaGenerator.d.ts.map +1 -0
  47. package/build/src/modeling/generators/oas_312/OasSchemaGenerator.js +295 -0
  48. package/build/src/modeling/generators/oas_312/OasSchemaGenerator.js.map +1 -0
  49. package/build/src/modeling/generators/oas_312/types.d.ts +1010 -0
  50. package/build/src/modeling/generators/oas_312/types.d.ts.map +1 -0
  51. package/build/src/modeling/generators/oas_312/types.js +2 -0
  52. package/build/src/modeling/generators/oas_312/types.js.map +1 -0
  53. package/build/src/modeling/generators/oas_320/OasGenerator.d.ts +16 -0
  54. package/build/src/modeling/generators/oas_320/OasGenerator.d.ts.map +1 -0
  55. package/build/src/modeling/generators/oas_320/OasGenerator.js +306 -0
  56. package/build/src/modeling/generators/oas_320/OasGenerator.js.map +1 -0
  57. package/build/src/modeling/generators/oas_320/OasSchemaGenerator.d.ts +25 -0
  58. package/build/src/modeling/generators/oas_320/OasSchemaGenerator.d.ts.map +1 -0
  59. package/build/src/modeling/generators/oas_320/OasSchemaGenerator.js +237 -0
  60. package/build/src/modeling/generators/oas_320/OasSchemaGenerator.js.map +1 -0
  61. package/build/src/modeling/generators/oas_320/types.d.ts +1219 -0
  62. package/build/src/modeling/generators/oas_320/types.d.ts.map +1 -0
  63. package/build/src/modeling/generators/oas_320/types.js +2 -0
  64. package/build/src/modeling/generators/oas_320/types.js.map +1 -0
  65. package/build/src/modeling/types.d.ts +50 -13
  66. package/build/src/modeling/types.d.ts.map +1 -1
  67. package/build/src/modeling/types.js.map +1 -1
  68. package/build/src/modeling/validation/api_model_rules.d.ts +1 -0
  69. package/build/src/modeling/validation/api_model_rules.d.ts.map +1 -1
  70. package/build/src/modeling/validation/api_model_rules.js +105 -29
  71. package/build/src/modeling/validation/api_model_rules.js.map +1 -1
  72. package/build/src/models/ProjectRequest.d.ts.map +1 -1
  73. package/build/src/models/ProjectRequest.js +0 -4
  74. package/build/src/models/ProjectRequest.js.map +1 -1
  75. package/build/src/models/store/Organization.d.ts +13 -0
  76. package/build/src/models/store/Organization.d.ts.map +1 -1
  77. package/build/src/models/store/Organization.js.map +1 -1
  78. package/build/src/models/transformers/ArcDexieTransformer.d.ts.map +1 -1
  79. package/build/src/models/transformers/ArcDexieTransformer.js +0 -4
  80. package/build/src/models/transformers/ArcDexieTransformer.js.map +1 -1
  81. package/build/src/models/transformers/ImportUtils.js +1 -1
  82. package/build/src/models/transformers/ImportUtils.js.map +1 -1
  83. package/build/src/models/transformers/PostmanBackupTransformer.d.ts.map +1 -1
  84. package/build/src/models/transformers/PostmanBackupTransformer.js +0 -4
  85. package/build/src/models/transformers/PostmanBackupTransformer.js.map +1 -1
  86. package/build/src/runtime/constants.d.ts +7 -0
  87. package/build/src/runtime/constants.d.ts.map +1 -0
  88. package/build/src/runtime/constants.js +8 -0
  89. package/build/src/runtime/constants.js.map +1 -0
  90. package/build/src/runtime/http-engine/ntlm/Des.d.ts.map +1 -1
  91. package/build/src/runtime/http-engine/ntlm/Des.js +1 -0
  92. package/build/src/runtime/http-engine/ntlm/Des.js.map +1 -1
  93. package/build/src/runtime/variables/EvalFunctions.d.ts.map +1 -1
  94. package/build/src/runtime/variables/EvalFunctions.js +0 -1
  95. package/build/src/runtime/variables/EvalFunctions.js.map +1 -1
  96. package/build/src/sdk/OrganizationsSdk.d.ts +17 -1
  97. package/build/src/sdk/OrganizationsSdk.d.ts.map +1 -1
  98. package/build/src/sdk/OrganizationsSdk.js +76 -0
  99. package/build/src/sdk/OrganizationsSdk.js.map +1 -1
  100. package/build/src/sdk/RouteBuilder.d.ts +2 -0
  101. package/build/src/sdk/RouteBuilder.d.ts.map +1 -1
  102. package/build/src/sdk/RouteBuilder.js +6 -0
  103. package/build/src/sdk/RouteBuilder.js.map +1 -1
  104. package/build/src/sdk/SdkMock.d.ts +12 -0
  105. package/build/src/sdk/SdkMock.d.ts.map +1 -1
  106. package/build/src/sdk/SdkMock.js +32 -0
  107. package/build/src/sdk/SdkMock.js.map +1 -1
  108. package/build/tsconfig.tsbuildinfo +1 -1
  109. package/eslint.config.js +6 -0
  110. package/package.json +3 -1
  111. package/src/authorization/Utils.ts +3 -3
  112. package/src/mocking/lib/Organization.ts +22 -1
  113. package/src/modeling/ApiModel.ts +23 -8
  114. package/src/modeling/ApiValidation.ts +2 -0
  115. package/src/modeling/DomainProperty.ts +22 -18
  116. package/src/modeling/DomainSerialization.ts +1 -1
  117. package/src/modeling/ExposedEntity.ts +44 -4
  118. package/src/modeling/actions/Action.ts +1 -0
  119. package/src/modeling/actions/ListAction.ts +12 -30
  120. package/src/modeling/actions/SearchAction.ts +11 -8
  121. package/src/modeling/generators/oas_312/OasGenerator.ts +1685 -0
  122. package/src/modeling/generators/oas_312/OasSchemaGenerator.ts +322 -0
  123. package/src/modeling/generators/oas_312/types.ts +1052 -0
  124. package/src/modeling/generators/oas_320/OasGenerator.ts +359 -0
  125. package/src/modeling/generators/oas_320/OasSchemaGenerator.ts +255 -0
  126. package/src/modeling/generators/oas_320/types.ts +1259 -0
  127. package/src/modeling/types.ts +55 -22
  128. package/src/modeling/validation/api_model_rules.ts +103 -32
  129. package/src/models/ProjectRequest.ts +0 -4
  130. package/src/models/store/Organization.ts +14 -0
  131. package/src/models/transformers/ArcDexieTransformer.ts +0 -4
  132. package/src/models/transformers/ImportUtils.ts +1 -1
  133. package/src/models/transformers/PostmanBackupTransformer.ts +0 -5
  134. package/src/runtime/constants.ts +9 -0
  135. package/src/runtime/http-engine/ntlm/Des.ts +1 -0
  136. package/src/runtime/variables/EvalFunctions.ts +0 -1
  137. package/src/sdk/OrganizationsSdk.ts +81 -1
  138. package/src/sdk/RouteBuilder.ts +8 -0
  139. package/src/sdk/SdkMock.ts +50 -0
  140. package/tests/test-utils.ts +6 -2
  141. package/tests/unit/decorators/observed.spec.ts +8 -24
  142. package/tests/unit/decorators/observed_recursive.spec.ts +0 -1
  143. package/tests/unit/events/EventsTestHelpers.ts +0 -1
  144. package/tests/unit/events/events_polyfills.ts +0 -1
  145. package/tests/unit/legacy-transformers/DataTestHelper.ts +0 -2
  146. package/tests/unit/legacy-transformers/LegacyExportProcessor.spec.ts +0 -1
  147. package/tests/unit/modeling/actions/ListAction.spec.ts +9 -69
  148. package/tests/unit/modeling/actions/SearchAction.spec.ts +9 -35
  149. package/tests/unit/modeling/api_model.spec.ts +28 -0
  150. package/tests/unit/modeling/definitions/sku.spec.ts +0 -2
  151. package/tests/unit/modeling/domain_property.spec.ts +20 -1
  152. package/tests/unit/modeling/exposed_entity.spec.ts +71 -0
  153. package/tests/unit/modeling/generators/OasGenerator.spec.ts +302 -0
  154. package/tests/unit/modeling/validation/api_model_rules.spec.ts +113 -15
package/eslint.config.js CHANGED
@@ -3,6 +3,7 @@ import pluginJs from '@eslint/js'
3
3
  import tseslint from 'typescript-eslint'
4
4
  import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
5
5
  import noOnlyTests from 'eslint-plugin-no-only-tests'
6
+ import jsdoc from 'eslint-plugin-jsdoc'
6
7
  // import eslintPluginPrettier from 'eslint-plugin-prettier'
7
8
 
8
9
  // import eslintConfigPrettier from 'eslint-config-prettier'
@@ -40,6 +41,7 @@ export default [
40
41
  // eslintConfigPrettier,
41
42
  eslintPluginPrettierRecommended,
42
43
  {
44
+ ...jsdoc.configs['flat/recommended-typescript'],
43
45
  files: ['**/*.ts'],
44
46
  languageOptions: {
45
47
  globals: {
@@ -69,6 +71,9 @@ export default [
69
71
  'no-console': ['error'],
70
72
  'no-redeclare': ['error'],
71
73
  },
74
+ plugins: {
75
+ jsdoc,
76
+ },
72
77
  },
73
78
  {
74
79
  files: [
@@ -106,6 +111,7 @@ export default [
106
111
  '@typescript-eslint/no-non-null-assertion': 'off',
107
112
  '@typescript-eslint/no-empty-function': 'off',
108
113
  'no-only-tests/no-only-tests': 'error',
114
+ '@typescript-eslint/no-explicit-any': 'off',
109
115
  'no-console': ['warn'],
110
116
  },
111
117
  },
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.19.18",
4
+ "version": "0.19.20",
5
5
  "license": "UNLICENSED",
6
6
  "exports": {
7
7
  "./browser.js": {
@@ -97,6 +97,7 @@
97
97
  "console-table-printer": "^2.11.2",
98
98
  "dompurify": "^3.2.6",
99
99
  "jsdom": "^29.0.0",
100
+ "json-schema-typed": "^8.0.2",
100
101
  "nanoid": "^5.1.5",
101
102
  "tslog": "^4.9.3",
102
103
  "ws": "^8.12.0",
@@ -129,6 +130,7 @@
129
130
  "cors": "^2.8.5",
130
131
  "eslint": "^10.0.1",
131
132
  "eslint-config-prettier": "^10.0.1",
133
+ "eslint-plugin-jsdoc": "^62.9.0",
132
134
  "eslint-plugin-no-only-tests": "^3.3.0",
133
135
  "eslint-plugin-prettier": "^5.2.3",
134
136
  "express": "^5.1.0",
@@ -32,20 +32,20 @@ export function sanityCheck(settings: IOAuth2Authorization): void {
32
32
  try {
33
33
  checkUrl(settings.authorizationUri as string)
34
34
  } catch (e) {
35
- throw new Error(`authorizationUri: ${(e as Error).message}`)
35
+ throw new Error(`authorizationUri: ${(e as Error).message}`, { cause: e })
36
36
  }
37
37
  if (settings.accessTokenUri) {
38
38
  try {
39
39
  checkUrl(settings.accessTokenUri)
40
40
  } catch (e) {
41
- throw new Error(`accessTokenUri: ${(e as Error).message}`)
41
+ throw new Error(`accessTokenUri: ${(e as Error).message}`, { cause: e })
42
42
  }
43
43
  }
44
44
  } else if (settings.accessTokenUri) {
45
45
  try {
46
46
  checkUrl(settings.accessTokenUri)
47
47
  } catch (e) {
48
- throw new Error(`accessTokenUri: ${(e as Error).message}`)
48
+ throw new Error(`accessTokenUri: ${(e as Error).message}`, { cause: e })
49
49
  }
50
50
  }
51
51
  }
@@ -1,9 +1,30 @@
1
1
  import { faker } from '@faker-js/faker'
2
2
  import { OrganizationKind } from '../../models/kinds.js'
3
3
  import { nanoid } from '../../nanoid.js'
4
- import type { OrganizationSchema } from '../../models/store/Organization.js'
4
+ import type { OrganizationSchema, OrganizationSlugValidateResponse } from '../../models/store/Organization.js'
5
5
 
6
6
  export class Organization {
7
+ organizationSlugGenerateResponse(name?: string): { slug: string } {
8
+ const finalName = name ?? faker.company.name()
9
+ let baseSlug = faker.helpers.slugify(finalName)
10
+ // Truncate to ensure there's room for a suffix if needed
11
+ if (baseSlug.length > 55) {
12
+ baseSlug = baseSlug.substring(0, 55)
13
+ }
14
+ const suffix = nanoid(6).toLowerCase()
15
+ return { slug: `${baseSlug}-${suffix}` }
16
+ }
17
+
18
+ organizationSlugValidateResponse(
19
+ init: Partial<OrganizationSlugValidateResponse> = {}
20
+ ): OrganizationSlugValidateResponse {
21
+ const { valid = true, reason } = init
22
+ return {
23
+ valid,
24
+ reason,
25
+ }
26
+ }
27
+
7
28
  /**
8
29
  * Generates a random organization object.
9
30
  * @param init Optional values to be present in the object.
@@ -3,13 +3,15 @@ import { ApiModelKind, DataDomainKind, ExposedEntityKind } from '../models/kinds
3
3
  import { type ThingSchema, Thing } from '../models/Thing.js'
4
4
  import type {
5
5
  AssociationTarget,
6
- AuthenticationConfiguration,
7
- AuthorizationConfiguration,
6
+ AuthenticationStrategy,
7
+ AuthorizationStrategy,
8
8
  ExposedEntitySchema,
9
9
  RolesBasedAccessControl,
10
10
  SessionConfiguration,
11
11
  UsernamePasswordConfiguration,
12
12
  ExposeOptions,
13
+ OffsetPaginationStrategy,
14
+ CursorPaginationStrategy,
13
15
  } from './types.js'
14
16
  import { DataDomain } from './DataDomain.js'
15
17
  import { DependentModel, type DependentModelSchema, type DomainDependency } from './DependentModel.js'
@@ -67,7 +69,6 @@ export interface ApiModelSchema extends DependentModelSchema {
67
69
  * Contains the name, display name, description, and the version of the API model schema.
68
70
  */
69
71
  info: ThingSchema
70
-
71
72
  /**
72
73
  * The designated Data Entity that represents a "User".
73
74
  * This entity should be marked with the "User" semantic in the Data Modeler.
@@ -80,13 +81,13 @@ export interface ApiModelSchema extends DependentModelSchema {
80
81
  * Configuration for how users prove their identity.
81
82
  * The API model is invalid if this is not set.
82
83
  */
83
- authentication?: AuthenticationConfiguration
84
+ authentication?: AuthenticationStrategy
84
85
 
85
86
  /**
86
87
  * Configuration for what authenticated users are allowed to do.
87
88
  * The API model is invalid if this is not set.
88
89
  */
89
- authorization?: AuthorizationConfiguration
90
+ authorization?: AuthorizationStrategy
90
91
 
91
92
  /**
92
93
  * Configuration for the transport and payload of the user session.
@@ -125,6 +126,12 @@ export interface ApiModelSchema extends DependentModelSchema {
125
126
  * The license information for the API.
126
127
  */
127
128
  license?: ApiLicense
129
+ /**
130
+ * The pagination strategy used by all endpoints in this API. The configuration
131
+ * is shared across all endpoints to ensure consistency and security.
132
+ * This defines how the results are paginated when retrieving a collection of resources.
133
+ */
134
+ pagination: CursorPaginationStrategy | OffsetPaginationStrategy
128
135
  }
129
136
 
130
137
  export class ApiModel extends DependentModel {
@@ -154,13 +161,13 @@ export class ApiModel extends DependentModel {
154
161
  * Configuration for how users prove their identity.
155
162
  * The API model is invalid if this is not set.
156
163
  */
157
- authentication?: AuthenticationConfiguration
164
+ authentication?: AuthenticationStrategy
158
165
 
159
166
  /**
160
167
  * Configuration for what authenticated users are allowed to do.
161
168
  * The API model is invalid if this is not set.
162
169
  */
163
- authorization?: AuthorizationConfiguration
170
+ authorization?: AuthorizationStrategy
164
171
 
165
172
  /**
166
173
  * Configuration for the transport and payload of the user session.
@@ -200,6 +207,11 @@ export class ApiModel extends DependentModel {
200
207
  * The license information for the API.
201
208
  */
202
209
  @observed({ deep: true }) accessor license: ApiLicense | undefined
210
+ /**
211
+ * The pagination strategy used by all endpoints in this API.
212
+ * This defines how the results are paginated when retrieving a collection of resources.
213
+ */
214
+ @observed({ deep: true }) accessor pagination: CursorPaginationStrategy | OffsetPaginationStrategy
203
215
 
204
216
  /**
205
217
  * When the initializing flag is set to true,
@@ -232,13 +244,14 @@ export class ApiModel extends DependentModel {
232
244
  }
233
245
 
234
246
  static createSchema(input: Partial<ApiModelSchema> = {}): ApiModelSchema {
235
- const { key = nanoid(), exposes = [] } = input
247
+ const { key = nanoid(), exposes = [], pagination } = input
236
248
  const info = Thing.fromJSON(input.info, { name: 'Unnamed API' }).toJSON()
237
249
  const result: ApiModelSchema = {
238
250
  kind: ApiModelKind,
239
251
  key,
240
252
  info,
241
253
  exposes,
254
+ pagination: pagination ? structuredClone(pagination) : { kind: 'cursor' },
242
255
  }
243
256
  if (input.user) {
244
257
  result.user = structuredClone(input.user)
@@ -298,6 +311,7 @@ export class ApiModel extends DependentModel {
298
311
  this.key = init.key
299
312
  this.info = new Thing(init.info)
300
313
  this.user = init.user
314
+ this.pagination = init.pagination ? structuredClone(init.pagination) : { kind: 'cursor' }
301
315
  if (init.authentication) {
302
316
  this.authentication = structuredClone(init.authentication)
303
317
  }
@@ -341,6 +355,7 @@ export class ApiModel extends DependentModel {
341
355
  key: this.key,
342
356
  info: this.info.toJSON(),
343
357
  exposes: Array.from(this.exposes.values()).map((e) => e.toJSON()),
358
+ pagination: structuredClone(toRaw(this, this.pagination)) as CursorPaginationStrategy | OffsetPaginationStrategy,
344
359
  }
345
360
  if (this.user) {
346
361
  result.user = { ...this.user }
@@ -7,6 +7,7 @@ import {
7
7
  validateApiModelSecurity,
8
8
  validateApiModelMetadata,
9
9
  validateExposedEntity,
10
+ validateApiPagination,
10
11
  } from './validation/api_model_rules.js'
11
12
 
12
13
  /**
@@ -58,6 +59,7 @@ export class ApiValidation {
58
59
  this.report.push(...validateApiModelInfo(this.model))
59
60
  this.report.push(...validateApiModelDependency(this.model))
60
61
  this.report.push(...validateApiModelSecurity(this.model))
62
+ this.report.push(...validateApiPagination(this.model))
61
63
  this.report.push(...validateApiModelMetadata(this.model))
62
64
 
63
65
  if (!this.model.exposes || this.model.exposes.size === 0) {
@@ -50,8 +50,14 @@ export interface DomainPropertySchema extends DomainElementSchema {
50
50
  unique?: boolean
51
51
  /**
52
52
  * Whether this property describes an indexed property of the entity.
53
+ * This enables a standard B-Tree index.
53
54
  */
54
55
  index?: boolean
56
+ /**
57
+ * Whether this property describes a search index of the entity.
58
+ * This enables a specialized Inverted Index (e.g., a GIN or `pg_trgm` index in PostgreSQL).
59
+ */
60
+ search?: boolean
55
61
  /**
56
62
  * Whether the property is read only in the schema.
57
63
  */
@@ -170,8 +176,14 @@ export class DomainProperty extends DomainElement {
170
176
 
171
177
  /**
172
178
  * Whether this property describes an indexed property of the entity.
179
+ * This enables a standard B-Tree index.
173
180
  */
174
181
  @observed() accessor index: boolean | undefined
182
+ /**
183
+ * Whether this property describes a search index of the entity.
184
+ * This enables a specialized Inverted Index (e.g., a GIN or `pg_trgm` index in PostgreSQL).
185
+ */
186
+ @observed() accessor search: boolean | undefined
175
187
 
176
188
  /**
177
189
  * Whether the property is read only in the schema.
@@ -239,6 +251,7 @@ export class DomainProperty extends DomainElement {
239
251
  required,
240
252
  type = DomainPropertyList.string,
241
253
  index,
254
+ search,
242
255
  primary,
243
256
  unique,
244
257
  readOnly,
@@ -270,6 +283,9 @@ export class DomainProperty extends DomainElement {
270
283
  if (typeof index === 'boolean') {
271
284
  result.index = index
272
285
  }
286
+ if (typeof search === 'boolean') {
287
+ result.search = search
288
+ }
273
289
  if (typeof primary === 'boolean') {
274
290
  result.primary = primary
275
291
  }
@@ -330,43 +346,30 @@ export class DomainProperty extends DomainElement {
330
346
 
331
347
  if (typeof init.multiple === 'boolean') {
332
348
  this.multiple = init.multiple
333
- } else {
334
- this.multiple = undefined
335
349
  }
336
350
  if (typeof init.required === 'boolean') {
337
351
  this.required = init.required
338
- } else {
339
- this.required = undefined
340
352
  }
341
353
  if (typeof init.index === 'boolean') {
342
354
  this.index = init.index
343
- } else {
344
- this.index = undefined
355
+ }
356
+ if (typeof init.search === 'boolean') {
357
+ this.search = init.search
345
358
  }
346
359
  if (typeof init.deprecated === 'boolean') {
347
360
  this.deprecated = init.deprecated
348
- } else {
349
- this.deprecated = undefined
350
361
  }
351
362
  if (typeof init.primary === 'boolean') {
352
363
  this.primary = init.primary
353
- } else {
354
- this.primary = undefined
355
364
  }
356
365
  if (typeof init.unique === 'boolean') {
357
366
  this.unique = init.unique
358
- } else {
359
- this.unique = undefined
360
367
  }
361
368
  if (typeof init.readOnly === 'boolean') {
362
369
  this.readOnly = init.readOnly
363
- } else {
364
- this.readOnly = undefined
365
370
  }
366
371
  if (typeof init.writeOnly === 'boolean') {
367
372
  this.writeOnly = init.writeOnly
368
- } else {
369
- this.writeOnly = undefined
370
373
  }
371
374
  if (Array.isArray(init.tags)) {
372
375
  this.tags = [...init.tags]
@@ -380,8 +383,6 @@ export class DomainProperty extends DomainElement {
380
383
  }
381
384
  if (init.schema) {
382
385
  this.schema = structuredClone(init.schema)
383
- } else {
384
- this.schema = undefined
385
386
  }
386
387
  if (Array.isArray(init.bindings)) {
387
388
  this.bindings = init.bindings.map((i) => structuredClone(i))
@@ -418,6 +419,9 @@ export class DomainProperty extends DomainElement {
418
419
  if (typeof this.index === 'boolean') {
419
420
  result.index = this.index
420
421
  }
422
+ if (typeof this.search === 'boolean') {
423
+ result.search = this.search
424
+ }
421
425
  if (typeof this.deprecated === 'boolean') {
422
426
  result.deprecated = this.deprecated
423
427
  }
@@ -513,7 +513,7 @@ export function deserialize(root: DataDomain, options: DeserializeOptions = {}):
513
513
  const message = `Failed to merge dependency "${dependency.key}": ${error instanceof Error ? error.message : String(error)}`
514
514
 
515
515
  if (mode === 'strict') {
516
- throw new Error(message)
516
+ throw new Error(message, { cause: error })
517
517
  }
518
518
 
519
519
  const issue: DeserializationIssue = {
@@ -1,14 +1,20 @@
1
- import { observed } from '../decorators/observed.js'
1
+ import { observed, toRaw } from '../decorators/observed.js'
2
2
  import { ExposedEntityKind } from '../models/kinds.js'
3
3
  import { nanoid } from '../nanoid.js'
4
4
  import { Action } from './actions/Action.js'
5
- import { restoreAction } from './actions/index.js'
5
+ import { type ApiActionSchema, restoreAction } from './actions/index.js'
6
6
  import type { ApiModel } from './ApiModel.js'
7
7
  import { ensureLeadingSlash, joinPaths } from './helpers/endpointHelpers.js'
8
8
  import { AccessRule } from './rules/AccessRule.js'
9
9
  import { type RateLimitRule, restoreAccessRule } from './rules/index.js'
10
10
  import { RateLimitingConfiguration } from './rules/RateLimitingConfiguration.js'
11
- import type { AssociationTarget, ExposeOptions, ExposeParentRef, ExposedEntitySchema } from './types.js'
11
+ import type {
12
+ AssociationTarget,
13
+ ExposeOptions,
14
+ ExposeParentRef,
15
+ ExposedEntitySchema,
16
+ PaginationContract,
17
+ } from './types.js'
12
18
 
13
19
  /**
14
20
  * A class that specializes in representing an exposed Data Entity within an API Model.
@@ -100,6 +106,14 @@ export class ExposedEntity extends EventTarget {
100
106
  */
101
107
  @observed() accessor rateLimiting: RateLimitingConfiguration | undefined
102
108
 
109
+ /**
110
+ * Pagination contract for this exposure.
111
+ * Defines a list of fields that can be used for filtering, sorting, and searching.
112
+ * The pagination contract is only valid for that specific exposure. It cannot be inherited
113
+ * by other exposures.
114
+ */
115
+ @observed({ deep: true }) accessor paginationContract: PaginationContract | undefined
116
+
103
117
  /**
104
118
  * When true, generation for this exposure hit configured limits
105
119
  *
@@ -138,6 +152,7 @@ export class ExposedEntity extends EventTarget {
138
152
  accessRule,
139
153
  rateLimiting,
140
154
  truncated,
155
+ paginationContract,
141
156
  } = input
142
157
  const result: ExposedEntitySchema = {
143
158
  kind: ExposedEntityKind,
@@ -168,6 +183,9 @@ export class ExposedEntity extends EventTarget {
168
183
  if (truncated !== undefined) {
169
184
  result.truncated = truncated
170
185
  }
186
+ if (paginationContract !== undefined) {
187
+ result.paginationContract = structuredClone(paginationContract)
188
+ }
171
189
  return result
172
190
  }
173
191
 
@@ -186,8 +204,13 @@ export class ExposedEntity extends EventTarget {
186
204
  this.exposeOptions = init.exposeOptions
187
205
  this.actions = init.actions ? init.actions.map((a) => restoreAction(this, a)) : []
188
206
  this.accessRule = init.accessRule ? init.accessRule.map((ar) => restoreAccessRule(ar)) : []
189
- this.rateLimiting = init.rateLimiting ? new RateLimitingConfiguration(init.rateLimiting) : undefined
207
+ if (init.rateLimiting) {
208
+ this.rateLimiting = new RateLimitingConfiguration(init.rateLimiting)
209
+ }
190
210
  this.truncated = init.truncated
211
+ if (init.paginationContract) {
212
+ this.paginationContract = structuredClone(init.paginationContract)
213
+ }
191
214
  this.#initializing = false
192
215
  }
193
216
 
@@ -233,6 +256,9 @@ export class ExposedEntity extends EventTarget {
233
256
  if (this.truncated !== undefined) {
234
257
  result.truncated = this.truncated
235
258
  }
259
+ if (this.paginationContract) {
260
+ result.paginationContract = structuredClone(toRaw(this, this.paginationContract))
261
+ }
236
262
  return result
237
263
  }
238
264
 
@@ -434,4 +460,18 @@ export class ExposedEntity extends EventTarget {
434
460
  }
435
461
  return rules
436
462
  }
463
+
464
+ /**
465
+ * Adds an action to the exposure.
466
+ * @param schema The schema of the action to add.
467
+ * @returns The added action.
468
+ */
469
+ addAction(schema: ApiActionSchema): Action {
470
+ if (this.actions.some((action) => action.kind === schema.kind)) {
471
+ throw new Error(`Action of kind "${schema.kind}" already exists for this exposure`)
472
+ }
473
+ const action = restoreAction(this, schema)
474
+ this.actions.push(action)
475
+ return action
476
+ }
437
477
  }
@@ -52,6 +52,7 @@ export class Action extends EventTarget implements ActionSchema {
52
52
 
53
53
  notifyChange() {
54
54
  this.dispatchEvent(new Event('change'))
55
+ // this.parent.api.notifyChange()
55
56
  }
56
57
 
57
58
  toJSON(): ActionSchema {
@@ -1,7 +1,6 @@
1
- import type { PaginationStrategy } from '../types.js'
2
1
  import { Action, type ActionSchema } from './Action.js'
3
- import { observed, toRaw } from '../../decorators/observed.js'
4
2
  import type { ExposedEntity } from '../ExposedEntity.js'
3
+ import { observed } from '../../decorators/observed.js'
5
4
 
6
5
  /**
7
6
  * Enables retrieving a collection of resources.
@@ -10,20 +9,9 @@ import type { ExposedEntity } from '../ExposedEntity.js'
10
9
  export interface ListActionSchema extends ActionSchema {
11
10
  kind: 'list'
12
11
  /**
13
- * The pagination strategy used for this action.
14
- * This defines how the results are paginated when retrieving a collection of resources.
15
- * It can be either 'cursor' or 'offset'.
12
+ * The time to live for the cache in seconds.
16
13
  */
17
- pagination: PaginationStrategy
18
- /**
19
- * Fields from the entity that can be used for filtering.
20
- * Must be marked as "indexable" in the Data Model.
21
- */
22
- filterableFields: string[]
23
- /**
24
- * Fields from the entity that can be used for sorting.
25
- */
26
- sortableFields: string[]
14
+ cacheTtl?: number
27
15
  }
28
16
 
29
17
  /**
@@ -32,29 +20,23 @@ export interface ListActionSchema extends ActionSchema {
32
20
  */
33
21
  export class ListAction extends Action implements ListActionSchema {
34
22
  @observed() override accessor kind: 'list'
35
- @observed({ deep: true }) accessor pagination: PaginationStrategy
36
- @observed({ deep: true }) accessor filterableFields: string[] = []
37
- @observed({ deep: true }) accessor sortableFields: string[] = []
23
+ @observed() accessor cacheTtl: number | undefined
38
24
 
39
25
  constructor(parent: ExposedEntity, state: Partial<ListActionSchema> = {}) {
40
26
  super(parent, state)
41
- this.kind = state.kind || 'list'
42
- this.pagination = state.pagination
43
- ? { ...state.pagination }
44
- : {
45
- kind: 'offset',
46
- }
47
- this.filterableFields = state.filterableFields ? [...state.filterableFields] : []
48
- this.sortableFields = state.sortableFields ? [...state.sortableFields] : []
27
+ this.kind = 'list'
28
+ this.cacheTtl = state.cacheTtl
49
29
  }
50
30
 
51
31
  override toJSON(): ListActionSchema {
52
- return {
32
+ const result: ListActionSchema = {
53
33
  ...(super.toJSON() as ListActionSchema),
54
- pagination: structuredClone(toRaw(this, this.pagination)) as PaginationStrategy,
55
- filterableFields: structuredClone(toRaw(this, this.filterableFields)) as string[],
56
- sortableFields: structuredClone(toRaw(this, this.sortableFields)) as string[],
34
+ kind: 'list',
35
+ }
36
+ if (this.cacheTtl !== undefined) {
37
+ result.cacheTtl = this.cacheTtl
57
38
  }
39
+ return result
58
40
  }
59
41
 
60
42
  static isListAction(action: Action): action is ListAction {
@@ -1,6 +1,6 @@
1
1
  import { Action, type ActionSchema } from './Action.js'
2
- import { observed, toRaw } from '../../decorators/observed.js'
3
2
  import type { ExposedEntity } from '../ExposedEntity.js'
3
+ import { observed } from '../../decorators/observed.js'
4
4
 
5
5
  /**
6
6
  * Enables keyword-based search across specified fields.
@@ -9,10 +9,10 @@ import type { ExposedEntity } from '../ExposedEntity.js'
9
9
  export interface SearchActionSchema extends ActionSchema {
10
10
  kind: 'search'
11
11
  /**
12
- * The fields within the entity to be included in the search scope.
13
- * Must be "indexable" and typically text-based.
12
+ * The maximum depth of the Abstract Syntax Tree (AST) that will be used to parse the search query.
13
+ * This is used to prevent denial of service attacks.
14
14
  */
15
- fields: string[]
15
+ maxAstDepth?: number
16
16
  }
17
17
 
18
18
  /**
@@ -21,20 +21,23 @@ export interface SearchActionSchema extends ActionSchema {
21
21
  */
22
22
  export class SearchAction extends Action implements SearchActionSchema {
23
23
  @observed() override accessor kind: 'search'
24
- @observed({ deep: true }) accessor fields: string[]
24
+ @observed() accessor maxAstDepth: number | undefined
25
25
 
26
26
  constructor(parent: ExposedEntity, state: Partial<SearchActionSchema> = {}) {
27
27
  super(parent, state)
28
28
  this.kind = 'search'
29
- this.fields = state.fields ? [...state.fields] : []
29
+ this.maxAstDepth = state.maxAstDepth
30
30
  }
31
31
 
32
32
  override toJSON(): SearchActionSchema {
33
- return {
33
+ const result: SearchActionSchema = {
34
34
  ...(super.toJSON() as SearchActionSchema),
35
35
  kind: 'search',
36
- fields: structuredClone(toRaw(this, this.fields)) as string[],
37
36
  }
37
+ if (this.maxAstDepth !== undefined) {
38
+ result.maxAstDepth = this.maxAstDepth
39
+ }
40
+ return result
38
41
  }
39
42
 
40
43
  static isSearchActionSchema(schema: ActionSchema): schema is SearchActionSchema {