ts-ag 0.0.1-dev.1
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/README.md +6 -0
- package/dist/browser.d.ts +2 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/lambda/browser.d.ts +4 -0
- package/dist/lambda/browser.d.ts.map +1 -0
- package/dist/lambda/browser.js +3 -0
- package/dist/lambda/client-types.d.ts +74 -0
- package/dist/lambda/client-types.d.ts.map +1 -0
- package/dist/lambda/client-types.js +1 -0
- package/dist/lambda/client.d.ts +26 -0
- package/dist/lambda/client.d.ts.map +1 -0
- package/dist/lambda/client.js +53 -0
- package/dist/lambda/deserializer.d.ts +3 -0
- package/dist/lambda/deserializer.d.ts.map +1 -0
- package/dist/lambda/deserializer.js +11 -0
- package/dist/lambda/handlerUtils.d.ts +31 -0
- package/dist/lambda/handlerUtils.d.ts.map +1 -0
- package/dist/lambda/handlerUtils.js +24 -0
- package/dist/lambda/index.d.ts +2 -0
- package/dist/lambda/index.d.ts.map +1 -0
- package/dist/lambda/index.js +1 -0
- package/dist/lambda/serializer.d.ts +7 -0
- package/dist/lambda/serializer.d.ts.map +1 -0
- package/dist/lambda/serializer.js +15 -0
- package/dist/ts-alias.d.ts +3 -0
- package/dist/ts-alias.d.ts.map +1 -0
- package/dist/ts-alias.js +167 -0
- package/package.json +59 -0
- package/src/browser.ts +1 -0
- package/src/index.ts +1 -0
- package/src/lambda/browser.ts +3 -0
- package/src/lambda/client-types.ts +63 -0
- package/src/lambda/client.ts +98 -0
- package/src/lambda/deserializer.ts +11 -0
- package/src/lambda/handlerUtils.ts +47 -0
- package/src/lambda/index.ts +1 -0
- package/src/lambda/serializer.ts +14 -0
- package/src/ts-alias.ts +194 -0
package/README.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
### Typescript
|
|
2
|
+
|
|
3
|
+
Although shadcn exists here in the repo it is not shipped. This is simply for tooling and nothing else, as such in the
|
|
4
|
+
`svelte.config.js` the aliases are added just for typescript, so that they are not resolved by the packaging process.
|
|
5
|
+
|
|
6
|
+
This allows consumers to bring their own shadcn components just so long as they resolve `$shadcn`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC"}
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lambda/browser.js';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lambda/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/lambda/browser.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a RawApiGatewayHandler response type to a fetch like response type.
|
|
3
|
+
*/
|
|
4
|
+
type ConvertToFetch<T> = T extends {
|
|
5
|
+
statusCode: number;
|
|
6
|
+
body: object;
|
|
7
|
+
headers: object;
|
|
8
|
+
} ? {
|
|
9
|
+
ok: T['statusCode'] extends 200 | 201 | 204 ? true : false;
|
|
10
|
+
json: () => Promise<T['body']>;
|
|
11
|
+
status: T['statusCode'];
|
|
12
|
+
} : T;
|
|
13
|
+
export type CleanResponse = Omit<Response, 'status' | 'ok' | 'json'>;
|
|
14
|
+
export type FetchResponse<T extends (...args: any) => any> = ConvertToFetch<Awaited<ReturnType<T>>> & CleanResponse;
|
|
15
|
+
/**
|
|
16
|
+
* Extracts the requestInput type from an API endpoint definition
|
|
17
|
+
* @template E - Union type of all API endpoints
|
|
18
|
+
* @template P - Path string literal type (e.g. 'payments/account')
|
|
19
|
+
* @template M - HTTP method string literal type (e.g. 'GET', 'POST')
|
|
20
|
+
*/
|
|
21
|
+
export type ApiInput<E extends {
|
|
22
|
+
path: string;
|
|
23
|
+
method: string;
|
|
24
|
+
requestInput: any;
|
|
25
|
+
}, P extends E['path'], M extends E['method']> = Extract<E, {
|
|
26
|
+
path: P;
|
|
27
|
+
method: M;
|
|
28
|
+
}>['requestInput'];
|
|
29
|
+
/**
|
|
30
|
+
* Extracts the requestOutput type from an API endpoint definition
|
|
31
|
+
* @template E - Union type of all API endpoints
|
|
32
|
+
* @template P - Path string literal type (e.g. 'payments/account')
|
|
33
|
+
* @template M - HTTP method string literal type (e.g. 'GET', 'POST')
|
|
34
|
+
*/
|
|
35
|
+
export type ApiOutput<E extends {
|
|
36
|
+
path: string;
|
|
37
|
+
method: string;
|
|
38
|
+
requestOutput: any;
|
|
39
|
+
}, P extends E['path'], M extends E['method']> = Extract<E, {
|
|
40
|
+
path: P;
|
|
41
|
+
method: M;
|
|
42
|
+
}>['requestOutput'];
|
|
43
|
+
/**
|
|
44
|
+
* Extracts the response type from an API endpoint definition
|
|
45
|
+
* @template E - Union type of all API endpoints
|
|
46
|
+
* @template P - Path string literal type (e.g. 'payments/account')
|
|
47
|
+
* @template M - HTTP method string literal type (e.g. 'GET', 'POST')
|
|
48
|
+
*/
|
|
49
|
+
export type ApiResponse<E extends {
|
|
50
|
+
path: string;
|
|
51
|
+
method: string;
|
|
52
|
+
response: any;
|
|
53
|
+
}, P extends E['path'], M extends E['method']> = Extract<E, {
|
|
54
|
+
path: P;
|
|
55
|
+
method: M;
|
|
56
|
+
}>['response'];
|
|
57
|
+
/**
|
|
58
|
+
* Extracts the sucessful body type from an API endpoint definition
|
|
59
|
+
* @template E - Union type of all API endpoints
|
|
60
|
+
* @template P - Path string literal type (e.g. 'payments/account')
|
|
61
|
+
* @template M - HTTP method string literal type (e.g. 'GET', 'POST')
|
|
62
|
+
*/
|
|
63
|
+
export type ApiSuccessBody<E extends {
|
|
64
|
+
path: string;
|
|
65
|
+
method: string;
|
|
66
|
+
response: any;
|
|
67
|
+
}, P extends E['path'], M extends E['method']> = Extract<Extract<E, {
|
|
68
|
+
path: P;
|
|
69
|
+
method: M;
|
|
70
|
+
}>['response'], {
|
|
71
|
+
status: 200;
|
|
72
|
+
}>['json'] extends () => Promise<infer R> ? R : unknown;
|
|
73
|
+
export {};
|
|
74
|
+
//# sourceMappingURL=client-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-types.d.ts","sourceRoot":"","sources":["../../src/lambda/client-types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,KAAK,cAAc,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACpF;IACE,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,SAAS,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,KAAK,CAAC;IAC3D,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/B,MAAM,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC;CACzB,GACD,CAAC,CAAC;AAEN,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,GAAG,MAAM,CAAC,CAAC;AACrE,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC;AAEpH;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,CAClB,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,GAAG,CAAA;CAAE,EAC7D,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EACnB,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,IACnB,OAAO,CAAC,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,CAAC,cAAc,CAAC,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,MAAM,SAAS,CACnB,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,GAAG,CAAA;CAAE,EAC9D,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EACnB,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,IACnB,OAAO,CAAC,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,CAAC,eAAe,CAAC,CAAC;AAExD;;;;;GAKG;AACH,MAAM,MAAM,WAAW,CACrB,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,GAAG,CAAA;CAAE,EACzD,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EACnB,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,IACnB,OAAO,CAAC,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,CAAC,UAAU,CAAC,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,MAAM,cAAc,CACxB,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,GAAG,CAAA;CAAE,EACzD,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EACnB,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,IACnB,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,CAAC,UAAU,CAAC,EAAE;IAAE,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC,CAAC,MAAM,CAAC,SAAS,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,GAC/G,CAAC,GACD,OAAO,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as v from 'valibot';
|
|
2
|
+
import type { ApiInput, ApiResponse } from './client-types.js';
|
|
3
|
+
declare const HTTPMethods: readonly ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"];
|
|
4
|
+
type HTTPMethod = (typeof HTTPMethods)[number];
|
|
5
|
+
export type ApiRequestFunction<API extends {
|
|
6
|
+
path: string;
|
|
7
|
+
method: string;
|
|
8
|
+
requestInput: any;
|
|
9
|
+
requestOutput: any;
|
|
10
|
+
response: any;
|
|
11
|
+
}> = <Path extends API['path'], Method extends Extract<API, {
|
|
12
|
+
path: Path;
|
|
13
|
+
}>['method']>(path: Path, method: Method, input: ApiInput<API, Path, Method>, headers?: HeadersInit) => Promise<ApiResponse<API, Path, Method>>;
|
|
14
|
+
/**
|
|
15
|
+
* @returns A function that can be used to make API requests with type safety
|
|
16
|
+
* @example const clientApiRequest = createApiRequest<ApiEndpoints>();
|
|
17
|
+
*/
|
|
18
|
+
export declare function createApiRequest<API extends {
|
|
19
|
+
path: string;
|
|
20
|
+
method: string;
|
|
21
|
+
requestInput: any;
|
|
22
|
+
requestOutput: any;
|
|
23
|
+
response: any;
|
|
24
|
+
}>(schemas: Partial<Record<API['path'], Partial<Record<HTTPMethod, v.GenericSchema | (() => v.GenericSchema)>>>>, apiUrl: string, env: string): ApiRequestFunction<API>;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/lambda/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAC;AAC7B,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAuD/D,QAAA,MAAM,WAAW,uEAAwE,CAAC;AAC1F,KAAK,UAAU,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/C,MAAM,MAAM,kBAAkB,CAC5B,GAAG,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,GAAG,CAAC;IAAC,aAAa,EAAE,GAAG,CAAC;IAAC,QAAQ,EAAE,GAAG,CAAA;CAAE,IAChG,CAAC,IAAI,SAAS,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,SAAS,OAAO,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC,EAClF,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,EAClC,OAAO,CAAC,EAAE,WAAW,KAClB,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;AAE7C;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,GAAG,CAAC;IAAC,aAAa,EAAE,GAAG,CAAC;IAAC,QAAQ,EAAE,GAAG,CAAA;CAAE,EAElG,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,aAAa,GAAG,CAAC,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,EAC7G,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV,kBAAkB,CAAC,GAAG,CAAC,CAkBzB"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { deserialize } from './deserializer.js';
|
|
2
|
+
import * as v from 'valibot';
|
|
3
|
+
const bodyMethods = ['POST', 'PUT', 'PATCH'];
|
|
4
|
+
const queryMethods = ['GET', 'DELETE'];
|
|
5
|
+
async function _apiRequest(path, method, input, schema, environment, apiUrl, headers) {
|
|
6
|
+
if (schema) {
|
|
7
|
+
v.parse(schema, input);
|
|
8
|
+
}
|
|
9
|
+
let url = `${apiUrl}${path}`;
|
|
10
|
+
if (queryMethods.includes(method)) {
|
|
11
|
+
const params = input ?? {};
|
|
12
|
+
const queryString = new URLSearchParams(Object.entries(params).reduce((acc, [key, value]) => {
|
|
13
|
+
if (Array.isArray(value)) {
|
|
14
|
+
value.forEach((v) => (acc[key] = String(v)));
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
acc[key] = String(value);
|
|
18
|
+
}
|
|
19
|
+
return acc;
|
|
20
|
+
}, {})).toString();
|
|
21
|
+
if (queryString)
|
|
22
|
+
url += `?${queryString}`;
|
|
23
|
+
}
|
|
24
|
+
headers = {
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
...(headers || {})
|
|
27
|
+
};
|
|
28
|
+
const response = await fetch(url, {
|
|
29
|
+
method,
|
|
30
|
+
headers,
|
|
31
|
+
body: bodyMethods.includes(method) ? JSON.stringify(input) : undefined,
|
|
32
|
+
credentials: 'include'
|
|
33
|
+
});
|
|
34
|
+
response.json = async () => {
|
|
35
|
+
return await deserialize(await response.text(), environment);
|
|
36
|
+
};
|
|
37
|
+
return response;
|
|
38
|
+
}
|
|
39
|
+
const HTTPMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'];
|
|
40
|
+
/**
|
|
41
|
+
* @returns A function that can be used to make API requests with type safety
|
|
42
|
+
* @example const clientApiRequest = createApiRequest<ApiEndpoints>();
|
|
43
|
+
*/
|
|
44
|
+
export function createApiRequest(schemas, apiUrl, env) {
|
|
45
|
+
return async function apiRequest(path, method, input, headers) {
|
|
46
|
+
// @ts-expect-error method can be used to index schemas
|
|
47
|
+
let schema = schemas[path]?.[method];
|
|
48
|
+
if (typeof schema === 'function') {
|
|
49
|
+
schema = schema();
|
|
50
|
+
}
|
|
51
|
+
return _apiRequest(path, method, input, schema, env, apiUrl, headers);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deserializer.d.ts","sourceRoot":"","sources":["../../src/lambda/deserializer.ts"],"names":[],"mappings":"AAAA,iBAAe,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,CAQjF;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
async function deserialize(data, env) {
|
|
2
|
+
if (env === 'production') {
|
|
3
|
+
const { parse: prodParse } = await import('@ungap/structured-clone/json');
|
|
4
|
+
return prodParse(data);
|
|
5
|
+
}
|
|
6
|
+
else {
|
|
7
|
+
const cycle = await import('cycle');
|
|
8
|
+
return JSON.parse(cycle.default.retrocycle(data));
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export { deserialize };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { APIGatewayProxyResultV2, Context } from 'aws-lambda';
|
|
2
|
+
/**
|
|
3
|
+
* A type for the raw proxy result - just using an object not a stirng for the body
|
|
4
|
+
*/
|
|
5
|
+
export type RawProxyResultV2 = {
|
|
6
|
+
statusCode?: number | undefined;
|
|
7
|
+
headers?: {
|
|
8
|
+
[header: string]: boolean | number | string;
|
|
9
|
+
} | undefined;
|
|
10
|
+
body?: object | undefined;
|
|
11
|
+
isBase64Encoded?: boolean | undefined;
|
|
12
|
+
cookies?: string[] | undefined;
|
|
13
|
+
};
|
|
14
|
+
export type APIGatewayHandler<E> = (event: E, context: Context) => Promise<APIGatewayProxyResultV2>;
|
|
15
|
+
export type RawApiGatewayHandler<E> = (event: E, context: Context) => Promise<RawProxyResultV2>;
|
|
16
|
+
/**
|
|
17
|
+
* Wraps a handler that returns the body as an object rather than a string.
|
|
18
|
+
*
|
|
19
|
+
* This means you can achieve proper type inference on your handler and have standardised serialisation
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
```ts
|
|
23
|
+
export type AuthorizerContext = {
|
|
24
|
+
details: JWTPayload;
|
|
25
|
+
} | null;
|
|
26
|
+
|
|
27
|
+
export const wrapHandler = baseWrapHandler<APIGatewayProxyEventV2WithLambdaAuthorizer<AuthorizerContext>>
|
|
28
|
+
|
|
29
|
+
*/
|
|
30
|
+
export declare function wrapHandler<E>(handler: RawApiGatewayHandler<E>): APIGatewayHandler<E>;
|
|
31
|
+
//# sourceMappingURL=handlerUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlerUtils.d.ts","sourceRoot":"","sources":["../../src/lambda/handlerUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAGnE;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,OAAO,CAAC,EACJ;QACE,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;KAC7C,GACD,SAAS,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CAChC,CAAC;AAGF,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;AAGpG,MAAM,MAAM,oBAAoB,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAEhG;;;;;;;;;;;;;EAaE;AACF,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAQrF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { stringify } from './serializer.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a handler that returns the body as an object rather than a string.
|
|
4
|
+
*
|
|
5
|
+
* This means you can achieve proper type inference on your handler and have standardised serialisation
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
```ts
|
|
9
|
+
export type AuthorizerContext = {
|
|
10
|
+
details: JWTPayload;
|
|
11
|
+
} | null;
|
|
12
|
+
|
|
13
|
+
export const wrapHandler = baseWrapHandler<APIGatewayProxyEventV2WithLambdaAuthorizer<AuthorizerContext>>
|
|
14
|
+
|
|
15
|
+
*/
|
|
16
|
+
export function wrapHandler(handler) {
|
|
17
|
+
return async (event, context) => {
|
|
18
|
+
const result = await handler(event, context);
|
|
19
|
+
return {
|
|
20
|
+
...result,
|
|
21
|
+
body: result.body ? await stringify(result.body) : undefined
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lambda/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './browser.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../../src/lambda/serializer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,GAAG,gBAQxC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns object into string.
|
|
3
|
+
*
|
|
4
|
+
* Uses different methods in production and otherwise for more readable output
|
|
5
|
+
*/
|
|
6
|
+
export async function stringify(data) {
|
|
7
|
+
if (process.env.PUBLIC_ENVIRONMENT === 'production') {
|
|
8
|
+
const { stringify: prodStrigify } = await import('@ungap/structured-clone/json');
|
|
9
|
+
return prodStrigify(data);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
const { decycle } = (await import('cycle')).default;
|
|
13
|
+
return JSON.stringify(decycle(data), null);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ts-alias.d.ts","sourceRoot":"","sources":["../src/ts-alias.ts"],"names":[],"mappings":""}
|
package/dist/ts-alias.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as chokidar from 'chokidar';
|
|
5
|
+
import { replaceTscAliasPaths } from 'tsc-alias';
|
|
6
|
+
import { glob } from 'glob';
|
|
7
|
+
import console from 'console';
|
|
8
|
+
// Cache for tsconfig.json files
|
|
9
|
+
const tsconfigCache = new Map();
|
|
10
|
+
// Parse command-line arguments
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
const watchMode = args.includes('-w') || args.includes('--watch');
|
|
13
|
+
/**
|
|
14
|
+
* Find all dist folders in the project directory, excluding certain patterns.
|
|
15
|
+
*/
|
|
16
|
+
async function findDistFolders(baseDir) {
|
|
17
|
+
const ignorePatterns = [
|
|
18
|
+
'**/node_modules/**',
|
|
19
|
+
'**/\\.git/**',
|
|
20
|
+
'**/\\.vscode/**',
|
|
21
|
+
'**/\\.idea/**',
|
|
22
|
+
'**/coverage/**',
|
|
23
|
+
'**/build/**',
|
|
24
|
+
'**/cdk.out/**'
|
|
25
|
+
];
|
|
26
|
+
const distFolders = await glob('**/dist', {
|
|
27
|
+
cwd: baseDir,
|
|
28
|
+
ignore: ignorePatterns,
|
|
29
|
+
absolute: true
|
|
30
|
+
});
|
|
31
|
+
return distFolders;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get the tsconfig.json file for a given dist folder.
|
|
35
|
+
* This function caches the tsconfig file to avoid reading it multiple times.
|
|
36
|
+
*/
|
|
37
|
+
function getTsconfig(distFolder) {
|
|
38
|
+
const projectRoot = path.dirname(distFolder);
|
|
39
|
+
const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
40
|
+
try {
|
|
41
|
+
const stats = fs.statSync(tsconfigPath);
|
|
42
|
+
const mtime = stats.mtimeMs;
|
|
43
|
+
// Check cache
|
|
44
|
+
const cached = tsconfigCache.get(tsconfigPath);
|
|
45
|
+
if (cached && cached.mtime === mtime) {
|
|
46
|
+
return cached.config;
|
|
47
|
+
}
|
|
48
|
+
// Read and cache the config
|
|
49
|
+
const config = JSON.parse(fs.readFileSync(tsconfigPath, 'utf8'));
|
|
50
|
+
tsconfigCache.set(tsconfigPath, { config, mtime });
|
|
51
|
+
return config;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error(`Error reading tsconfig at ${tsconfigPath}:`, error);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Process the dist folder by replacing TypeScript alias paths with relative paths.
|
|
60
|
+
*/
|
|
61
|
+
async function processDistFolder(distFolder) {
|
|
62
|
+
const projectRoot = path.dirname(distFolder);
|
|
63
|
+
const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
64
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
65
|
+
console.warn(`No tsconfig.json found at ${tsconfigPath}`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const tsconfig = getTsconfig(distFolder);
|
|
69
|
+
if (!tsconfig) {
|
|
70
|
+
console.warn(`Invalid tsconfig.json found for ${distFolder}`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await replaceTscAliasPaths({
|
|
75
|
+
configFile: tsconfigPath,
|
|
76
|
+
outDir: distFolder
|
|
77
|
+
});
|
|
78
|
+
console.log(`Successfully processed aliases in ${distFolder}`);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
console.error(`Error processing aliases in ${distFolder}:`, error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Watch the dist folder for changes and process it when files are added or changed.
|
|
86
|
+
*/
|
|
87
|
+
function watchDistFolder(distFolder) {
|
|
88
|
+
console.log(`Setting up watcher for: ${distFolder}`);
|
|
89
|
+
const watcher = chokidar.watch(distFolder, {
|
|
90
|
+
persistent: true,
|
|
91
|
+
ignoreInitial: true,
|
|
92
|
+
awaitWriteFinish: {
|
|
93
|
+
stabilityThreshold: 300,
|
|
94
|
+
pollInterval: 100
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
watcher.on('add', (filePath) => {
|
|
98
|
+
if (filePath.endsWith('.js') || filePath.endsWith('.jsx')) {
|
|
99
|
+
console.log(`File added: ${filePath}`);
|
|
100
|
+
processDistFolder(distFolder);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
watcher.on('change', (filePath) => {
|
|
104
|
+
if (filePath.endsWith('.js') || filePath.endsWith('.jsx')) {
|
|
105
|
+
console.log(`File changed: ${filePath}`);
|
|
106
|
+
processDistFolder(distFolder);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
return watcher;
|
|
110
|
+
}
|
|
111
|
+
// Main function
|
|
112
|
+
async function main() {
|
|
113
|
+
const baseDir = process.cwd();
|
|
114
|
+
console.log(`Searching for dist folders in: ${baseDir}`);
|
|
115
|
+
const distFolders = await findDistFolders(baseDir);
|
|
116
|
+
// Process all folders initially if any exist
|
|
117
|
+
if (distFolders.length > 0) {
|
|
118
|
+
console.log(`Found ${distFolders.length} dist folders:`);
|
|
119
|
+
distFolders.forEach((folder) => console.log(` - ${folder}`));
|
|
120
|
+
for (const folder of distFolders) {
|
|
121
|
+
await processDistFolder(folder);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.log('No dist folders found initially');
|
|
126
|
+
}
|
|
127
|
+
// Set up watchers if in watch mode
|
|
128
|
+
if (watchMode) {
|
|
129
|
+
console.log('Watch mode enabled, monitoring for changes...');
|
|
130
|
+
// Set up watchers for existing dist folders
|
|
131
|
+
distFolders.forEach(watchDistFolder);
|
|
132
|
+
// Watch for new dist folders being created
|
|
133
|
+
console.log('Watching for new dist folders...');
|
|
134
|
+
const dirWatcher = chokidar.watch(baseDir, {
|
|
135
|
+
persistent: true,
|
|
136
|
+
ignoreInitial: true,
|
|
137
|
+
depth: 5, // Adjust depth as needed for your project structure
|
|
138
|
+
ignored: [
|
|
139
|
+
'**/node_modules/**',
|
|
140
|
+
'**/\\.git/**',
|
|
141
|
+
'**/\\.vscode/**',
|
|
142
|
+
'**/\\.idea/**',
|
|
143
|
+
'**/coverage/**',
|
|
144
|
+
'**/build/**',
|
|
145
|
+
'**/cdk.out/**',
|
|
146
|
+
// Don't watch the contents of existing dist folders (they'll be watched separately)
|
|
147
|
+
...distFolders.map((folder) => `${folder}/**`)
|
|
148
|
+
]
|
|
149
|
+
});
|
|
150
|
+
// Handle directory creation events
|
|
151
|
+
dirWatcher.on('addDir', async (dirPath) => {
|
|
152
|
+
if (path.basename(dirPath) === 'dist') {
|
|
153
|
+
// Make sure it's not already being watched
|
|
154
|
+
if (!distFolders.includes(dirPath)) {
|
|
155
|
+
console.log(`New dist folder detected: ${dirPath}`);
|
|
156
|
+
distFolders.push(dirPath);
|
|
157
|
+
await processDistFolder(dirPath);
|
|
158
|
+
watchDistFolder(dirPath);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
main().catch((error) => {
|
|
165
|
+
console.error('Error in ts-alias script:', error);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ts-ag",
|
|
3
|
+
"description": "Useful TS stuff",
|
|
4
|
+
"version": "0.0.1-dev.1",
|
|
5
|
+
"author": "Alexander Hornung",
|
|
6
|
+
"bugs": "https://github.com/ageorgeh/ts-ag/issues",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"dequal": "^2.0.3",
|
|
9
|
+
"glob": "11.0.2",
|
|
10
|
+
"chokidar": "4.0.3",
|
|
11
|
+
"tsc-alias": "1.8.16",
|
|
12
|
+
"valibot": "1.1.0",
|
|
13
|
+
"cycle": "1.0.3",
|
|
14
|
+
"@ungap/structured-clone": "1.3.0",
|
|
15
|
+
"jose": "6.0.11"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"jiti": "2.4.2",
|
|
20
|
+
"@eslint/js": "^9.26.0",
|
|
21
|
+
"eslint": "^9.26.0",
|
|
22
|
+
"eslint-plugin-import": "^2.31.0",
|
|
23
|
+
"globals": "^16.1.0",
|
|
24
|
+
"prettier": "^3.5.3",
|
|
25
|
+
"typescript": "^5.8.3",
|
|
26
|
+
"typescript-eslint": "^8.32.0",
|
|
27
|
+
"typescript-svelte-plugin": "0.3.47",
|
|
28
|
+
"npm-check-updates": "^18.0.1",
|
|
29
|
+
"@types/node": "^22.15.17",
|
|
30
|
+
"@types/aws-lambda": "8.10.149",
|
|
31
|
+
"@types/ungap__structured-clone": "1.2.0"
|
|
32
|
+
},
|
|
33
|
+
"type": "module",
|
|
34
|
+
"files": [
|
|
35
|
+
"./src",
|
|
36
|
+
"./dist"
|
|
37
|
+
],
|
|
38
|
+
"exports": {
|
|
39
|
+
"./package.json": "./package.json",
|
|
40
|
+
".": {
|
|
41
|
+
"browser": {
|
|
42
|
+
"types": "./dist/browser.d.ts",
|
|
43
|
+
"default": "./dist/browser.js"
|
|
44
|
+
},
|
|
45
|
+
"default": {
|
|
46
|
+
"types": "./dist/index.d.ts",
|
|
47
|
+
"default": "./dist/index.js"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"repository": "ageorgeh/ts-ag",
|
|
52
|
+
"scripts": {
|
|
53
|
+
"tsc:build": "tsc",
|
|
54
|
+
"tsc:watch": "tsc --watch",
|
|
55
|
+
"publish:local": "pnpm version prerelease --preid dev --no-git-tag-version && pnpm publish --registry http://localhost:4873 --tag dev --access public --no-git-checks --json > ./publishLocal.json",
|
|
56
|
+
"publish:prerelease": "pnpm publish --tag dev --access public --no-git-checks --registry=https://registry.npmjs.org/ --json > ./publish.json",
|
|
57
|
+
"version:prerelease": "pnpm version prerelease --preid dev --no-git-tag-version"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/browser.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lambda/browser.js';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lambda/index.js';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a RawApiGatewayHandler response type to a fetch like response type.
|
|
3
|
+
*/
|
|
4
|
+
type ConvertToFetch<T> = T extends { statusCode: number; body: object; headers: object }
|
|
5
|
+
? {
|
|
6
|
+
ok: T['statusCode'] extends 200 | 201 | 204 ? true : false;
|
|
7
|
+
json: () => Promise<T['body']>;
|
|
8
|
+
status: T['statusCode'];
|
|
9
|
+
}
|
|
10
|
+
: T;
|
|
11
|
+
|
|
12
|
+
export type CleanResponse = Omit<Response, 'status' | 'ok' | 'json'>;
|
|
13
|
+
export type FetchResponse<T extends (...args: any) => any> = ConvertToFetch<Awaited<ReturnType<T>>> & CleanResponse;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extracts the requestInput type from an API endpoint definition
|
|
17
|
+
* @template E - Union type of all API endpoints
|
|
18
|
+
* @template P - Path string literal type (e.g. 'payments/account')
|
|
19
|
+
* @template M - HTTP method string literal type (e.g. 'GET', 'POST')
|
|
20
|
+
*/
|
|
21
|
+
export type ApiInput<
|
|
22
|
+
E extends { path: string; method: string; requestInput: any },
|
|
23
|
+
P extends E['path'],
|
|
24
|
+
M extends E['method']
|
|
25
|
+
> = Extract<E, { path: P; method: M }>['requestInput'];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extracts the requestOutput type from an API endpoint definition
|
|
29
|
+
* @template E - Union type of all API endpoints
|
|
30
|
+
* @template P - Path string literal type (e.g. 'payments/account')
|
|
31
|
+
* @template M - HTTP method string literal type (e.g. 'GET', 'POST')
|
|
32
|
+
*/
|
|
33
|
+
export type ApiOutput<
|
|
34
|
+
E extends { path: string; method: string; requestOutput: any },
|
|
35
|
+
P extends E['path'],
|
|
36
|
+
M extends E['method']
|
|
37
|
+
> = Extract<E, { path: P; method: M }>['requestOutput'];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Extracts the response type from an API endpoint definition
|
|
41
|
+
* @template E - Union type of all API endpoints
|
|
42
|
+
* @template P - Path string literal type (e.g. 'payments/account')
|
|
43
|
+
* @template M - HTTP method string literal type (e.g. 'GET', 'POST')
|
|
44
|
+
*/
|
|
45
|
+
export type ApiResponse<
|
|
46
|
+
E extends { path: string; method: string; response: any },
|
|
47
|
+
P extends E['path'],
|
|
48
|
+
M extends E['method']
|
|
49
|
+
> = Extract<E, { path: P; method: M }>['response'];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extracts the sucessful body type from an API endpoint definition
|
|
53
|
+
* @template E - Union type of all API endpoints
|
|
54
|
+
* @template P - Path string literal type (e.g. 'payments/account')
|
|
55
|
+
* @template M - HTTP method string literal type (e.g. 'GET', 'POST')
|
|
56
|
+
*/
|
|
57
|
+
export type ApiSuccessBody<
|
|
58
|
+
E extends { path: string; method: string; response: any },
|
|
59
|
+
P extends E['path'],
|
|
60
|
+
M extends E['method']
|
|
61
|
+
> = Extract<Extract<E, { path: P; method: M }>['response'], { status: 200 }>['json'] extends () => Promise<infer R>
|
|
62
|
+
? R
|
|
63
|
+
: unknown;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { deserialize } from './deserializer.js';
|
|
2
|
+
import * as v from 'valibot';
|
|
3
|
+
import type { ApiInput, ApiResponse } from './client-types.js';
|
|
4
|
+
|
|
5
|
+
const bodyMethods = ['POST', 'PUT', 'PATCH'] as const;
|
|
6
|
+
const queryMethods = ['GET', 'DELETE'] as const;
|
|
7
|
+
|
|
8
|
+
async function _apiRequest<T = Response>(
|
|
9
|
+
path: string,
|
|
10
|
+
method: 'GET' | 'POST' | 'DELETE',
|
|
11
|
+
input: object,
|
|
12
|
+
schema: v.GenericSchema | undefined,
|
|
13
|
+
environment: string | 'production',
|
|
14
|
+
apiUrl: string,
|
|
15
|
+
headers?: HeadersInit
|
|
16
|
+
): Promise<T> {
|
|
17
|
+
if (schema) {
|
|
18
|
+
v.parse(schema, input);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let url = `${apiUrl}${path}`;
|
|
22
|
+
|
|
23
|
+
if (queryMethods.includes(method as any)) {
|
|
24
|
+
const params = input ?? {};
|
|
25
|
+
const queryString = new URLSearchParams(
|
|
26
|
+
Object.entries(params).reduce(
|
|
27
|
+
(acc, [key, value]) => {
|
|
28
|
+
if (Array.isArray(value)) {
|
|
29
|
+
value.forEach((v) => (acc[key] = String(v)));
|
|
30
|
+
} else {
|
|
31
|
+
acc[key] = String(value);
|
|
32
|
+
}
|
|
33
|
+
return acc;
|
|
34
|
+
},
|
|
35
|
+
{} as Record<string, string>
|
|
36
|
+
)
|
|
37
|
+
).toString();
|
|
38
|
+
if (queryString) url += `?${queryString}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
headers = {
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
...(headers || {})
|
|
44
|
+
};
|
|
45
|
+
const response = await fetch(url, {
|
|
46
|
+
method,
|
|
47
|
+
headers,
|
|
48
|
+
body: bodyMethods.includes(method as any) ? JSON.stringify(input) : undefined,
|
|
49
|
+
credentials: 'include'
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
response.json = async () => {
|
|
53
|
+
return await deserialize(await response.text(), environment);
|
|
54
|
+
};
|
|
55
|
+
return response as unknown as T;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const HTTPMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'] as const;
|
|
59
|
+
type HTTPMethod = (typeof HTTPMethods)[number];
|
|
60
|
+
|
|
61
|
+
export type ApiRequestFunction<
|
|
62
|
+
API extends { path: string; method: string; requestInput: any; requestOutput: any; response: any }
|
|
63
|
+
> = <Path extends API['path'], Method extends Extract<API, { path: Path }>['method']>(
|
|
64
|
+
path: Path,
|
|
65
|
+
method: Method,
|
|
66
|
+
input: ApiInput<API, Path, Method>,
|
|
67
|
+
headers?: HeadersInit
|
|
68
|
+
) => Promise<ApiResponse<API, Path, Method>>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @returns A function that can be used to make API requests with type safety
|
|
72
|
+
* @example const clientApiRequest = createApiRequest<ApiEndpoints>();
|
|
73
|
+
*/
|
|
74
|
+
export function createApiRequest<
|
|
75
|
+
API extends { path: string; method: string; requestInput: any; requestOutput: any; response: any }
|
|
76
|
+
>(
|
|
77
|
+
schemas: Partial<Record<API['path'], Partial<Record<HTTPMethod, v.GenericSchema | (() => v.GenericSchema)>>>>,
|
|
78
|
+
apiUrl: string,
|
|
79
|
+
env: string
|
|
80
|
+
): ApiRequestFunction<API> {
|
|
81
|
+
return async function apiRequest(path, method, input, headers) {
|
|
82
|
+
// @ts-expect-error method can be used to index schemas
|
|
83
|
+
let schema = schemas[path]?.[method];
|
|
84
|
+
if (typeof schema === 'function') {
|
|
85
|
+
schema = schema();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return _apiRequest<ApiResponse<API, typeof path, typeof method>>(
|
|
89
|
+
path as string,
|
|
90
|
+
method as 'GET' | 'POST',
|
|
91
|
+
input,
|
|
92
|
+
schema,
|
|
93
|
+
env,
|
|
94
|
+
apiUrl,
|
|
95
|
+
headers
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
async function deserialize(data: string, env: string | 'production'): Promise<any> {
|
|
2
|
+
if (env === 'production') {
|
|
3
|
+
const { parse: prodParse } = await import('@ungap/structured-clone/json');
|
|
4
|
+
return prodParse(data);
|
|
5
|
+
} else {
|
|
6
|
+
const cycle = await import('cycle');
|
|
7
|
+
return JSON.parse(cycle.default.retrocycle(data));
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export { deserialize };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { APIGatewayProxyResultV2, Context } from 'aws-lambda';
|
|
2
|
+
import { stringify } from './serializer.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A type for the raw proxy result - just using an object not a stirng for the body
|
|
6
|
+
*/
|
|
7
|
+
export type RawProxyResultV2 = {
|
|
8
|
+
statusCode?: number | undefined;
|
|
9
|
+
headers?:
|
|
10
|
+
| {
|
|
11
|
+
[header: string]: boolean | number | string;
|
|
12
|
+
}
|
|
13
|
+
| undefined;
|
|
14
|
+
body?: object | undefined;
|
|
15
|
+
isBase64Encoded?: boolean | undefined;
|
|
16
|
+
cookies?: string[] | undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// The type of the handler returned from wrapHandler
|
|
20
|
+
export type APIGatewayHandler<E> = (event: E, context: Context) => Promise<APIGatewayProxyResultV2>;
|
|
21
|
+
|
|
22
|
+
// The type of the handler passed into wrapHandler
|
|
23
|
+
export type RawApiGatewayHandler<E> = (event: E, context: Context) => Promise<RawProxyResultV2>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Wraps a handler that returns the body as an object rather than a string.
|
|
27
|
+
*
|
|
28
|
+
* This means you can achieve proper type inference on your handler and have standardised serialisation
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
```ts
|
|
32
|
+
export type AuthorizerContext = {
|
|
33
|
+
details: JWTPayload;
|
|
34
|
+
} | null;
|
|
35
|
+
|
|
36
|
+
export const wrapHandler = baseWrapHandler<APIGatewayProxyEventV2WithLambdaAuthorizer<AuthorizerContext>>
|
|
37
|
+
|
|
38
|
+
*/
|
|
39
|
+
export function wrapHandler<E>(handler: RawApiGatewayHandler<E>): APIGatewayHandler<E> {
|
|
40
|
+
return async (event: E, context: Context): Promise<APIGatewayProxyResultV2> => {
|
|
41
|
+
const result = await handler(event, context);
|
|
42
|
+
return {
|
|
43
|
+
...result,
|
|
44
|
+
body: result.body ? await stringify(result.body) : undefined
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './browser.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns object into string.
|
|
3
|
+
*
|
|
4
|
+
* Uses different methods in production and otherwise for more readable output
|
|
5
|
+
*/
|
|
6
|
+
export async function stringify(data: any) {
|
|
7
|
+
if (process.env.PUBLIC_ENVIRONMENT === 'production') {
|
|
8
|
+
const { stringify: prodStrigify } = await import('@ungap/structured-clone/json');
|
|
9
|
+
return prodStrigify(data);
|
|
10
|
+
} else {
|
|
11
|
+
const { decycle } = (await import('cycle')).default;
|
|
12
|
+
return JSON.stringify(decycle(data), null);
|
|
13
|
+
}
|
|
14
|
+
}
|
package/src/ts-alias.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import * as chokidar from 'chokidar';
|
|
6
|
+
import { replaceTscAliasPaths } from 'tsc-alias';
|
|
7
|
+
import { glob } from 'glob';
|
|
8
|
+
import console from 'console';
|
|
9
|
+
|
|
10
|
+
// Cache for tsconfig.json files
|
|
11
|
+
const tsconfigCache: Map<string, { config: any; mtime: number }> = new Map();
|
|
12
|
+
|
|
13
|
+
// Parse command-line arguments
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
const watchMode = args.includes('-w') || args.includes('--watch');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Find all dist folders in the project directory, excluding certain patterns.
|
|
19
|
+
*/
|
|
20
|
+
async function findDistFolders(baseDir: string): Promise<string[]> {
|
|
21
|
+
const ignorePatterns = [
|
|
22
|
+
'**/node_modules/**',
|
|
23
|
+
'**/\\.git/**',
|
|
24
|
+
'**/\\.vscode/**',
|
|
25
|
+
'**/\\.idea/**',
|
|
26
|
+
'**/coverage/**',
|
|
27
|
+
'**/build/**',
|
|
28
|
+
'**/cdk.out/**'
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const distFolders = await glob('**/dist', {
|
|
32
|
+
cwd: baseDir,
|
|
33
|
+
ignore: ignorePatterns,
|
|
34
|
+
absolute: true
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return distFolders;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the tsconfig.json file for a given dist folder.
|
|
42
|
+
* This function caches the tsconfig file to avoid reading it multiple times.
|
|
43
|
+
*/
|
|
44
|
+
function getTsconfig(distFolder: string): any {
|
|
45
|
+
const projectRoot = path.dirname(distFolder);
|
|
46
|
+
const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const stats = fs.statSync(tsconfigPath);
|
|
50
|
+
const mtime = stats.mtimeMs;
|
|
51
|
+
|
|
52
|
+
// Check cache
|
|
53
|
+
const cached = tsconfigCache.get(tsconfigPath);
|
|
54
|
+
if (cached && cached.mtime === mtime) {
|
|
55
|
+
return cached.config;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Read and cache the config
|
|
59
|
+
const config = JSON.parse(fs.readFileSync(tsconfigPath, 'utf8'));
|
|
60
|
+
tsconfigCache.set(tsconfigPath, { config, mtime });
|
|
61
|
+
|
|
62
|
+
return config;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error(`Error reading tsconfig at ${tsconfigPath}:`, error);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Process the dist folder by replacing TypeScript alias paths with relative paths.
|
|
71
|
+
*/
|
|
72
|
+
async function processDistFolder(distFolder: string): Promise<void> {
|
|
73
|
+
const projectRoot = path.dirname(distFolder);
|
|
74
|
+
const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
75
|
+
|
|
76
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
77
|
+
console.warn(`No tsconfig.json found at ${tsconfigPath}`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const tsconfig = getTsconfig(distFolder);
|
|
82
|
+
|
|
83
|
+
if (!tsconfig) {
|
|
84
|
+
console.warn(`Invalid tsconfig.json found for ${distFolder}`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await replaceTscAliasPaths({
|
|
90
|
+
configFile: tsconfigPath,
|
|
91
|
+
outDir: distFolder
|
|
92
|
+
});
|
|
93
|
+
console.log(`Successfully processed aliases in ${distFolder}`);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error(`Error processing aliases in ${distFolder}:`, error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Watch the dist folder for changes and process it when files are added or changed.
|
|
101
|
+
*/
|
|
102
|
+
function watchDistFolder(distFolder: string): chokidar.FSWatcher {
|
|
103
|
+
console.log(`Setting up watcher for: ${distFolder}`);
|
|
104
|
+
|
|
105
|
+
const watcher = chokidar.watch(distFolder, {
|
|
106
|
+
persistent: true,
|
|
107
|
+
ignoreInitial: true,
|
|
108
|
+
awaitWriteFinish: {
|
|
109
|
+
stabilityThreshold: 300,
|
|
110
|
+
pollInterval: 100
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
watcher.on('add', (filePath) => {
|
|
115
|
+
if (filePath.endsWith('.js') || filePath.endsWith('.jsx')) {
|
|
116
|
+
console.log(`File added: ${filePath}`);
|
|
117
|
+
processDistFolder(distFolder);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
watcher.on('change', (filePath) => {
|
|
122
|
+
if (filePath.endsWith('.js') || filePath.endsWith('.jsx')) {
|
|
123
|
+
console.log(`File changed: ${filePath}`);
|
|
124
|
+
processDistFolder(distFolder);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return watcher;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Main function
|
|
132
|
+
async function main(): Promise<void> {
|
|
133
|
+
const baseDir = process.cwd();
|
|
134
|
+
console.log(`Searching for dist folders in: ${baseDir}`);
|
|
135
|
+
|
|
136
|
+
const distFolders = await findDistFolders(baseDir);
|
|
137
|
+
|
|
138
|
+
// Process all folders initially if any exist
|
|
139
|
+
if (distFolders.length > 0) {
|
|
140
|
+
console.log(`Found ${distFolders.length} dist folders:`);
|
|
141
|
+
distFolders.forEach((folder) => console.log(` - ${folder}`));
|
|
142
|
+
|
|
143
|
+
for (const folder of distFolders) {
|
|
144
|
+
await processDistFolder(folder);
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
console.log('No dist folders found initially');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Set up watchers if in watch mode
|
|
151
|
+
if (watchMode) {
|
|
152
|
+
console.log('Watch mode enabled, monitoring for changes...');
|
|
153
|
+
|
|
154
|
+
// Set up watchers for existing dist folders
|
|
155
|
+
distFolders.forEach(watchDistFolder);
|
|
156
|
+
|
|
157
|
+
// Watch for new dist folders being created
|
|
158
|
+
console.log('Watching for new dist folders...');
|
|
159
|
+
const dirWatcher = chokidar.watch(baseDir, {
|
|
160
|
+
persistent: true,
|
|
161
|
+
ignoreInitial: true,
|
|
162
|
+
depth: 5, // Adjust depth as needed for your project structure
|
|
163
|
+
ignored: [
|
|
164
|
+
'**/node_modules/**',
|
|
165
|
+
'**/\\.git/**',
|
|
166
|
+
'**/\\.vscode/**',
|
|
167
|
+
'**/\\.idea/**',
|
|
168
|
+
'**/coverage/**',
|
|
169
|
+
'**/build/**',
|
|
170
|
+
'**/cdk.out/**',
|
|
171
|
+
// Don't watch the contents of existing dist folders (they'll be watched separately)
|
|
172
|
+
...distFolders.map((folder) => `${folder}/**`)
|
|
173
|
+
]
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Handle directory creation events
|
|
177
|
+
dirWatcher.on('addDir', async (dirPath) => {
|
|
178
|
+
if (path.basename(dirPath) === 'dist') {
|
|
179
|
+
// Make sure it's not already being watched
|
|
180
|
+
if (!distFolders.includes(dirPath)) {
|
|
181
|
+
console.log(`New dist folder detected: ${dirPath}`);
|
|
182
|
+
distFolders.push(dirPath);
|
|
183
|
+
await processDistFolder(dirPath);
|
|
184
|
+
watchDistFolder(dirPath);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
main().catch((error) => {
|
|
192
|
+
console.error('Error in ts-alias script:', error);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
});
|