@seliseblocks/client 0.2.0 → 0.3.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.
@@ -289,13 +289,13 @@ export class BlocksDataClient {
289
289
  */
290
290
  create: (payload) => this.graphql({
291
291
  operationName: createField,
292
- query: `mutation ${createField}($input: ${schemaName}InsertInput!) {
293
- ${createField}(input: $input) {
294
- acknowledged
295
- itemId
296
- message
297
- totalImpactedData
298
- }
292
+ query: `mutation ${createField}($input: ${schemaName}InsertInput!) {
293
+ ${createField}(input: $input) {
294
+ acknowledged
295
+ itemId
296
+ message
297
+ totalImpactedData
298
+ }
299
299
  }`,
300
300
  variables: { input: payload }
301
301
  }),
@@ -306,13 +306,13 @@ export class BlocksDataClient {
306
306
  */
307
307
  delete: (itemId, deleteOptions = {}) => this.graphql({
308
308
  operationName: deleteField,
309
- query: `mutation ${deleteField}($filter: String, $input: ${schemaName}DeleteInput!) {
310
- ${deleteField}(filter: $filter, input: $input) {
311
- acknowledged
312
- itemId
313
- message
314
- totalImpactedData
315
- }
309
+ query: `mutation ${deleteField}($filter: String, $input: ${schemaName}DeleteInput!) {
310
+ ${deleteField}(filter: $filter, input: $input) {
311
+ acknowledged
312
+ itemId
313
+ message
314
+ totalImpactedData
315
+ }
316
316
  }`,
317
317
  variables: {
318
318
  filter: itemIdFilter(itemId),
@@ -354,13 +354,13 @@ export class BlocksDataClient {
354
354
  */
355
355
  update: (itemId, payload) => this.graphql({
356
356
  operationName: updateField,
357
- query: `mutation ${updateField}($filter: String, $input: ${schemaName}UpdateInput!) {
358
- ${updateField}(filter: $filter, input: $input) {
359
- acknowledged
360
- itemId
361
- message
362
- totalImpactedData
363
- }
357
+ query: `mutation ${updateField}($filter: String, $input: ${schemaName}UpdateInput!) {
358
+ ${updateField}(filter: $filter, input: $input) {
359
+ acknowledged
360
+ itemId
361
+ message
362
+ totalImpactedData
363
+ }
364
364
  }`,
365
365
  variables: {
366
366
  filter: itemIdFilter(itemId),
@@ -472,18 +472,18 @@ function dynamicQueryInput(options) {
472
472
  };
473
473
  }
474
474
  function collectionListQuery(fieldName, selection) {
475
- return `query ${fieldName}($input: DynamicQueryInput) {
476
- ${fieldName}(input: $input) {
477
- items {
478
- ${indent(selection, 6)}
479
- }
480
- totalCount
481
- pageNo
482
- pageSize
483
- totalPages
484
- hasNextPage
485
- hasPreviousPage
486
- }
475
+ return `query ${fieldName}($input: DynamicQueryInput) {
476
+ ${fieldName}(input: $input) {
477
+ items {
478
+ ${indent(selection, 6)}
479
+ }
480
+ totalCount
481
+ pageNo
482
+ pageSize
483
+ totalPages
484
+ hasNextPage
485
+ hasPreviousPage
486
+ }
487
487
  }`;
488
488
  }
489
489
  function selectionFor(fields) {
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Types for code that runs INSIDE a Blocks function -- the sandbox contract the runner's
3
+ * bootstrap hands every handler. Type-only: a function's own package.json does not need this
4
+ * package at runtime. Use it as a devDependency for editor completion and checking:
5
+ *
6
+ * ```js
7
+ * // index.js (the function's package.json declares "type": "module")
8
+ * /** @type {import("@seliseblocks/client/functions").FunctionHandler} *\/
9
+ * export default async function handler(input, ctx) {
10
+ * ctx.log.info("called", { path: input.path });
11
+ * return { ok: true };
12
+ * }
13
+ * ```
14
+ *
15
+ * Runtime: Node 24, ES modules. Every run gets the same fixed profile -- 100 millicores,
16
+ * 128 MB, 30 s wall clock, 10 concurrent runs per function, one retry after 5 s.
17
+ */
18
+ /** HTTP verbs a trigger may accept. */
19
+ export type FunctionHttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
20
+ /**
21
+ * What an HTTP or Test invocation hands the handler. Anything after `/fn/{id}` is `path`; the
22
+ * body arrives parsed when it is JSON. A workflow step passes the previous step's output
23
+ * instead of this shape, so a handler used from both should check `input.method`.
24
+ */
25
+ export interface FunctionInput {
26
+ /** One of the trigger's accepted methods -- any other is refused with 405 before the handler runs. */
27
+ readonly method: FunctionHttpMethod;
28
+ /** `orders/42` for a call to `…/fn/{id}/orders/42`; `""` for the root. */
29
+ readonly path: string;
30
+ /** A repeated key is an array. */
31
+ readonly query: Readonly<Record<string, string | string[]>>;
32
+ /** Lower-cased. Content negotiation, provenance and webhook signatures -- never a credential. */
33
+ readonly headers: Readonly<Record<string, string>>;
34
+ /** Parsed JSON, the raw text for other content types, null for no body (always null on GET). */
35
+ readonly body: unknown;
36
+ /**
37
+ * The exact request bytes, base64 -- what a webhook signature is computed over. Null with no
38
+ * body, or when the body is too large to carry twice.
39
+ */
40
+ readonly rawBody: string | null;
41
+ }
42
+ /** Who invoked this run. An empty identity on a public call -- never a privileged token. */
43
+ export interface FunctionCallerContext {
44
+ readonly tenantId: string;
45
+ /** null when the trigger is public. */
46
+ readonly userId: string | null;
47
+ readonly roles: readonly string[];
48
+ readonly permissions: readonly string[];
49
+ readonly isAuthenticated: boolean;
50
+ }
51
+ /** Calling Blocks APIs as the caller. */
52
+ export interface FunctionBlocksAccess {
53
+ /**
54
+ * A fresh, short-lived Blocks access token for the user who invoked this run -- pass it as the
55
+ * `Authorization: Bearer` of a Blocks API call, with `ctx.context.tenantId` as `x-blocks-key`.
56
+ * Fetched on demand (one IAM round trip per run; later calls in the same run get the same
57
+ * token). Resolves to `undefined` when there is no signed-in caller (a public trigger, a
58
+ * scheduled workflow) or none could be issued. Masked in logs.
59
+ */
60
+ getAccessToken(): Promise<string | undefined>;
61
+ }
62
+ /**
63
+ * The function's variables, as plain strings, snapshotted at deploy. A variable bound to a
64
+ * secret (`{{secret.<id>}}`) arrives as its real value and is masked in logs.
65
+ */
66
+ export interface FunctionEnv {
67
+ readonly [key: string]: string | undefined;
68
+ }
69
+ /** This run's own metadata. */
70
+ export interface FunctionRun {
71
+ readonly id: string;
72
+ /** Active version number this run executed on. */
73
+ readonly version: number;
74
+ /** 1 for the first try; 2 when the retry re-ran it. */
75
+ readonly attempt: number;
76
+ readonly invokedBy: {
77
+ readonly type: "http" | "workflow" | "test" | "replay";
78
+ /** What started the run (e.g. the workflow), or null when there is none. */
79
+ readonly id: string | null;
80
+ };
81
+ }
82
+ /** Structured log lines kept on the run (`blocks logic function logs <runId>`); `console` is captured too. */
83
+ export interface FunctionLogger {
84
+ debug(message: string, data?: unknown): void;
85
+ info(message: string, data?: unknown): void;
86
+ warn(message: string, data?: unknown): void;
87
+ error(message: string, data?: unknown): void;
88
+ }
89
+ /**
90
+ * Everything the sandbox hands a function, fresh for every call.
91
+ *
92
+ * HTTP calls of a deployed function reuse a warm sandbox: module-level code runs once and its
93
+ * variables live on to the next call -- maybe another user's. Keep connections and caches at
94
+ * module level; keep request data (input, ctx, tokens) inside the handler. Await everything, or
95
+ * hand work that may finish after the answer to `waitUntil`.
96
+ */
97
+ export interface FunctionContext {
98
+ readonly context: FunctionCallerContext;
99
+ readonly blocks: FunctionBlocksAccess;
100
+ readonly env: FunctionEnv;
101
+ readonly run: FunctionRun;
102
+ readonly log: FunctionLogger;
103
+ /**
104
+ * Finish work after the answer is sent: the caller gets the result at once, the promise runs
105
+ * on (within the time limit) and the sandbox is not reused until it settles. Use it instead of
106
+ * a promise you do not await -- work still running after the answer makes the sandbox be replaced.
107
+ */
108
+ waitUntil(promise: Promise<unknown>): void;
109
+ }
110
+ /**
111
+ * Return this from the handler to control the HTTP answer when the trigger's response mode is
112
+ * `sync`. Anything else you return is sent as 200 + JSON.
113
+ *
114
+ * - `statusCode` 200-599 (1xx is refused). 204/205/304 never carry a body.
115
+ * - `headers`: only content and caching headers pass (content-type, content-language,
116
+ * content-disposition, cache-control, expires, last-modified, etag, vary, retry-after),
117
+ * `location` on a 3xx to an http(s) or relative URL, and `x-*` (except x-forwarded-*,
118
+ * x-real-ip, x-original-*, x-blocks-*). `set-cookie` is always dropped.
119
+ * - `body`: a string is sent as is; anything else as JSON.
120
+ *
121
+ * A failed run answers 502, a timed-out one 504 (`{ error, runId }`). A call that takes too
122
+ * long gets 202 + a poll token instead -- the run still finishes.
123
+ */
124
+ export interface FunctionHttpResponse {
125
+ statusCode: number;
126
+ headers?: Record<string, string | string[]>;
127
+ body?: unknown;
128
+ }
129
+ /**
130
+ * The module's default export. `input` and `ctx` exist only inside the handler: top-level code
131
+ * runs once when the sandbox starts (the place for clients, caches and constants), and touching
132
+ * `ctx` there fails the run before the handler is called. An `async function*` handler streams
133
+ * each yielded piece to the caller.
134
+ */
135
+ export type FunctionHandler<TResult = unknown> = (input: FunctionInput, ctx: FunctionContext) => TResult | Promise<TResult> | AsyncIterable<unknown>;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Types for code that runs INSIDE a Blocks function -- the sandbox contract the runner's
3
+ * bootstrap hands every handler. Type-only: a function's own package.json does not need this
4
+ * package at runtime. Use it as a devDependency for editor completion and checking:
5
+ *
6
+ * ```js
7
+ * // index.js (the function's package.json declares "type": "module")
8
+ * /** @type {import("@seliseblocks/client/functions").FunctionHandler} *\/
9
+ * export default async function handler(input, ctx) {
10
+ * ctx.log.info("called", { path: input.path });
11
+ * return { ok: true };
12
+ * }
13
+ * ```
14
+ *
15
+ * Runtime: Node 24, ES modules. Every run gets the same fixed profile -- 100 millicores,
16
+ * 128 MB, 30 s wall clock, 10 concurrent runs per function, one retry after 5 s.
17
+ */
18
+ export {};
@@ -16,8 +16,32 @@ function buildMessage(status, statusText, body) {
16
16
  const message = record.message ?? record.detail ?? record.error_description ?? record.error;
17
17
  if (typeof message === "string" && message)
18
18
  return `Blocks API ${status} ${statusText}: ${message}`;
19
+ // Validation bodies (ASP.NET problem details and Blocks `errors` maps) carry the reason only in
20
+ // `errors` / `title`; without this the message would be the bare status line.
21
+ const details = describeErrors(record.errors ?? record.Errors);
22
+ const title = typeof record.title === "string" ? record.title : "";
23
+ if (details)
24
+ return `Blocks API ${status} ${statusText}: ${details}`;
25
+ if (title)
26
+ return `Blocks API ${status} ${statusText}: ${title}`;
19
27
  }
20
28
  if (typeof body === "string" && body)
21
29
  return `Blocks API ${status} ${statusText}: ${body}`;
22
30
  return `Blocks API ${status} ${statusText}`;
23
31
  }
32
+ const MAX_ERROR_ENTRIES = 5;
33
+ /** `{ Field: ["a", "b"] }` or `{ Field: "a" }` → `"Field: a, b; Other: c"` (first entries only). */
34
+ function describeErrors(errors) {
35
+ if (typeof errors !== "object" || !errors || Array.isArray(errors))
36
+ return "";
37
+ const parts = [];
38
+ for (const [field, value] of Object.entries(errors)) {
39
+ const texts = (Array.isArray(value) ? value : [value]).filter((text) => typeof text === "string" && text.length > 0);
40
+ if (texts.length === 0)
41
+ continue;
42
+ parts.push(field ? `${field}: ${texts.join(", ")}` : texts.join(", "));
43
+ if (parts.length === MAX_ERROR_ENTRIES)
44
+ break;
45
+ }
46
+ return parts.join("; ");
47
+ }
@@ -7,5 +7,16 @@ export declare class BlocksHttpClient {
7
7
  private readonly fetchImpl;
8
8
  constructor(config: BlocksResolvedConfig, auth: BlocksAuthenticationClient, fetchImpl?: typeof fetch);
9
9
  request<T>(path: string, options?: BlocksRequestOptions): Promise<T>;
10
+ /**
11
+ * What: sends a Blocks API request and returns the raw `Response`, unread.
12
+ * Why: some answers carry meaning in the status and headers rather than only the
13
+ * body -- a function invocation's 202 vs. its synchronous answer, `Retry-After`,
14
+ * a streamed body -- which `request` reduces to parsed JSON.
15
+ * How: same credentials, `x-blocks-key`, same-origin guard and one-shot
16
+ * `onUnauthorized` retry as `request`; never throws on a non-2xx status. `Accept`
17
+ * is left to the caller's headers.
18
+ */
19
+ send(path: string, options?: BlocksRequestOptions): Promise<Response>;
10
20
  external<T = unknown>(url: string, options?: BlocksExternalRequestOptions): Promise<T>;
11
21
  }
22
+ export declare function parseBody(response: Response): Promise<unknown>;
@@ -11,11 +11,26 @@ export class BlocksHttpClient {
11
11
  throw new Error("Blocks client requires fetch.");
12
12
  }
13
13
  async request(path, options = {}) {
14
+ const response = await this.send(path, { ...options, headers: withJsonAccept(options.headers) });
15
+ const body = await parseBody(response);
16
+ if (!response.ok)
17
+ throw new BlocksApiError(response.status, response.statusText, body);
18
+ return body;
19
+ }
20
+ /**
21
+ * What: sends a Blocks API request and returns the raw `Response`, unread.
22
+ * Why: some answers carry meaning in the status and headers rather than only the
23
+ * body -- a function invocation's 202 vs. its synchronous answer, `Retry-After`,
24
+ * a streamed body -- which `request` reduces to parsed JSON.
25
+ * How: same credentials, `x-blocks-key`, same-origin guard and one-shot
26
+ * `onUnauthorized` retry as `request`; never throws on a non-2xx status. `Accept`
27
+ * is left to the caller's headers.
28
+ */
29
+ async send(path, options = {}) {
14
30
  const url = buildUrl(this.config.apiUrl, path, options.query);
15
31
  const rawBody = isBodyInit(options.body) ? options.body : undefined;
16
- const send = async (accessTokenOverride) => {
32
+ const attempt = async (accessTokenOverride) => {
17
33
  const headers = new Headers(options.headers);
18
- headers.set("Accept", "application/json");
19
34
  headers.set("x-blocks-key", this.config.xBlocksKey);
20
35
  if (options.body !== undefined && !rawBody && !headers.has("Content-Type")) {
21
36
  headers.set("Content-Type", "application/json");
@@ -31,10 +46,11 @@ export class BlocksHttpClient {
31
46
  // without this the browser never sends it back on subsequent Blocks API calls.
32
47
  credentials: "include",
33
48
  headers,
34
- method: options.method ?? (options.body === undefined ? "GET" : "POST")
49
+ method: options.method ?? (options.body === undefined ? "GET" : "POST"),
50
+ signal: options.signal
35
51
  });
36
52
  };
37
- let response = await send();
53
+ let response = await attempt();
38
54
  // A 401 on a token the caller supplied directly (options.accessToken) is the
39
55
  // caller's problem to resolve, not ours. Otherwise, give the app one chance to
40
56
  // recover through its own onUnauthorized hook (expected to dedupe concurrent
@@ -42,12 +58,9 @@ export class BlocksHttpClient {
42
58
  if (response.status === 401 && options.auth !== false && options.accessToken === undefined && this.config.onUnauthorized) {
43
59
  const freshToken = await this.config.onUnauthorized();
44
60
  if (freshToken)
45
- response = await send(freshToken);
61
+ response = await attempt(freshToken);
46
62
  }
47
- const body = await parseBody(response);
48
- if (!response.ok)
49
- throw new BlocksApiError(response.status, response.statusText, body);
50
- return body;
63
+ return response;
51
64
  }
52
65
  async external(url, options = {}) {
53
66
  const response = await this.fetchImpl(url, {
@@ -78,6 +91,12 @@ function sameOriginOnly(path, baseUrl) {
78
91
  }
79
92
  return target;
80
93
  }
94
+ /** `request` always asks for JSON, as it did before `send` existed. */
95
+ function withJsonAccept(headers) {
96
+ const merged = new Headers(headers);
97
+ merged.set("Accept", "application/json");
98
+ return merged;
99
+ }
81
100
  function isBodyInit(value) {
82
101
  return typeof Blob !== "undefined" && value instanceof Blob
83
102
  || typeof FormData !== "undefined" && value instanceof FormData
@@ -91,12 +110,19 @@ function buildUrl(baseUrl, path, query) {
91
110
  ? sameOriginOnly(path, baseUrl)
92
111
  : new URL(path.replace(/^\/+/, ""), `${baseUrl}/`);
93
112
  for (const [key, value] of Object.entries(query ?? {})) {
94
- if (value !== undefined)
113
+ if (value === undefined)
114
+ continue;
115
+ if (Array.isArray(value)) {
116
+ for (const item of value)
117
+ url.searchParams.append(key, String(item));
118
+ }
119
+ else {
95
120
  url.searchParams.set(key, String(value));
121
+ }
96
122
  }
97
123
  return url.toString();
98
124
  }
99
- async function parseBody(response) {
125
+ export async function parseBody(response) {
100
126
  if (response.status === 204)
101
127
  return undefined;
102
128
  const text = await response.text();
@@ -1,5 +1,5 @@
1
1
  import { BlocksHttpClient } from "../http/http-client.js";
2
- import { BlocksBaseResponse, BlocksGetMyOrganizationsResponse, BlocksGetOrganizationResponse, BlocksGetOrganizationsResponse, BlocksGetPermissionResponse, BlocksGetPermissionsResponse, BlocksGetRoleResponse, BlocksGetRolesResponse, BlocksGetUserResponse, BlocksGetUsersResponse, BlocksIamListRequest, BlocksMeResponse } from "./types.js";
2
+ import { BlocksActivateUserRequest, BlocksBaseResponse, BlocksGetMyOrganizationsResponse, BlocksGetOrganizationResponse, BlocksGetOrganizationsResponse, BlocksGetPermissionResponse, BlocksGetPermissionsResponse, BlocksGetRoleResponse, BlocksGetRolesResponse, BlocksGetUserResponse, BlocksGetUsersResponse, BlocksIamListRequest, BlocksMeResponse } from "./types.js";
3
3
  export declare class BlocksIAMClient {
4
4
  private readonly http;
5
5
  constructor(http: BlocksHttpClient);
@@ -36,9 +36,10 @@ export declare class BlocksIAMClient {
36
36
  /**
37
37
  * What: activates a user through `POST /iam/v4/iam/users/activate`.
38
38
  * Why: admin UIs need to restore access for a previously inactive account.
39
- * How: pass IAM's activate payload for the target user.
39
+ * How: pass `userId` and a non-empty `reason` -- IAM requires the reason (deactivate does
40
+ * not take one) and answers 400 without it.
40
41
  */
41
- activate: (request: Record<string, unknown>) => Promise<BlocksBaseResponse>;
42
+ activate: (request: BlocksActivateUserRequest) => Promise<BlocksBaseResponse>;
42
43
  /**
43
44
  * What: queries users through `POST /iam/v4/iam/users`.
44
45
  * Why: IAM uses a POST-read contract for paging/filtering user lists.
@@ -66,18 +67,23 @@ export declare class BlocksIAMClient {
66
67
  */
67
68
  revokeAccess: (request: Record<string, unknown>) => Promise<BlocksBaseResponse>;
68
69
  /**
69
- * What: checks email availability through `GET /iam/v4/iam/email/available`.
70
+ * What: checks email availability through `POST /iam/v4/iam/email/available`.
70
71
  * Why: signup and invite forms can validate duplicate email state before submit.
71
- * How: pass the email query fields IAM expects; this is public but still sends `x-blocks-key`.
72
- */
73
- emailAvailable: (query: Record<string, string | undefined>) => Promise<{
72
+ * How: pass `{ email }` as a signed-in user whose role has `blocks-iam::iam::email-available` -- IAM
73
+ * protects it since 2026-10-14 (it was public, and it reveals whether an address is registered), so a
74
+ * pre-login signup form cannot call it. The address travels in the body; IAM deprecated the GET form.
75
+ */
76
+ emailAvailable: (query: {
77
+ email?: string;
78
+ Email?: string;
79
+ } & Record<string, string | undefined>) => Promise<{
74
80
  isAvailable?: boolean;
75
81
  IsAvailable?: boolean;
76
82
  }>;
77
83
  /**
78
- * What: checks whether a user exists through `GET /iam/v4/iam/users/exists`.
84
+ * What: checks whether a user exists through `POST /iam/v4/iam/users/exists`.
79
85
  * Why: account and invite flows may need an existence check by email.
80
- * How: pass the email string; the SDK sends it as the `email` query parameter.
86
+ * How: pass the email string; it is sent in the body (IAM deprecated the GET form).
81
87
  */
82
88
  exists: (email: string) => Promise<unknown>;
83
89
  };
@@ -181,9 +187,11 @@ export declare class BlocksIAMClient {
181
187
  /**
182
188
  * What: lists organizations through `GET /iam/v4/iam/organizations`.
183
189
  * Why: admin UIs need organization browsing and selection.
184
- * How: pass IAM-supported query fields such as paging/search options.
190
+ * How: pass IAM's query fields -- `Page` (from 0), `PageSize`, `Filter.Search`, `Filter.IsDisabled`,
191
+ * `Filter.ParentOrganizationId`, `Filter.Ids` (an array). IAM refuses any `Filter.*` field unless
192
+ * `Filter.Search` is a non-empty term; to filter without a search, filter the returned list instead.
185
193
  */
186
- list: (query?: Record<string, string | number | boolean | undefined>) => Promise<BlocksGetOrganizationsResponse>;
194
+ list: (query?: Record<string, string | number | boolean | ReadonlyArray<string> | undefined>) => Promise<BlocksGetOrganizationsResponse>;
187
195
  /**
188
196
  * What: reads one organization through `GET /iam/v4/iam/organizations/{id}`.
189
197
  * Why: organization detail screens need one organization record.
@@ -40,7 +40,8 @@ export class BlocksIAMClient {
40
40
  /**
41
41
  * What: activates a user through `POST /iam/v4/iam/users/activate`.
42
42
  * Why: admin UIs need to restore access for a previously inactive account.
43
- * How: pass IAM's activate payload for the target user.
43
+ * How: pass `userId` and a non-empty `reason` -- IAM requires the reason (deactivate does
44
+ * not take one) and answers 400 without it.
44
45
  */
45
46
  activate: (request) => this.http.request("/iam/v4/iam/users/activate", { body: request }),
46
47
  /**
@@ -70,20 +71,21 @@ export class BlocksIAMClient {
70
71
  */
71
72
  revokeAccess: (request) => this.http.request("/iam/v4/iam/users/revoke-access", { body: request }),
72
73
  /**
73
- * What: checks email availability through `GET /iam/v4/iam/email/available`.
74
+ * What: checks email availability through `POST /iam/v4/iam/email/available`.
74
75
  * Why: signup and invite forms can validate duplicate email state before submit.
75
- * How: pass the email query fields IAM expects; this is public but still sends `x-blocks-key`.
76
+ * How: pass `{ email }` as a signed-in user whose role has `blocks-iam::iam::email-available` -- IAM
77
+ * protects it since 2026-10-14 (it was public, and it reveals whether an address is registered), so a
78
+ * pre-login signup form cannot call it. The address travels in the body; IAM deprecated the GET form.
76
79
  */
77
80
  emailAvailable: (query) => this.http.request("/iam/v4/iam/email/available", {
78
- auth: false,
79
- query
81
+ body: { email: query.email ?? query.Email }
80
82
  }),
81
83
  /**
82
- * What: checks whether a user exists through `GET /iam/v4/iam/users/exists`.
84
+ * What: checks whether a user exists through `POST /iam/v4/iam/users/exists`.
83
85
  * Why: account and invite flows may need an existence check by email.
84
- * How: pass the email string; the SDK sends it as the `email` query parameter.
86
+ * How: pass the email string; it is sent in the body (IAM deprecated the GET form).
85
87
  */
86
- exists: (email) => this.http.request("/iam/v4/iam/users/exists", { query: { email } })
88
+ exists: (email) => this.http.request("/iam/v4/iam/users/exists", { body: { email } })
87
89
  };
88
90
  permissions = {
89
91
  /**
@@ -185,7 +187,9 @@ export class BlocksIAMClient {
185
187
  /**
186
188
  * What: lists organizations through `GET /iam/v4/iam/organizations`.
187
189
  * Why: admin UIs need organization browsing and selection.
188
- * How: pass IAM-supported query fields such as paging/search options.
190
+ * How: pass IAM's query fields -- `Page` (from 0), `PageSize`, `Filter.Search`, `Filter.IsDisabled`,
191
+ * `Filter.ParentOrganizationId`, `Filter.Ids` (an array). IAM refuses any `Filter.*` field unless
192
+ * `Filter.Search` is a non-empty term; to filter without a search, filter the returned list instead.
189
193
  */
190
194
  list: (query = {}) => this.http.request("/iam/v4/iam/organizations", { query }),
191
195
  /**
@@ -1,3 +1,9 @@
1
+ /** `POST /iam/v4/iam/users/activate`. IAM requires both fields. */
2
+ export type BlocksActivateUserRequest = {
3
+ userId: string;
4
+ /** Why the account is re-enabled. Required by IAM; an empty value is refused. */
5
+ reason: string;
6
+ };
1
7
  export type BlocksBaseResponse = Record<string, unknown> & {
2
8
  errors?: unknown;
3
9
  isSuccess?: boolean;
package/dist/index.d.ts CHANGED
@@ -8,14 +8,18 @@ export { BlocksApiError } from "./http/errors.js";
8
8
  export type { BlocksHttpClient } from "./http/http-client.js";
9
9
  export { BlocksIAMClient } from "./iam/iam-client.js";
10
10
  export { BlocksLocalizationClient } from "./localization/localization-client.js";
11
+ export { BlocksLogicClient } from "./logic/logic-client.js";
12
+ export { BlocksFunctionRunError, BlocksFunctionsClient } from "./logic/functions-client.js";
13
+ export { BlocksProxyClient } from "./logic/proxy-client.js";
11
14
  export { BlocksMailClient } from "./mail/mail-client.js";
12
15
  export { BlocksMfaClient } from "./mfa/mfa-client.js";
13
16
  export { BlocksNotifierClient } from "./notifier/notifier-client.js";
14
17
  export type { BlocksAuthJsonOptions, BlocksAuthPassThroughResponse, BlocksAuthResponse, BlocksClientCredentialsRequest, BlocksAuthLoginRequest, BlocksIdpCallbackRequest, BlocksIdpInitiateRequest, BlocksIdpInitiateResponse, BlocksLoginOption, BlocksLogoutRequest, BlocksOidcRefreshRequest, BlocksOidcUserInfo, BlocksRefreshRequest, BlocksSocialLoginRequest, BlocksSwitchOrganizationRequest } from "./auth/auth-client.js";
15
18
  export type { BlocksExternalRequestOptions, BlocksRequestOptions } from "./types.js";
16
- export type { BlocksBaseResponse, BlocksGetMyOrganizationsResponse, BlocksGetOrganizationResponse, BlocksGetOrganizationsResponse, BlocksGetPermissionResponse, BlocksGetPermissionsResponse, BlocksGetRoleResponse, BlocksGetRolesResponse, BlocksGetUserResponse, BlocksGetUsersResponse, BlocksIamListRequest, BlocksMeResponse, BlocksOrganization, BlocksPermission, BlocksQueryListResponse, BlocksQueryResponse, BlocksRole, BlocksUser } from "./iam/types.js";
19
+ export type { BlocksActivateUserRequest, BlocksBaseResponse, BlocksGetMyOrganizationsResponse, BlocksGetOrganizationResponse, BlocksGetOrganizationsResponse, BlocksGetPermissionResponse, BlocksGetPermissionsResponse, BlocksGetRoleResponse, BlocksGetRolesResponse, BlocksGetUserResponse, BlocksGetUsersResponse, BlocksIamListRequest, BlocksMeResponse, BlocksOrganization, BlocksPermission, BlocksQueryListResponse, BlocksQueryResponse, BlocksRole, BlocksUser } from "./iam/types.js";
17
20
  export type { BlocksDataCollection, BlocksDataCollectionOptions, BlocksDataDeleteOptions, BlocksDataGetOptions, BlocksDataListOptions, BlocksDataSchema, BlocksDirectoryCreateRequest, BlocksDirectoryDeleteRequest, BlocksDirectoryMoveRequest, BlocksDirectoryUpdateRequest, BlocksFileCopyRequest, BlocksFileCreateVersionRequest, BlocksFileDeleteRequest, BlocksFileGetOptions, BlocksFileInfoListRequest, BlocksFileListRequest, BlocksFileMoveRequest, BlocksFileRenameRequest, BlocksFileUpdateAdditionalInfoRequest, BlocksFileUploadRequest, BlocksFileVersionsRequest, BlocksGraphqlRequest, BlocksLocalStorageUploadRequest, BlocksPagedResult, BlocksRegexGenerateRequest, BlocksSchemaFieldValidationOptions, BlocksSchemaListOptions, BlocksSchemaValidationListOptions, BlocksStorageAccessPolicyRequest, BlocksStorageEffect, BlocksStorageObject, BlocksStorageObjectListRequest, BlocksStorageObjectPageRequest, BlocksStorageObjectSearchRequest, BlocksStorageObjectsResponse, BlocksStorageObjectType, BlocksStoragePermission, BlocksStoragePermissionFlags, BlocksStoragePrincipalType, BlocksStorageResourceRequest, BlocksStorageResourceType, BlocksStorageRevokeAccessRequest, BlocksStorageShareRequest, BlocksStorageToggleInheritanceRequest, BlocksUploadToUrlRequest } from "./data/types.js";
18
21
  export type { BlocksLocalizationDictionary, BlocksLocalizationKey, BlocksLocalizationKeyResource, BlocksLocalizationKeysByNamesRequest, BlocksLocalizationKeysByNamesResponse, BlocksLocalizationLanguage, BlocksLocalizationModule, BlocksLocalizationTranslationOptions } from "./localization/types.js";
19
22
  export type { BlocksMfaBackupCodeUseRequest, BlocksMfaConfig, BlocksMfaConfigSaveRequest, BlocksMfaGenerateRequest, BlocksMfaPassThroughResponse, BlocksMfaResendRequest, BlocksMfaSetMethodRequest, BlocksMfaVerifyRequest, BlocksMfaVerifyTotpSetupRequest } from "./mfa/types.js";
20
23
  export type { BlocksMailPassThroughResponse, BlocksSendMailRequest, BlocksSendMailToAnyRequest } from "./mail/types.js";
21
24
  export type { BlocksGetNotificationsOptions, BlocksGetNotificationsResponse, BlocksGetUnreadNotificationsBySubscriptionFilterRequest, BlocksMarkNotificationAsReadRequest, BlocksNotifierPassThroughResponse, BlocksNotifierSubscriptionFilter, BlocksNotifyRequest, BlocksOfflineNotification } from "./notifier/types.js";
25
+ export type { BlocksFunctionAccepted, BlocksFunctionAnswer, BlocksFunctionCallOptions, BlocksFunctionInvokeOptions, BlocksFunctionInvokeResult, BlocksFunctionRunResult, BlocksFunctionTerminalStatus, BlocksFunctionWaitOptions, BlocksHttpMethod, BlocksProxyRequestOptions, BlocksQueryValue } from "./logic/types.js";
package/dist/index.js CHANGED
@@ -4,6 +4,9 @@ export { BlocksDataClient } from "./data/data-client.js";
4
4
  export { BlocksApiError } from "./http/errors.js";
5
5
  export { BlocksIAMClient } from "./iam/iam-client.js";
6
6
  export { BlocksLocalizationClient } from "./localization/localization-client.js";
7
+ export { BlocksLogicClient } from "./logic/logic-client.js";
8
+ export { BlocksFunctionRunError, BlocksFunctionsClient } from "./logic/functions-client.js";
9
+ export { BlocksProxyClient } from "./logic/proxy-client.js";
7
10
  export { BlocksMailClient } from "./mail/mail-client.js";
8
11
  export { BlocksMfaClient } from "./mfa/mfa-client.js";
9
12
  export { BlocksNotifierClient } from "./notifier/notifier-client.js";
@@ -0,0 +1,73 @@
1
+ import { BlocksHttpClient } from "../http/http-client.js";
2
+ import { BlocksFunctionAccepted, BlocksFunctionCallOptions, BlocksFunctionInvokeOptions, BlocksFunctionInvokeResult, BlocksFunctionRunResult, BlocksFunctionWaitOptions } from "./types.js";
3
+ /** A run that finished without an answer: FAILED, TIMED_OUT, CANCELLED or RESOURCE_EXCEEDED. */
4
+ export declare class BlocksFunctionRunError extends Error {
5
+ readonly runId: string;
6
+ readonly status: string;
7
+ readonly errorCode?: string | null | undefined;
8
+ readonly errorMessage?: string | null | undefined;
9
+ constructor(runId: string, status: string, errorCode?: string | null | undefined, errorMessage?: string | null | undefined);
10
+ }
11
+ /**
12
+ * Calling deployed Blocks functions from app code. Creating, saving, deploying and
13
+ * inspecting functions is CLI work (`blocks logic function *`), not exposed here.
14
+ */
15
+ export declare class BlocksFunctionsClient {
16
+ private readonly http;
17
+ private readonly apiUrl;
18
+ constructor(http: BlocksHttpClient, apiUrl: string);
19
+ /**
20
+ * What: the absolute public URL of a function (optionally with a sub-path).
21
+ * Why: third parties (webhooks, other backends) call the function directly and need its address.
22
+ * How: callers send this project's `x-blocks-key`, plus a Blocks bearer token unless the trigger is Public.
23
+ */
24
+ url(functionId: string, path?: string): string;
25
+ /**
26
+ * What: invokes a deployed function and returns either its answer or the queued run.
27
+ * Why: functions run async (202 + poll token) unless deployed as sync, and callers need to know which happened.
28
+ * How: `kind: "answered"` carries the function's own status/headers/body; `kind: "accepted"` carries
29
+ * `runId` + `pollToken` for `waitForResult`. Any non-2xx is thrown as `BlocksApiError` -- the platform's
30
+ * refusals (`FUNCTION_INVOKE_*` codes: 401/403/404/405/413/429/503), a failed or timed-out sync run
31
+ * (502/504 with `runId`), and a status the function chose itself. Use `call` for just the value.
32
+ */
33
+ invoke<T = unknown>(functionId: string, options?: BlocksFunctionInvokeOptions): Promise<BlocksFunctionInvokeResult<T>>;
34
+ /**
35
+ * What: invokes a function and returns the unread `Response`.
36
+ * Why: a streaming function (an `async function*` handler) sends its pieces as they come; reading
37
+ * them needs the body stream, which `invoke` buffers.
38
+ * How: same request as `invoke`. Pass `headers: { Accept: "text/event-stream" }` for Server-Sent
39
+ * Events (`data:` per piece, then `event: done` or `event: error`); otherwise the pieces arrive as
40
+ * chunked text. Never throws on a non-2xx status -- check `response.ok`.
41
+ */
42
+ invokeRaw(functionId: string, options?: BlocksFunctionInvokeOptions): Promise<Response>;
43
+ /**
44
+ * What: reads a queued run's status and, once it finished, its result.
45
+ * Why: an async invocation answers 202 at once; this is how its outcome is collected.
46
+ * How: pass the `runId` and `pollToken` from the 202. No user session is needed -- the token is
47
+ * the capability -- and any wrong, expired or foreign token is the same 404.
48
+ */
49
+ getResult(runId: string, pollToken: string, options?: {
50
+ signal?: AbortSignal;
51
+ }): Promise<BlocksFunctionRunResult>;
52
+ /**
53
+ * What: the full run record (input, result, attempts, timings) for a signed-in caller.
54
+ * Why: an admin screen or a caller without a poll token may need the whole run.
55
+ * How: requires the `blocks-logic::function::read` permission; reads within the caller's own tenant.
56
+ */
57
+ getRun<T = Record<string, unknown>>(runId: string): Promise<T>;
58
+ /**
59
+ * What: polls a queued run until it finishes.
60
+ * Why: turns the 202 + poll token into the final outcome without hand-written polling.
61
+ * How: resolves with the terminal `BlocksFunctionRunResult` (check `status`); rejects on `timeoutMs`
62
+ * (the run itself keeps going) or `signal` abort.
63
+ */
64
+ waitForResult(accepted: Pick<BlocksFunctionAccepted, "runId" | "pollToken">, options?: BlocksFunctionWaitOptions): Promise<BlocksFunctionRunResult>;
65
+ /**
66
+ * What: invokes a function and returns its value, however it was deployed.
67
+ * Why: most callers want the handler's return value, not the transport details.
68
+ * How: a sync answer's body is returned as is; an async run is awaited and its result parsed. A
69
+ * returned `{ statusCode, headers, body }` is unwrapped to `body` (a 4xx/5xx `statusCode` throws
70
+ * `BlocksApiError`). A run that fails throws `BlocksFunctionRunError`.
71
+ */
72
+ call<T = unknown>(functionId: string, options?: BlocksFunctionCallOptions): Promise<T>;
73
+ }