@harperfast/harper 5.2.0-alpha.4 → 5.2.0-alpha.6

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 (173) hide show
  1. package/agent/agent.ts +152 -10
  2. package/agent/bestPractices.ts +58 -0
  3. package/agent/mcpTools.ts +122 -0
  4. package/agent/operations.ts +10 -1
  5. package/agent/registryTools.ts +117 -0
  6. package/agent/session.ts +16 -7
  7. package/agent/tools/fsTools.ts +74 -27
  8. package/agent/tools/inspectorTool.ts +459 -0
  9. package/agent/toolset.ts +27 -7
  10. package/agent/types.ts +2 -0
  11. package/components/Application.ts +566 -85
  12. package/components/OptionsWatcher.ts +62 -2
  13. package/components/Scope.ts +31 -8
  14. package/components/componentLoader.ts +5 -1
  15. package/components/componentSecrets.ts +514 -55
  16. package/components/deploymentRecorder.ts +7 -6
  17. package/components/gitCredentialHelper.js +115 -0
  18. package/components/gitCredentialServer.ts +251 -0
  19. package/components/mcp/tools/application.ts +197 -23
  20. package/components/mcp/tools/operations.ts +1 -1
  21. package/components/mcp/tools/schemas/derive.ts +6 -2
  22. package/components/operations.js +21 -19
  23. package/components/operationsValidation.js +86 -21
  24. package/components/secretOperations.ts +110 -30
  25. package/config/harperConfigEnvVars.ts +90 -5
  26. package/config-root.schema.json +4 -0
  27. package/dist/agent/agent.d.ts +24 -3
  28. package/dist/agent/agent.js +172 -10
  29. package/dist/agent/agent.js.map +1 -1
  30. package/dist/agent/bestPractices.d.ts +24 -0
  31. package/dist/agent/bestPractices.js +60 -0
  32. package/dist/agent/bestPractices.js.map +1 -0
  33. package/dist/agent/mcpTools.d.ts +24 -0
  34. package/dist/agent/mcpTools.js +115 -0
  35. package/dist/agent/mcpTools.js.map +1 -0
  36. package/dist/agent/operations.js +10 -1
  37. package/dist/agent/operations.js.map +1 -1
  38. package/dist/agent/registryTools.d.ts +45 -0
  39. package/dist/agent/registryTools.js +113 -0
  40. package/dist/agent/registryTools.js.map +1 -0
  41. package/dist/agent/session.js +16 -7
  42. package/dist/agent/session.js.map +1 -1
  43. package/dist/agent/tools/fsTools.js +70 -28
  44. package/dist/agent/tools/fsTools.js.map +1 -1
  45. package/dist/agent/tools/inspectorTool.d.ts +40 -0
  46. package/dist/agent/tools/inspectorTool.js +428 -0
  47. package/dist/agent/tools/inspectorTool.js.map +1 -0
  48. package/dist/agent/toolset.d.ts +16 -6
  49. package/dist/agent/toolset.js +17 -7
  50. package/dist/agent/toolset.js.map +1 -1
  51. package/dist/agent/types.d.ts +2 -0
  52. package/dist/components/Application.d.ts +73 -18
  53. package/dist/components/Application.js +475 -64
  54. package/dist/components/Application.js.map +1 -1
  55. package/dist/components/OptionsWatcher.d.ts +1 -1
  56. package/dist/components/OptionsWatcher.js +65 -2
  57. package/dist/components/OptionsWatcher.js.map +1 -1
  58. package/dist/components/Scope.d.ts +8 -6
  59. package/dist/components/Scope.js +22 -6
  60. package/dist/components/Scope.js.map +1 -1
  61. package/dist/components/componentLoader.js +5 -1
  62. package/dist/components/componentLoader.js.map +1 -1
  63. package/dist/components/componentSecrets.d.ts +33 -7
  64. package/dist/components/componentSecrets.js +473 -59
  65. package/dist/components/componentSecrets.js.map +1 -1
  66. package/dist/components/deploymentRecorder.d.ts +2 -2
  67. package/dist/components/deploymentRecorder.js +1 -1
  68. package/dist/components/deploymentRecorder.js.map +1 -1
  69. package/dist/components/gitCredentialHelper.d.ts +1 -0
  70. package/dist/components/gitCredentialHelper.js +113 -0
  71. package/dist/components/gitCredentialHelper.js.map +1 -0
  72. package/dist/components/gitCredentialServer.d.ts +33 -0
  73. package/dist/components/gitCredentialServer.js +236 -0
  74. package/dist/components/gitCredentialServer.js.map +1 -0
  75. package/dist/components/mcp/tools/application.d.ts +19 -0
  76. package/dist/components/mcp/tools/application.js +180 -22
  77. package/dist/components/mcp/tools/application.js.map +1 -1
  78. package/dist/components/mcp/tools/operations.d.ts +13 -0
  79. package/dist/components/mcp/tools/operations.js +1 -0
  80. package/dist/components/mcp/tools/operations.js.map +1 -1
  81. package/dist/components/mcp/tools/schemas/derive.js +6 -2
  82. package/dist/components/mcp/tools/schemas/derive.js.map +1 -1
  83. package/dist/components/operations.js +23 -21
  84. package/dist/components/operations.js.map +1 -1
  85. package/dist/components/operationsValidation.js +84 -21
  86. package/dist/components/operationsValidation.js.map +1 -1
  87. package/dist/components/secretOperations.d.ts +33 -11
  88. package/dist/components/secretOperations.js +90 -26
  89. package/dist/components/secretOperations.js.map +1 -1
  90. package/dist/config/harperConfigEnvVars.d.ts +21 -0
  91. package/dist/config/harperConfigEnvVars.js +95 -5
  92. package/dist/config/harperConfigEnvVars.js.map +1 -1
  93. package/dist/index.d.ts +4 -0
  94. package/dist/index.js +14 -1
  95. package/dist/index.js.map +1 -1
  96. package/dist/resources/Resource.d.ts +19 -0
  97. package/dist/resources/Resource.js +93 -2
  98. package/dist/resources/Resource.js.map +1 -1
  99. package/dist/resources/Table.d.ts +9 -0
  100. package/dist/resources/Table.js +256 -50
  101. package/dist/resources/Table.js.map +1 -1
  102. package/dist/resources/analytics/read.js +28 -25
  103. package/dist/resources/analytics/read.js.map +1 -1
  104. package/dist/resources/defineResource.d.ts +180 -0
  105. package/dist/resources/defineResource.js +505 -0
  106. package/dist/resources/defineResource.js.map +1 -0
  107. package/dist/resources/defineTable.d.ts +221 -0
  108. package/dist/resources/defineTable.js +227 -0
  109. package/dist/resources/defineTable.js.map +1 -0
  110. package/dist/resources/jsonSchemaTypes.d.ts +2 -0
  111. package/dist/resources/jsonSchemaTypes.js +12 -4
  112. package/dist/resources/jsonSchemaTypes.js.map +1 -1
  113. package/dist/resources/openApi.js +69 -13
  114. package/dist/resources/openApi.js.map +1 -1
  115. package/dist/resources/search.js +6 -8
  116. package/dist/resources/search.js.map +1 -1
  117. package/dist/server/graphqlQuerying.js +4 -2
  118. package/dist/server/graphqlQuerying.js.map +1 -1
  119. package/dist/server/http.d.ts +12 -0
  120. package/dist/server/http.js +35 -15
  121. package/dist/server/http.js.map +1 -1
  122. package/dist/server/serverHelpers/serverUtilities.js +8 -5
  123. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  124. package/dist/server/serverHelpers/uwsServer.js +23 -0
  125. package/dist/server/serverHelpers/uwsServer.js.map +1 -1
  126. package/dist/utility/errors/hdbError.d.ts +21 -0
  127. package/dist/utility/errors/hdbError.js +23 -1
  128. package/dist/utility/errors/hdbError.js.map +1 -1
  129. package/dist/validation/configValidator.js +22 -6
  130. package/dist/validation/configValidator.js.map +1 -1
  131. package/index.ts +33 -0
  132. package/npm-shrinkwrap.json +9197 -15236
  133. package/package.json +5 -3
  134. package/resources/DESIGN.md +42 -15
  135. package/resources/Resource.ts +99 -2
  136. package/resources/Table.ts +275 -71
  137. package/resources/analytics/read.ts +30 -25
  138. package/resources/defineResource.ts +651 -0
  139. package/resources/defineTable.ts +407 -0
  140. package/resources/jsonSchemaTypes.ts +12 -4
  141. package/resources/openApi.ts +68 -16
  142. package/resources/search.ts +5 -8
  143. package/server/graphqlQuerying.ts +4 -2
  144. package/server/http.ts +40 -16
  145. package/server/serverHelpers/serverUtilities.ts +19 -6
  146. package/server/serverHelpers/uwsServer.ts +24 -0
  147. package/studio/web/assets/{Chat-D4FIaBu7.js → Chat-CTjtL8Z4.js} +2 -2
  148. package/studio/web/assets/{Chat-D4FIaBu7.js.map → Chat-CTjtL8Z4.js.map} +1 -1
  149. package/studio/web/assets/{FloatingChat-DGUC3L4r.js → FloatingChat-CafHR4Ur.js} +4 -4
  150. package/studio/web/assets/{FloatingChat-DGUC3L4r.js.map → FloatingChat-CafHR4Ur.js.map} +1 -1
  151. package/studio/web/assets/{applications-D8Am9ikM.js → applications-Buh_q0Vj.js} +2 -2
  152. package/studio/web/assets/{applications-D8Am9ikM.js.map → applications-Buh_q0Vj.js.map} +1 -1
  153. package/studio/web/assets/{index-HGL4WHdb.js → index-0hXeECkS.js} +6 -6
  154. package/studio/web/assets/{index-HGL4WHdb.js.map → index-0hXeECkS.js.map} +1 -1
  155. package/studio/web/assets/{index.lazy-Dk5j1AoQ.js → index.lazy-B00B7VBT.js} +4 -4
  156. package/studio/web/assets/{index.lazy-Dk5j1AoQ.js.map → index.lazy-B00B7VBT.js.map} +1 -1
  157. package/studio/web/assets/{profile-7bu_CF1f.js → profile-Cg2wwYPn.js} +2 -2
  158. package/studio/web/assets/{profile-7bu_CF1f.js.map → profile-Cg2wwYPn.js.map} +1 -1
  159. package/studio/web/assets/{setComponentFile-MfYm9F94.js → setComponentFile-DCaDIvyB.js} +2 -2
  160. package/studio/web/assets/{setComponentFile-MfYm9F94.js.map → setComponentFile-DCaDIvyB.js.map} +1 -1
  161. package/studio/web/assets/{setup-SKA4UhdE.js → setup-CAVcAQjK.js} +2 -2
  162. package/studio/web/assets/{setup-SKA4UhdE.js.map → setup-CAVcAQjK.js.map} +1 -1
  163. package/studio/web/assets/{status-DetVLUxR.js → status-BRXorNdD.js} +2 -2
  164. package/studio/web/assets/{status-DetVLUxR.js.map → status-BRXorNdD.js.map} +1 -1
  165. package/studio/web/assets/{swagger-ui-react-DLeYARY2.js → swagger-ui-react-Dy1D62vO.js} +2 -2
  166. package/studio/web/assets/{swagger-ui-react-DLeYARY2.js.map → swagger-ui-react-Dy1D62vO.js.map} +1 -1
  167. package/studio/web/assets/{tsMode-DGO-jIF7.js → tsMode-A8gbL74v.js} +2 -2
  168. package/studio/web/assets/{tsMode-DGO-jIF7.js.map → tsMode-A8gbL74v.js.map} +1 -1
  169. package/studio/web/assets/{useEntityRestURL-4M2i6bo4.js → useEntityRestURL-cDodrVcQ.js} +2 -2
  170. package/studio/web/assets/{useEntityRestURL-4M2i6bo4.js.map → useEntityRestURL-cDodrVcQ.js.map} +1 -1
  171. package/studio/web/index.html +1 -1
  172. package/utility/errors/hdbError.ts +32 -0
  173. package/validation/configValidator.ts +23 -6
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Code-first schema authoring (`defineTable` + `types`), the canonical model.
3
+ *
4
+ * The dev names exactly ONE thing — `Track` (singular). `defineTable` eagerly registers the
5
+ * table through the same `table()` factory GraphQL drives (`resources/databases.ts`) and returns
6
+ * the live, registered class: `Track.get/put/post/patch/update/query/search` work immediately,
7
+ * `new Track()` / `instanceof Track` hold, and every per-verb shape is a *projection* inferred
8
+ * from the one definition, discoverable as members (`Track.$record`, `Track.$insert`, …) — never
9
+ * a parallel name.
10
+ *
11
+ * The anchoring constraint (`--conditions=typestrip`): runtime metadata is carried as values;
12
+ * TypeScript types are *derived* from those values. Everything here is erasable syntax — the
13
+ * values survive type-stripping, the types are inferred, nothing can drift.
14
+ *
15
+ * Both authoring front-ends compile to the same typeDef shape (`{ table, database, attributes,
16
+ * properties, schemaDefined }`), so DDL/migration semantics are shared rather than reimplemented.
17
+ * Relationships take a lazy thunk (`types.relation(() => Album, { from: 'albumId' })`); the
18
+ * target class is resolved on first use (the same late-binding contract GraphQL's
19
+ * `connectPropertyType` relies on — `definition` must exist at registration, `definition.tableClass`
20
+ * only by query time), so forward references and cycles between tables just work AT RUNTIME.
21
+ *
22
+ * A truly MUTUAL pair (`Book.author` -> `Author`, `Author.books` -> `Book`) is a different problem
23
+ * at the TYPE level: TypeScript's const-initializer inference is eager, so `typeof Book` and
24
+ * `typeof Author` end up depending on each other and both collapse to `any` (TS7022). `relationOf`
25
+ * / `hasManyOf` are the escape hatch — see their doc comments below.
26
+ */
27
+ import { type Table } from './databases.ts';
28
+ export interface Flags {
29
+ nullable?: boolean;
30
+ primaryKey?: boolean;
31
+ serverManaged?: boolean;
32
+ indexed?: boolean;
33
+ relation?: 'one' | 'many';
34
+ }
35
+ /**
36
+ * A schema field. `_ts`/`_flags` are phantom type carriers (never present at runtime); the
37
+ * flag getters return a fresh field with the flag applied at both the value and type level.
38
+ */
39
+ export interface Field<TS, F extends Flags = {}> {
40
+ readonly _ts: TS;
41
+ readonly _flags: F;
42
+ readonly kind: string;
43
+ readonly meta: Record<string, unknown>;
44
+ readonly nullable: Field<TS, F & {
45
+ nullable: true;
46
+ }>;
47
+ readonly indexed: Field<TS, F & {
48
+ indexed: true;
49
+ }>;
50
+ readonly primaryKey: Field<TS, F & {
51
+ primaryKey: true;
52
+ }>;
53
+ }
54
+ /** Date additionally carries the server-managed timestamp flags. */
55
+ export interface DateField<F extends Flags = {}> extends Field<Date, F> {
56
+ readonly createdTime: Field<Date, F & {
57
+ serverManaged: true;
58
+ }>;
59
+ readonly updatedTime: Field<Date, F & {
60
+ serverManaged: true;
61
+ }>;
62
+ }
63
+ /** The read-record type of a related table handle. */
64
+ type RecordOf<T> = T extends {
65
+ readonly $record: infer R;
66
+ } ? R : never;
67
+ export type RelationField<Target, Card extends 'one' | 'many'> = Field<Card extends 'many' ? RecordOf<Target>[] : RecordOf<Target>, {
68
+ relation: Card;
69
+ }>;
70
+ /**
71
+ * The field vocabulary the dev destructures: `const { id, string, int, date } = types`.
72
+ * Pure-flag builders are values (getter chains); only arg-taking builders stay functions.
73
+ * Every member mirrors a GraphQL directive/type so the two front-ends stay in lockstep.
74
+ */
75
+ export declare const types: {
76
+ id: Field<string>;
77
+ string: Field<string>;
78
+ int: Field<number>;
79
+ float: Field<number>;
80
+ long: Field<number>;
81
+ boolean: Field<boolean>;
82
+ date: DateField;
83
+ bytes: Field<Uint8Array>;
84
+ any: Field<unknown>;
85
+ enum: <const E extends readonly string[]>(values: E) => Field<E[number]>;
86
+ relation: <T>(target: () => T, opts: {
87
+ from: string;
88
+ }) => RelationField<T, "one">;
89
+ hasMany: <T>(target: () => T, opts: {
90
+ to: string;
91
+ }) => RelationField<T, "many">;
92
+ /**
93
+ * `relation`'s escape hatch for a MUTUAL pair (`Book.author` <-> `Author.books`): TS's
94
+ * const-initializer inference is eager, so `typeof Book` and `typeof Author` would each depend
95
+ * on the other and both collapse to `any` (TS7022) if both sides used `relation`/`hasMany`.
96
+ * `relationOf<R>` breaks the cycle — `target` is typed `() => any` (no inference is pulled from
97
+ * the argument), and the related RECORD shape is supplied explicitly via `<R>`. Declare a small
98
+ * interface for the record once, on whichever side of the pair you close second; the runtime
99
+ * behavior (lazy thunk, resolved on first query-time use) is identical to `relation`.
100
+ */
101
+ relationOf: <R>(target: () => any, opts: {
102
+ from: string;
103
+ }) => RelationField<{
104
+ readonly $record: R;
105
+ }, "one">;
106
+ /** `hasMany`'s escape hatch for a mutual pair — see `relationOf`. */
107
+ hasManyOf: <R>(target: () => any, opts: {
108
+ to: string;
109
+ }) => RelationField<{
110
+ readonly $record: R;
111
+ }, "many">;
112
+ };
113
+ export type Shape = Record<string, Field<any, any>>;
114
+ type TsOf<F> = F extends Field<infer TS, any> ? TS : never;
115
+ type FlagsOf<F> = F extends Field<any, infer Fl> ? Fl : {};
116
+ type IsNullable<F> = FlagsOf<F> extends {
117
+ nullable: true;
118
+ } ? true : false;
119
+ type IsPrimaryKey<F> = FlagsOf<F> extends {
120
+ primaryKey: true;
121
+ } ? true : false;
122
+ type IsServerManaged<F> = FlagsOf<F> extends {
123
+ serverManaged: true;
124
+ } ? true : false;
125
+ type IsIndexed<F> = FlagsOf<F> extends {
126
+ indexed: true;
127
+ } ? true : false;
128
+ type IsRelation<F> = FlagsOf<F> extends {
129
+ relation: 'one' | 'many';
130
+ } ? true : false;
131
+ /** Server-managed and relation fields are read-only on the record. */
132
+ type IsReadOnly<F> = IsServerManaged<F> extends true ? true : IsRelation<F>;
133
+ type Resolve<T> = {
134
+ [K in keyof T]: T[K];
135
+ };
136
+ /** The record's data fields: server-managed/relations readonly, others mutable; nullable -> optional. */
137
+ type RecordData<S> = Resolve<{
138
+ readonly [K in keyof S as IsReadOnly<S[K]> extends true ? K : never]: TsOf<S[K]>;
139
+ } & {
140
+ [K in keyof S as IsReadOnly<S[K]> extends false ? (IsNullable<S[K]> extends false ? K : never) : never]: TsOf<S[K]>;
141
+ } & {
142
+ [K in keyof S as IsReadOnly<S[K]> extends false ? (IsNullable<S[K]> extends true ? K : never) : never]?: TsOf<S[K]>;
143
+ }>;
144
+ type WritableOf<S> = {
145
+ [K in keyof S as IsServerManaged<S[K]> extends false ? (IsRelation<S[K]> extends false ? K : never) : never]: S[K];
146
+ };
147
+ /** INSERT (POST): writable fields; PK OPTIONAL (server can generate it); nullable -> optional. */
148
+ type InsertOf<S, W = WritableOf<S>> = Resolve<{
149
+ [K in keyof W as IsPrimaryKey<W[K]> extends false ? (IsNullable<W[K]> extends false ? K : never) : never]: TsOf<W[K]>;
150
+ } & {
151
+ [K in keyof W as IsPrimaryKey<W[K]> extends true ? K : IsNullable<W[K]> extends true ? K : never]?: TsOf<W[K]>;
152
+ }>;
153
+ /** UPSERT (PUT): full replace — writable fields, PK REQUIRED (you're naming the row). */
154
+ type UpsertOf<S, W = WritableOf<S>> = Resolve<{
155
+ [K in keyof W as IsNullable<W[K]> extends false ? K : never]: TsOf<W[K]>;
156
+ } & {
157
+ [K in keyof W as IsNullable<W[K]> extends true ? K : never]?: TsOf<W[K]>;
158
+ }>;
159
+ /** PATCH: partial update — every writable non-PK field optional. */
160
+ type PatchOf<S, W = WritableOf<S>> = Resolve<{
161
+ [K in keyof W as IsPrimaryKey<W[K]> extends true ? never : K]?: TsOf<W[K]>;
162
+ }>;
163
+ /** QUERY / filter: indexed fields only, all optional (the base filter grammar keys to these). */
164
+ type QueryOf<S> = Resolve<{
165
+ [K in keyof S as IsIndexed<S[K]> extends true ? K : never]?: TsOf<S[K]>;
166
+ }>;
167
+ /** The read-only response variant — the whole record, nothing writable. */
168
+ type ReadVariant<S> = Readonly<RecordData<S>>;
169
+ /** The LIVE instance — what `new Track()` / `Track.update()` yield; writable fields mutable. */
170
+ type InstanceOf<S> = RecordData<S>;
171
+ /** The primary-key property and its value type (falls back to string|number when none declared). */
172
+ type PkKey<S> = {
173
+ [K in keyof S]: IsPrimaryKey<S[K]> extends true ? K : never;
174
+ }[keyof S];
175
+ type IdOf<S> = [PkKey<S>] extends [never] ? string | number : TsOf<S[PkKey<S>]>;
176
+ type MaybePromise<T> = T | Promise<T>;
177
+ interface TypedVerbs<S extends Shape> {
178
+ get(id: IdOf<S>, context?: any): MaybePromise<ReadVariant<S> | undefined>;
179
+ put(record: UpsertOf<S>, context?: any): MaybePromise<unknown>;
180
+ post(record: InsertOf<S>, context?: any): MaybePromise<unknown>;
181
+ patch(id: IdOf<S>, changes: PatchOf<S>, context?: any): MaybePromise<unknown>;
182
+ update(id: IdOf<S>, updates?: PatchOf<S>, context?: any): MaybePromise<InstanceOf<S>>;
183
+ delete(id: IdOf<S>, context?: any): MaybePromise<unknown>;
184
+ search(query?: any, context?: any): AsyncIterable<ReadVariant<S>>;
185
+ query(query?: any, context?: any): AsyncIterable<ReadVariant<S>>;
186
+ }
187
+ /**
188
+ * The single canonical handle `defineTable` returns: the registered table class, with
189
+ * • the verbs typed by the projections,
190
+ * • phantom `$*` members so each projection is discoverable as `(typeof Track)['$insert']`,
191
+ * • a construct signature so `Track` doubles as the instance type via `InstanceType<typeof Track>`.
192
+ */
193
+ export type TableHandle<S extends Shape = Shape> = Omit<Table, 'get' | 'put' | 'post' | 'patch' | 'update' | 'delete' | 'search' | 'query'> & TypedVerbs<S> & {
194
+ readonly $record: ReadVariant<S>;
195
+ readonly $insert: InsertOf<S>;
196
+ readonly $upsert: UpsertOf<S>;
197
+ readonly $patch: PatchOf<S>;
198
+ readonly $query: QueryOf<S>;
199
+ } & (new (...args: any[]) => InstanceOf<S>);
200
+ /** `@table` directive parity — options GraphQL can declare on a type, plus the database. */
201
+ export interface DefineTableOptions {
202
+ /** Database (schema) the table lives in; defaults to the `data` database. */
203
+ database?: string;
204
+ audit?: boolean;
205
+ replicate?: boolean;
206
+ sealed?: boolean;
207
+ expiration?: number;
208
+ eviction?: number;
209
+ scanInterval?: number;
210
+ splitSegments?: boolean;
211
+ trackDeletes?: boolean;
212
+ randomAccessFields?: boolean;
213
+ }
214
+ /**
215
+ * Declare AND register a table from a TypeScript value. Eager: the returned value is the live,
216
+ * registered table class (the same class `tables.<Name>` holds), typed by the shape — the import
217
+ * IS the typed handle. Defining the same table again re-asserts the schema (add/remove attributes,
218
+ * index changes) through the same evolution path GraphQL reloads take.
219
+ */
220
+ export declare function defineTable<S extends Shape>(name: string, shape: S, options?: DefineTableOptions): TableHandle<S>;
221
+ export {};
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ /**
3
+ * Code-first schema authoring (`defineTable` + `types`), the canonical model.
4
+ *
5
+ * The dev names exactly ONE thing — `Track` (singular). `defineTable` eagerly registers the
6
+ * table through the same `table()` factory GraphQL drives (`resources/databases.ts`) and returns
7
+ * the live, registered class: `Track.get/put/post/patch/update/query/search` work immediately,
8
+ * `new Track()` / `instanceof Track` hold, and every per-verb shape is a *projection* inferred
9
+ * from the one definition, discoverable as members (`Track.$record`, `Track.$insert`, …) — never
10
+ * a parallel name.
11
+ *
12
+ * The anchoring constraint (`--conditions=typestrip`): runtime metadata is carried as values;
13
+ * TypeScript types are *derived* from those values. Everything here is erasable syntax — the
14
+ * values survive type-stripping, the types are inferred, nothing can drift.
15
+ *
16
+ * Both authoring front-ends compile to the same typeDef shape (`{ table, database, attributes,
17
+ * properties, schemaDefined }`), so DDL/migration semantics are shared rather than reimplemented.
18
+ * Relationships take a lazy thunk (`types.relation(() => Album, { from: 'albumId' })`); the
19
+ * target class is resolved on first use (the same late-binding contract GraphQL's
20
+ * `connectPropertyType` relies on — `definition` must exist at registration, `definition.tableClass`
21
+ * only by query time), so forward references and cycles between tables just work AT RUNTIME.
22
+ *
23
+ * A truly MUTUAL pair (`Book.author` -> `Author`, `Author.books` -> `Book`) is a different problem
24
+ * at the TYPE level: TypeScript's const-initializer inference is eager, so `typeof Book` and
25
+ * `typeof Author` end up depending on each other and both collapse to `any` (TS7022). `relationOf`
26
+ * / `hasManyOf` are the escape hatch — see their doc comments below.
27
+ */
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.types = void 0;
30
+ exports.defineTable = defineTable;
31
+ const databases_ts_1 = require("./databases.js");
32
+ const jsonSchemaTypes_ts_1 = require("./jsonSchemaTypes.js");
33
+ // Runtime builders — getters return FRESH fields (immutable chaining; no shared-state bug).
34
+ function makeField(kind, meta = {}) {
35
+ return {
36
+ kind,
37
+ meta,
38
+ get nullable() {
39
+ return makeField(kind, { ...meta, nullable: true });
40
+ },
41
+ get indexed() {
42
+ return makeField(kind, { ...meta, indexed: true });
43
+ },
44
+ get primaryKey() {
45
+ return makeField(kind, { ...meta, primaryKey: true });
46
+ },
47
+ };
48
+ }
49
+ function makeDateField(meta = {}) {
50
+ return Object.defineProperties(makeField('Date', meta), {
51
+ createdTime: {
52
+ get: () => makeField('Date', { ...meta, assignCreatedTime: true }),
53
+ enumerable: true,
54
+ configurable: true,
55
+ },
56
+ updatedTime: {
57
+ get: () => makeField('Date', { ...meta, assignUpdatedTime: true }),
58
+ enumerable: true,
59
+ configurable: true,
60
+ },
61
+ });
62
+ }
63
+ /**
64
+ * The field vocabulary the dev destructures: `const { id, string, int, date } = types`.
65
+ * Pure-flag builders are values (getter chains); only arg-taking builders stay functions.
66
+ * Every member mirrors a GraphQL directive/type so the two front-ends stay in lockstep.
67
+ */
68
+ exports.types = {
69
+ id: makeField('ID'),
70
+ string: makeField('String'),
71
+ int: makeField('Int'),
72
+ float: makeField('Float'),
73
+ long: makeField('Long'),
74
+ boolean: makeField('Boolean'),
75
+ date: makeDateField(),
76
+ bytes: makeField('Bytes'),
77
+ any: makeField('Any'),
78
+ // Narrows the TS type to the literal union; stored as a String column. The narrowing is
79
+ // advisory at runtime today (the shared Table.validate has no enum case — same as GraphQL);
80
+ // the literal set is retained on the attribute for downstream surfaces.
81
+ enum: (values) => makeField('String', { enum: values }),
82
+ // many-to-one: this table holds the foreign key named `from`. Lazy thunk — forward refs are fine.
83
+ relation: (target, opts) => makeField('relation', { target, from: opts.from }),
84
+ // one-to-many: the related table holds the foreign key named `to`.
85
+ hasMany: (target, opts) => makeField('relation', { target, to: opts.to }),
86
+ /**
87
+ * `relation`'s escape hatch for a MUTUAL pair (`Book.author` <-> `Author.books`): TS's
88
+ * const-initializer inference is eager, so `typeof Book` and `typeof Author` would each depend
89
+ * on the other and both collapse to `any` (TS7022) if both sides used `relation`/`hasMany`.
90
+ * `relationOf<R>` breaks the cycle — `target` is typed `() => any` (no inference is pulled from
91
+ * the argument), and the related RECORD shape is supplied explicitly via `<R>`. Declare a small
92
+ * interface for the record once, on whichever side of the pair you close second; the runtime
93
+ * behavior (lazy thunk, resolved on first query-time use) is identical to `relation`.
94
+ */
95
+ relationOf: (target, opts) => makeField('relation', { target, from: opts.from }),
96
+ /** `hasMany`'s escape hatch for a mutual pair — see `relationOf`. */
97
+ hasManyOf: (target, opts) => makeField('relation', { target, to: opts.to }),
98
+ };
99
+ function memoize(fn) {
100
+ let value;
101
+ let resolved = false;
102
+ return () => {
103
+ if (!resolved) {
104
+ value = fn();
105
+ resolved = true;
106
+ }
107
+ return value;
108
+ };
109
+ }
110
+ /**
111
+ * Compile a shape into the same `{ table, database, attributes, properties }` typeDef
112
+ * `resources/graphql.ts` builds, so registration takes the identical `table()` path.
113
+ *
114
+ * Nullability parity: GraphQL leaves plain fields' `nullable` undefined and marks `!` fields
115
+ * `nullable: false` (it never emits `nullable: true`). We map identically — `.nullable` leaves
116
+ * the attribute unmarked, everything else writable is `nullable: false` — so fragments AND
117
+ * validation semantics match. PK and server-managed fields stay unmarked (the PK machinery and
118
+ * server assignment own their presence; marking them required would reject valid writes).
119
+ *
120
+ * Relations resolve lazily: `definition` exists at registration (the resolver-build contract in
121
+ * Table.ts) but its `tableClass` getter defers the thunk to first use, by which time the target
122
+ * is defined — forward references and cycles are safe.
123
+ */
124
+ function compileTypeDef(name, shape, options) {
125
+ const attributes = [];
126
+ const properties = {};
127
+ const declared = new Set(Object.keys(shape));
128
+ for (const [attrName, f] of Object.entries(shape)) {
129
+ const meta = f.meta ?? {};
130
+ if (f.kind === 'relation') {
131
+ const resolveClass = memoize(meta.target);
132
+ // The definition mirrors what GraphQL's connectPropertyType attaches (the target
133
+ // typeDef): `type` and `attributes` feed OpenAPI's includeDefinitionInSchema (so the
134
+ // emitted $ref component is always defined), `tableClass` feeds the query-time
135
+ // resolvers. All lazy — read only after every table is defined.
136
+ const definition = {};
137
+ Object.defineProperty(definition, 'tableClass', { get: resolveClass, enumerable: true, configurable: true });
138
+ Object.defineProperty(definition, 'type', {
139
+ get: () => resolveClass().tableName,
140
+ enumerable: true,
141
+ configurable: true,
142
+ });
143
+ Object.defineProperty(definition, 'attributes', {
144
+ get: () => resolveClass().attributes,
145
+ enumerable: true,
146
+ configurable: true,
147
+ });
148
+ let attr;
149
+ if (meta.from) {
150
+ // many-to-one: this table holds the foreign key. Like GraphQL, the FK must be a
151
+ // declared field — that makes it typed, projected into `properties`, and writable
152
+ // in $insert/$upsert (the relation itself is a read-only projection).
153
+ if (!declared.has(meta.from)) {
154
+ throw new Error(`Table "${name}": relation "${attrName}" references foreign key "${meta.from}", which must be declared in the shape (e.g. \`${meta.from}: id.indexed\`)`);
155
+ }
156
+ attr = { name: attrName, relationship: { from: meta.from } };
157
+ Object.defineProperty(attr, 'type', {
158
+ get: () => resolveClass().tableName,
159
+ enumerable: true,
160
+ configurable: true,
161
+ });
162
+ // non-enumerable, mirroring GraphQL's connectPropertyType
163
+ Object.defineProperty(attr, 'definition', { value: definition, configurable: true });
164
+ }
165
+ else {
166
+ // one-to-many: the related table holds the foreign key
167
+ const elements = {};
168
+ Object.defineProperty(elements, 'type', {
169
+ get: () => resolveClass().tableName,
170
+ enumerable: true,
171
+ configurable: true,
172
+ });
173
+ Object.defineProperty(elements, 'definition', { value: definition, configurable: true });
174
+ attr = { name: attrName, type: 'array', relationship: { to: meta.to }, elements };
175
+ }
176
+ attributes.push(attr);
177
+ // fragment reads the lazy `type`, so project it lazily too (memoized — cold surface)
178
+ let fragment;
179
+ Object.defineProperty(properties, attrName, {
180
+ get: () => (fragment ??= (0, jsonSchemaTypes_ts_1.attributeToFragment)(attr)),
181
+ enumerable: true,
182
+ configurable: true,
183
+ });
184
+ continue;
185
+ }
186
+ const attr = { name: attrName, type: f.kind };
187
+ if (meta.primaryKey)
188
+ attr.isPrimaryKey = true;
189
+ if (meta.indexed)
190
+ attr.indexed = true;
191
+ if (meta.assignCreatedTime)
192
+ attr.assignCreatedTime = true;
193
+ if (meta.assignUpdatedTime)
194
+ attr.assignUpdatedTime = true;
195
+ if (meta.enum)
196
+ attr.enum = meta.enum; // enum -> String column, literal set retained for downstream surfaces
197
+ if (!meta.nullable && !meta.primaryKey && !meta.assignCreatedTime && !meta.assignUpdatedTime)
198
+ attr.nullable = false; // required, like GraphQL `!`
199
+ attributes.push(attr);
200
+ properties[attrName] = (0, jsonSchemaTypes_ts_1.attributeToFragment)(attr);
201
+ }
202
+ const { database, ...tableOptions } = options;
203
+ // `schemaDefined: true` matches @table so the existing-Table re-assert in `table()` fires on
204
+ // every reload (DDL parity: added/removed attributes and index changes are applied).
205
+ return {
206
+ table: name,
207
+ type: name,
208
+ database: database ?? 'data',
209
+ attributes,
210
+ properties,
211
+ schemaDefined: true,
212
+ ...tableOptions,
213
+ };
214
+ }
215
+ /**
216
+ * Declare AND register a table from a TypeScript value. Eager: the returned value is the live,
217
+ * registered table class (the same class `tables.<Name>` holds), typed by the shape — the import
218
+ * IS the typed handle. Defining the same table again re-asserts the schema (add/remove attributes,
219
+ * index changes) through the same evolution path GraphQL reloads take.
220
+ */
221
+ function defineTable(name, shape, options = {}) {
222
+ const typeDef = compileTypeDef(name, shape, options);
223
+ const tableClass = (0, databases_ts_1.table)(typeDef);
224
+ typeDef.tableClass = tableClass;
225
+ return tableClass;
226
+ }
227
+ //# sourceMappingURL=defineTable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defineTable.js","sourceRoot":"","sources":["../../resources/defineTable.ts"],"names":[],"mappings":"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"}
@@ -43,6 +43,8 @@ export interface AttributeLike {
43
43
  assignUpdatedTime?: boolean;
44
44
  nullable?: boolean;
45
45
  elements?: AttributeLike;
46
+ /** Sub-attributes of a nested object field (the same array form `Table.validate` iterates). */
47
+ properties?: AttributeLike[];
46
48
  }
