@smartlyio/oats-runtime 4.4.0 → 4.4.1-alpha.18
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/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/make.d.ts +5 -3
- package/dist/make.js +65 -7
- package/dist/make.js.map +1 -1
- package/dist/reflection-type.d.ts +1 -0
- package/dist/reflection-type.js.map +1 -1
- package/dist/runtime.d.ts +6 -0
- package/dist/runtime.js +19 -1
- package/dist/runtime.js.map +1 -1
- package/dist/serialize.d.ts +1 -0
- package/dist/serialize.js +41 -0
- package/dist/serialize.js.map +1 -0
- package/dist/server.d.ts +9 -1
- package/dist/server.js +36 -10
- package/dist/server.js.map +1 -1
- package/dist/type-tag.d.ts +7 -0
- package/dist/type-tag.js +64 -0
- package/dist/type-tag.js.map +1 -0
- package/jest.config.js +38 -0
- package/package.json +2 -2
- package/src/client.ts +2 -1
- package/src/make.ts +79 -8
- package/src/reflection-type.ts +1 -0
- package/src/runtime.ts +21 -0
- package/src/serialize.ts +36 -0
- package/src/server.ts +71 -14
- package/src/type-tag.ts +63 -0
package/src/server.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { assert } from './assert';
|
|
2
2
|
import safeNavigation from '@smartlyio/safe-navigation';
|
|
3
3
|
import { Make, MakeOptions, Maker, ValidationError, validationErrorPrinter } from './make';
|
|
4
|
+
import { serialize } from './serialize';
|
|
4
5
|
|
|
5
6
|
export type { RedirectStatus } from './redirect';
|
|
6
7
|
|
|
@@ -128,17 +129,34 @@ function voidify(value: object | undefined | null) {
|
|
|
128
129
|
return null;
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
function cleanHeaders<H>(maker: Maker<any, H>, headers: object) {
|
|
132
|
-
|
|
132
|
+
function cleanHeaders<H>(mode: Mode, maker: Maker<any, H>, headers: object) {
|
|
133
|
+
// note: we now expect that on client side headers are type conforming so do not need to lowercase those
|
|
134
|
+
// this is slightly breaking change is somebody has subverted the type checker
|
|
135
|
+
const normalized = voidify(mode === 'server' ? lowercaseObject(headers) : headers);
|
|
133
136
|
const acceptsNull = maker(null);
|
|
134
137
|
if (acceptsNull.isSuccess()) {
|
|
135
138
|
return acceptsNull.success();
|
|
136
139
|
}
|
|
137
140
|
return maker(normalized, {
|
|
138
|
-
unknownField: 'drop'
|
|
141
|
+
unknownField: 'drop',
|
|
142
|
+
convertFromNetwork: mode === 'server'
|
|
139
143
|
}).success(throwRequestValidationError.bind(null, 'headers'));
|
|
140
144
|
}
|
|
141
145
|
|
|
146
|
+
function serializeWhenClient(mode: Mode, value: any) {
|
|
147
|
+
if (mode === 'client') {
|
|
148
|
+
return serialize(value);
|
|
149
|
+
}
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function getOutBody<Body extends RequestBody<any>>(mode: Mode, value: Body): Body {
|
|
154
|
+
if (!value) {
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
return { contentType: value.contentType, value: serializeWhenClient(mode, value.value) } as Body;
|
|
158
|
+
}
|
|
159
|
+
|
|
142
160
|
export function safe<
|
|
143
161
|
H extends Headers,
|
|
144
162
|
P extends Params,
|
|
@@ -153,7 +171,7 @@ export function safe<
|
|
|
153
171
|
body: Maker<any, Body>,
|
|
154
172
|
response: Maker<any, R>,
|
|
155
173
|
endpoint: Endpoint<H, P, Q, Body, R, RC>,
|
|
156
|
-
{ validationOptions = {} }: HandlerOptions = {}
|
|
174
|
+
{ validationOptions = {}, mode }: HandlerOptions & InternalHandlerOptions = { mode: 'client' }
|
|
157
175
|
): Endpoint<
|
|
158
176
|
Headers,
|
|
159
177
|
Params,
|
|
@@ -163,24 +181,51 @@ export function safe<
|
|
|
163
181
|
RequestContext
|
|
164
182
|
> {
|
|
165
183
|
return async ctx => {
|
|
184
|
+
// note: data coming to client adapter needs to be serialized and data coming from the adapter needs conversion from network
|
|
185
|
+
// note: data coming to server adapter needs to be deserialized and data coming from the adapter needs serialization
|
|
186
|
+
// this is all very clear.
|
|
187
|
+
const internalMakeOptions = { convertFromNetwork: mode === 'server' };
|
|
166
188
|
const result = await endpoint({
|
|
167
189
|
path: ctx.path,
|
|
168
190
|
method: ctx.method,
|
|
169
191
|
servers: ctx.servers,
|
|
170
192
|
op: ctx.op,
|
|
171
|
-
headers: cleanHeaders(headers, ctx.headers),
|
|
172
|
-
|
|
173
|
-
|
|
193
|
+
headers: serializeWhenClient(mode, cleanHeaders(mode, headers, ctx.headers)),
|
|
194
|
+
// note: path params come to the adapter in network format always as those are gleaned from the path definition
|
|
195
|
+
params: params(voidify(ctx.params), {
|
|
196
|
+
...validationOptions.params,
|
|
197
|
+
...internalMakeOptions,
|
|
198
|
+
convertFromNetwork: true
|
|
199
|
+
}).success(throwRequestValidationError.bind(null, 'params')),
|
|
200
|
+
query: serializeWhenClient(
|
|
201
|
+
mode,
|
|
202
|
+
query(ctx.query || {}, { ...validationOptions.query, ...internalMakeOptions }).success(
|
|
203
|
+
throwRequestValidationError.bind(null, 'query')
|
|
204
|
+
)
|
|
174
205
|
),
|
|
175
|
-
|
|
176
|
-
|
|
206
|
+
body: getOutBody<Body>(
|
|
207
|
+
mode,
|
|
208
|
+
body(voidify(ctx.body), internalMakeOptions).success(
|
|
209
|
+
throwRequestValidationError.bind(null, 'body')
|
|
210
|
+
)
|
|
177
211
|
),
|
|
178
|
-
body: body(voidify(ctx.body)).success(throwRequestValidationError.bind(null, 'body')),
|
|
179
212
|
requestContext: ctx.requestContext as any
|
|
180
213
|
});
|
|
181
|
-
|
|
214
|
+
const responseValue = response(result, { convertFromNetwork: mode === 'client' }).success(
|
|
182
215
|
throwResponseValidationError.bind(null, `body ${ctx.path}`, result.value.value)
|
|
183
216
|
);
|
|
217
|
+
if (mode === 'client' || !responseValue) {
|
|
218
|
+
return responseValue;
|
|
219
|
+
}
|
|
220
|
+
// the response must be serialized for transferring from server to client
|
|
221
|
+
return {
|
|
222
|
+
...responseValue,
|
|
223
|
+
headers: serialize(responseValue.headers),
|
|
224
|
+
value: {
|
|
225
|
+
...responseValue.value,
|
|
226
|
+
value: serialize(responseValue.value.value)
|
|
227
|
+
}
|
|
228
|
+
};
|
|
184
229
|
};
|
|
185
230
|
}
|
|
186
231
|
|
|
@@ -202,7 +247,7 @@ interface CheckingTree {
|
|
|
202
247
|
[method: string]: {
|
|
203
248
|
safeHandler: (
|
|
204
249
|
e: Endpoint<any, any, any, any, any, any>,
|
|
205
|
-
opts?: HandlerOptions
|
|
250
|
+
opts?: HandlerOptions & InternalHandlerOptions
|
|
206
251
|
) => SafeEndpoint;
|
|
207
252
|
op: string;
|
|
208
253
|
servers: string[];
|
|
@@ -228,7 +273,10 @@ function createTree(handlers: Handler[]): CheckingTree {
|
|
|
228
273
|
memo[element.path] = {};
|
|
229
274
|
}
|
|
230
275
|
memo[element.path][element.method] = {
|
|
231
|
-
safeHandler: (
|
|
276
|
+
safeHandler: (
|
|
277
|
+
e: Endpoint<any, any, any, any, any, any>,
|
|
278
|
+
opts?: HandlerOptions & InternalHandlerOptions
|
|
279
|
+
) =>
|
|
232
280
|
safe(
|
|
233
281
|
element.headers,
|
|
234
282
|
element.params,
|
|
@@ -260,6 +308,15 @@ export type ServerAdapter = (
|
|
|
260
308
|
servers: string[]
|
|
261
309
|
) => void;
|
|
262
310
|
|
|
311
|
+
type Mode = 'client' | 'server';
|
|
312
|
+
export interface InternalHandlerOptions {
|
|
313
|
+
/** whether we are calling this on client side or in server side.
|
|
314
|
+
* This may have effect on eg network <-> ts property mapping.
|
|
315
|
+
* This value is set automatically by oats.
|
|
316
|
+
* */
|
|
317
|
+
mode: Mode;
|
|
318
|
+
}
|
|
319
|
+
|
|
263
320
|
export interface HandlerOptions {
|
|
264
321
|
/**
|
|
265
322
|
* Options for request schema validation.
|
|
@@ -287,7 +344,7 @@ export function createHandlerFactory<Spec>(
|
|
|
287
344
|
path,
|
|
288
345
|
endpointWrapper.op,
|
|
289
346
|
assertMethod(method),
|
|
290
|
-
endpointWrapper.safeHandler(methodHandler, opts),
|
|
347
|
+
endpointWrapper.safeHandler(methodHandler, { ...opts, mode: 'server' }),
|
|
291
348
|
endpointWrapper.servers
|
|
292
349
|
);
|
|
293
350
|
});
|
package/src/type-tag.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Type } from './reflection-type';
|
|
2
|
+
import { ValueClass } from './value-class';
|
|
3
|
+
|
|
4
|
+
export function withType<A>(to: A, type: Type[]): A {
|
|
5
|
+
if (type.length === 0) {
|
|
6
|
+
return to;
|
|
7
|
+
}
|
|
8
|
+
// todo: is it possible to confuse typing by re-use of valueclassed values
|
|
9
|
+
// eg. with value 'v: SomeValueClass' passing it to multiple makers
|
|
10
|
+
// -> no? the re-use happens only if the valueclass instance matches so it
|
|
11
|
+
// will not mutate the tagged reflection types
|
|
12
|
+
const previous = getTypeSet(to);
|
|
13
|
+
if (previous) {
|
|
14
|
+
type.forEach(type => previous.add(type));
|
|
15
|
+
return to;
|
|
16
|
+
}
|
|
17
|
+
const newType = new Set(type);
|
|
18
|
+
// tag each constructed object with a hidden type property
|
|
19
|
+
Object.defineProperty(to, reflection, {
|
|
20
|
+
enumerable: false,
|
|
21
|
+
value: newType
|
|
22
|
+
});
|
|
23
|
+
return to;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getTypeSet(value: Record<string, any>): Set<Type> | undefined {
|
|
27
|
+
if (!value || typeof value !== 'object') {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// NOTE: prefer using the added reflection type instead of the contstructor type
|
|
32
|
+
// the added reflection type will have types from eg allOf
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
const t: Set<Type> = value[reflection];
|
|
35
|
+
if (t && t.size > 0) {
|
|
36
|
+
return t;
|
|
37
|
+
}
|
|
38
|
+
if (value instanceof ValueClass) {
|
|
39
|
+
// a bit of leap here to trust that all ValueClasses have generated `reflection`
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
const classType = new Set([value.constructor.reflection().definition]);
|
|
42
|
+
// tag each constructed object with a hidden type property
|
|
43
|
+
// this ensures that ValueClasses have a mutable reflection type property
|
|
44
|
+
Object.defineProperty(value, reflection, {
|
|
45
|
+
enumerable: false,
|
|
46
|
+
value: classType
|
|
47
|
+
});
|
|
48
|
+
return classType;
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Get reflection type from a made value for serialization.
|
|
54
|
+
* Note that only directly made object values or ValueClasses can be used
|
|
55
|
+
* */
|
|
56
|
+
export function getType(value: Record<string, any>): Type[] | undefined {
|
|
57
|
+
const t = getTypeSet(value);
|
|
58
|
+
if (t) {
|
|
59
|
+
return [...t];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const reflection = Symbol('reflection');
|