@quadrokit/client 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +69 -0
- package/dist/generate/codegen.d.ts +3 -0
- package/dist/generate/codegen.d.ts.map +1 -0
- package/dist/generate/codegen.js +282 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/{src/index.ts → dist/index.js} +1 -1
- package/dist/runtime/collection.d.ts +29 -0
- package/dist/runtime/collection.d.ts.map +1 -0
- package/dist/runtime/collection.js +94 -0
- package/dist/runtime/data-class.d.ts +42 -0
- package/dist/runtime/data-class.d.ts.map +1 -0
- package/dist/runtime/data-class.js +99 -0
- package/dist/runtime/datastore.d.ts +8 -0
- package/dist/runtime/datastore.d.ts.map +1 -0
- package/dist/runtime/datastore.js +15 -0
- package/dist/runtime/errors.d.ts +6 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +10 -0
- package/dist/runtime/http.d.ts +16 -0
- package/dist/runtime/http.d.ts.map +1 -0
- package/dist/runtime/http.js +54 -0
- package/dist/runtime/index.d.ts +9 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +7 -0
- package/dist/runtime/paths.d.ts +16 -0
- package/dist/runtime/paths.d.ts.map +1 -0
- package/dist/runtime/paths.js +2 -0
- package/dist/runtime/query.d.ts +14 -0
- package/dist/runtime/query.d.ts.map +1 -0
- package/dist/runtime/query.js +76 -0
- package/dist/runtime/unwrap.d.ts +4 -0
- package/dist/runtime/unwrap.d.ts.map +1 -0
- package/dist/runtime/unwrap.js +31 -0
- package/package.json +13 -6
- package/src/cli.ts +0 -82
- package/src/generate/codegen.ts +0 -317
- package/src/runtime/collection.ts +0 -135
- package/src/runtime/data-class.ts +0 -156
- package/src/runtime/datastore.ts +0 -25
- package/src/runtime/errors.ts +0 -11
- package/src/runtime/http.ts +0 -64
- package/src/runtime/index.ts +0 -23
- package/src/runtime/paths.ts +0 -42
- package/src/runtime/query.ts +0 -104
- package/src/runtime/unwrap.ts +0 -33
- package/tsconfig.json +0 -9
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Call nested datastore paths like `authentify/login` (POST by default). */
|
|
2
|
+
export async function callDatastorePath(http, segments, init) {
|
|
3
|
+
const path = `/${segments.join('/')}`;
|
|
4
|
+
const method = init?.method ?? 'POST';
|
|
5
|
+
if (method === 'GET') {
|
|
6
|
+
return http.json(path, { method: 'GET' });
|
|
7
|
+
}
|
|
8
|
+
return http.json(path, {
|
|
9
|
+
method: 'POST',
|
|
10
|
+
body: init?.body !== undefined ? JSON.stringify(init.body) : undefined,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export function createDatastoreNamespace(_http, tree) {
|
|
14
|
+
return tree;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/runtime/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;gBAET,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAMzC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface QuadroFetchOptions {
|
|
2
|
+
baseURL: string;
|
|
3
|
+
fetchImpl?: typeof fetch;
|
|
4
|
+
/** Extra headers on every request (e.g. Authorization for generate). */
|
|
5
|
+
defaultHeaders?: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
export declare function normalizeBaseURL(baseURL: string): string;
|
|
8
|
+
export declare class QuadroHttp {
|
|
9
|
+
private readonly opts;
|
|
10
|
+
constructor(opts: QuadroFetchOptions);
|
|
11
|
+
get baseURL(): string;
|
|
12
|
+
request(path: string, init?: RequestInit): Promise<Response>;
|
|
13
|
+
json<T>(path: string, init?: RequestInit): Promise<T>;
|
|
14
|
+
void(path: string, init?: RequestInit): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/runtime/http.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACxC;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,kBAAkB;IAErD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAEK,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAwBhE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,CAAC,CAAC;IAYzD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;CAOhE"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { QuadroHttpError } from './errors.js';
|
|
2
|
+
export function normalizeBaseURL(baseURL) {
|
|
3
|
+
return baseURL.replace(/\/$/, '');
|
|
4
|
+
}
|
|
5
|
+
export class QuadroHttp {
|
|
6
|
+
opts;
|
|
7
|
+
constructor(opts) {
|
|
8
|
+
this.opts = opts;
|
|
9
|
+
}
|
|
10
|
+
get baseURL() {
|
|
11
|
+
return normalizeBaseURL(this.opts.baseURL);
|
|
12
|
+
}
|
|
13
|
+
async request(path, init = {}) {
|
|
14
|
+
const url = path.startsWith('http')
|
|
15
|
+
? path
|
|
16
|
+
: `${this.baseURL}${path.startsWith('/') ? '' : '/'}${path}`;
|
|
17
|
+
const headers = new Headers(init.headers);
|
|
18
|
+
if (!headers.has('Accept')) {
|
|
19
|
+
headers.set('Accept', 'application/json');
|
|
20
|
+
}
|
|
21
|
+
if (init.body !== undefined && !headers.has('Content-Type')) {
|
|
22
|
+
headers.set('Content-Type', 'application/json');
|
|
23
|
+
}
|
|
24
|
+
for (const [k, v] of Object.entries(this.opts.defaultHeaders ?? {})) {
|
|
25
|
+
if (!headers.has(k)) {
|
|
26
|
+
headers.set(k, v);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const fetchFn = this.opts.fetchImpl ?? fetch;
|
|
30
|
+
return fetchFn(url, {
|
|
31
|
+
credentials: 'include',
|
|
32
|
+
...init,
|
|
33
|
+
headers,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async json(path, init = {}) {
|
|
37
|
+
const res = await this.request(path, init);
|
|
38
|
+
const text = await res.text();
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
throw new QuadroHttpError(res.status, text);
|
|
41
|
+
}
|
|
42
|
+
if (!text) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
return JSON.parse(text);
|
|
46
|
+
}
|
|
47
|
+
async void(path, init = {}) {
|
|
48
|
+
const res = await this.request(path, init);
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
const text = await res.text();
|
|
51
|
+
throw new QuadroHttpError(res.status, text);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { type CollectionContext, type CollectionHandle, type CollectionOptions, createCollection, } from './collection.js';
|
|
2
|
+
export { attachRelatedApis, type DataClassRuntimeConfig, type EntityNavigationConfig, makeDataClassApi, makeRelatedCollectionApi, } from './data-class.js';
|
|
3
|
+
export { callDatastorePath, createDatastoreNamespace } from './datastore.js';
|
|
4
|
+
export { QuadroHttpError } from './errors.js';
|
|
5
|
+
export { normalizeBaseURL, type QuadroFetchOptions, QuadroHttp, } from './http.js';
|
|
6
|
+
export type { Paths1, SelectedEntity } from './paths.js';
|
|
7
|
+
export { buildListSearchParams, type ListQueryParams } from './query.js';
|
|
8
|
+
export { unwrapEntity, unwrapEntityList } from './unwrap.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,gBAAgB,GACjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,iBAAiB,EACjB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EACL,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,UAAU,GACX,MAAM,WAAW,CAAA;AAClB,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AACxD,OAAO,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AACxE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createCollection, } from './collection.js';
|
|
2
|
+
export { attachRelatedApis, makeDataClassApi, makeRelatedCollectionApi, } from './data-class.js';
|
|
3
|
+
export { callDatastorePath, createDatastoreNamespace } from './datastore.js';
|
|
4
|
+
export { QuadroHttpError } from './errors.js';
|
|
5
|
+
export { normalizeBaseURL, QuadroHttp, } from './http.js';
|
|
6
|
+
export { buildListSearchParams } from './query.js';
|
|
7
|
+
export { unwrapEntity, unwrapEntityList } from './unwrap.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Type-level helpers for `$select` dot paths (compile-time only). */
|
|
2
|
+
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
3
|
+
type PickOne<T, H extends string> = H extends keyof T & string ? Pick<T, H> : H extends `${infer K}.${infer R}` ? K extends keyof T & string ? NonNullable<T[K]> extends infer U ? U extends object ? {
|
|
4
|
+
[P in K]: PickOne<U, R>;
|
|
5
|
+
} : never : never : never : never;
|
|
6
|
+
declare const selectBrand: unique symbol;
|
|
7
|
+
/** Entity narrowed to selected paths (branded for nominal distinction). */
|
|
8
|
+
export type SelectedEntity<T, S extends readonly string[]> = UnionToIntersection<S[number] extends infer H ? (H extends string ? PickOne<T, H> : never) : never> extends infer O ? O extends object ? O & {
|
|
9
|
+
readonly [selectBrand]: S;
|
|
10
|
+
} : never : never;
|
|
11
|
+
/** Flat union of first-level and nested dot paths (codegen emits concrete unions). */
|
|
12
|
+
export type Paths1<T> = T extends object ? {
|
|
13
|
+
[K in keyof T & string]: NonNullable<T[K]> extends infer U ? U extends object ? K | `${K}.${Paths1<U>}` : K : K;
|
|
14
|
+
}[keyof T & string] : never;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/runtime/paths.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,GAAG,KAAK,CAAC,SAAS,CACjF,CAAC,EAAE,MAAM,CAAC,KACP,IAAI,GACL,CAAC,GACD,KAAK,CAAA;AAET,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,GAC1D,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GACV,CAAC,SAAS,GAAG,MAAM,CAAC,IAAI,MAAM,CAAC,EAAE,GAC/B,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,GACxB,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,GAC/B,CAAC,SAAS,MAAM,GACd;KAAG,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;CAAE,GAC3B,KAAK,GACP,KAAK,GACP,KAAK,GACP,KAAK,CAAA;AAEX,OAAO,CAAC,MAAM,WAAW,EAAE,OAAO,MAAM,CAAA;AAExC,2EAA2E;AAC3E,MAAM,MAAM,cAAc,CAAC,CAAC,EAAE,CAAC,SAAS,SAAS,MAAM,EAAE,IACvD,mBAAmB,CACjB,CAAC,CAAC,MAAM,CAAC,SAAS,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,KAAK,CAC/E,SAAS,MAAM,CAAC,GACb,CAAC,SAAS,MAAM,GACd,CAAC,GAAG;IAAE,QAAQ,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;CAAE,GACjC,KAAK,GACP,KAAK,CAAA;AAEX,sFAAsF;AACtF,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,GACpC;KACG,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,GACtD,CAAC,SAAS,MAAM,GACd,CAAC,GAAG,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,GACvB,CAAC,GACH,CAAC;CACN,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GACnB,KAAK,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Build 4D REST query params ($skip, $top, $filter, $expand, $select). */
|
|
2
|
+
export interface ListQueryParams {
|
|
3
|
+
page?: number;
|
|
4
|
+
pageSize?: number;
|
|
5
|
+
/** Dot-paths like `manager.name` → $expand + nested $select where possible. */
|
|
6
|
+
select?: readonly string[];
|
|
7
|
+
/** OData-style filter string (4D REST). */
|
|
8
|
+
filter?: string;
|
|
9
|
+
orderby?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function buildListSearchParams(className: string, opts: ListQueryParams, _relationMap: Record<string, string>): string;
|
|
12
|
+
/** Query string for a single entity (only $select / $expand, no paging). */
|
|
13
|
+
export declare function buildEntityParams(className: string, select: readonly string[] | undefined, _relationMap: Record<string, string>): string;
|
|
14
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/runtime/query.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1B,2CAA2C;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,eAAe,EACrB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACnC,MAAM,CAwBR;AAED,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACrC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACnC,MAAM,CAcR"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** Build 4D REST query params ($skip, $top, $filter, $expand, $select). */
|
|
2
|
+
export function buildListSearchParams(className, opts, _relationMap) {
|
|
3
|
+
const params = new URLSearchParams();
|
|
4
|
+
const page = opts.page ?? 1;
|
|
5
|
+
const pageSize = opts.pageSize ?? 50;
|
|
6
|
+
const skip = (page - 1) * pageSize;
|
|
7
|
+
if (skip > 0) {
|
|
8
|
+
params.set('$skip', String(skip));
|
|
9
|
+
}
|
|
10
|
+
params.set('$top', String(pageSize));
|
|
11
|
+
if (opts.filter) {
|
|
12
|
+
params.set('$filter', opts.filter);
|
|
13
|
+
}
|
|
14
|
+
if (opts.orderby) {
|
|
15
|
+
params.set('$orderby', opts.orderby);
|
|
16
|
+
}
|
|
17
|
+
const { expand, selectRoot } = buildExpandAndSelect(className, opts.select ?? [], _relationMap);
|
|
18
|
+
if (selectRoot.length) {
|
|
19
|
+
params.set('$select', selectRoot.join(','));
|
|
20
|
+
}
|
|
21
|
+
if (expand.length) {
|
|
22
|
+
params.set('$expand', expand.join(','));
|
|
23
|
+
}
|
|
24
|
+
const q = params.toString();
|
|
25
|
+
return q ? `?${q}` : '';
|
|
26
|
+
}
|
|
27
|
+
/** Query string for a single entity (only $select / $expand, no paging). */
|
|
28
|
+
export function buildEntityParams(className, select, _relationMap) {
|
|
29
|
+
if (!select?.length) {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
const { expand, selectRoot } = buildExpandAndSelect(className, select, _relationMap);
|
|
33
|
+
const params = new URLSearchParams();
|
|
34
|
+
if (selectRoot.length) {
|
|
35
|
+
params.set('$select', selectRoot.join(','));
|
|
36
|
+
}
|
|
37
|
+
if (expand.length) {
|
|
38
|
+
params.set('$expand', expand.join(','));
|
|
39
|
+
}
|
|
40
|
+
const q = params.toString();
|
|
41
|
+
return q ? `?${q}` : '';
|
|
42
|
+
}
|
|
43
|
+
function buildExpandAndSelect(_className, paths, _relationMap) {
|
|
44
|
+
if (!paths.length) {
|
|
45
|
+
return { expand: [], selectRoot: [] };
|
|
46
|
+
}
|
|
47
|
+
const rootSelect = new Set();
|
|
48
|
+
const expandParts = [];
|
|
49
|
+
const byRel = new Map();
|
|
50
|
+
for (const p of paths) {
|
|
51
|
+
const dot = p.indexOf('.');
|
|
52
|
+
if (dot === -1) {
|
|
53
|
+
rootSelect.add(p);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const rel = p.slice(0, dot);
|
|
57
|
+
const rest = p.slice(dot + 1);
|
|
58
|
+
if (!byRel.has(rel)) {
|
|
59
|
+
byRel.set(rel, new Set());
|
|
60
|
+
}
|
|
61
|
+
byRel.get(rel).add(rest);
|
|
62
|
+
}
|
|
63
|
+
for (const [rel, nested] of byRel) {
|
|
64
|
+
const nestedList = [...nested];
|
|
65
|
+
const inner = nestedList.length ? `($select=${nestedList.join(',')})` : '';
|
|
66
|
+
// Use navigation property name (4D / OData $expand), not the target class name.
|
|
67
|
+
expandParts.push(`${rel}${inner}`);
|
|
68
|
+
if (!rootSelect.has(rel)) {
|
|
69
|
+
rootSelect.add(rel);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
expand: expandParts,
|
|
74
|
+
selectRoot: [...rootSelect],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Normalize 4D REST JSON (array or `{ ClassName: [...] }`). */
|
|
2
|
+
export declare function unwrapEntityList<T>(className: string, body: unknown): T[];
|
|
3
|
+
export declare function unwrapEntity<T>(className: string, body: unknown): T | null;
|
|
4
|
+
//# sourceMappingURL=unwrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unwrap.d.ts","sourceRoot":"","sources":["../../src/runtime/unwrap.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAEhE,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,CAAC,EAAE,CAiBzE;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,CAAC,GAAG,IAAI,CAW1E"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Normalize 4D REST JSON (array or `{ ClassName: [...] }`). */
|
|
2
|
+
export function unwrapEntityList(className, body) {
|
|
3
|
+
if (Array.isArray(body)) {
|
|
4
|
+
return body;
|
|
5
|
+
}
|
|
6
|
+
if (body && typeof body === 'object' && className in body) {
|
|
7
|
+
const v = body[className];
|
|
8
|
+
if (Array.isArray(v)) {
|
|
9
|
+
return v;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
if (body && typeof body === 'object' && '__ENTITIES' in body) {
|
|
13
|
+
const v = body.__ENTITIES;
|
|
14
|
+
if (Array.isArray(v)) {
|
|
15
|
+
return v;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
export function unwrapEntity(className, body) {
|
|
21
|
+
if (body == null) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
if (Array.isArray(body)) {
|
|
25
|
+
return body[0] ?? null;
|
|
26
|
+
}
|
|
27
|
+
if (typeof body === 'object' && className in body) {
|
|
28
|
+
return body[className] ?? null;
|
|
29
|
+
}
|
|
30
|
+
return body;
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quadrokit/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Typed 4D REST client and catalog code generator for QuadroKit",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
6
8
|
"bin": {
|
|
7
|
-
"quadrokit-client": "./
|
|
9
|
+
"quadrokit-client": "./dist/cli.js"
|
|
8
10
|
},
|
|
9
11
|
"exports": {
|
|
10
12
|
".": {
|
|
11
|
-
"types": "./
|
|
12
|
-
"import": "./
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
13
15
|
},
|
|
14
16
|
"./runtime": {
|
|
15
|
-
"types": "./
|
|
16
|
-
"import": "./
|
|
17
|
+
"types": "./dist/runtime/index.d.ts",
|
|
18
|
+
"import": "./dist/runtime/index.js"
|
|
17
19
|
}
|
|
18
20
|
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
19
24
|
"scripts": {
|
|
25
|
+
"build": "tsc -p tsconfig.build.json",
|
|
26
|
+
"prepublishOnly": "bun run build",
|
|
20
27
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
21
28
|
"generate:fixture": "bun run src/cli.ts generate --url file://../../assets/catalog.json --out ../../.quadrokit/generated-demo"
|
|
22
29
|
},
|
package/src/cli.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { fileURLToPath } from 'node:url'
|
|
3
|
-
import type { CatalogJson } from '@quadrokit/shared'
|
|
4
|
-
import { writeGenerated } from './generate/codegen.js'
|
|
5
|
-
|
|
6
|
-
function parseArgs(argv: string[]): {
|
|
7
|
-
command: string
|
|
8
|
-
url?: string
|
|
9
|
-
token?: string
|
|
10
|
-
out: string
|
|
11
|
-
} {
|
|
12
|
-
let command = ''
|
|
13
|
-
let url: string | undefined
|
|
14
|
-
let token: string | undefined
|
|
15
|
-
let out = '.quadrokit/generated'
|
|
16
|
-
|
|
17
|
-
for (let i = 0; i < argv.length; i++) {
|
|
18
|
-
const a = argv[i]
|
|
19
|
-
if (a === '--url' && argv[i + 1]) {
|
|
20
|
-
url = argv[++i]
|
|
21
|
-
} else if (a === '--token' && argv[i + 1]) {
|
|
22
|
-
token = argv[++i]
|
|
23
|
-
} else if (a === '--out' && argv[i + 1]) {
|
|
24
|
-
out = argv[++i]
|
|
25
|
-
} else if (!a.startsWith('-') && !command) {
|
|
26
|
-
command = a
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return { command, url, token, out }
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function loadCatalog(url: string, token?: string): Promise<CatalogJson> {
|
|
34
|
-
if (url.startsWith('file:')) {
|
|
35
|
-
const path = fileURLToPath(url)
|
|
36
|
-
const file = Bun.file(path)
|
|
37
|
-
const text = await file.text()
|
|
38
|
-
return JSON.parse(text) as CatalogJson
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const headers: Record<string, string> = {
|
|
42
|
-
Accept: 'application/json',
|
|
43
|
-
}
|
|
44
|
-
if (token) {
|
|
45
|
-
headers.Authorization = `Bearer ${token}`
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const res = await fetch(url, { headers })
|
|
49
|
-
if (!res.ok) {
|
|
50
|
-
const t = await res.text()
|
|
51
|
-
throw new Error(`Failed to fetch catalog (${res.status}): ${t.slice(0, 500)}`)
|
|
52
|
-
}
|
|
53
|
-
return res.json() as Promise<CatalogJson>
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function main() {
|
|
57
|
-
const argv = process.argv.slice(2)
|
|
58
|
-
const { command, url, token, out } = parseArgs(argv)
|
|
59
|
-
|
|
60
|
-
if (command !== 'generate') {
|
|
61
|
-
console.error(`Usage: quadrokit-client generate --url <catalog_url> [--token <token>] [--out <dir>]
|
|
62
|
-
|
|
63
|
-
Examples:
|
|
64
|
-
quadrokit-client generate --url http://localhost:7080/rest/\\$catalog --token secret
|
|
65
|
-
quadrokit-client generate --url file://./assets/catalog.json --out .quadrokit/generated
|
|
66
|
-
`)
|
|
67
|
-
process.exit(1)
|
|
68
|
-
}
|
|
69
|
-
if (!url) {
|
|
70
|
-
console.error('Error: --url is required for generate.')
|
|
71
|
-
process.exit(1)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const catalog = await loadCatalog(url, token)
|
|
75
|
-
await writeGenerated(out, catalog)
|
|
76
|
-
console.error(`Wrote generated client to ${out}`)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
main().catch((e) => {
|
|
80
|
-
console.error(e)
|
|
81
|
-
process.exit(1)
|
|
82
|
-
})
|