json-rest-api 1.0.29 → 2.1.0

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 (294) hide show
  1. package/LICENSE +10 -670
  2. package/LICENSE-GPL-3.0 +674 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +39 -30
  5. package/docs/API.md +744 -1273
  6. package/docs/GUIDE/01-getting-started.md +338 -0
  7. package/docs/GUIDE/02-resources-and-relationships.md +33 -0
  8. package/docs/GUIDE/03-running-example.md +87 -0
  9. package/docs/GUIDE/04-creating-and-querying.md +181 -0
  10. package/docs/GUIDE/05-belongs-to.md +177 -0
  11. package/docs/GUIDE/06-has-many.md +184 -0
  12. package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
  13. package/docs/GUIDE/08-many-to-many.md +184 -0
  14. package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
  15. package/docs/GUIDE/10-put-and-patch.md +174 -0
  16. package/docs/GUIDE/11-relationship-endpoints.md +174 -0
  17. package/docs/GUIDE/12-field-transformations.md +287 -0
  18. package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
  19. package/docs/GUIDE/14-custom-resource-methods.md +125 -0
  20. package/docs/GUIDE/15-query-projections.md +266 -0
  21. package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
  22. package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
  23. package/docs/GUIDE/18-labels.md +86 -0
  24. package/docs/GUIDE/19-managed-transactions.md +179 -0
  25. package/docs/GUIDE/20-transaction-outcomes.md +207 -0
  26. package/docs/GUIDE/21-schema-and-migrations.md +733 -0
  27. package/docs/GUIDE/22-version-field-migrations.md +208 -0
  28. package/docs/GUIDE/23-fastify.md +179 -0
  29. package/docs/GUIDE/24-cors.md +172 -0
  30. package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
  31. package/docs/GUIDE/26-file-uploads.md +278 -0
  32. package/docs/GUIDE/27-bulk-operations.md +277 -0
  33. package/docs/GUIDE/28-socketio.md +325 -0
  34. package/docs/GUIDE/29-writing-plugins.md +278 -0
  35. package/docs/GUIDE/30-backend-capabilities.md +103 -0
  36. package/docs/GUIDE/31-positioning.md +181 -0
  37. package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
  38. package/docs/GUIDE/33-migrating-to-v2.md +3838 -0
  39. package/docs/GUIDE/index.md +51 -59
  40. package/docs/QUICKSTART.md +120 -287
  41. package/docs/architecture.md +75 -0
  42. package/docs/contributing.md +178 -0
  43. package/index.d.ts +37 -0
  44. package/index.js +3 -22
  45. package/lib/enhanced-logger.js +91 -85
  46. package/lib/error-context.js +445 -5
  47. package/lib/error-formatter.js +219 -59
  48. package/lib/knex-transaction.js +70 -0
  49. package/lib/logger-types.d.ts +22 -0
  50. package/lib/missing-package.js +4 -0
  51. package/lib/rest-api-errors.js +103 -8
  52. package/lib/runtime/json-rest-api.js +172 -0
  53. package/lib/transaction-types.d.ts +59 -0
  54. package/package.json +97 -27
  55. package/plugins/core/bulk-operations-plugin.js +191 -184
  56. package/plugins/core/connectors/express-plugin.js +148 -219
  57. package/plugins/core/connectors/fastify-plugin.js +125 -82
  58. package/plugins/core/connectors/lib/busboy-detector.js +74 -156
  59. package/plugins/core/connectors/lib/connector-core.js +144 -20
  60. package/plugins/core/connectors/lib/formidable-detector.js +63 -120
  61. package/plugins/core/connectors/lib/http-validators.js +77 -0
  62. package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
  63. package/plugins/core/connectors/lib/request-helpers.js +10 -1
  64. package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
  65. package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
  66. package/plugins/core/file-handling-plugin.js +87 -177
  67. package/plugins/core/lib/anyapi/anyapi-registry.js +586 -418
  68. package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
  69. package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
  70. package/plugins/core/lib/anyapi/schema-utils.js +120 -56
  71. package/plugins/core/lib/anyapi/stored-definition.js +109 -0
  72. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
  73. package/plugins/core/lib/dbIntrospection.js +228 -21
  74. package/plugins/core/lib/dbTablesOperations.js +409 -92
  75. package/plugins/core/lib/fractional-positioning.js +12 -48
  76. package/plugins/core/lib/querying/identifier-query.js +19 -0
  77. package/plugins/core/lib/querying/include-query-helpers.js +364 -0
  78. package/plugins/core/lib/querying/include-to-many.js +428 -0
  79. package/plugins/core/lib/querying/include-to-one.js +579 -0
  80. package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
  81. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
  82. package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
  83. package/plugins/core/lib/querying/knex-process-includes.js +5 -110
  84. package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
  85. package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
  86. package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
  87. package/plugins/core/lib/querying/knex-window-queries.js +62 -253
  88. package/plugins/core/lib/querying/query-builder-utils.js +44 -4
  89. package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
  90. package/plugins/core/lib/querying/query-constraint.js +27 -0
  91. package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
  92. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  93. package/plugins/core/lib/querying/sort-helpers.js +13 -1
  94. package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
  95. package/plugins/core/lib/querying/url-helpers.js +11 -4
  96. package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
  97. package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
  98. package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
  99. package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
  100. package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
  101. package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
  102. package/plugins/core/lib/querying-writing/field-utils.js +113 -92
  103. package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
  104. package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
  105. package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
  106. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
  107. package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
  108. package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
  109. package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
  110. package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
  111. package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
  112. package/plugins/core/lib/querying-writing/response-options.js +35 -0
  113. package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
  114. package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
  115. package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
  116. package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
  117. package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
  118. package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
  119. package/plugins/core/lib/storage/storage-adapter.js +128 -62
  120. package/plugins/core/lib/storage/storage-mapping.js +78 -28
  121. package/plugins/core/lib/storage/storage-types.d.ts +502 -0
  122. package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
  123. package/plugins/core/lib/writing/relationship-processor.js +94 -158
  124. package/plugins/core/lib/writing/resource-version.js +172 -0
  125. package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
  126. package/plugins/core/rest-api-anyapi-knex-plugin.js +290 -2475
  127. package/plugins/core/rest-api-autofilter-plugin.js +17 -19
  128. package/plugins/core/rest-api-cors-plugin.js +52 -205
  129. package/plugins/core/rest-api-knex-plugin.js +45 -1077
  130. package/plugins/core/rest-api-label-plugin.js +25 -38
  131. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
  132. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
  133. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
  134. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
  135. package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
  136. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
  137. package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
  138. package/plugins/core/rest-api-plugin-methods/common.js +291 -395
  139. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
  140. package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
  141. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
  142. package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
  143. package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
  144. package/plugins/core/rest-api-plugin-methods/get.js +36 -68
  145. package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
  146. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
  147. package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
  148. package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
  149. package/plugins/core/rest-api-plugin-methods/post.js +51 -51
  150. package/plugins/core/rest-api-plugin-methods/put.js +69 -148
  151. package/plugins/core/rest-api-plugin-methods/query.js +37 -81
  152. package/plugins/core/rest-api-plugin-methods/release.js +2 -1
  153. package/plugins/core/rest-api-plugin.js +32 -46
  154. package/plugins/core/rest-api-positioning-plugin.js +183 -628
  155. package/plugins/core/rest-api-query-projections-plugin.js +19 -102
  156. package/plugins/core/rest-api-row-policy-plugin.js +3 -3
  157. package/plugins/core/socketio-plugin.js +364 -344
  158. package/plugins/storage/local-storage.js +40 -36
  159. package/types/anyapi.d.ts +30 -0
  160. package/types/autofilter.d.ts +57 -0
  161. package/types/bulk-methods.d.ts +63 -0
  162. package/types/errors.d.ts +118 -0
  163. package/types/fastify-options.d.ts +19 -0
  164. package/types/file-detectors.d.ts +33 -0
  165. package/types/file-storage.d.ts +48 -0
  166. package/types/hook-context.d.ts +180 -0
  167. package/types/plugin-options.d.ts +65 -0
  168. package/types/relationship-methods.d.ts +74 -0
  169. package/types/representations.d.ts +50 -0
  170. package/types/resource-methods.d.ts +95 -0
  171. package/types/resource-schema.d.ts +157 -0
  172. package/types/row-policy.d.ts +31 -0
  173. package/types/runtime.d.ts +132 -0
  174. package/types/transactions.d.ts +21 -0
  175. package/.claude/agents/library-guide-expert.md +0 -30
  176. package/.claude/agents/rest-api-internals-expert.md +0 -54
  177. package/.claude/settings.local.json +0 -70
  178. package/AGENTS.md +0 -34
  179. package/CLAUDE.md +0 -160
  180. package/CRUSH.md +0 -55
  181. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
  182. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
  183. package/TODO.todo +0 -13
  184. package/connectors_ideas/fastify.js +0 -217
  185. package/connectors_ideas/koa-plugin.js +0 -112
  186. package/docs/.bundle/config +0 -2
  187. package/docs/COMPARISON.md +0 -433
  188. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
  189. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
  190. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
  191. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
  192. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
  193. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
  194. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
  195. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
  196. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
  197. package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
  198. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
  199. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
  200. package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
  201. package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
  202. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
  203. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
  204. package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
  205. package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
  206. package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
  207. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
  208. package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
  209. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
  210. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
  211. package/docs/Gemfile +0 -6
  212. package/docs/Gemfile.lock +0 -308
  213. package/docs/ONBOARDING.md +0 -315
  214. package/docs/_config.yml +0 -36
  215. package/docs/_layouts/default.html +0 -123
  216. package/docs/apple-touch-icon.svg +0 -4
  217. package/docs/assets/css/style.scss +0 -392
  218. package/docs/favicon.svg +0 -4
  219. package/docs/index.md +0 -164
  220. package/docs/mini-guides/file-uploads-complete.md +0 -161
  221. package/eslint.config.js +0 -13
  222. package/examples/url-override-example.js +0 -137
  223. package/fixing_plan.md +0 -112
  224. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
  225. package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
  226. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
  227. package/plugins/storage/s3-storage.js +0 -93
  228. package/quickTest.js +0 -136
  229. package/scripts/debug-anyapi-review-filter.mjs +0 -63
  230. package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
  231. package/scripts/inspect-anyapi-descriptor.mjs +0 -19
  232. package/scripts/run-docs.js +0 -133
  233. package/test-debug.js +0 -59
  234. package/tests/README.md +0 -202
  235. package/tests/TEST_TEMPLATE.test.js +0 -178
  236. package/tests/anyapi-basic.test.js +0 -105
  237. package/tests/anyapi-cursor-pagination.test.js +0 -148
  238. package/tests/anyapi-custom-idproperty.test.js +0 -163
  239. package/tests/autofilter.test.js +0 -463
  240. package/tests/bulk-operations.test.js +0 -657
  241. package/tests/computed-fields.test.js +0 -586
  242. package/tests/cors.test.js +0 -834
  243. package/tests/curl.test.js +0 -467
  244. package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
  245. package/tests/custom-id-simplified-post.test.js +0 -75
  246. package/tests/custom-idproperty-relationships.test.js +0 -932
  247. package/tests/db-introspection.test.js +0 -521
  248. package/tests/db-table-operations.test.js +0 -798
  249. package/tests/fastify-plugin.test.js +0 -184
  250. package/tests/field-getters.test.js +0 -370
  251. package/tests/field-setters.test.js +0 -306
  252. package/tests/file-handling.test.js +0 -155
  253. package/tests/fixtures/api-configs.js +0 -2566
  254. package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
  255. package/tests/helpers/fake-fastify.js +0 -99
  256. package/tests/helpers/storage-mode.js +0 -65
  257. package/tests/helpers/test-utils.js +0 -203
  258. package/tests/http-connectors-parity.test.js +0 -395
  259. package/tests/id-normalization.test.js +0 -444
  260. package/tests/include-depth-validation.test.js +0 -331
  261. package/tests/includes.test.js +0 -427
  262. package/tests/local-storage.test.js +0 -96
  263. package/tests/logical-id-write-validation.test.js +0 -151
  264. package/tests/nested-includes.test.js +0 -617
  265. package/tests/pagination-boundaries.test.js +0 -154
  266. package/tests/pagination-contract.test.js +0 -122
  267. package/tests/pagination-cursor-multifield.test.js +0 -641
  268. package/tests/pagination-enhanced.test.js +0 -563
  269. package/tests/polymorphic-relationships.test.js +0 -347
  270. package/tests/polymorphic-simple.test.js +0 -148
  271. package/tests/positioning-advanced.test.js +0 -692
  272. package/tests/positioning-extended.test.js +0 -915
  273. package/tests/positioning-multifilter.test.js +0 -699
  274. package/tests/positioning.test.js +0 -560
  275. package/tests/post-commit-hooks.test.js +0 -120
  276. package/tests/projected-fields.test.js +0 -319
  277. package/tests/public-exports.test.js +0 -15
  278. package/tests/put-create-parity.test.js +0 -216
  279. package/tests/queries.test.js +0 -521
  280. package/tests/query-limits.test.js +0 -584
  281. package/tests/relationship-endpoints.test.js +0 -620
  282. package/tests/relationship-include-storage-adapter.test.js +0 -56
  283. package/tests/relationships.test.js +0 -681
  284. package/tests/rest-api.test.js +0 -337
  285. package/tests/return-record-settings.test.js +0 -184
  286. package/tests/row-policy.test.js +0 -489
  287. package/tests/s3-storage.test.js +0 -38
  288. package/tests/searchschema-knex-behavior.test.js +0 -187
  289. package/tests/searchschema-merge.test.js +0 -151
  290. package/tests/socketio.test.js +0 -860
  291. package/tests/storage-mapping.test.js +0 -362
  292. package/tests/temporal-boundaries.test.js +0 -208
  293. package/tests/test-development-guide.md +0 -370
  294. package/tests/virtual-fields.test.js +0 -404
