@stonecrop/graphql-client 0.11.1 → 0.11.3
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 +35 -130
- package/dist/client.js +38 -93
- package/dist/graphql-client.d.ts +28 -114
- package/dist/graphql-client.js +66 -7467
- package/dist/graphql-client.js.map +1 -1
- package/dist/index.js +0 -61
- package/dist/src/client.d.ts +24 -11
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -18
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/types/index.d.ts +7 -38
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/package.json +3 -5
- package/src/client.ts +42 -123
- package/src/index.ts +3 -70
- package/src/types/index.ts +6 -39
- package/dist/gql/schema.js +0 -53
- package/dist/queries.js +0 -19
- package/dist/query.js +0 -231
- package/dist/src/gql/schema.d.ts +0 -7
- package/dist/src/gql/schema.d.ts.map +0 -1
- package/dist/src/queries.d.ts +0 -9
- package/dist/src/queries.d.ts.map +0 -1
- package/dist/src/query.d.ts +0 -35
- package/dist/src/query.d.ts.map +0 -1
- package/src/gql/index.d.ts +0 -6
- package/src/gql/schema.gql +0 -45
- package/src/gql/schema.ts +0 -55
- package/src/queries.ts +0 -21
- package/src/query.ts +0 -303
package/dist/index.js
CHANGED
|
@@ -1,62 +1 @@
|
|
|
1
|
-
import { Decimal } from 'decimal.js';
|
|
2
|
-
import { GraphQLClient } from 'graphql-request';
|
|
3
|
-
import { queries } from './queries';
|
|
4
|
-
import typeDefs from './gql/schema';
|
|
5
|
-
/**
|
|
6
|
-
* Parse the response from the GraphQL server. Converts the stringified JSON to JSON and converts the stringified numbers to Decimal.
|
|
7
|
-
* @param obj - The response from the GraphQL server
|
|
8
|
-
* @returns The parsed response
|
|
9
|
-
* @example
|
|
10
|
-
* const response = '{"data":{"getMeta":{"id":"Issue","name":"Issue","workflow":"{\"machineId\":null,\"name\":\"save\",\"id\":\"1\"}","schema":"[{\"label\":\"Subject\",\"id\":\"1\"}]","actions":"[{\"eventName\":\"save\",\"id\":\"1\"}]"}}}'
|
|
11
|
-
* const parsedResponse = metaParser(response)
|
|
12
|
-
* console.log(parsedResponse)
|
|
13
|
-
* /* Output: {"id": "Issue", "name": "Issue", "workflow": { "machineId": null, "name": "save", "id": "1" }, "schema": [{ "label": "Subject", "id": "1" }], "actions": [{ "eventName": "save", "id": "1" }]}
|
|
14
|
-
*/
|
|
15
|
-
const metaParser = (obj) => {
|
|
16
|
-
return JSON.parse(obj, (key, value) => {
|
|
17
|
-
if (typeof value === 'string') {
|
|
18
|
-
try {
|
|
19
|
-
return JSON.parse(value, (_key, value) => {
|
|
20
|
-
if (typeof value === 'string' && !isNaN(Number(value))) {
|
|
21
|
-
return new Decimal(value);
|
|
22
|
-
}
|
|
23
|
-
return value;
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
// if the value is not a stringified JSON, return as it is
|
|
28
|
-
return value;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
else if (!isNaN(Number(value))) {
|
|
32
|
-
return new Decimal(value);
|
|
33
|
-
}
|
|
34
|
-
return value;
|
|
35
|
-
});
|
|
36
|
-
};
|
|
37
|
-
/**
|
|
38
|
-
* Get meta information for a doctype
|
|
39
|
-
* @param doctype - The doctype to get meta information for
|
|
40
|
-
* @param url - The URL to send the request to
|
|
41
|
-
* @returns The meta information for the doctype
|
|
42
|
-
* @public
|
|
43
|
-
*/
|
|
44
|
-
const methods = {
|
|
45
|
-
getMeta: async (doctype, url) => {
|
|
46
|
-
const client = new GraphQLClient(url || '/graphql', {
|
|
47
|
-
fetch: window.fetch,
|
|
48
|
-
jsonSerializer: {
|
|
49
|
-
stringify: obj => JSON.stringify(obj), // process the request object before sending; leave as default JSON
|
|
50
|
-
parse: metaParser, // process the response meta object
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
const { getMeta } = await client.request({
|
|
54
|
-
document: queries.getMeta,
|
|
55
|
-
variables: { doctype },
|
|
56
|
-
});
|
|
57
|
-
return getMeta;
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
export { queries, typeDefs, methods };
|
|
61
1
|
export { StonecropClient } from './client';
|
|
62
|
-
export { buildRecordQuery, buildListQuery } from './query';
|
package/dist/src/client.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { DataClient, DoctypeMeta, DoctypeContext, DoctypeRef, GetRecordOptions, GetRecordsOptions } from '@stonecrop/schema';
|
|
2
|
+
import type { GetRecordResult } from './types';
|
|
2
3
|
export type { DoctypeContext, DoctypeRef };
|
|
4
|
+
export type { GetRecordResult };
|
|
3
5
|
/**
|
|
4
6
|
* Options for creating a Stonecrop client
|
|
5
7
|
* @public
|
|
@@ -9,27 +11,31 @@ export interface StonecropClientOptions {
|
|
|
9
11
|
endpoint: string;
|
|
10
12
|
/** Additional HTTP headers to include in requests */
|
|
11
13
|
headers?: Record<string, string>;
|
|
12
|
-
/** Doctype registry for nested query building */
|
|
13
|
-
registry?: Map<string, DoctypeMeta>;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Client for interacting with Stonecrop GraphQL API
|
|
16
|
+
* Client for interacting with Stonecrop GraphQL API.
|
|
17
|
+
*
|
|
18
|
+
* Acts as a transport layer — it passes requests to the middleware and returns
|
|
19
|
+
* merged results. Does not construct queries itself.
|
|
20
|
+
*
|
|
17
21
|
* @public
|
|
18
22
|
*/
|
|
19
23
|
export declare class StonecropClient implements DataClient {
|
|
20
24
|
private endpoint;
|
|
21
25
|
private headers;
|
|
22
26
|
private metaCache;
|
|
23
|
-
private registry?;
|
|
24
27
|
constructor(options: StonecropClientOptions);
|
|
25
28
|
/**
|
|
26
|
-
* Execute a GraphQL query
|
|
29
|
+
* Execute a GraphQL query against the configured endpoint.
|
|
30
|
+
*
|
|
27
31
|
* @param query - GraphQL query string
|
|
28
32
|
* @param variables - Query variables
|
|
33
|
+
* @throws Error if the GraphQL response contains errors
|
|
29
34
|
*/
|
|
30
35
|
query<T = unknown>(query: string, variables?: Record<string, unknown>): Promise<T>;
|
|
31
36
|
/**
|
|
32
|
-
* Execute a GraphQL mutation
|
|
37
|
+
* Execute a GraphQL mutation. Delegates to query() since both use POST.
|
|
38
|
+
*
|
|
33
39
|
* @param mutation - GraphQL mutation string
|
|
34
40
|
* @param variables - Mutation variables
|
|
35
41
|
*/
|
|
@@ -46,16 +52,20 @@ export declare class StonecropClient implements DataClient {
|
|
|
46
52
|
/**
|
|
47
53
|
* Get a single record by ID.
|
|
48
54
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
55
|
+
* Routes through the stonecropRecord resolver which handles nested data
|
|
56
|
+
* fetching based on the includeNested option.
|
|
51
57
|
*
|
|
52
58
|
* @param doctype - Doctype reference (name and optional slug)
|
|
53
59
|
* @param recordId - Record ID to fetch
|
|
54
60
|
* @param options - Query options (includeNested, maxDepth)
|
|
55
61
|
*/
|
|
56
|
-
getRecord(doctype: DoctypeRef, recordId: string, options?: GetRecordOptions): Promise<
|
|
62
|
+
getRecord(doctype: DoctypeRef, recordId: string, options?: GetRecordOptions): Promise<GetRecordResult>;
|
|
57
63
|
/**
|
|
58
|
-
* Get multiple records with optional filtering and pagination
|
|
64
|
+
* Get multiple records with optional filtering and pagination.
|
|
65
|
+
*
|
|
66
|
+
* Returns flat arrays — the middleware merges connection format (\{ nodes: [...] \})
|
|
67
|
+
* into plain arrays before returning.
|
|
68
|
+
*
|
|
59
69
|
* @param doctype - Doctype reference (name and optional slug)
|
|
60
70
|
* @param options - Query options (filters, orderBy, limit, offset)
|
|
61
71
|
*/
|
|
@@ -72,7 +82,10 @@ export declare class StonecropClient implements DataClient {
|
|
|
72
82
|
error: string | null;
|
|
73
83
|
}>;
|
|
74
84
|
/**
|
|
75
|
-
* Clear the cached doctype metadata
|
|
85
|
+
* Clear the cached doctype metadata.
|
|
86
|
+
*
|
|
87
|
+
* Call this if the server-side doctype schema has changed and you need
|
|
88
|
+
* to fetch fresh metadata (e.g., after adding a new field).
|
|
76
89
|
*/
|
|
77
90
|
clearMetaCache(): void;
|
|
78
91
|
}
|
package/dist/src/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,UAAU,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,UAAU,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,CAAA;AAC1C,YAAY,EAAE,eAAe,EAAE,CAAA;AAE/B;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACtC,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED;;;;;;;GAOG;AACH,qBAAa,eAAgB,YAAW,UAAU;IACjD,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,SAAS,CAAsC;gBAE3C,OAAO,EAAE,sBAAsB;IAQ3C;;;;;;OAMG;IACG,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAmBxF;;;;;OAKG;IACG,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAI5F;;;OAGG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAuDnE;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAmD1C;;;;;;;;;OASG;IACG,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IA4B5G;;;;;;;;OAQG;IACG,UAAU,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAiCtG;;;;;OAKG;IACG,SAAS,CACd,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,OAAO,EAAE,GACd,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAuBrE;;;;;OAKG;IACH,cAAc,IAAI,IAAI;CAGtB"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,20 +1,4 @@
|
|
|
1
|
-
import { queries } from './queries';
|
|
2
|
-
import typeDefs from './gql/schema';
|
|
3
|
-
import type { MetaResponse } from './types';
|
|
4
|
-
/**
|
|
5
|
-
* Get meta information for a doctype
|
|
6
|
-
* @param doctype - The doctype to get meta information for
|
|
7
|
-
* @param url - The URL to send the request to
|
|
8
|
-
* @returns The meta information for the doctype
|
|
9
|
-
* @public
|
|
10
|
-
*/
|
|
11
|
-
declare const methods: {
|
|
12
|
-
getMeta: (doctype: string, url?: string) => Promise<MetaResponse>;
|
|
13
|
-
};
|
|
14
|
-
export { queries, typeDefs, methods };
|
|
15
|
-
export { StonecropClient } from './client';
|
|
16
|
-
export { buildRecordQuery, buildListQuery } from './query';
|
|
17
|
-
export type { StonecropClientOptions, DoctypeContext } from './client';
|
|
18
|
-
export type { Meta, MetaParser, MetaResponse } from './types';
|
|
19
1
|
export type { DoctypeMeta } from '@stonecrop/schema';
|
|
2
|
+
export { StonecropClient, type StonecropClientOptions, type DoctypeContext } from './client';
|
|
3
|
+
export type { GetRecordResult } from './types';
|
|
20
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEpD,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAA;AAC5F,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA"}
|
|
@@ -1,45 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file
|
|
2
|
+
* @file Types for the GraphQL client.
|
|
3
3
|
* @public
|
|
4
4
|
*/
|
|
5
|
+
import type { GetRecordResult as SchemaGetRecordResult } from '@stonecrop/schema';
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
+
* Result from getRecord - includes the record data and any unknown links requested
|
|
7
8
|
* @public
|
|
8
9
|
*/
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
response: {
|
|
14
|
-
getMeta: MetaResponse;
|
|
15
|
-
};
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* The type of the response from the `getRecords` query.
|
|
19
|
-
* @public
|
|
20
|
-
*/
|
|
21
|
-
export type MetaResponse = {
|
|
22
|
-
id: string;
|
|
23
|
-
name: string;
|
|
24
|
-
workflow: {
|
|
25
|
-
id: string;
|
|
26
|
-
name: string;
|
|
27
|
-
machineId?: string;
|
|
28
|
-
};
|
|
29
|
-
schema: {
|
|
30
|
-
id: string;
|
|
31
|
-
label: string;
|
|
32
|
-
}[];
|
|
33
|
-
actions: {
|
|
34
|
-
id: string;
|
|
35
|
-
eventName: string;
|
|
36
|
-
}[];
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* The type of the response from the `getMeta` query.
|
|
40
|
-
* @public
|
|
41
|
-
*/
|
|
42
|
-
export type MetaParser = {
|
|
43
|
-
data: Meta['response'];
|
|
44
|
-
};
|
|
10
|
+
export interface GetRecordResult extends SchemaGetRecordResult {
|
|
11
|
+
/** Link names that were requested but don't exist in the doctype schema */
|
|
12
|
+
unknownLinks?: string[];
|
|
13
|
+
}
|
|
45
14
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,eAAe,IAAI,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AAEjF;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,qBAAqB;IAC7D,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB"}
|
package/dist/types/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stonecrop/graphql-client",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": {
|
|
@@ -30,12 +30,10 @@
|
|
|
30
30
|
],
|
|
31
31
|
"sideEffects": false,
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"decimal.js": "^10.6.0",
|
|
34
33
|
"graphql": "^16.12.0",
|
|
35
|
-
"graphql-request": "^7.4.0",
|
|
36
34
|
"pluralize": "^8.0.0",
|
|
37
|
-
"@stonecrop/schema": "0.11.
|
|
38
|
-
"@stonecrop/stonecrop": "0.11.
|
|
35
|
+
"@stonecrop/schema": "0.11.3",
|
|
36
|
+
"@stonecrop/stonecrop": "0.11.3"
|
|
39
37
|
},
|
|
40
38
|
"devDependencies": {
|
|
41
39
|
"@eslint/js": "^9.39.2",
|
package/src/client.ts
CHANGED
|
@@ -6,25 +6,10 @@ import type {
|
|
|
6
6
|
GetRecordOptions,
|
|
7
7
|
GetRecordsOptions,
|
|
8
8
|
} from '@stonecrop/schema'
|
|
9
|
-
import {
|
|
10
|
-
import pluralize from 'pluralize'
|
|
11
|
-
|
|
12
|
-
import { buildRecordQuery } from './query'
|
|
9
|
+
import type { GetRecordResult } from './types'
|
|
13
10
|
|
|
14
11
|
export type { DoctypeContext, DoctypeRef }
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Default inflection functions for PostGraphile Amber preset conventions.
|
|
18
|
-
* These match the middleware's default inflection so the client builds
|
|
19
|
-
* queries the server can execute.
|
|
20
|
-
* @internal
|
|
21
|
-
*/
|
|
22
|
-
const defaultRecordFieldName = (tableName: string): string => {
|
|
23
|
-
const singularName = pluralize.singular(tableName)
|
|
24
|
-
return `${snakeToCamel(singularName)}ById`
|
|
25
|
-
}
|
|
26
|
-
const defaultRecordArgName = (_tableName: string): string => 'id'
|
|
27
|
-
const defaultRecordArgType = (_tableName: string): string => 'UUID!'
|
|
12
|
+
export type { GetRecordResult }
|
|
28
13
|
|
|
29
14
|
/**
|
|
30
15
|
* Options for creating a Stonecrop client
|
|
@@ -35,19 +20,20 @@ export interface StonecropClientOptions {
|
|
|
35
20
|
endpoint: string
|
|
36
21
|
/** Additional HTTP headers to include in requests */
|
|
37
22
|
headers?: Record<string, string>
|
|
38
|
-
/** Doctype registry for nested query building */
|
|
39
|
-
registry?: Map<string, DoctypeMeta>
|
|
40
23
|
}
|
|
41
24
|
|
|
42
25
|
/**
|
|
43
|
-
* Client for interacting with Stonecrop GraphQL API
|
|
26
|
+
* Client for interacting with Stonecrop GraphQL API.
|
|
27
|
+
*
|
|
28
|
+
* Acts as a transport layer — it passes requests to the middleware and returns
|
|
29
|
+
* merged results. Does not construct queries itself.
|
|
30
|
+
*
|
|
44
31
|
* @public
|
|
45
32
|
*/
|
|
46
33
|
export class StonecropClient implements DataClient {
|
|
47
34
|
private endpoint: string
|
|
48
35
|
private headers: Record<string, string>
|
|
49
36
|
private metaCache: Map<string, DoctypeMeta> = new Map()
|
|
50
|
-
private registry?: Map<string, DoctypeMeta>
|
|
51
37
|
|
|
52
38
|
constructor(options: StonecropClientOptions) {
|
|
53
39
|
this.endpoint = options.endpoint
|
|
@@ -55,13 +41,14 @@ export class StonecropClient implements DataClient {
|
|
|
55
41
|
'Content-Type': 'application/json',
|
|
56
42
|
...options.headers,
|
|
57
43
|
}
|
|
58
|
-
this.registry = options.registry
|
|
59
44
|
}
|
|
60
45
|
|
|
61
46
|
/**
|
|
62
|
-
* Execute a GraphQL query
|
|
47
|
+
* Execute a GraphQL query against the configured endpoint.
|
|
48
|
+
*
|
|
63
49
|
* @param query - GraphQL query string
|
|
64
50
|
* @param variables - Query variables
|
|
51
|
+
* @throws Error if the GraphQL response contains errors
|
|
65
52
|
*/
|
|
66
53
|
async query<T = unknown>(query: string, variables?: Record<string, unknown>): Promise<T> {
|
|
67
54
|
const response = await fetch(this.endpoint, {
|
|
@@ -83,7 +70,8 @@ export class StonecropClient implements DataClient {
|
|
|
83
70
|
}
|
|
84
71
|
|
|
85
72
|
/**
|
|
86
|
-
* Execute a GraphQL mutation
|
|
73
|
+
* Execute a GraphQL mutation. Delegates to query() since both use POST.
|
|
74
|
+
*
|
|
87
75
|
* @param mutation - GraphQL mutation string
|
|
88
76
|
* @param variables - Mutation variables
|
|
89
77
|
*/
|
|
@@ -207,65 +195,47 @@ export class StonecropClient implements DataClient {
|
|
|
207
195
|
/**
|
|
208
196
|
* Get a single record by ID.
|
|
209
197
|
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
198
|
+
* Routes through the stonecropRecord resolver which handles nested data
|
|
199
|
+
* fetching based on the includeNested option.
|
|
212
200
|
*
|
|
213
201
|
* @param doctype - Doctype reference (name and optional slug)
|
|
214
202
|
* @param recordId - Record ID to fetch
|
|
215
203
|
* @param options - Query options (includeNested, maxDepth)
|
|
216
204
|
*/
|
|
217
|
-
async getRecord(
|
|
218
|
-
doctype: DoctypeRef,
|
|
219
|
-
recordId: string,
|
|
220
|
-
options?: GetRecordOptions
|
|
221
|
-
): Promise<Record<string, unknown> | null> {
|
|
222
|
-
// Nested path: build query with sub-selections
|
|
223
|
-
if (options?.includeNested) {
|
|
224
|
-
const meta = await this.getMeta({ doctype: doctype.name })
|
|
225
|
-
if (!meta) return null
|
|
226
|
-
|
|
227
|
-
const query = buildRecordQuery(
|
|
228
|
-
meta,
|
|
229
|
-
defaultRecordFieldName,
|
|
230
|
-
defaultRecordArgName,
|
|
231
|
-
defaultRecordArgType,
|
|
232
|
-
this.registry,
|
|
233
|
-
options
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
const result = await this.query<Record<string, unknown>>(query, { id: recordId })
|
|
237
|
-
|
|
238
|
-
const queryName = defaultRecordFieldName(meta.tableName || doctype.name)
|
|
239
|
-
const record = result[queryName] as Record<string, unknown> | undefined
|
|
240
|
-
|
|
241
|
-
if (!record) return null
|
|
242
|
-
|
|
243
|
-
if (meta.links && this.registry) {
|
|
244
|
-
return mergeNestedResults(record, meta, this.registry)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return record
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Flat path: original query
|
|
205
|
+
async getRecord(doctype: DoctypeRef, recordId: string, options?: GetRecordOptions): Promise<GetRecordResult> {
|
|
251
206
|
const result = await this.query<{
|
|
252
|
-
stonecropRecord: { data: Record<string, unknown> | null }
|
|
207
|
+
stonecropRecord: { data: Record<string, unknown> | null; unknownLinks?: string[] }
|
|
253
208
|
}>(
|
|
254
|
-
`
|
|
255
|
-
|
|
256
|
-
stonecropRecord(doctype: $doctype, id: $id) {
|
|
209
|
+
`query GetRecord($doctype: String!, $id: String!, $options: JSON) {
|
|
210
|
+
stonecropRecord(doctype: $doctype, id: $id, options: $options) {
|
|
257
211
|
data
|
|
212
|
+
unknownLinks
|
|
258
213
|
}
|
|
214
|
+
}`,
|
|
215
|
+
{
|
|
216
|
+
doctype: doctype.name,
|
|
217
|
+
id: recordId,
|
|
218
|
+
options: options?.includeNested
|
|
219
|
+
? {
|
|
220
|
+
includeNested: options.includeNested,
|
|
221
|
+
maxDepth: options.maxDepth,
|
|
222
|
+
}
|
|
223
|
+
: undefined,
|
|
259
224
|
}
|
|
260
|
-
`,
|
|
261
|
-
{ doctype: doctype.name, id: recordId }
|
|
262
225
|
)
|
|
263
226
|
|
|
264
|
-
return
|
|
227
|
+
return {
|
|
228
|
+
record: result.stonecropRecord?.data ?? null,
|
|
229
|
+
unknownLinks: result.stonecropRecord?.unknownLinks,
|
|
230
|
+
}
|
|
265
231
|
}
|
|
266
232
|
|
|
267
233
|
/**
|
|
268
|
-
* Get multiple records with optional filtering and pagination
|
|
234
|
+
* Get multiple records with optional filtering and pagination.
|
|
235
|
+
*
|
|
236
|
+
* Returns flat arrays — the middleware merges connection format (\{ nodes: [...] \})
|
|
237
|
+
* into plain arrays before returning.
|
|
238
|
+
*
|
|
269
239
|
* @param doctype - Doctype reference (name and optional slug)
|
|
270
240
|
* @param options - Query options (filters, orderBy, limit, offset)
|
|
271
241
|
*/
|
|
@@ -336,63 +306,12 @@ export class StonecropClient implements DataClient {
|
|
|
336
306
|
}
|
|
337
307
|
|
|
338
308
|
/**
|
|
339
|
-
* Clear the cached doctype metadata
|
|
309
|
+
* Clear the cached doctype metadata.
|
|
310
|
+
*
|
|
311
|
+
* Call this if the server-side doctype schema has changed and you need
|
|
312
|
+
* to fetch fresh metadata (e.g., after adding a new field).
|
|
340
313
|
*/
|
|
341
314
|
clearMetaCache(): void {
|
|
342
315
|
this.metaCache.clear()
|
|
343
316
|
}
|
|
344
317
|
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Merge nested connection results into flat arrays.
|
|
348
|
-
*
|
|
349
|
-
* For `noneOrMany`/`atLeastOne` links, the query returns `{ nodes: [...] }`.
|
|
350
|
-
* This flattens them to just `[]` for easier consumption.
|
|
351
|
-
*
|
|
352
|
-
* For `one`/`atMostOne` links, the result is already flat.
|
|
353
|
-
*
|
|
354
|
-
* @internal
|
|
355
|
-
*/
|
|
356
|
-
function mergeNestedResults(
|
|
357
|
-
record: Record<string, unknown>,
|
|
358
|
-
meta: DoctypeMeta,
|
|
359
|
-
registry: Map<string, DoctypeMeta>
|
|
360
|
-
): Record<string, unknown> {
|
|
361
|
-
if (!meta.links) return record
|
|
362
|
-
|
|
363
|
-
const merged = { ...record }
|
|
364
|
-
|
|
365
|
-
for (const [fieldname, link] of Object.entries(meta.links)) {
|
|
366
|
-
const isMany = link.cardinality === 'noneOrMany' || link.cardinality === 'atLeastOne'
|
|
367
|
-
|
|
368
|
-
if (isMany) {
|
|
369
|
-
// Connection result: { nodes: [...] } → flatten to []
|
|
370
|
-
const targetMeta = registry.get(link.target)
|
|
371
|
-
if (!targetMeta) continue
|
|
372
|
-
|
|
373
|
-
const connectionField = getConnectionFieldFromTarget(targetMeta, meta.tableName || '')
|
|
374
|
-
const connectionResult = merged[connectionField] as { nodes?: unknown[] } | undefined
|
|
375
|
-
if (connectionResult?.nodes) {
|
|
376
|
-
merged[fieldname] = connectionResult.nodes
|
|
377
|
-
delete merged[connectionField]
|
|
378
|
-
} else {
|
|
379
|
-
merged[fieldname] = []
|
|
380
|
-
delete merged[connectionField]
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
// 'one'/'atMostOne' links are already at the right fieldname
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return merged
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Derive the connection field name matching the query builder's convention.
|
|
391
|
-
* @internal
|
|
392
|
-
*/
|
|
393
|
-
function getConnectionFieldFromTarget(targetMeta: DoctypeMeta, parentTableName: string): string {
|
|
394
|
-
const targetPlural = pluralize.plural(targetMeta.tableName || '')
|
|
395
|
-
const targetPascal = toPascalCase(targetPlural)
|
|
396
|
-
const fkPascal = toPascalCase(parentTableName) + 'Id'
|
|
397
|
-
return `${targetPascal}By${fkPascal}`
|
|
398
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -1,71 +1,4 @@
|
|
|
1
|
-
import { Decimal } from 'decimal.js'
|
|
2
|
-
import { GraphQLClient } from 'graphql-request'
|
|
3
|
-
|
|
4
|
-
import { queries } from './queries'
|
|
5
|
-
import typeDefs from './gql/schema'
|
|
6
|
-
import type { Meta, MetaParser, MetaResponse } from './types'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Parse the response from the GraphQL server. Converts the stringified JSON to JSON and converts the stringified numbers to Decimal.
|
|
10
|
-
* @param obj - The response from the GraphQL server
|
|
11
|
-
* @returns The parsed response
|
|
12
|
-
* @example
|
|
13
|
-
* const response = '{"data":{"getMeta":{"id":"Issue","name":"Issue","workflow":"{\"machineId\":null,\"name\":\"save\",\"id\":\"1\"}","schema":"[{\"label\":\"Subject\",\"id\":\"1\"}]","actions":"[{\"eventName\":\"save\",\"id\":\"1\"}]"}}}'
|
|
14
|
-
* const parsedResponse = metaParser(response)
|
|
15
|
-
* console.log(parsedResponse)
|
|
16
|
-
* /* Output: {"id": "Issue", "name": "Issue", "workflow": { "machineId": null, "name": "save", "id": "1" }, "schema": [{ "label": "Subject", "id": "1" }], "actions": [{ "eventName": "save", "id": "1" }]}
|
|
17
|
-
*/
|
|
18
|
-
const metaParser = (obj: string): MetaParser => {
|
|
19
|
-
return JSON.parse(obj, (key, value) => {
|
|
20
|
-
if (typeof value === 'string') {
|
|
21
|
-
try {
|
|
22
|
-
return JSON.parse(value, (_key, value) => {
|
|
23
|
-
if (typeof value === 'string' && !isNaN(Number(value))) {
|
|
24
|
-
return new Decimal(value)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return value
|
|
28
|
-
})
|
|
29
|
-
} catch {
|
|
30
|
-
// if the value is not a stringified JSON, return as it is
|
|
31
|
-
return value
|
|
32
|
-
}
|
|
33
|
-
} else if (!isNaN(Number(value))) {
|
|
34
|
-
return new Decimal(value as string | number)
|
|
35
|
-
}
|
|
36
|
-
return value
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get meta information for a doctype
|
|
42
|
-
* @param doctype - The doctype to get meta information for
|
|
43
|
-
* @param url - The URL to send the request to
|
|
44
|
-
* @returns The meta information for the doctype
|
|
45
|
-
* @public
|
|
46
|
-
*/
|
|
47
|
-
const methods = {
|
|
48
|
-
getMeta: async (doctype: string, url?: string): Promise<MetaResponse> => {
|
|
49
|
-
const client = new GraphQLClient(url || '/graphql', {
|
|
50
|
-
fetch: window.fetch,
|
|
51
|
-
jsonSerializer: {
|
|
52
|
-
stringify: obj => JSON.stringify(obj), // process the request object before sending; leave as default JSON
|
|
53
|
-
parse: metaParser, // process the response meta object
|
|
54
|
-
},
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
const { getMeta } = await client.request<Meta['response'], Meta['variables']>({
|
|
58
|
-
document: queries.getMeta,
|
|
59
|
-
variables: { doctype },
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
return getMeta
|
|
63
|
-
},
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export { queries, typeDefs, methods }
|
|
67
|
-
export { StonecropClient } from './client'
|
|
68
|
-
export { buildRecordQuery, buildListQuery } from './query'
|
|
69
|
-
export type { StonecropClientOptions, DoctypeContext } from './client'
|
|
70
|
-
export type { Meta, MetaParser, MetaResponse } from './types'
|
|
71
1
|
export type { DoctypeMeta } from '@stonecrop/schema'
|
|
2
|
+
|
|
3
|
+
export { StonecropClient, type StonecropClientOptions, type DoctypeContext } from './client'
|
|
4
|
+
export type { GetRecordResult } from './types'
|
package/src/types/index.ts
CHANGED
|
@@ -1,48 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file
|
|
2
|
+
* @file Types for the GraphQL client.
|
|
3
3
|
* @public
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
* The type of the response from the `getMeta` query.
|
|
8
|
-
* @public
|
|
9
|
-
*/
|
|
10
|
-
export type Meta = {
|
|
11
|
-
variables: {
|
|
12
|
-
doctype: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
response: {
|
|
16
|
-
getMeta: MetaResponse
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* The type of the response from the `getRecords` query.
|
|
22
|
-
* @public
|
|
23
|
-
*/
|
|
24
|
-
export type MetaResponse = {
|
|
25
|
-
id: string
|
|
26
|
-
name: string
|
|
27
|
-
workflow: {
|
|
28
|
-
id: string
|
|
29
|
-
name: string
|
|
30
|
-
machineId?: string
|
|
31
|
-
}
|
|
32
|
-
schema: {
|
|
33
|
-
id: string
|
|
34
|
-
label: string
|
|
35
|
-
}[]
|
|
36
|
-
actions: {
|
|
37
|
-
id: string
|
|
38
|
-
eventName: string
|
|
39
|
-
}[]
|
|
40
|
-
}
|
|
6
|
+
import type { GetRecordResult as SchemaGetRecordResult } from '@stonecrop/schema'
|
|
41
7
|
|
|
42
8
|
/**
|
|
43
|
-
*
|
|
9
|
+
* Result from getRecord - includes the record data and any unknown links requested
|
|
44
10
|
* @public
|
|
45
11
|
*/
|
|
46
|
-
export
|
|
47
|
-
|
|
12
|
+
export interface GetRecordResult extends SchemaGetRecordResult {
|
|
13
|
+
/** Link names that were requested but don't exist in the doctype schema */
|
|
14
|
+
unknownLinks?: string[]
|
|
48
15
|
}
|