47
49
  /**
48
50
  * Project a single attribute to its JSON Schema fragment. Recursive on the
@@ -35,10 +35,18 @@ exports.DATA_TYPES = {
35
35
  */
36
36
  function attributeToFragment(attr) {
37
37
  const fragment = {};
38
- // GraphQL parser emits list types as `prop.type === 'array'` with `.elements`
39
- // describing the inner type. Map that to JSON Schema's items shape; otherwise
40
- // fall through to the primitive mapping.
41
- if (attr.type === 'array' && attr.elements) {
38
+ // A nested object field carries its sub-attributes on `.properties` (the array form the parser
39
+ // emits and `Table.validate` iterates) recurse into them rather than emitting the bare GraphQL
40
+ // type name as a JSON-Schema `type`. GraphQL parser emits list types as `prop.type === 'array'`
41
+ // with `.elements`; map that to JSON Schema's items shape; otherwise fall through to the primitive
42
+ // mapping.
43
+ if (attr.properties) {
44
+ fragment.type = 'object';
45
+ fragment.properties = {};
46
+ for (const sub of attr.properties)
47
+ fragment.properties[sub.name] = attributeToFragment(sub);
48
+ }
49
+ else if (attr.type === 'array' && attr.elements) {
42
50
  fragment.type = 'array';
43
51
  fragment.items = attributeToFragment(attr.elements);
44
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"jsonSchemaTypes.js","sourceRoot":"","sources":["../../resources/jsonSchemaTypes.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AAyDH,kDAoBC;AAOD,sEAMC;AArEY,QAAA,UAAU,GAAmC;IACzD,GAAG,EAAE,SAAS;IACd,KAAK,EAAE,QAAQ;IACf,IAAI,EAAE,SAAS;IACf,EAAE,EAAE,QAAQ;IACZ,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,SAAS;IAClB,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,QAAQ;IACf,MAAM,EAAE,SAAS;CACjB,CAAC;AAmBF;;;;;;GAMG;AACH,SAAgB,mBAAmB,CAAC,IAAmB;IACtD,MAAM,QAAQ,GAAuB,EAAE,CAAC;IACxC,8EAA8E;IAC9E,8EAA8E;IAC9E,yCAAyC;IACzC,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC5C,QAAQ,CAAC,IAAI,GAAG,OAAO,CAAC;QACxB,QAAQ,CAAC,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrD,CAAC;SAAM,CAAC;QACP,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,kBAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC/D,IAAI,QAAQ;YAAE,QAAQ,CAAC,IAAI,GAAG,QAAQ,CAAC;aAClC,IAAI,IAAI,CAAC,IAAI;YAAE,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IAC/C,CAAC;IACD,IAAI,IAAI,CAAC,WAAW;QAAE,QAAQ,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;IAC9D,IAAI,IAAI,CAAC,YAAY;QAAE,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC;IAClD,IAAI,IAAI,CAAC,iBAAiB;QAAE,QAAQ,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAC9D,IAAI,IAAI,CAAC,iBAAiB;QAAE,QAAQ,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAC9D,IAAI,IAAI,CAAC,MAAM;QAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC;IACxC,IAAI,IAAI,CAAC,QAAQ;QAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC5C,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,SAAgB,6BAA6B,CAAC,UAA2B;IACxE,MAAM,MAAM,GAAuC,EAAE,CAAC;IACtD,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"jsonSchemaTypes.js","sourceRoot":"","sources":["../../resources/jsonSchemaTypes.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AA2DH,kDA0BC;AAOD,sEAMC;AA7EY,QAAA,UAAU,GAAmC;IACzD,GAAG,EAAE,SAAS;IACd,KAAK,EAAE,QAAQ;IACf,IAAI,EAAE,SAAS;IACf,EAAE,EAAE,QAAQ;IACZ,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,SAAS;IAClB,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,QAAQ;IACf,MAAM,EAAE,SAAS;CACjB,CAAC;AAqBF;;;;;;GAMG;AACH,SAAgB,mBAAmB,CAAC,IAAmB;IACtD,MAAM,QAAQ,GAAuB,EAAE,CAAC;IACxC,+FAA+F;IAC/F,iGAAiG;IACjG,gGAAgG;IAChG,mGAAmG;IACnG,WAAW;IACX,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACrB,QAAQ,CAAC,IAAI,GAAG,QAAQ,CAAC;QACzB,QAAQ,CAAC,UAAU,GAAG,EAAE,CAAC;QACzB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,UAAU;YAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC7F,CAAC;SAAM,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnD,QAAQ,CAAC,IAAI,GAAG,OAAO,CAAC;QACxB,QAAQ,CAAC,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrD,CAAC;SAAM,CAAC;QACP,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,kBAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC/D,IAAI,QAAQ;YAAE,QAAQ,CAAC,IAAI,GAAG,QAAQ,CAAC;aAClC,IAAI,IAAI,CAAC,IAAI;YAAE,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IAC/C,CAAC;IACD,IAAI,IAAI,CAAC,WAAW;QAAE,QAAQ,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;IAC9D,IAAI,IAAI,CAAC,YAAY;QAAE,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC;IAClD,IAAI,IAAI,CAAC,iBAAiB;QAAE,QAAQ,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAC9D,IAAI,IAAI,CAAC,iBAAiB;QAAE,QAAQ,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAC9D,IAAI,IAAI,CAAC,MAAM;QAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC;IACxC,IAAI,IAAI,CAAC,QAAQ;QAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC5C,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,SAAgB,6BAA6B,CAAC,UAA2B;IACxE,MAAM,MAAM,GAAuC,EAAE,CAAC;IACtD,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC"}
@@ -274,9 +274,19 @@ function generateJsonApi(resources, serverHttpURL) {
274
274
  });
275
275
  const tableDoc = entry.Resource.description;
276
276
  const withDoc = (sentence) => (tableDoc ? `${tableDoc} ${sentence}` : sentence);
277
- const responseSchema = { type: 'object' };
278
- // custom (non-table) resources have no generated schema component, so request bodies are loosely typed
277
+ const genericResponseSchema = { type: 'object' };
278
+ // custom (non-table) resources have no generated schema component, so request bodies default to
279
+ // loosely typed — UNLESS a request contract declares the query/body/response,
280
+ // in which case each verb is driven off the shared JsonSchemaFragment below.
279
281
  const genericRequestBody = { content: { 'application/json': { schema: { type: 'object' } } } };
282
+ const inputSchemas = entry.Resource.inputSchemas ?? {};
283
+ const outputSchemas = entry.Resource.outputSchemas ?? {};
284
+ // query params declared on the verb's contract, appended to the path params
285
+ const paramsFor = (verb) => [...pathParams, ...queryParametersFromFragment(inputSchemas[verb]?.query)];
286
+ const bodyFor = (verb) => inputSchemas[verb]?.body
287
+ ? { content: { 'application/json': { schema: fragmentToOpenApiSchema(inputSchemas[verb].body) } } }
288
+ : genericRequestBody;
289
+ const responseFor = (verb) => outputSchemas[verb] ? fragmentToOpenApiSchema(outputSchemas[verb]) : genericResponseSchema;
280
290
  const hasPost = prototype.post !== Resource_ts_1.Resource.prototype.post || prototype.update;
281
291
  const hasPut = typeof prototype.put === 'function';
282
292
  const hasGet = typeof prototype.get === 'function';
@@ -286,37 +296,37 @@ function generateJsonApi(resources, serverHttpURL) {
286
296
  api.paths[url] = {};
287
297
  api.paths[url].options = new Options(pathParams, security, { '200': new ResponseOptions200() }, 'retrieve information about the communication options available for a target resource or the server as a whole, without performing any resource action');
288
298
  if (hasGet) {
289
- api.paths[url].get = new Get(pathParams, security, { '200': new Response200(responseSchema) }, withDoc('handle a GET request for this route'));
299
+ api.paths[url].get = new Get(paramsFor('get'), security, { '200': new Response200(responseFor('get')) }, withDoc('handle a GET request for this route'));
290
300
  }
291
301
  if (hasPost) {
292
302
  api.paths[url].post = {
293
303
  description: withDoc('handle a POST request for this route'),
294
- parameters: pathParams,
304
+ parameters: paramsFor('post'),
295
305
  security,
296
- requestBody: genericRequestBody,
297
- responses: { '200': new Response200(responseSchema) },
306
+ requestBody: bodyFor('post'),
307
+ responses: { '200': new Response200(responseFor('post')) },
298
308
  };
299
309
  }
300
310
  if (hasPut) {
301
311
  api.paths[url].put = {
302
312
  description: withDoc('handle a PUT request for this route'),
303
- parameters: pathParams,
313
+ parameters: paramsFor('put'),
304
314
  security,
305
- requestBody: genericRequestBody,
306
- responses: { '200': new Response200(responseSchema) },
315
+ requestBody: bodyFor('put'),
316
+ responses: { '200': new Response200(responseFor('put')) },
307
317
  };
308
318
  }
309
319
  if (hasPatch) {
310
320
  api.paths[url].patch = {
311
321
  description: withDoc('handle a PATCH request for this route'),
312
- parameters: pathParams,
322
+ parameters: paramsFor('patch'),
313
323
  security,
314
- requestBody: genericRequestBody,
315
- responses: { '200': new Response200(responseSchema) },
324
+ requestBody: bodyFor('patch'),
325
+ responses: { '200': new Response200(responseFor('patch')) },
316
326
  };
317
327
  }
318
328
  if (hasDelete) {
319
- api.paths[url].delete = new Delete(pathParams, security, withDoc('handle a DELETE request for this route'), {
329
+ api.paths[url].delete = new Delete(paramsFor('delete'), security, withDoc('handle a DELETE request for this route'), {
320
330
  '204': new Response204(),
321
331
  });
322
332
  }
@@ -436,4 +446,50 @@ function Parameter(name, i, type) {
436
446
  this.in = i;
437
447
  this.schema = type;
438
448
  }
449
+ /**
450
+ * Project a shared `JsonSchemaFragment` (from a request contract's inputSchemas/outputSchemas) into an
451
+ * OpenAPI schema object. The fragment vocabulary already mirrors JSON Schema, so this mostly copies the
452
+ * standard keys recursively and drops Harper-only metadata (primaryKey, assignCreatedTime, hidden).
453
+ */
454
+ function fragmentToOpenApiSchema(fragment) {
455
+ if (!fragment || typeof fragment !== 'object')
456
+ return { type: 'object' };
457
+ const schema = {};
458
+ if (fragment.type != null)
459
+ schema.type = fragment.type;
460
+ if (fragment.format)
461
+ schema.format = fragment.format;
462
+ if (fragment.description)
463
+ schema.description = fragment.description;
464
+ if (fragment.enum)
465
+ schema.enum = fragment.enum;
466
+ if (fragment.nullable)
467
+ schema.nullable = true;
468
+ if (fragment.const !== undefined)
469
+ schema.const = fragment.const;
470
+ if (fragment.items)
471
+ schema.items = fragmentToOpenApiSchema(fragment.items);
472
+ if (fragment.properties) {
473
+ schema.properties = {};
474
+ for (const [key, sub] of Object.entries(fragment.properties))
475
+ schema.properties[key] = fragmentToOpenApiSchema(sub);
476
+ if (fragment.required)
477
+ schema.required = fragment.required;
478
+ if (fragment.additionalProperties !== undefined)
479
+ schema.additionalProperties = fragment.additionalProperties;
480
+ }
481
+ return schema;
482
+ }
483
+ /** Build OpenAPI query `Parameter`s from a contract verb's query fragment (`{ type:'object', properties }`). */
484
+ function queryParametersFromFragment(queryFragment) {
485
+ if (!queryFragment?.properties)
486
+ return [];
487
+ const required = new Set(queryFragment.required ?? []);
488
+ return Object.entries(queryFragment.properties).map(([name, sub]) => {
489
+ const parameter = new Parameter(name, 'query', fragmentToOpenApiSchema(sub));
490
+ if (required.has(name))
491
+ parameter.required = true;
492
+ return parameter;
493
+ });
494
+ }
439
495
  //# sourceMappingURL=openApi.js.map