@keboola/api-client 52.0.0 → 53.0.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.
Files changed (90) hide show
  1. package/AGENTS.md +33 -6
  2. package/API-SURFACE.md +4 -1
  3. package/CHANGELOG.md +16 -0
  4. package/README.md +1 -1
  5. package/dist/constants/index.cjs +3 -2
  6. package/dist/constants/index.d.cts +1 -1
  7. package/dist/constants/index.d.ts +1 -1
  8. package/dist/constants/index.js +2 -1
  9. package/dist/{constants-Dqi0ewGv.js → constants-DLPkpnGv.js} +4 -32
  10. package/dist/{constants-Dqi0ewGv.js.map → constants-DLPkpnGv.js.map} +1 -1
  11. package/dist/{constants-d0Do9NSN.cjs → constants-Day8CU5i.cjs} +3 -37
  12. package/dist/{constants-d0Do9NSN.cjs.map → constants-Day8CU5i.cjs.map} +1 -1
  13. package/dist/encryption/index.d.cts +1 -1
  14. package/dist/encryption/index.d.ts +1 -1
  15. package/dist/encryption/types.d.cts +1 -1
  16. package/dist/encryption/types.d.ts +1 -1
  17. package/dist/{index-81wn1jc3.d.cts → index-B0prazAi.d.cts} +11 -2
  18. package/dist/{index-Df8lZNSH.d.cts → index-BL7uXOiY.d.cts} +2 -2
  19. package/dist/{index-B5rELADq.d.cts → index-BNwo-zaJ.d.cts} +2 -1
  20. package/dist/{index-B7el5pJU.d.cts → index-BUKMdbKh.d.cts} +10 -5
  21. package/dist/{index-CveF5RFQ.d.ts → index-BoKrc8wy.d.ts} +2 -2
  22. package/dist/{index-C7JcNvbd.d.ts → index-BxgHNJ3Q.d.ts} +10 -5
  23. package/dist/{index-Dhyrg5zd.d.ts → index-CwUurckF.d.ts} +2 -1
  24. package/dist/{index-JoKfPQND.d.ts → index-D0Gnp2QM.d.ts} +11 -2
  25. package/dist/index.cjs +4 -4
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +2 -2
  28. package/dist/index.d.ts +2 -2
  29. package/dist/index.js +3 -3
  30. package/dist/management/index.cjs +1 -1
  31. package/dist/management/index.d.cts +1 -1
  32. package/dist/management/index.d.ts +1 -1
  33. package/dist/management/index.js +1 -1
  34. package/dist/management/types.d.cts +2 -2
  35. package/dist/management/types.d.ts +2 -2
  36. package/dist/{management-Cz5ut18k.js → management-CmgsWmf0.js} +15 -1
  37. package/dist/management-CmgsWmf0.js.map +1 -0
  38. package/dist/{management-CUXgB2rb.cjs → management-Drs5G-hf.cjs} +15 -1
  39. package/dist/management-Drs5G-hf.cjs.map +1 -0
  40. package/dist/queryService/index.d.cts +1 -1
  41. package/dist/queryService/index.d.ts +1 -1
  42. package/dist/queryService/types.d.cts +1 -1
  43. package/dist/queryService/types.d.ts +1 -1
  44. package/dist/queue/index.d.cts +1 -1
  45. package/dist/queue/index.d.ts +1 -1
  46. package/dist/readJwtClaims-CMZ3cNC_.js +28 -0
  47. package/dist/readJwtClaims-CMZ3cNC_.js.map +1 -0
  48. package/dist/readJwtClaims-SVJgo04x.cjs +33 -0
  49. package/dist/readJwtClaims-SVJgo04x.cjs.map +1 -0
  50. package/dist/sdk/configurations/index.d.cts +2 -2
  51. package/dist/sdk/configurations/index.d.ts +2 -2
  52. package/dist/sdk/dataAppServer/index.cjs +266 -0
  53. package/dist/sdk/dataAppServer/index.cjs.map +1 -0
  54. package/dist/sdk/dataAppServer/index.d.cts +55 -0
  55. package/dist/sdk/dataAppServer/index.d.ts +55 -0
  56. package/dist/sdk/dataAppServer/index.js +264 -0
  57. package/dist/sdk/dataAppServer/index.js.map +1 -0
  58. package/dist/sdk/developerPortal/index.cjs +3 -15
  59. package/dist/sdk/developerPortal/index.cjs.map +1 -1
  60. package/dist/sdk/developerPortal/index.js +3 -15
  61. package/dist/sdk/developerPortal/index.js.map +1 -1
  62. package/dist/sdk/queryService/index.d.cts +1 -1
  63. package/dist/sdk/queryService/index.d.ts +1 -1
  64. package/dist/sdk/semanticLayer/index.cjs +1 -1
  65. package/dist/sdk/semanticLayer/index.d.cts +5 -0
  66. package/dist/sdk/semanticLayer/index.d.ts +5 -0
  67. package/dist/sdk/semanticLayer/index.js +1 -1
  68. package/dist/sdk/vault/index.d.cts +1 -1
  69. package/dist/sdk/vault/index.d.ts +1 -1
  70. package/dist/{semanticLayer-BdPW_wLo.js → semanticLayer-CfjDnmsY.js} +16 -1
  71. package/dist/semanticLayer-CfjDnmsY.js.map +1 -0
  72. package/dist/{semanticLayer-djom-J78.cjs → semanticLayer-D8D_3hk-.cjs} +16 -1
  73. package/dist/semanticLayer-D8D_3hk-.cjs.map +1 -0
  74. package/dist/serviceIds-Bg1pWiPE.cjs +37 -0
  75. package/dist/serviceIds-Bg1pWiPE.cjs.map +1 -0
  76. package/dist/serviceIds-RAVtP4nK.js +32 -0
  77. package/dist/serviceIds-RAVtP4nK.js.map +1 -0
  78. package/dist/storage/index.d.cts +2 -2
  79. package/dist/storage/index.d.ts +2 -2
  80. package/dist/storage/types.d.cts +2 -2
  81. package/dist/storage/types.d.ts +2 -2
  82. package/dist/{types-CLtYOszN.d.cts → types-CsRfoHQw.d.cts} +174 -2
  83. package/dist/{types-Ey59ZILC.d.ts → types-DNldEa7i.d.ts} +174 -2
  84. package/package.json +14 -1
  85. package/surface/management.md +1 -0
  86. package/surface/sdk.md +2 -0
  87. package/dist/management-CUXgB2rb.cjs.map +0 -1
  88. package/dist/management-Cz5ut18k.js.map +0 -1
  89. package/dist/semanticLayer-BdPW_wLo.js.map +0 -1
  90. package/dist/semanticLayer-djom-J78.cjs.map +0 -1
