@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.
- package/agent/agent.ts +152 -10
- package/agent/bestPractices.ts +58 -0
- package/agent/mcpTools.ts +122 -0
- package/agent/operations.ts +10 -1
- package/agent/registryTools.ts +117 -0
- package/agent/session.ts +16 -7
- package/agent/tools/fsTools.ts +74 -27
- package/agent/tools/inspectorTool.ts +459 -0
- package/agent/toolset.ts +27 -7
- package/agent/types.ts +2 -0
- package/components/Application.ts +566 -85
- package/components/OptionsWatcher.ts +62 -2
- package/components/Scope.ts +31 -8
- package/components/componentLoader.ts +5 -1
- package/components/componentSecrets.ts +514 -55
- package/components/deploymentRecorder.ts +7 -6
- package/components/gitCredentialHelper.js +115 -0
- package/components/gitCredentialServer.ts +251 -0
- package/components/mcp/tools/application.ts +197 -23
- package/components/mcp/tools/operations.ts +1 -1
- package/components/mcp/tools/schemas/derive.ts +6 -2
- package/components/operations.js +21 -19
- package/components/operationsValidation.js +86 -21
- package/components/secretOperations.ts +110 -30
- package/config/harperConfigEnvVars.ts +90 -5
- package/config-root.schema.json +4 -0
- package/dist/agent/agent.d.ts +24 -3
- package/dist/agent/agent.js +172 -10
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/bestPractices.d.ts +24 -0
- package/dist/agent/bestPractices.js +60 -0
- package/dist/agent/bestPractices.js.map +1 -0
- package/dist/agent/mcpTools.d.ts +24 -0
- package/dist/agent/mcpTools.js +115 -0
- package/dist/agent/mcpTools.js.map +1 -0
- package/dist/agent/operations.js +10 -1
- package/dist/agent/operations.js.map +1 -1
- package/dist/agent/registryTools.d.ts +45 -0
- package/dist/agent/registryTools.js +113 -0
- package/dist/agent/registryTools.js.map +1 -0
- package/dist/agent/session.js +16 -7
- package/dist/agent/session.js.map +1 -1
- package/dist/agent/tools/fsTools.js +70 -28
- package/dist/agent/tools/fsTools.js.map +1 -1
- package/dist/agent/tools/inspectorTool.d.ts +40 -0
- package/dist/agent/tools/inspectorTool.js +428 -0
- package/dist/agent/tools/inspectorTool.js.map +1 -0
- package/dist/agent/toolset.d.ts +16 -6
- package/dist/agent/toolset.js +17 -7
- package/dist/agent/toolset.js.map +1 -1
- package/dist/agent/types.d.ts +2 -0
- package/dist/components/Application.d.ts +73 -18
- package/dist/components/Application.js +475 -64
- package/dist/components/Application.js.map +1 -1
- package/dist/components/OptionsWatcher.d.ts +1 -1
- package/dist/components/OptionsWatcher.js +65 -2
- package/dist/components/OptionsWatcher.js.map +1 -1
- package/dist/components/Scope.d.ts +8 -6
- package/dist/components/Scope.js +22 -6
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/componentLoader.js +5 -1
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/componentSecrets.d.ts +33 -7
- package/dist/components/componentSecrets.js +473 -59
- package/dist/components/componentSecrets.js.map +1 -1
- package/dist/components/deploymentRecorder.d.ts +2 -2
- package/dist/components/deploymentRecorder.js +1 -1
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/gitCredentialHelper.d.ts +1 -0
- package/dist/components/gitCredentialHelper.js +113 -0
- package/dist/components/gitCredentialHelper.js.map +1 -0
- package/dist/components/gitCredentialServer.d.ts +33 -0
- package/dist/components/gitCredentialServer.js +236 -0
- package/dist/components/gitCredentialServer.js.map +1 -0
- package/dist/components/mcp/tools/application.d.ts +19 -0
- package/dist/components/mcp/tools/application.js +180 -22
- package/dist/components/mcp/tools/application.js.map +1 -1
- package/dist/components/mcp/tools/operations.d.ts +13 -0
- package/dist/components/mcp/tools/operations.js +1 -0
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/mcp/tools/schemas/derive.js +6 -2
- package/dist/components/mcp/tools/schemas/derive.js.map +1 -1
- package/dist/components/operations.js +23 -21
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.js +84 -21
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/secretOperations.d.ts +33 -11
- package/dist/components/secretOperations.js +90 -26
- package/dist/components/secretOperations.js.map +1 -1
- package/dist/config/harperConfigEnvVars.d.ts +21 -0
- package/dist/config/harperConfigEnvVars.js +95 -5
- package/dist/config/harperConfigEnvVars.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/resources/Resource.d.ts +19 -0
- package/dist/resources/Resource.js +93 -2
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/Table.d.ts +9 -0
- package/dist/resources/Table.js +256 -50
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/read.js +28 -25
- package/dist/resources/analytics/read.js.map +1 -1
- package/dist/resources/defineResource.d.ts +180 -0
- package/dist/resources/defineResource.js +505 -0
- package/dist/resources/defineResource.js.map +1 -0
- package/dist/resources/defineTable.d.ts +221 -0
- package/dist/resources/defineTable.js +227 -0
- package/dist/resources/defineTable.js.map +1 -0
- package/dist/resources/jsonSchemaTypes.d.ts +2 -0
- package/dist/resources/jsonSchemaTypes.js +12 -4
- package/dist/resources/jsonSchemaTypes.js.map +1 -1
- package/dist/resources/openApi.js +69 -13
- package/dist/resources/openApi.js.map +1 -1
- package/dist/resources/search.js +6 -8
- package/dist/resources/search.js.map +1 -1
- package/dist/server/graphqlQuerying.js +4 -2
- package/dist/server/graphqlQuerying.js.map +1 -1
- package/dist/server/http.d.ts +12 -0
- package/dist/server/http.js +35 -15
- package/dist/server/http.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.js +8 -5
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.js +23 -0
- package/dist/server/serverHelpers/uwsServer.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +21 -0
- package/dist/utility/errors/hdbError.js +23 -1
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/validation/configValidator.js +22 -6
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +33 -0
- package/npm-shrinkwrap.json +9197 -15236
- package/package.json +5 -3
- package/resources/DESIGN.md +42 -15
- package/resources/Resource.ts +99 -2
- package/resources/Table.ts +275 -71
- package/resources/analytics/read.ts +30 -25
- package/resources/defineResource.ts +651 -0
- package/resources/defineTable.ts +407 -0
- package/resources/jsonSchemaTypes.ts +12 -4
- package/resources/openApi.ts +68 -16
- package/resources/search.ts +5 -8
- package/server/graphqlQuerying.ts +4 -2
- package/server/http.ts +40 -16
- package/server/serverHelpers/serverUtilities.ts +19 -6
- package/server/serverHelpers/uwsServer.ts +24 -0
- package/studio/web/assets/{Chat-D4FIaBu7.js → Chat-CTjtL8Z4.js} +2 -2
- package/studio/web/assets/{Chat-D4FIaBu7.js.map → Chat-CTjtL8Z4.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-DGUC3L4r.js → FloatingChat-CafHR4Ur.js} +4 -4
- package/studio/web/assets/{FloatingChat-DGUC3L4r.js.map → FloatingChat-CafHR4Ur.js.map} +1 -1
- package/studio/web/assets/{applications-D8Am9ikM.js → applications-Buh_q0Vj.js} +2 -2
- package/studio/web/assets/{applications-D8Am9ikM.js.map → applications-Buh_q0Vj.js.map} +1 -1
- package/studio/web/assets/{index-HGL4WHdb.js → index-0hXeECkS.js} +6 -6
- package/studio/web/assets/{index-HGL4WHdb.js.map → index-0hXeECkS.js.map} +1 -1
- package/studio/web/assets/{index.lazy-Dk5j1AoQ.js → index.lazy-B00B7VBT.js} +4 -4
- package/studio/web/assets/{index.lazy-Dk5j1AoQ.js.map → index.lazy-B00B7VBT.js.map} +1 -1
- package/studio/web/assets/{profile-7bu_CF1f.js → profile-Cg2wwYPn.js} +2 -2
- package/studio/web/assets/{profile-7bu_CF1f.js.map → profile-Cg2wwYPn.js.map} +1 -1
- package/studio/web/assets/{setComponentFile-MfYm9F94.js → setComponentFile-DCaDIvyB.js} +2 -2
- package/studio/web/assets/{setComponentFile-MfYm9F94.js.map → setComponentFile-DCaDIvyB.js.map} +1 -1
- package/studio/web/assets/{setup-SKA4UhdE.js → setup-CAVcAQjK.js} +2 -2
- package/studio/web/assets/{setup-SKA4UhdE.js.map → setup-CAVcAQjK.js.map} +1 -1
- package/studio/web/assets/{status-DetVLUxR.js → status-BRXorNdD.js} +2 -2
- package/studio/web/assets/{status-DetVLUxR.js.map → status-BRXorNdD.js.map} +1 -1
- package/studio/web/assets/{swagger-ui-react-DLeYARY2.js → swagger-ui-react-Dy1D62vO.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-DLeYARY2.js.map → swagger-ui-react-Dy1D62vO.js.map} +1 -1
- package/studio/web/assets/{tsMode-DGO-jIF7.js → tsMode-A8gbL74v.js} +2 -2
- package/studio/web/assets/{tsMode-DGO-jIF7.js.map → tsMode-A8gbL74v.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-4M2i6bo4.js → useEntityRestURL-cDodrVcQ.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-4M2i6bo4.js.map → useEntityRestURL-cDodrVcQ.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/utility/errors/hdbError.ts +32 -0
- 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
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
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;;;
|
|
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
|
|
278
|
-
// custom (non-table) resources have no generated schema component, so request bodies
|
|
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(
|
|
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:
|
|
304
|
+
parameters: paramsFor('post'),
|
|
295
305
|
security,
|
|
296
|
-
requestBody:
|
|
297
|
-
responses: { '200': new Response200(
|
|
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:
|
|
313
|
+
parameters: paramsFor('put'),
|
|
304
314
|
security,
|
|
305
|
-
requestBody:
|
|
306
|
-
responses: { '200': new Response200(
|
|
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:
|
|
322
|
+
parameters: paramsFor('patch'),
|
|
313
323
|
security,
|
|
314
|
-
requestBody:
|
|
315
|
-
responses: { '200': new Response200(
|
|
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(
|
|
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
|