Line data Source code
1 : /// The spec of client functions, see docs/design/client_functions.md. The core reads the signatures of the same document.
2 :
3 : /// An option of a function or an attribute of a schema
4 : export interface ClientFunctionAttribute {
5 : /// The name in a script
6 : name: string;
7 : /// The spelling of the API, the name if absent
8 : api_name?: string;
9 : /// Options only: `path`, `query`, `header`, `body` or `body_root`. A path option fills the placeholder of its `api_name`.
10 : in?: string;
11 : /// The type of a scalar
12 : type?: string;
13 : /// The schema of an object
14 : schema?: string;
15 : /// Is the value a map? Its keys are free, the schema holds for its values.
16 : map?: boolean;
17 : /// Is the option required?
18 : required?: boolean;
19 : /// Query options with arrays or objects, `repeat` by default
20 : style?: 'repeat' | 'csv' | 'deep';
21 : /// Path options whose value may contain `/`
22 : multi_segment?: boolean;
23 : }
24 :
25 : /// A named object, or a union of named objects
26 : export interface ClientFunctionSchema {
27 : attributes?: ClientFunctionAttribute[];
28 : /// A union: the schemas that a value can be. A name means the same in all of them.
29 : variants?: string[];
30 : }
31 :
32 : /// A result column
33 : export interface ClientFunctionColumn {
34 : /// The name in a script
35 : name: string;
36 : /// The spelling of the API, the name if absent
37 : api_name?: string;
38 : /// VARCHAR, TIMESTAMPTZ, DATE, INTEGER, BIGINT, DOUBLE, BOOLEAN or JSON
39 : type?: string;
40 : }
41 :
42 : /// When a list of pages ends
43 : export type ClientFunctionPagingEnd = 'empty_page' | 'short_page' | { total: string } | { has_more: string };
44 :
45 : /// Where a value of a response is
46 : export type ClientFunctionValueSource =
47 : /// A JSON pointer into the body
48 : | { body: string }
49 : /// A response header
50 : | { header: string }
51 : /// The entry with this `rel` of a `Link` header
52 : | { link: string }
53 : /// A JSON pointer into the last row of the page
54 : | { last_row: string };
55 :
56 : /// How a function pages
57 : export interface ClientFunctionPaging {
58 : kind: 'url' | 'token' | 'offset' | 'page';
59 : /// Where the value is
60 : from?: ClientFunctionValueSource;
61 : /// The option that takes the value
62 : option?: string;
63 : /// The option with the page size
64 : size?: string;
65 : /// The first offset or page
66 : start?: number;
67 : /// When the list ends
68 : end?: ClientFunctionPagingEnd;
69 : }
70 :
71 : /// How a function answers before it finishes
72 : export interface ClientFunctionPending {
73 : /// The status that means not finished
74 : status: number;
75 : /// The header with the URL to poll, or a repeat of the request
76 : poll: { header: string } | { repeat: true };
77 : }
78 :
79 : /// How a function reports errors
80 : export interface ClientFunctionErrors {
81 : /// JSON pointers to try for the error message
82 : message?: string[];
83 : /// A pointer that fails the statement when it's present, for a 200 that carries errors
84 : error_if?: string;
85 : /// The statuses that mean an empty result
86 : empty_on?: number[];
87 : /// Is the message a JSON string?
88 : message_is_json?: boolean;
89 : }
90 :
91 : /// A client function
92 : export interface ClientFunction {
93 : name: string;
94 : /// A grouping for documents and lists
95 : family?: string;
96 : /// A call that completion shows
97 : example?: string;
98 : method?: string;
99 : host?: string;
100 : path?: string;
101 : options?: ClientFunctionAttribute[];
102 : /// The format of the body: `json` by default, `form`, or a media type
103 : body?: string;
104 : rows?: string;
105 : columns?: ClientFunctionColumn[];
106 : paging?: ClientFunctionPaging;
107 : /// Overrides the default of the spec, null for none
108 : pending?: ClientFunctionPending | null;
109 : errors?: ClientFunctionErrors;
110 : effect?: 'read' | 'write';
111 : /// The escape hatch: takes `method`, `path`, `host` and `body`
112 : raw?: boolean;
113 : }
114 :
115 : /// A host that functions name, the binding of the connection resolves it
116 : export interface ClientFunctionHost {
117 : base_path?: string;
118 : }
119 :
120 : /// The spec of client functions
121 : export interface ClientFunctionSpec {
122 : format: number;
123 : namespace: string;
124 : hosts?: Record<string, ClientFunctionHost>;
125 : errors?: ClientFunctionErrors;
126 : pending?: ClientFunctionPending | null;
127 : schemas?: Record<string, ClientFunctionSchema>;
128 : functions: ClientFunction[];
129 : }
130 :
131 : /// A value that the runtime fills in
132 : export interface ClientFunctionCallBinding {
133 : /// A JSON pointer into the options
134 : pointer: string;
135 : /// The name of the parameter
136 : param: string;
137 : }
138 :
139 : /// A call as the core describes it, `ClientFunctionCall.description` of the analyzed script
140 : export interface ClientFunctionCallDescription {
141 : namespace: string;
142 : function: string;
143 : effect: 'read' | 'write';
144 : explain: boolean;
145 : options: Record<string, unknown>;
146 : bindings?: ClientFunctionCallBinding[];
147 : }
148 :
149 : /// A call that failed, the status is set if a service answered
150 1 : export class ClientFunctionError extends Error {
151 1 : constructor(message: string, public readonly status: number | null = null) {
152 1 : super(message);
153 1 : this.name = 'ClientFunctionError';
154 1 : }
155 1 : }
156 :
157 : /// The function of a call
158 1 : export function findClientFunction(spec: ClientFunctionSpec, name: string): ClientFunction {
159 1 : const fn = spec.functions.find(f => f.name === name);
160 1 : if (fn === undefined) {
161 0 : throw new ClientFunctionError(`${spec.namespace} has no function ${name}`);
162 : }
163 1 : return fn;
164 1 : }
165 :
166 : /// The pending rule of a function
167 1 : export function pendingOf(spec: ClientFunctionSpec, fn: ClientFunction): ClientFunctionPending | null {
168 1 : return fn.pending === undefined ? (spec.pending ?? null) : fn.pending;
169 1 : }
170 :
171 : /// The segments of a JSON pointer
172 1 : export function jsonPointerSegments(pointer: string): string[] {
173 1 : return pointer.split('/').slice(1).map(segment => segment.replace(/~1/g, '/').replace(/~0/g, '~'));
174 1 : }
175 :
176 : /// Read a JSON pointer, undefined if the path doesn't exist
177 1 : export function readJsonPointer(value: unknown, pointer: string): unknown {
178 1 : if (pointer === '') {
179 1 : return value;
180 : }
181 1 : let current = value;
182 1 : for (const segment of jsonPointerSegments(pointer)) {
183 1 : if (Array.isArray(current)) {
184 1 : current = current[Number(segment)];
185 1 : } else if (typeof current === 'object' && current !== null) {
186 1 : current = (current as Record<string, unknown>)[segment];
187 : } else {
188 1 : return undefined;
189 : }
190 : }
191 1 : return current;
192 1 : }
|