@@ -0,0 +1,266 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_createFetchClient = require("../../createFetchClient-CDKSkd7i.cjs");
3
+ const require_storage = require("../../storage-BMrYlyLB.cjs");
4
+ const require_RowLevelSecurityError = require("../../RowLevelSecurityError-RK_NhhAx.cjs");
5
+ const require_transientRetries = require("../../transientRetries-CBorl4Ue.cjs");
6
+ const require_isPlainObject = require("../../isPlainObject-CcvQt0qB.cjs");
7
+ const require_serviceIds = require("../../serviceIds-Bg1pWiPE.cjs");
8
+ const require_queryService = require("../../queryService-C3BQ6GKW.cjs");
9
+ const require_sdk_queryService_index = require("../queryService/index.cjs");
10
+ const require_readJwtClaims = require("../../readJwtClaims-SVJgo04x.cjs");
11
+ let node_async_hooks = require("node:async_hooks");
12
+ let node_fs_promises = require("node:fs/promises");
13
+ // src/sdks/dataAppServer/express.ts
14
+ /** Answers a row-level security refusal with 403 and passes any other error on. */
15
+ const createExpressErrorHandler = () => (err, _req, res, next) => {
16
+ if (res.headersSent || !require_RowLevelSecurityError.isRowLevelSecurityError(err)) {
17
+ next(err);
18
+ return;
19
+ }
20
+ res.status(require_createFetchClient.HttpStatus.FORBIDDEN).json({
21
+ error: err.message,
22
+ code: "ROW_LEVEL_SECURITY"
23
+ });
24
+ };
25
+ // src/sdks/dataAppServer/requestContext.ts
26
+ const isHeadersLike = (headers) => "get" in headers && typeof headers.get === "function";
27
+ const appUserTokenOf = (request) => {
28
+ const headers = "headers" in request ? request.headers : request;
29
+ if (isHeadersLike(headers)) return headers.get("X-Kbc-App-User-Token") || void 0;
30
+ const value = headers[require_createFetchClient.APP_USER_TOKEN_HEADER.toLowerCase()];
31
+ return (Array.isArray(value) ? value[0] : value) || void 0;
32
+ };
33
+ const userOf = (token) => {
34
+ const { idp, sub, email } = require_readJwtClaims.readJwtClaims(token) ?? {};
35
+ if (typeof idp !== "string" || typeof sub !== "string" || !idp || !sub) return void 0;
36
+ return typeof email === "string" && email ? {
37
+ idp,
38
+ sub,
39
+ email
40
+ } : {
41
+ idp,
42
+ sub
43
+ };
44
+ };
45
+ /**
46
+ * The signed-in user of the request being handled, kept across every `await` the request makes.
47
+ *
48
+ * - Work the request starts keeps its user: a timer, a promise cached at module level, a listener.
49
+ * - `currentUser()` reads the token payload unverified: safe only behind Keboola's apps proxy.
50
+ * - `currentUserKey()` is the same for every token of one user (`idp` + `sub`): a per-user cache key.
51
+ */
52
+ const createRequestContext = () => {
53
+ const storage = new node_async_hooks.AsyncLocalStorage();
54
+ const current = () => storage.getStore();
55
+ return {
56
+ runWithRequest: (request, fn) => {
57
+ const appUserToken = appUserTokenOf(request);
58
+ return storage.run({
59
+ appUserToken,
60
+ user: appUserToken ? userOf(appUserToken) : void 0
61
+ }, fn);
62
+ },
63
+ current,
64
+ currentUser: () => current()?.user,
65
+ currentUserKey: () => {
66
+ const user = current()?.user;
67
+ return user && JSON.stringify([user.idp, user.sub]);
68
+ }
69
+ };
70
+ };
71
+ // src/sdks/dataAppServer/parseCell.ts
72
+ const FLOAT_TYPE = /^(real|float|float64)$/i;
73
+ const DECIMAL_TYPE = /^(fixed|integer|int64|numeric|bignumeric)$/i;
74
+ const DECIMAL = /^-?\d+(\.\d+)?$/;
75
+ const EPOCH = /^-?\d+(\.\d+)?( \d+)?$/;
76
+ const BIGQUERY_TIMESTAMP = /^(\S+) (\S+) \+0000 UTC$/;
77
+ const DAY_MS = 864e5;
78
+ /** Every decimal of at most this many significant digits survives a round trip through a double. */
79
+ const EXACT_DIGITS = 15;
80
+ const significantDigits = (decimal) => decimal.replace(/[-.]/g, "").replace(/^0+/, "").replace(/0+$/, "").length;
81
+ const decimalOf = (cell) => {
82
+ const n = Number(cell);
83
+ return DECIMAL.test(cell) && (Number.isSafeInteger(n) || significantDigits(cell) <= EXACT_DIGITS) ? n : cell;
84
+ };
85
+ const floatOf = (cell) => {
86
+ const n = Number(cell);
87
+ return Number.isFinite(n) ? n : cell;
88
+ };
89
+ const epochIso = (cell) => (/* @__PURE__ */ new Date(parseFloat(cell) * 1e3)).toISOString();
90
+ const timestampOf = (cell) => EPOCH.test(cell) ? epochIso(cell) : cell.replace(BIGQUERY_TIMESTAMP, "$1T$2Z");
91
+ /**
92
+ * The parser for a Query Service cell (always a string) of one column type.
93
+ *
94
+ * 1. A decimal is a number only when it keeps every digit, else the string (`NUMBER(38,6)`, IDs).
95
+ * 2. Floats and booleans are typed; a Snowflake `date` (epoch days) becomes `YYYY-MM-DD`.
96
+ * 3. Timestamps become ISO (`timestamp_ntz` without `Z`); other types keep the string.
97
+ */
98
+ const cellParser = (type) => {
99
+ if (DECIMAL_TYPE.test(type)) return decimalOf;
100
+ if (FLOAT_TYPE.test(type)) return floatOf;
101
+ if (/^bool(ean)?$/i.test(type)) return (cell) => cell === "true";
102
+ if (/^date$/i.test(type)) return (cell) => EPOCH.test(cell) ? (/* @__PURE__ */ new Date(Number(cell) * DAY_MS)).toISOString().slice(0, 10) : cell;
103
+ if (/^timestamp_ntz$/i.test(type)) return (cell) => EPOCH.test(cell) ? epochIso(cell).slice(0, -1) : cell;
104
+ if (/^timestamp/i.test(type)) return timestampOf;
105
+ return (cell) => cell;
106
+ };
107
+ // src/sdks/dataAppServer/runQuery.ts
108
+ const PAGE_SIZE = 1e4;
109
+ const TIMEOUT = 6e4;
110
+ const refusalWarning = (scope) => {
111
+ const prefix = "[dataAppServer] row-level security refused";
112
+ if (scope?.appUserToken) return `${prefix} the query for this user`;
113
+ return `${prefix} a query sent without ${require_createFetchClient.APP_USER_TOKEN_HEADER}: ${scope ? "the request has none" : "it ran outside a user request (startup, timer, background job)"}`;
114
+ };
115
+ const toRecords = ({ columns = [], data = [], numberOfRows }, capped) => {
116
+ const parsers = columns.map(({ name, type }) => ({
117
+ key: name.toLowerCase(),
118
+ parse: cellParser(type)
119
+ }));
120
+ const keys = /* @__PURE__ */ new Set();
121
+ for (const { key } of parsers) {
122
+ if (keys.has(key)) throw new Error(`runQuery: two columns are named "${key}"; alias one with AS`);
123
+ keys.add(key);
124
+ }
125
+ const rows = data.map((row) => {
126
+ const record = {};
127
+ parsers.forEach(({ key, parse }, i) => {
128
+ const cell = row[i];
129
+ record[key] = cell == null ? null : parse(cell);
130
+ });
131
+ return record;
132
+ });
133
+ if (!capped && numberOfRows !== void 0 && rows.length < numberOfRows) throw new Error(`runQuery: got ${rows.length} of ${numberOfRows} rows`);
134
+ return rows;
135
+ };
136
+ /**
137
+ * Runs one SQL statement in the app's workspace and resolves every row as a record.
138
+ *
139
+ * - Sends the request's user token with the app's Storage token; without a user, the Storage token.
140
+ * - A repeated column name, or fewer rows than counted (no `maxRows`), throws instead of losing data.
141
+ */
142
+ const createRunQuery = (settings, requestContext) => {
143
+ const queryService = require_sdk_queryService_index.createQueryServiceSdk({ queryServiceClient: require_queryService.createQueryServiceClient({
144
+ baseUrl: settings.queryServiceUrl,
145
+ auth: async () => {
146
+ const storageToken = await settings.storageToken();
147
+ const appUserToken = requestContext.current()?.appUserToken;
148
+ return appUserToken ? {
149
+ type: "app-user-token",
150
+ token: appUserToken,
151
+ storageToken
152
+ } : {
153
+ type: "sapi-token",
154
+ token: storageToken
155
+ };
156
+ }
157
+ }) });
158
+ return async (sql, options = {}) => {
159
+ const scope = requestContext.current();
160
+ const [result] = await queryService.executeQuery(settings.branchId(), await settings.workspaceId(), {
161
+ statements: [sql],
162
+ transactional: false
163
+ }, {
164
+ ...options,
165
+ pageSize: PAGE_SIZE,
166
+ timeout: options.timeout ?? TIMEOUT
167
+ }).catch((error) => {
168
+ if (require_RowLevelSecurityError.isRowLevelSecurityError(error)) console.warn(refusalWarning(scope));
169
+ throw error;
170
+ });
171
+ return result ? toRecords(result, options.maxRows !== void 0) : [];
172
+ };
173
+ };
174
+ // src/sdks/dataAppServer/settings.ts
175
+ /** A setting the app needs is missing or unreadable; the message names the variable or file. */
176
+ var AppConfigError = class extends Error {
177
+ constructor(message, options) {
178
+ super(message, options);
179
+ this.name = "AppConfigError";
180
+ }
181
+ };
182
+ const STACK_INDEX_TIMEOUT = 1e4;
183
+ const required = (env, name) => {
184
+ const value = env[name];
185
+ if (!value) throw new AppConfigError(`Missing environment variable ${name}`);
186
+ return value;
187
+ };
188
+ const storageTokenFrom = async (env) => {
189
+ const file = env.STORAGE_API_TOKEN_PATH;
190
+ if (!file) {
191
+ if (env.KBC_TOKEN) return env.KBC_TOKEN;
192
+ throw new AppConfigError("Missing Storage token: set STORAGE_API_TOKEN_PATH or KBC_TOKEN");
193
+ }
194
+ const token = (await (0, node_fs_promises.readFile)(file, "utf8").catch((error) => {
195
+ throw new AppConfigError(`Cannot read the file in STORAGE_API_TOKEN_PATH: ${file}`, { cause: error });
196
+ })).trim();
197
+ if (!token) throw new AppConfigError(`The token file in STORAGE_API_TOKEN_PATH is empty: ${file}`);
198
+ return token;
199
+ };
200
+ const manifestWorkspaceId = async (file) => {
201
+ let manifest;
202
+ try {
203
+ manifest = JSON.parse(await (0, node_fs_promises.readFile)(file, "utf8"));
204
+ } catch (error) {
205
+ throw new AppConfigError(`Cannot read the JSON in KBC_WORKSPACE_MANIFEST_PATH: ${file}`, { cause: error });
206
+ }
207
+ const id = require_isPlainObject.isPlainObject(manifest) ? manifest.workspaceId : void 0;
208
+ if (typeof id === "string" && id || typeof id === "number") return String(id);
209
+ throw new AppConfigError(`The manifest in KBC_WORKSPACE_MANIFEST_PATH has no workspaceId: ${file}`);
210
+ };
211
+ const queryServiceUrlFromIndex = async (kbcUrl, token) => {
212
+ const { services } = await require_storage.createStorageClient({
213
+ baseUrl: kbcUrl,
214
+ auth: {
215
+ type: "sapi-token",
216
+ token
217
+ }
218
+ }).getStackInfo({ exclude: "components" }, AbortSignal.timeout(STACK_INDEX_TIMEOUT));
219
+ const url = services.find(({ id }) => id === require_serviceIds.ServiceId.Query)?.url;
220
+ if (!url) throw new AppConfigError("The stack index of KBC_URL lists no query service");
221
+ return url;
222
+ };
223
+ /**
224
+ * What the platform provides a data app, read on first use; a missing value throws `AppConfigError`.
225
+ *
226
+ * - The Storage token is read on every call: the platform rotates the file in `STORAGE_API_TOKEN_PATH`.
227
+ * - The workspace ID and the Query Service URL are read once per process.
228
+ * - The Query Service URL is `QUERY_SERVICE_URL`, or the stack index of `KBC_URL` (local runs).
229
+ */
230
+ const createSettings = (env) => {
231
+ const storageToken = () => storageTokenFrom(env);
232
+ const manifest = env.KBC_WORKSPACE_MANIFEST_PATH;
233
+ return {
234
+ branchId: () => required(env, "BRANCH_ID"),
235
+ storageToken,
236
+ workspaceId: require_transientRetries.lazy(() => manifest ? manifestWorkspaceId(manifest) : required(env, "WORKSPACE_ID")),
237
+ queryServiceUrl: require_transientRetries.lazy(async () => env.QUERY_SERVICE_URL || queryServiceUrlFromIndex(required(env, "KBC_URL"), await storageToken())),
238
+ hasWorkspace: Boolean(manifest || env.WORKSPACE_ID)
239
+ };
240
+ };
241
+ // src/sdks/dataAppServer/initializeApp.ts
242
+ /**
243
+ * Sets up Keboola access for this data app process; call it once.
244
+ *
245
+ * - Synchronous, no network call, never throws: each setting is read on first use.
246
+ * - `workspace` is `undefined` when no workspace is configured (the app has no Storage access).
247
+ * - `server.expressMiddleware()` goes first and `server.expressErrorHandler()` last.
248
+ */
249
+ const initializeApp = ({ env = process.env } = {}) => {
250
+ const settings = createSettings(env);
251
+ const requestContext = createRequestContext();
252
+ return {
253
+ server: {
254
+ runWithRequest: requestContext.runWithRequest,
255
+ currentUser: requestContext.currentUser,
256
+ currentUserKey: requestContext.currentUserKey,
257
+ expressMiddleware: () => (req, _res, next) => requestContext.runWithRequest(req, next),
258
+ expressErrorHandler: createExpressErrorHandler
259
+ },
260
+ workspace: settings.hasWorkspace ? { runQuery: createRunQuery(settings, requestContext) } : void 0
261
+ };
262
+ };
263
+ exports.AppConfigError = AppConfigError;
264
+ exports.initializeApp = initializeApp;
265
+
266
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["isRowLevelSecurityError","HttpStatus","APP_USER_TOKEN_HEADER","readJwtClaims","AsyncLocalStorage","APP_USER_TOKEN_HEADER","createQueryServiceSdk","createQueryServiceClient","isRowLevelSecurityError","readFile","isPlainObject","createStorageClient","ServiceId","lazy"],"sources":["../../../src/sdks/dataAppServer/express.ts","../../../src/sdks/dataAppServer/requestContext.ts","../../../src/sdks/dataAppServer/parseCell.ts","../../../src/sdks/dataAppServer/runQuery.ts","../../../src/sdks/dataAppServer/settings.ts","../../../src/sdks/dataAppServer/initializeApp.ts"],"sourcesContent":["import { isRowLevelSecurityError } from '../../errors';\nimport { HttpStatus } from '../../internal';\n\ntype ErrorResponse = {\n headersSent: boolean;\n status(code: number): { json(body: unknown): unknown };\n};\n\n/** Answers a row-level security refusal with 403 and passes any other error on. */\nexport const createExpressErrorHandler =\n () => (err: unknown, _req: unknown, res: ErrorResponse, next: (err?: unknown) => void) => {\n if (res.headersSent || !isRowLevelSecurityError(err)) {\n next(err);\n return;\n }\n res.status(HttpStatus.FORBIDDEN).json({ error: err.message, code: 'ROW_LEVEL_SECURITY' });\n };\n","import { AsyncLocalStorage } from 'node:async_hooks';\n\nimport { APP_USER_TOKEN_HEADER } from '../../internal';\nimport { readJwtClaims } from '../../utils/readJwtClaims';\n\ntype NodeHeaders = Record<string, string | string[] | undefined>;\n\ntype HeadersLike = { get(name: string): string | null };\n\n/** A Fetch `Request` or `Headers`, or a Node request (Express, Fastify) with its lower-case `headers`. */\nexport type AppRequest = HeadersLike | { headers: HeadersLike | NodeHeaders };\n\n/** The signed-in user as the app user token names them; `email` only when the IdP verified it. */\nexport type AppUser = { idp: string; sub: string; email?: string };\n\ntype Scope = { appUserToken: string | undefined; user: AppUser | undefined };\n\nexport type RequestContext = ReturnType<typeof createRequestContext>;\n\nconst isHeadersLike = (headers: object): headers is HeadersLike =>\n 'get' in headers && typeof headers.get === 'function';\n\nconst appUserTokenOf = (request: AppRequest): string | undefined => {\n const headers = 'headers' in request ? request.headers : request;\n if (isHeadersLike(headers)) return headers.get(APP_USER_TOKEN_HEADER) || undefined;\n const value = headers[APP_USER_TOKEN_HEADER.toLowerCase()];\n return (Array.isArray(value) ? value[0] : value) || undefined;\n};\n\nconst userOf = (token: string): AppUser | undefined => {\n const { idp, sub, email } = readJwtClaims(token) ?? {};\n if (typeof idp !== 'string' || typeof sub !== 'string' || !idp || !sub) return undefined;\n return typeof email === 'string' && email ? { idp, sub, email } : { idp, sub };\n};\n\n/**\n * The signed-in user of the request being handled, kept across every `await` the request makes.\n *\n * - Work the request starts keeps its user: a timer, a promise cached at module level, a listener.\n * - `currentUser()` reads the token payload unverified: safe only behind Keboola's apps proxy.\n * - `currentUserKey()` is the same for every token of one user (`idp` + `sub`): a per-user cache key.\n */\nexport const createRequestContext = () => {\n const storage = new AsyncLocalStorage<Scope>();\n const current = () => storage.getStore();\n\n return {\n runWithRequest: <T>(request: AppRequest, fn: () => T): T => {\n const appUserToken = appUserTokenOf(request);\n return storage.run(\n { appUserToken, user: appUserToken ? userOf(appUserToken) : undefined },\n fn,\n );\n },\n current,\n currentUser: () => current()?.user,\n currentUserKey: () => {\n const user = current()?.user;\n return user && JSON.stringify([user.idp, user.sub]);\n },\n };\n};\n","const FLOAT_TYPE = /^(real|float|float64)$/i;\nconst DECIMAL_TYPE = /^(fixed|integer|int64|numeric|bignumeric)$/i;\nconst DECIMAL = /^-?\\d+(\\.\\d+)?$/;\nconst EPOCH = /^-?\\d+(\\.\\d+)?( \\d+)?$/;\nconst BIGQUERY_TIMESTAMP = /^(\\S+) (\\S+) \\+0000 UTC$/;\nconst DAY_MS = 86_400_000;\n/** Every decimal of at most this many significant digits survives a round trip through a double. */\nconst EXACT_DIGITS = 15;\n\nconst significantDigits = (decimal: string) =>\n decimal.replace(/[-.]/g, '').replace(/^0+/, '').replace(/0+$/, '').length;\n\nconst decimalOf = (cell: string) => {\n const n = Number(cell);\n return DECIMAL.test(cell) && (Number.isSafeInteger(n) || significantDigits(cell) <= EXACT_DIGITS)\n ? n\n : cell;\n};\n\nconst floatOf = (cell: string) => {\n const n = Number(cell);\n return Number.isFinite(n) ? n : cell;\n};\n\nconst epochIso = (cell: string) => new Date(parseFloat(cell) * 1000).toISOString();\n\nconst timestampOf = (cell: string) =>\n EPOCH.test(cell) ? epochIso(cell) : cell.replace(BIGQUERY_TIMESTAMP, '$1T$2Z');\n\n/**\n * The parser for a Query Service cell (always a string) of one column type.\n *\n * 1. A decimal is a number only when it keeps every digit, else the string (`NUMBER(38,6)`, IDs).\n * 2. Floats and booleans are typed; a Snowflake `date` (epoch days) becomes `YYYY-MM-DD`.\n * 3. Timestamps become ISO (`timestamp_ntz` without `Z`); other types keep the string.\n */\nexport const cellParser = (type: string): ((cell: string) => unknown) => {\n if (DECIMAL_TYPE.test(type)) return decimalOf;\n if (FLOAT_TYPE.test(type)) return floatOf;\n if (/^bool(ean)?$/i.test(type)) return (cell) => cell === 'true';\n if (/^date$/i.test(type)) {\n return (cell) =>\n EPOCH.test(cell) ? new Date(Number(cell) * DAY_MS).toISOString().slice(0, 10) : cell;\n }\n if (/^timestamp_ntz$/i.test(type)) {\n return (cell) => (EPOCH.test(cell) ? epochIso(cell).slice(0, -1) : cell);\n }\n if (/^timestamp/i.test(type)) return timestampOf;\n return (cell) => cell;\n};\n","import { createQueryServiceClient, type QueryServiceCredential } from '../../clients/queryService';\nimport type { QueryResult } from '../../clients/queryService/types';\nimport { isRowLevelSecurityError } from '../../errors';\nimport { APP_USER_TOKEN_HEADER } from '../../internal';\nimport { createQueryServiceSdk, type ExecuteQueryOptions } from '../queryService';\n\nimport { cellParser } from './parseCell';\nimport type { RequestContext } from './requestContext';\nimport type { Settings } from './settings';\n\nexport type RunQueryOptions = Pick<ExecuteQueryOptions, 'timeout' | 'maxRows' | 'signal'>;\n\nconst PAGE_SIZE = 10_000;\nconst TIMEOUT = 60_000;\n\nconst refusalWarning = (scope: ReturnType<RequestContext['current']>) => {\n const prefix = '[dataAppServer] row-level security refused';\n if (scope?.appUserToken) return `${prefix} the query for this user`;\n const reason = scope\n ? 'the request has none'\n : 'it ran outside a user request (startup, timer, background job)';\n return `${prefix} a query sent without ${APP_USER_TOKEN_HEADER}: ${reason}`;\n};\n\nconst toRecords = ({ columns = [], data = [], numberOfRows }: QueryResult, capped: boolean) => {\n const parsers = columns.map(({ name, type }) => ({\n key: name.toLowerCase(),\n parse: cellParser(type),\n }));\n const keys = new Set<string>();\n for (const { key } of parsers) {\n if (keys.has(key))\n throw new Error(`runQuery: two columns are named \"${key}\"; alias one with AS`);\n keys.add(key);\n }\n const rows = data.map((row) => {\n const record: Record<string, unknown> = {};\n parsers.forEach(({ key, parse }, i) => {\n const cell = row[i];\n record[key] = cell == null ? null : parse(cell);\n });\n return record;\n });\n if (!capped && numberOfRows !== undefined && rows.length < numberOfRows) {\n throw new Error(`runQuery: got ${rows.length} of ${numberOfRows} rows`);\n }\n return rows;\n};\n\n/**\n * Runs one SQL statement in the app's workspace and resolves every row as a record.\n *\n * - Sends the request's user token with the app's Storage token; without a user, the Storage token.\n * - A repeated column name, or fewer rows than counted (no `maxRows`), throws instead of losing data.\n */\nexport const createRunQuery = (settings: Settings, requestContext: RequestContext) => {\n const queryService = createQueryServiceSdk({\n queryServiceClient: createQueryServiceClient({\n baseUrl: settings.queryServiceUrl,\n auth: async (): Promise<QueryServiceCredential> => {\n const storageToken = await settings.storageToken();\n const appUserToken = requestContext.current()?.appUserToken;\n return appUserToken\n ? { type: 'app-user-token', token: appUserToken, storageToken }\n : { type: 'sapi-token', token: storageToken };\n },\n }),\n });\n\n return async (sql: string, options: RunQueryOptions = {}): Promise<Record<string, unknown>[]> => {\n const scope = requestContext.current();\n const [result] = await queryService\n .executeQuery(\n settings.branchId(),\n await settings.workspaceId(),\n { statements: [sql], transactional: false },\n { ...options, pageSize: PAGE_SIZE, timeout: options.timeout ?? TIMEOUT },\n )\n .catch((error: unknown) => {\n if (isRowLevelSecurityError(error)) console.warn(refusalWarning(scope));\n throw error;\n });\n return result ? toRecords(result, options.maxRows !== undefined) : [];\n };\n};\n","import { readFile } from 'node:fs/promises';\n\nimport { createStorageClient } from '../../clients/storage';\nimport { ServiceId } from '../../constants/serviceIds';\nimport { lazy } from '../../utils/async';\nimport { isPlainObject } from '../../utils/isPlainObject';\n\nexport type Env = Record<string, string | undefined>;\n\n/** A setting the app needs is missing or unreadable; the message names the variable or file. */\nexport class AppConfigError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'AppConfigError';\n }\n}\n\nexport type Settings = ReturnType<typeof createSettings>;\n\nconst STACK_INDEX_TIMEOUT = 10_000;\n\nconst required = (env: Env, name: string) => {\n const value = env[name];\n if (!value) throw new AppConfigError(`Missing environment variable ${name}`);\n return value;\n};\n\nconst storageTokenFrom = async (env: Env) => {\n const file = env.STORAGE_API_TOKEN_PATH;\n if (!file) {\n if (env.KBC_TOKEN) return env.KBC_TOKEN;\n throw new AppConfigError('Missing Storage token: set STORAGE_API_TOKEN_PATH or KBC_TOKEN');\n }\n const content = await readFile(file, 'utf8').catch((error: unknown) => {\n throw new AppConfigError(`Cannot read the file in STORAGE_API_TOKEN_PATH: ${file}`, {\n cause: error,\n });\n });\n const token = content.trim();\n if (!token)\n throw new AppConfigError(`The token file in STORAGE_API_TOKEN_PATH is empty: ${file}`);\n return token;\n};\n\nconst manifestWorkspaceId = async (file: string) => {\n let manifest: unknown;\n try {\n manifest = JSON.parse(await readFile(file, 'utf8'));\n } catch (error) {\n throw new AppConfigError(`Cannot read the JSON in KBC_WORKSPACE_MANIFEST_PATH: ${file}`, {\n cause: error,\n });\n }\n const id = isPlainObject(manifest) ? manifest.workspaceId : undefined;\n if ((typeof id === 'string' && id) || typeof id === 'number') return String(id);\n throw new AppConfigError(\n `The manifest in KBC_WORKSPACE_MANIFEST_PATH has no workspaceId: ${file}`,\n );\n};\n\nconst queryServiceUrlFromIndex = async (kbcUrl: string, token: string) => {\n const { services } = await createStorageClient({\n baseUrl: kbcUrl,\n auth: { type: 'sapi-token', token },\n }).getStackInfo({ exclude: 'components' }, AbortSignal.timeout(STACK_INDEX_TIMEOUT));\n const url = services.find(({ id }) => id === ServiceId.Query)?.url;\n if (!url) throw new AppConfigError('The stack index of KBC_URL lists no query service');\n return url;\n};\n\n/**\n * What the platform provides a data app, read on first use; a missing value throws `AppConfigError`.\n *\n * - The Storage token is read on every call: the platform rotates the file in `STORAGE_API_TOKEN_PATH`.\n * - The workspace ID and the Query Service URL are read once per process.\n * - The Query Service URL is `QUERY_SERVICE_URL`, or the stack index of `KBC_URL` (local runs).\n */\nexport const createSettings = (env: Env) => {\n const storageToken = () => storageTokenFrom(env);\n const manifest = env.KBC_WORKSPACE_MANIFEST_PATH;\n\n return {\n branchId: () => required(env, 'BRANCH_ID'),\n storageToken,\n workspaceId: lazy(() =>\n manifest ? manifestWorkspaceId(manifest) : required(env, 'WORKSPACE_ID'),\n ),\n queryServiceUrl: lazy(\n async () =>\n env.QUERY_SERVICE_URL ||\n queryServiceUrlFromIndex(required(env, 'KBC_URL'), await storageToken()),\n ),\n hasWorkspace: Boolean(manifest || env.WORKSPACE_ID),\n };\n};\n","import { createExpressErrorHandler } from './express';\nimport { type AppRequest, createRequestContext } from './requestContext';\nimport { createRunQuery } from './runQuery';\nimport { createSettings, type Env } from './settings';\n\n/** `env` replaces `process.env` (tests, local runs). */\nexport type DataAppServerOptions = { env?: Env };\n\n/**\n * Sets up Keboola access for this data app process; call it once.\n *\n * - Synchronous, no network call, never throws: each setting is read on first use.\n * - `workspace` is `undefined` when no workspace is configured (the app has no Storage access).\n * - `server.expressMiddleware()` goes first and `server.expressErrorHandler()` last.\n */\nexport const initializeApp = ({ env = process.env }: DataAppServerOptions = {}) => {\n const settings = createSettings(env);\n const requestContext = createRequestContext();\n\n return {\n server: {\n runWithRequest: requestContext.runWithRequest,\n currentUser: requestContext.currentUser,\n currentUserKey: requestContext.currentUserKey,\n expressMiddleware: () => (req: AppRequest, _res: unknown, next: () => void) =>\n requestContext.runWithRequest(req, next),\n expressErrorHandler: createExpressErrorHandler,\n },\n workspace: settings.hasWorkspace\n ? { runQuery: createRunQuery(settings, requestContext) }\n : undefined,\n };\n};\n\nexport type DataAppServer = ReturnType<typeof initializeApp>;\n"],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { b as ExecuteQueryOptions } from "../../index-BUKMdbKh.cjs";
2
+ type NodeHeaders = Record<string, string | string[] | undefined>;
3
+ type HeadersLike = {
4
+ get(name: string): string | null;
5
+ };
6
+ /** A Fetch `Request` or `Headers`, or a Node request (Express, Fastify) with its lower-case `headers`. */
7
+ type AppRequest = HeadersLike | {
8
+ headers: HeadersLike | NodeHeaders;
9
+ };
10
+ /** The signed-in user as the app user token names them; `email` only when the IdP verified it. */
11
+ type AppUser = {
12
+ idp: string;
13
+ sub: string;
14
+ email?: string;
15
+ };
16
+ type Env = Record<string, string | undefined>;
17
+ /** A setting the app needs is missing or unreadable; the message names the variable or file. */
18
+ declare class AppConfigError extends Error {
19
+ constructor(message: string, options?: ErrorOptions);
20
+ }
21
+ type RunQueryOptions = Pick<ExecuteQueryOptions, 'timeout' | 'maxRows' | 'signal'>;
22
+ type ErrorResponse = {
23
+ headersSent: boolean;
24
+ status(code: number): {
25
+ json(body: unknown): unknown;
26
+ };
27
+ };
28
+ /** Answers a row-level security refusal with 403 and passes any other error on. */
29
+ declare const createExpressErrorHandler: () => (err: unknown, _req: unknown, res: ErrorResponse, next: (err?: unknown) => void) => void;
30
+ /** `env` replaces `process.env` (tests, local runs). */
31
+ type DataAppServerOptions = {
32
+ env?: Env;
33
+ };
34
+ /**
35
+ * Sets up Keboola access for this data app process; call it once.
36
+ *
37
+ * - Synchronous, no network call, never throws: each setting is read on first use.
38
+ * - `workspace` is `undefined` when no workspace is configured (the app has no Storage access).
39
+ * - `server.expressMiddleware()` goes first and `server.expressErrorHandler()` last.
40
+ */
41
+ declare const initializeApp: ({ env }?: DataAppServerOptions) => {
42
+ server: {
43
+ runWithRequest: <T>(request: AppRequest, fn: () => T) => T;
44
+ currentUser: () => AppUser | undefined;
45
+ currentUserKey: () => string | undefined;
46
+ expressMiddleware: () => (req: AppRequest, _res: unknown, next: () => void) => void;
47
+ expressErrorHandler: typeof createExpressErrorHandler;
48
+ };
49
+ workspace: {
50
+ runQuery: (sql: string, options?: RunQueryOptions) => Promise<Record<string, unknown>[]>;
51
+ } | undefined;
52
+ };
53
+ type DataAppServer = ReturnType<typeof initializeApp>;
54
+ export { AppConfigError, type AppRequest, type AppUser, type DataAppServer, type DataAppServerOptions, type RunQueryOptions, initializeApp };
55
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1,55 @@
1
+ import { b as ExecuteQueryOptions } from "../../index-BxgHNJ3Q.js";
2
+ type NodeHeaders = Record<string, string | string[] | undefined>;
3
+ type HeadersLike = {
4
+ get(name: string): string | null;
5
+ };
6
+ /** A Fetch `Request` or `Headers`, or a Node request (Express, Fastify) with its lower-case `headers`. */
7
+ type AppRequest = HeadersLike | {
8
+ headers: HeadersLike | NodeHeaders;
9
+ };
10
+ /** The signed-in user as the app user token names them; `email` only when the IdP verified it. */
11
+ type AppUser = {
12
+ idp: string;
13
+ sub: string;
14
+ email?: string;
15
+ };
16
+ type Env = Record<string, string | undefined>;
17
+ /** A setting the app needs is missing or unreadable; the message names the variable or file. */
18
+ declare class AppConfigError extends Error {
19
+ constructor(message: string, options?: ErrorOptions);
20
+ }
21
+ type RunQueryOptions = Pick<ExecuteQueryOptions, 'timeout' | 'maxRows' | 'signal'>;
22
+ type ErrorResponse = {
23
+ headersSent: boolean;
24
+ status(code: number): {
25
+ json(body: unknown): unknown;
26
+ };
27
+ };
28
+ /** Answers a row-level security refusal with 403 and passes any other error on. */
29
+ declare const createExpressErrorHandler: () => (err: unknown, _req: unknown, res: ErrorResponse, next: (err?: unknown) => void) => void;
30
+ /** `env` replaces `process.env` (tests, local runs). */
31
+ type DataAppServerOptions = {
32
+ env?: Env;
33
+ };
34
+ /**
35
+ * Sets up Keboola access for this data app process; call it once.
36
+ *
37
+ * - Synchronous, no network call, never throws: each setting is read on first use.
38
+ * - `workspace` is `undefined` when no workspace is configured (the app has no Storage access).
39
+ * - `server.expressMiddleware()` goes first and `server.expressErrorHandler()` last.
40
+ */
41
+ declare const initializeApp: ({ env }?: DataAppServerOptions) => {
42
+ server: {
43
+ runWithRequest: <T>(request: AppRequest, fn: () => T) => T;
44
+ currentUser: () => AppUser | undefined;
45
+ currentUserKey: () => string | undefined;
46
+ expressMiddleware: () => (req: AppRequest, _res: unknown, next: () => void) => void;
47
+ expressErrorHandler: typeof createExpressErrorHandler;
48
+ };
49
+ workspace: {
50
+ runQuery: (sql: string, options?: RunQueryOptions) => Promise<Record<string, unknown>[]>;
51
+ } | undefined;
52
+ };
53
+ type DataAppServer = ReturnType<typeof initializeApp>;
54
+ export { AppConfigError, type AppRequest, type AppUser, type DataAppServer, type DataAppServerOptions, type RunQueryOptions, initializeApp };
55
+ //# sourceMappingURL=index.d.ts.map