@@ -0,0 +1,65 @@
1
+ import type { DirectResourceId, ResourceFormat, WriteReturning } from './representations.js'
2
+ import type { RemovedResourceOptions } from './resource-methods.js'
3
+ import type { Knex } from 'knex'
4
+ import type { AnyapiRegistry, AnyapiSchemaChange, AnyapiSlotCounts } from './anyapi.js'
5
+
6
+ export type RestApiPluginOptions = RemovedResourceOptions & {
7
+ format?: ResourceFormat
8
+ returning?: WriteReturning
9
+ queryDefaultLimit?: number
10
+ queryMaxLimit?: number
11
+ includeDepthLimit?: number
12
+ enablePaginationCounts?: boolean
13
+ idProperty?: string
14
+ normalizeId?: (value: unknown) => DirectResourceId | null | undefined
15
+ }
16
+
17
+ export interface HttpConnectorOptions {
18
+ mountPath?: string
19
+ publicBaseUrl?: string
20
+ strictContentType?: boolean
21
+ handle404?: boolean
22
+ httpValidators?: boolean
23
+ }
24
+
25
+ export interface ResourceVersionOptions {
26
+ // Compilation validates that this names a stored, untransformed string attribute.
27
+ versionField?: string
28
+ }
29
+
30
+ export type CorsOrigin = string | RegExp | ((origin: string) => boolean | Promise<boolean>) | CorsOrigin[]
31
+ export interface CorsPluginOptions {
32
+ origin?: CorsOrigin
33
+ credentials?: boolean
34
+ methods?: string[]
35
+ allowedHeaders?: string[]
36
+ exposedHeaders?: string[]
37
+ maxAge?: number
38
+ optionsSuccessStatus?: number
39
+ }
40
+
41
+ export interface RestApiKnexPluginOptions {
42
+ knex: Knex
43
+ }
44
+ export interface RestApiAnyapiKnexPluginOptions extends RestApiKnexPluginOptions {
45
+ tenantId?: string
46
+ // Share one registry across tenant APIs on the same Knex instance.
47
+ registry?: AnyapiRegistry
48
+ // false when migrations own the canonical tables.
49
+ ensureSchema?: boolean
50
+ // false leaves a shared Knex instance open when this API is released.
51
+ ownsKnex?: boolean
52
+ slots?: AnyapiSlotCounts
53
+ // Called after a committed schema change, for example to notify other servers.
54
+ onSchemaChanged?: (change: AnyapiSchemaChange) => void | Promise<void>
55
+ // Milliseconds between schema version checks; 0 checks on every request.
56
+ schemaCheckInterval?: number
57
+ }
58
+ export interface BulkOperationsPluginOptions {
59
+ maxBulkOperations?: number
60
+ defaultAtomic?: boolean
61
+ }
62
+ export interface LabelPluginOptions {
63
+ preferNameFields?: string[]
64
+ disable?: boolean
65
+ }
@@ -0,0 +1,74 @@
1
+ import type {
2
+ CollectionResult, DirectResourceId, InputResourceIdentifier, JsonApiDocument,
3
+ JsonApiResource, PlainResource, ResourceFormat, ResourceIdentifier
4
+ } from './representations.js'
5
+ import type { QueryParams, RemovedResourceOptions, SelectionParams, TransactionParam } from './resource-methods.js'
6
+ import type { ManagedTransactionParam } from './transactions.js'
7
+
8
+ // Type-only descriptions of the resource's configured relationships.
9
+ export interface ToOneRelationship<Fields extends object = Record<string, unknown>, Type extends string = string> {
10
+ cardinality: 'one'
11
+ fields: Fields
12
+ type: Type
13
+ }
14
+ export interface ToManyRelationship<Fields extends object = Record<string, unknown>, Type extends string = string> {
15
+ cardinality: 'many'
16
+ fields: Fields
17
+ type: Type
18
+ }
19
+ type RelationshipShape = ToOneRelationship<object> | ToManyRelationship<object>
20
+ type RelationshipKeys<Relations> = Extract<keyof Relations, string>
21
+ type ToManyKeys<Relations> = {
22
+ [Key in keyof Relations]: Extract<Relations[Key], { cardinality: 'many' }> extends never ? never : Key
23
+ }[keyof Relations] & string
24
+ export type RelationshipLinkage<Relation extends RelationshipShape> = Relation extends ToManyRelationship<object>
25
+ ? ResourceIdentifier<Relation['type']>[] : ResourceIdentifier<Relation['type']> | null
26
+ export type RelationshipInput<Relation extends RelationshipShape> = Relation extends ToManyRelationship<object>
27
+ ? InputResourceIdentifier<Relation['type']>[] : InputResourceIdentifier<Relation['type']> | null
28
+ export type RelatedResourceResult<Relation extends RelationshipShape, Format extends ResourceFormat> =
29
+ Relation extends ToManyRelationship<object>
30
+ ? CollectionResult<Format, Relation['fields'], Relation['type']>
31
+ : Format extends 'plain' ? PlainResource<Relation['fields']> | null
32
+ : JsonApiDocument<JsonApiResource<Relation['fields'], Relation['type']> | null>
33
+ type RelationshipOptions<Name extends string> = RemovedResourceOptions & TransactionParam & {
34
+ id: DirectResourceId
35
+ relationshipName: Name
36
+ }
37
+ type RelatedQueryParams<Relation extends RelationshipShape> = Relation extends ToManyRelationship<object> ? QueryParams : SelectionParams
38
+ type RelationshipWriteOptions<Name extends string> = Omit<RelationshipOptions<Name>, 'transaction'> & ManagedTransactionParam
39
+
40
+ export interface RelationshipMethods<
41
+ Relations extends { [Key in keyof Relations]: RelationshipShape } = Record<string, ToOneRelationship | ToManyRelationship>,
42
+ DefaultFormat extends ResourceFormat = 'plain', Context extends object = object
43
+ > {
44
+ getRelationship<Name extends RelationshipKeys<Relations>>(
45
+ params: RelationshipOptions<Name> & { format?: ResourceFormat }, context?: Context
46
+ ): Promise<JsonApiDocument<RelationshipLinkage<Relations[Name]>>>
47
+ getRelated<Name extends RelationshipKeys<Relations>, Format extends ResourceFormat = DefaultFormat>(
48
+ params: RelationshipOptions<Name> & {
49
+ format?: Format
50
+ queryParams?: NoInfer<RelatedQueryParams<Relations[Name]>>
51
+ }, context?: Context
52
+ ): Promise<RelatedResourceResult<Relations[Name], Format>>
53
+ patchRelationship<Name extends RelationshipKeys<Relations>>(
54
+ params: RelationshipWriteOptions<Name> & {
55
+ format?: ResourceFormat
56
+ relationshipData: NoInfer<RelationshipInput<Relations[Name]>>
57
+ expectedVersion?: string
58
+ }, context?: Context
59
+ ): Promise<void>
60
+ postRelationship<Name extends ToManyKeys<Relations>>(
61
+ params: RelationshipWriteOptions<Name> & {
62
+ format?: ResourceFormat
63
+ relationshipData: InputResourceIdentifier<NoInfer<Relations[Name]['type']>>[]
64
+ expectedVersion?: string
65
+ }, context?: Context
66
+ ): Promise<void>
67
+ deleteRelationship<Name extends ToManyKeys<Relations>>(
68
+ params: RelationshipWriteOptions<Name> & {
69
+ format?: ResourceFormat
70
+ relationshipData: InputResourceIdentifier<NoInfer<Relations[Name]['type']>>[]
71
+ expectedVersion?: string
72
+ }, context?: Context
73
+ ): Promise<void>
74
+ }
@@ -0,0 +1,50 @@
1
+ export type ResourceFormat = 'plain' | 'jsonapi'
2
+ export type WriteReturning = 'none' | 'minimal' | 'full'
3
+ export type InputResourceId = string | number
4
+ export type DirectResourceId = InputResourceId | bigint
5
+
6
+ export interface ResourceIdentifier<Type extends string = string> {
7
+ type: Type
8
+ id: string
9
+ }
10
+ export interface InputResourceIdentifier<Type extends string = string> {
11
+ type: Type
12
+ id: InputResourceId
13
+ }
14
+ export type JsonApiLink = string | { href: string; meta?: Record<string, unknown> } | null
15
+ export type JsonApiLinks = Record<string, JsonApiLink>
16
+ export interface JsonApiRelationship {
17
+ data?: ResourceIdentifier | ResourceIdentifier[] | null
18
+ links?: JsonApiLinks
19
+ meta?: Record<string, unknown>
20
+ }
21
+ export interface JsonApiResource<Attributes extends object = Record<string, unknown>, Type extends string = string> extends ResourceIdentifier<Type> {
22
+ attributes?: Partial<Attributes>
23
+ relationships?: Record<string, JsonApiRelationship>
24
+ links?: JsonApiLinks
25
+ meta?: Record<string, unknown>
26
+ }
27
+ export interface JsonApiDocument<Data = JsonApiResource | JsonApiResource[] | null> {
28
+ data: Data
29
+ included?: JsonApiResource[]
30
+ links?: JsonApiLinks
31
+ meta?: Record<string, unknown>
32
+ jsonapi?: { version?: string; meta?: Record<string, unknown> }
33
+ }
34
+
35
+ // Supply relationship properties in Fields too; sparse fieldsets can omit either.
36
+ export type PlainResource<Fields extends object = Record<string, unknown>> = { id: string } & Partial<Fields>
37
+ export interface PlainCollection<Fields extends object = Record<string, unknown>> {
38
+ data: PlainResource<Fields>[]
39
+ links?: JsonApiLinks
40
+ meta?: Record<string, unknown>
41
+ }
42
+ export type SingleResourceResult<Format extends ResourceFormat, Fields extends object = Record<string, unknown>, Type extends string = string> =
43
+ Format extends 'plain' ? PlainResource<Fields> : JsonApiDocument<JsonApiResource<Fields, Type>>
44
+ export type CollectionResult<Format extends ResourceFormat, Fields extends object = Record<string, unknown>, Type extends string = string> =
45
+ Format extends 'plain' ? PlainCollection<Fields> : JsonApiDocument<JsonApiResource<Fields, Type>[]>
46
+ export type WriteResult<Format extends ResourceFormat, Returning extends WriteReturning, Fields extends object = Record<string, unknown>, Type extends string = string> =
47
+ Returning extends 'none' ? void
48
+ : Returning extends 'minimal'
49
+ ? Format extends 'plain' ? ResourceIdentifier<Type> : JsonApiDocument<ResourceIdentifier<Type>>
50
+ : SingleResourceResult<Format, Fields, Type>
@@ -0,0 +1,95 @@
1
+ import type { Knex } from 'knex'
2
+ import type { ManagedTransactionParam } from './transactions.js'
3
+ import type {
4
+ CollectionResult, DirectResourceId, InputResourceId, InputResourceIdentifier, ResourceFormat,
5
+ SingleResourceResult, WriteResult, WriteReturning
6
+ } from './representations.js'
7
+
8
+ export type RemovedResourceOptions = {
9
+ [Key in 'simplified' | 'simplifiedApi' | 'simplifiedTransport' | 'returnFullRecord' |
10
+ 'returnRecordApi' | 'returnRecordTransport' | 'isTransport' | 'inputRecord' | 'inputRecords']?: never
11
+ }
12
+ export interface SelectionParams {
13
+ include?: string[]
14
+ fields?: Record<string, string>
15
+ }
16
+ export type PageParams = { size?: number; [extension: string]: unknown } & (
17
+ { number?: number; after?: never; before?: never } |
18
+ { number?: never; after: string; before?: never } |
19
+ { number?: never; after?: never; before: string }
20
+ )
21
+ export interface QueryParams extends SelectionParams {
22
+ filters?: Record<string, unknown>
23
+ sort?: string[]
24
+ page?: PageParams
25
+ }
26
+ export interface TransactionParam {
27
+ transaction?: Knex.Transaction
28
+ }
29
+ export interface JsonApiWriteDocument<Input extends object, Type extends string> {
30
+ data: {
31
+ type: Type
32
+ id?: InputResourceId
33
+ attributes?: Partial<Input>
34
+ relationships?: Record<string, { data: InputResourceIdentifier | InputResourceIdentifier[] | null }>
35
+ }
36
+ meta?: Record<string, unknown>
37
+ links?: Record<string, unknown>
38
+ jsonapi?: Record<string, unknown>
39
+ included?: never
40
+ errors?: never
41
+ }
42
+ export type ResourceWriteInput<Input extends object, Type extends string> =
43
+ { data: Partial<Input> & { id?: InputResourceId }; document?: never } |
44
+ { data?: never; document: JsonApiWriteDocument<Input, Type> }
45
+ export type ResourceWriteTarget<Input extends object, Type extends string> =
46
+ ({ id: DirectResourceId } & ResourceWriteInput<Input, Type>) |
47
+ {
48
+ id?: never
49
+ data: Partial<Input> & { id: InputResourceId }
50
+ document?: never
51
+ } |
52
+ {
53
+ id?: never
54
+ data?: never
55
+ document: JsonApiWriteDocument<Input, Type> & { data: { id: InputResourceId } }
56
+ }
57
+ export type ReadOptions<Format extends ResourceFormat> = RemovedResourceOptions & TransactionParam & {
58
+ format?: Format
59
+ queryParams?: SelectionParams
60
+ }
61
+ export type WriteOptions<Format extends ResourceFormat, Returning extends WriteReturning> = Omit<ReadOptions<Format>, 'transaction'> & ManagedTransactionParam & {
62
+ returning?: Returning
63
+ }
64
+
65
+ // Format selects output only; data and document select the input representation.
66
+ // Output fields and writable input fields can differ (computed/read-only fields).
67
+ export interface ResourceCoreMethods<
68
+ Fields extends object = Record<string, unknown>,
69
+ Input extends object = Fields,
70
+ Type extends string = string,
71
+ DefaultFormat extends ResourceFormat = 'plain',
72
+ DefaultReturning extends WriteReturning = 'full',
73
+ Context extends object = object
74
+ > {
75
+ get<Format extends ResourceFormat = DefaultFormat>(
76
+ params: ReadOptions<Format> & { id: DirectResourceId }, context?: Context
77
+ ): Promise<SingleResourceResult<Format, Fields, Type>>
78
+ query<Format extends ResourceFormat = DefaultFormat>(
79
+ params?: RemovedResourceOptions & TransactionParam & { format?: Format; queryParams?: QueryParams }, context?: Context
80
+ ): Promise<CollectionResult<Format, Fields, Type>>
81
+ post<Format extends ResourceFormat = DefaultFormat, Returning extends WriteReturning = DefaultReturning>(
82
+ params: WriteOptions<Format, Returning> & ResourceWriteInput<Input, Type> & {
83
+ expectedVersion?: never
84
+ }, context?: Context
85
+ ): Promise<WriteResult<Format, Returning, Fields, Type>>
86
+ put<Format extends ResourceFormat = DefaultFormat, Returning extends WriteReturning = DefaultReturning>(
87
+ params: WriteOptions<Format, Returning> & ResourceWriteTarget<Input, Type> & { expectedVersion?: string }, context?: Context
88
+ ): Promise<WriteResult<Format, Returning, Fields, Type>>
89
+ patch<Format extends ResourceFormat = DefaultFormat, Returning extends WriteReturning = DefaultReturning>(
90
+ params: WriteOptions<Format, Returning> & ResourceWriteTarget<Input, Type> & { expectedVersion?: string }, context?: Context
91
+ ): Promise<WriteResult<Format, Returning, Fields, Type>>
92
+ delete(
93
+ params: RemovedResourceOptions & ManagedTransactionParam & { id: DirectResourceId; format?: ResourceFormat; expectedVersion?: string }, context?: Context
94
+ ): Promise<void>
95
+ }
@@ -0,0 +1,157 @@
1
+ import type { StorageFieldDefinition, StorageQuery } from '../plugins/core/lib/storage/storage-types.js'
2
+ import type { FileStorage } from './file-storage.js'
3
+
4
+ export type ResourceFieldType = 'string' | 'number' | 'integer' | 'boolean' | 'id' |
5
+ 'date' | 'time' | 'dateTime' | 'epochSeconds' | 'epochMilliseconds' |
6
+ 'array' | 'object' | 'file' | 'blob' | 'serialize' | 'none'
7
+
8
+ // The schema library accepts Schema instances here, not raw nested field maps.
9
+ export interface ResourceNestedSchema<Schema extends ResourceSchema = ResourceSchema> {
10
+ structure: Schema
11
+ operations: object
12
+ validateWith(...args: never[]): unknown
13
+ toJsonSchema(...args: never[]): unknown
14
+ cleanup(...args: never[]): unknown
15
+ }
16
+
17
+ export interface ResourceFieldOptions {
18
+ required?: boolean
19
+ nullable?: boolean
20
+ nullOnEmpty?: boolean
21
+ defaultTo?: unknown
22
+ enum?: readonly unknown[]
23
+ setValues?: readonly string[]
24
+ min?: number
25
+ max?: number
26
+ minLength?: number
27
+ maxLength?: number
28
+ length?: number
29
+ pattern?: string | RegExp
30
+ notEmpty?: boolean
31
+ noTrim?: boolean
32
+ uppercase?: boolean
33
+ lowercase?: boolean
34
+ strictBoolean?: boolean
35
+ stringTrueWhen?: string
36
+ stringFalseWhen?: string
37
+ unsigned?: boolean
38
+ precision?: number
39
+ scale?: number
40
+ temporalPrecision?: number
41
+ indexed?: boolean
42
+ unique?: boolean
43
+ primary?: boolean
44
+ comment?: string
45
+ hidden?: boolean
46
+ normallyHidden?: boolean
47
+ virtual?: boolean
48
+ computed?: boolean
49
+ include?: ResourceRelationshipOptions['include']
50
+ search?: boolean | ResourceSearchOptions | Record<string, ResourceSearchOptions>
51
+ dependencies?: readonly string[]
52
+ runGetterAfter?: readonly string[]
53
+ runSetterAfter?: readonly string[]
54
+ items?: ResourceField | ResourceNestedSchema
55
+ values?: ResourceField | ResourceNestedSchema
56
+ schema?: ResourceNestedSchema
57
+ additionalProperties?: true
58
+ // Custom callbacks retain their explicit annotations; inference does not run them.
59
+ getter?(value: unknown, context: Record<string, unknown>): unknown
60
+ setter?(value: unknown, context: Record<string, unknown>): unknown
61
+ compute?(context: Record<string, unknown>): unknown
62
+ validator?(value: unknown, object: Record<string, unknown>, context: unknown): string | void
63
+ }
64
+
65
+ export type ResourceField = ResourceFieldOptions & (
66
+ { type: Exclude<ResourceFieldType, 'file'>; belongsTo?: never; as?: never; storage?: StorageFieldDefinition['storage'] } |
67
+ { type: 'file'; belongsTo?: never; as?: never; storage?: StorageFieldDefinition['storage'] | FileStorage } |
68
+ { type?: ResourceFieldType; belongsTo: string; as: string; storage?: StorageFieldDefinition['storage'] }
69
+ )
70
+ export type ResourceSchema = Readonly<Record<string, ResourceField>>
71
+
72
+ export interface ResourceSearchOptions extends ResourceFieldOptions {
73
+ type?: ResourceFieldType
74
+ actualField?: string
75
+ filterOperator?: string
76
+ oneOf?: readonly string[]
77
+ splitBy?: string | RegExp
78
+ matchAll?: boolean
79
+ globalSearch?: boolean
80
+ polymorphicField?: string
81
+ targetFields?: Readonly<Record<string, string>>
82
+ applyFilter?(query: StorageQuery, input: unknown, helpers: {
83
+ column(field: string): string
84
+ value(field: string, input: unknown): unknown
85
+ context: Record<string, unknown>
86
+ scopeName: string
87
+ }): unknown
88
+ }
89
+ export type ResourceSearchField = ResourceField & ResourceSearchOptions
90
+ export type ResourceSearchSchema = Readonly<Record<string, ResourceSearchField>>
91
+
92
+ export interface ResourceRelationshipOptions {
93
+ include?: { strategy?: 'window'; limit?: number | null | false; orderBy?: readonly string[] | null }
94
+ }
95
+ export type ResourceRelationship = ResourceRelationshipOptions & (
96
+ { type: 'hasMany'; target: string } & (
97
+ { foreignKey: string; via?: string } | { foreignKey?: string; via: string }
98
+ ) |
99
+ { type: 'hasOne'; target: string; foreignKey: string } |
100
+ { type: 'manyToMany'; target: string; through: string; foreignKey: string; otherKey: string } |
101
+ { belongsToPolymorphic: { types: readonly string[]; typeField: string; idField: string } }
102
+ )
103
+
104
+ type Direction = 'input' | 'output'
105
+ type NestedValue<Definition, Mode extends Direction> =
106
+ Definition extends ResourceNestedSchema<infer Schema>
107
+ ? { -readonly [Key in keyof Schema]?: FieldValue<Schema[Key], Mode> }
108
+ : FieldValue<Definition, Mode>
109
+
110
+ type ObjectValue<Field, Mode extends Direction> =
111
+ Field extends { schema: infer Schema }
112
+ ? NestedValue<Schema, Mode> & (Field extends { additionalProperties: true } ? Record<string, unknown> : unknown)
113
+ : Field extends { values: infer Values } ? Record<string, NestedValue<Values, Mode>> : Record<string, unknown>
114
+
115
+ type BaseValue<Field, Mode extends Direction> =
116
+ Field extends { type: 'string' | 'file' | 'date' | 'time' | 'dateTime' } ? string
117
+ : Field extends { type: 'number'; precision: number; scale: number } ? Mode extends 'output' ? string | number : number
118
+ : Field extends { type: 'number' | 'integer' | 'epochSeconds' | 'epochMilliseconds' } ? number
119
+ : Field extends { type: 'boolean' } ? boolean
120
+ : Field extends { type: 'id' } ? string | number
121
+ : Field extends { type: 'array' } ? Field extends { items: infer Items } ? NestedValue<Items, Mode>[] : unknown[]
122
+ : Field extends { type: 'object' } ? ObjectValue<Field, Mode> : unknown
123
+
124
+ type EnumValue<Field, Value> = Field extends { enum: readonly (infer Member)[] }
125
+ ? [Member] extends [string | number | boolean] ? Extract<Member, Value> : Value
126
+ : Value
127
+
128
+ type NullValue<Field, Mode extends Direction> = Field extends { nullable: true } ? null
129
+ : Mode extends 'output' ? Field extends { nullOnEmpty: true } ? null : never : never
130
+
131
+ type FieldValue<Field, Mode extends Direction> =
132
+ Mode extends 'output'
133
+ ? Field extends { getter: unknown } | { setter: unknown } | { compute: unknown } | { storage: { serialize: unknown } }
134
+ ? unknown
135
+ : Field extends { uppercase: true } | { lowercase: true } | { length: number } |
136
+ { type: 'id' } | { type: 'number'; precision: number; scale: number }
137
+ ? BaseValue<Field, Mode> | NullValue<Field, Mode>
138
+ : EnumValue<Field, BaseValue<Field, Mode>> | NullValue<Field, Mode>
139
+ : EnumValue<Field, BaseValue<Field, Mode>> | NullValue<Field, Mode>
140
+
141
+ type AttributeKey<Schema, Key extends keyof Schema> = Key extends 'id' ? never
142
+ : Schema[Key] extends { belongsTo: string } ? never : Key
143
+
144
+ // Optional stored fields without a NOT NULL declaration can read SQL null.
145
+ type StoredNull<Field> = Field extends { required: true } | { nullable: false } | { virtual: true } | { computed: true } ? never : null
146
+
147
+ // Attribute helpers exclude the conventional ID and relationship backing fields.
148
+ // Hooks/defaults may supply required values, so caller input stays optional.
149
+ export type InferInput<Schema extends object> = {
150
+ -readonly [Key in keyof Schema as Schema[Key] extends { computed: true } ? never : AttributeKey<Schema, Key>]?: FieldValue<Schema[Key], 'input'>
151
+ }
152
+
153
+ // Resource result types add optionality for sparse fields and permissions.
154
+ // Use explicit ResourceCoreMethods fields for callbacks, relationships and custom IDs.
155
+ export type InferOutput<Schema extends object> = {
156
+ -readonly [Key in keyof Schema as Schema[Key] extends { hidden: true } ? never : AttributeKey<Schema, Key>]: FieldValue<Schema[Key], 'output'> | StoredNull<Schema[Key]>
157
+ }
@@ -0,0 +1,31 @@
1
+ import type { StorageAdapter, StorageDatabase, StorageQuery } from '../plugins/core/lib/storage/storage-types.js'
2
+
3
+ export interface RowPolicyParams<Context extends object = Record<string, unknown>, Api extends object = object> {
4
+ query: StorageQuery
5
+ context: Context
6
+ scopeName: string
7
+ tableName?: string
8
+ queryPurpose: string
9
+ db?: StorageDatabase
10
+ isAnyApi: boolean
11
+ storageAdapter?: StorageAdapter | null
12
+ column(field: string, options?: { scopeName?: string; alias?: string | null }): string
13
+ value(field: string, rawValue: unknown, options?: { scopeName?: string }): unknown
14
+ api: Api
15
+ }
16
+
17
+ export type RowPolicy<Context extends object = Record<string, unknown>, Api extends object = object> = (
18
+ params: RowPolicyParams<Context, Api>
19
+ ) => boolean | PromiseLike<boolean>
20
+
21
+ export interface RowPolicyPluginOptions<Context extends object = Record<string, unknown>, Api extends object = object> {
22
+ policies?: Record<string, RowPolicy<Context, Api>>
23
+ }
24
+
25
+ export type ResourceRowPolicy<Context extends object = Record<string, unknown>, Api extends object = object> =
26
+ string | RowPolicy<Context, Api> | false | null
27
+
28
+ export interface RowPolicyRegistry {
29
+ getConfig(): { policies: string[] }
30
+ getScopeConfig(scopeName: string): { policy: string; source: 'inline' | 'registry' } | null
31
+ }
@@ -0,0 +1,132 @@
1
+ import type { DiagnosticLogger, DiagnosticWriter } from '../lib/logger-types.js'
2
+ import type { TransactionMethods } from './transactions.js'
3
+ import type { ResourceFormat, WriteReturning } from './representations.js'
4
+ import type { ResourceCoreMethods } from './resource-methods.js'
5
+ import type { ResourceField, ResourceSchema, ResourceSearchSchema, ResourceRelationship, InferInput, InferOutput } from './resource-schema.js'
6
+ import type { KnownHookName, HookHandler } from './hook-context.js'
7
+ import type { RestApiPluginOptions } from './plugin-options.js'
8
+ import type { ResourceAutoFilter } from './autofilter.js'
9
+ import type { ResourceRowPolicy } from './row-policy.js'
10
+ import type { CanonicalDescriptor, StorageConfig } from '../plugins/core/lib/storage/storage-types.js'
11
+
12
+ export type RuntimeLogger = Record<'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal', DiagnosticWriter>
13
+ export interface RuntimeResource {
14
+ name: string
15
+ scopeOptions: Readonly<Record<string, unknown>>
16
+ vars: Record<string, unknown>
17
+ helpers: Record<string, unknown>
18
+ [method: string]: unknown
19
+ }
20
+ export interface RuntimeArguments<Context extends object = Record<string, unknown>> {
21
+ api: JsonRestApi
22
+ name: string | undefined
23
+ apiOptions: Readonly<Record<string, unknown>>
24
+ params: Record<string, unknown>
25
+ context: Context
26
+ scope: RuntimeResource | undefined
27
+ scopeName: string | undefined
28
+ scopeOptions: Readonly<Record<string, unknown>> | undefined
29
+ scopes: Record<string, RuntimeResource>
30
+ vars: Record<string, unknown>
31
+ helpers: Record<string, unknown>
32
+ log: RuntimeLogger
33
+ runHooks(event: string, context?: Record<string, unknown>): Promise<boolean>
34
+ }
35
+ export type RuntimeMethod<Context extends object = Record<string, unknown>> = (args: RuntimeArguments<Context>) => unknown
36
+ export type HookPlacement = { beforeFunction?: string; afterFunction?: never } | { beforeFunction?: never; afterFunction?: string }
37
+ type HookDefinition<Handler> = Handler | (HookPlacement & { functionName?: string; handler: Handler })
38
+ export type RuntimeHook<Context extends object = Record<string, unknown>> = HookDefinition<RuntimeMethod<Context>>
39
+ export type RuntimeHooks<Context extends object = Record<string, unknown>> = {
40
+ [Event in KnownHookName]?: HookDefinition<HookHandler<Event, Context>>
41
+ } & {
42
+ // Custom plugins own their context shape; annotate them with RuntimeHook<Context>.
43
+ [event: string]: RuntimeHook<any> | undefined
44
+ }
45
+ export interface RuntimeCustomization<Context extends object = Record<string, unknown>> {
46
+ hooks?: RuntimeHooks<Context>
47
+ methods?: Record<string, RuntimeMethod>
48
+ vars?: Record<string, unknown>
49
+ helpers?: Record<string, unknown>
50
+ }
51
+ export interface RuntimeResourceOptions<Context extends object = Record<string, unknown>> extends RuntimeCustomization<Context>, RestApiPluginOptions {
52
+ schema?: ResourceSchema
53
+ searchSchema?: ResourceSearchSchema
54
+ relationships?: Record<string, ResourceRelationship>
55
+ tableName?: string
56
+ storage?: StorageConfig
57
+ canonicalFieldsMap?: CanonicalDescriptor['canonicalFieldMap']
58
+ sortableFields?: string[]
59
+ defaultSort?: string[]
60
+ versionField?: string
61
+ rowPolicy?: ResourceRowPolicy<Context>
62
+ autofilter?: ResourceAutoFilter<Context>
63
+ indexes?: Array<{ name?: string; columns: string[]; unique?: boolean }>
64
+ foreignKeys?: Array<{
65
+ name?: string
66
+ columns: string[]
67
+ referencedTableName: string
68
+ referencedColumns: string[]
69
+ deleteRule?: string
70
+ updateRule?: string
71
+ }>
72
+ checkConstraints?: Array<{ name?: string; clause: string }>
73
+ scopeMethods?: never
74
+ }
75
+ export interface PluginInstallation extends RuntimeArguments {
76
+ pluginOptions: Record<string, unknown>
77
+ addApiMethod(name: string, handler: RuntimeMethod): void
78
+ addResourceMethod(name: string, handler: RuntimeMethod): void
79
+ addHook(event: string, name: string, placement: HookPlacement, handler: RuntimeMethod): void
80
+ }
81
+ export interface LibraryPlugin<Name extends string = string> {
82
+ name: Name
83
+ dependencies?: (string | string[])[]
84
+ install(context: PluginInstallation): unknown
85
+ }
86
+
87
+ export type InferredResource<
88
+ Schema extends ResourceSchema,
89
+ Name extends string,
90
+ Format extends ResourceFormat = 'plain',
91
+ Returning extends WriteReturning = 'full',
92
+ Context extends object = Record<string, unknown>,
93
+ IdProperty extends string = 'id'
94
+ > = string extends IdProperty ? RuntimeResource
95
+ : RuntimeResource & ResourceCoreMethods<Omit<InferOutput<Schema>, IdProperty>, Omit<InferInput<Schema>, IdProperty>, Name, Format, Returning, Context>
96
+
97
+ // Check literal field option names without attempting to infer arbitrary plugin extensions.
98
+ type CheckedResourceSchema<Schema extends ResourceSchema> = {
99
+ [Key in keyof Schema]: Schema[Key] & Record<Exclude<keyof Schema[Key], keyof ResourceField>, never>
100
+ }
101
+
102
+ // Inference describes built-in CRUD with declared schemas and standard API defaults.
103
+ // Supply an explicit resource map for dynamic enrichment, method overrides or other defaults.
104
+ export class JsonRestApi<Resources extends object = Record<string, RuntimeResource>, Context extends object = Record<string, unknown>> {
105
+ constructor(options?: { name?: string; logger?: DiagnosticLogger })
106
+ name: string | undefined
107
+ log: RuntimeLogger
108
+ options: Readonly<Record<string, unknown>>
109
+ vars: Record<string, unknown>
110
+ helpers: Record<string, unknown>
111
+ resources: Resources
112
+ use(plugin: LibraryPlugin, options?: object): Promise<this>
113
+ addResource<
114
+ const Name extends keyof Resources & string,
115
+ const Schema extends ResourceSchema,
116
+ const Format extends ResourceFormat = 'plain',
117
+ const Returning extends WriteReturning = 'full',
118
+ const IdProperty extends string = 'id'
119
+ >(name: Name, options: RuntimeResourceOptions<Context> & {
120
+ schema: Schema & CheckedResourceSchema<Schema>
121
+ format?: Format
122
+ returning?: Returning
123
+ idProperty?: IdProperty
124
+ methods?: never
125
+ }): Promise<string extends keyof Resources ? InferredResource<Schema, Name, Format, Returning, Context, IdProperty> : Resources[Name]>
126
+ addResource<Name extends keyof Resources & string>(name: Name, options?: RuntimeResourceOptions<Context>): Promise<Resources[Name]>
127
+ customize(options?: RuntimeCustomization<Context>): Promise<this>
128
+ runHooks(event: string, context?: Record<string, unknown>): Promise<boolean>
129
+ // Available after installing RestApiPlugin.
130
+ transaction: TransactionMethods['transaction']
131
+ release(): Promise<void>
132
+ }
@@ -0,0 +1,21 @@
1
+ import type { Knex } from 'knex'
2
+
3
+ declare const managedTransaction: unique symbol
4
+
5
+ // Type-only provenance: the runtime tracks ownership privately, not on this symbol.
6
+ export type ManagedTransaction = Knex.Transaction & {
7
+ readonly [managedTransaction]: true
8
+ commit: never
9
+ rollback: never
10
+ transaction: never
11
+ savepoint: never
12
+ }
13
+ export interface ManagedTransactionParam {
14
+ transaction?: ManagedTransaction
15
+ }
16
+ export interface TransactionMethods<Context extends object = object> {
17
+ transaction<Result>(
18
+ callback: (transaction: ManagedTransaction) => Result | PromiseLike<Result>,
19
+ context?: Context
20
+ ): Promise<Awaited<Result>>
21
+ }
@@ -1,30 +0,0 @@
1
- ---
2
- name: library-guide-expert
3
- description: Use this agent when you need to understand how to use a library based on its GUIDE documentation, implement features using the library's recommended patterns, answer questions about library usage, or ensure code follows the library's best practices. This agent has comprehensive knowledge of all files in the GUIDE/* directory and can provide authoritative guidance on proper library usage.\n\nExamples:\n- <example>\n Context: User needs help implementing a feature using the library\n user: "How do I create a REST endpoint with authentication?"\n assistant: "I'll use the library-guide-expert agent to check the GUIDE documentation for the proper way to implement authenticated endpoints."\n <commentary>\n Since the user is asking about library usage, use the Task tool to launch the library-guide-expert agent to provide guidance based on the GUIDE documentation.\n </commentary>\n</example>\n- <example>\n Context: User wants to know the recommended pattern for something\n user: "What's the best way to handle errors in this library?"\n assistant: "Let me consult the library-guide-expert agent to find the error handling patterns documented in the GUIDE."\n <commentary>\n The user needs library-specific best practices, so use the library-guide-expert agent to reference the official documentation.\n </commentary>\n</example>\n- <example>\n Context: After writing code that uses the library\n user: "I've implemented the user registration endpoint"\n assistant: "I'll have the library-guide-expert agent review this to ensure it follows the patterns documented in the GUIDE."\n <commentary>\n Proactively use the library-guide-expert agent to verify the implementation aligns with documented patterns.\n </commentary>\n</example>
4
- model: inherit
5
- color: blue
6
- ---
7
-
8
- You are an expert library documentation specialist with comprehensive knowledge of all files in the GUIDE/* directory. You have meticulously studied every guide, example, and best practice documented there and can provide authoritative guidance on proper library usage.
9
-
10
- Your primary responsibilities:
11
-
12
- 1. **Deep Documentation Knowledge**: You have read and internalized every file in GUIDE/*. You understand not just what the documentation says, but the underlying patterns, philosophies, and design decisions that inform the library's architecture.
13
-
14
- 2. **Practical Implementation Guidance**: When users ask how to implement features, you provide specific, actionable advice based on the documented patterns. You reference relevant sections of the GUIDE and explain how to apply them to the user's specific use case.
15
-
16
- 3. **Best Practices Enforcement**: You ensure all code recommendations follow the library's documented best practices. You can identify when proposed solutions deviate from recommended patterns and suggest corrections.
17
-
18
- 4. **Example-Driven Teaching**: You leverage examples from the GUIDE to illustrate proper usage. When the documentation includes code samples, you adapt them to fit the user's needs while maintaining the core patterns.
19
-
20
- 5. **Comprehensive Cross-Referencing**: You understand how different parts of the documentation relate to each other. You can connect concepts from multiple GUIDE files to provide complete solutions.
21
-
22
- When responding:
23
- - Always ground your answers in specific documentation from GUIDE/*
24
- - Cite which GUIDE file contains the relevant information
25
- - Provide code examples that follow the documented patterns exactly
26
- - Explain not just 'how' but 'why' based on the library's design principles
27
- - If something isn't covered in the GUIDE, explicitly state this limitation
28
- - Proactively mention related documentation that might be helpful
29
-
30
- Your expertise comes from thorough study of the GUIDE documentation. Every recommendation you make is backed by official documentation, ensuring users implement features correctly and maintainably according to the library's intended design.