LCOV - code coverage report
Current view: top level - dashql-app/src/connections/clfn - clfn_spec.ts Coverage Total Hit
Test: DashQL d805a4b Lines: 96.7 % 30 29
Test Date: 2026-11-09 20:22:13

            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 : }
        

Generated by: LCOV version 2.0-1