@mondaydotcomorg/z2h-cli 0.24.2 → 0.25.1
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/dist/esm/shadow/scaffold.d.ts.map +1 -1
- package/dist/esm/shadow/scaffold.mjs +1 -98
- package/dist/shadow/scaffold.d.ts.map +1 -1
- package/dist/shadow/scaffold.js +5 -102
- package/handler-api/backend-runner/channels/channel-map.ts +1 -0
- package/handler-api/backend-runner/channels/llm-channel/llm.channel.ts +3 -0
- package/handler-api/backend-runner/channels/snowflake-channel/run-query.ts +82 -5
- package/handler-api/backend-runner/channels/snowflake-channel/snowflake.channel.ts +23 -0
- package/package.json +2 -1
- package/src/shadow/scaffold.ts +6 -104
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/shadow/scaffold.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/shadow/scaffold.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;GAGG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvG"}
|
|
@@ -1,104 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { ensureDir, writeFile, writeJson } from 'fs-extra';
|
|
3
|
+
import { WRAPPER_TEMPLATE, tridentrcTemplate, dtsFileTemplate, TSCONFIG_TEMPLATE } from '@mondaydotcomorg/z2h-shared-utils/mf-scaffold';
|
|
3
4
|
|
|
4
|
-
const WRAPPER_TEMPLATE = `import '@vibe/core/tokens';
|
|
5
|
-
import ConsumerApp from '../../src/index';
|
|
6
|
-
import React from 'react';
|
|
7
|
-
import * as ReactDOM from 'react-dom/client';
|
|
8
|
-
import { ErrorBoundary } from 'react-error-boundary';
|
|
9
|
-
|
|
10
|
-
const roots = new Map<HTMLElement, ReactDOM.Root>();
|
|
11
|
-
|
|
12
|
-
export function init() {}
|
|
13
|
-
|
|
14
|
-
export function render(element: HTMLElement) {
|
|
15
|
-
const root = ReactDOM.createRoot(element);
|
|
16
|
-
root.render(
|
|
17
|
-
<ErrorBoundary fallback={<div style={{ padding: 16 }}>This app crashed.</div>}>
|
|
18
|
-
<ConsumerApp />
|
|
19
|
-
</ErrorBoundary>,
|
|
20
|
-
);
|
|
21
|
-
roots.set(element, root);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function unmount({ element }: { element: HTMLElement }) {
|
|
25
|
-
const root = roots.get(element);
|
|
26
|
-
if (root) {
|
|
27
|
-
root.unmount();
|
|
28
|
-
roots.delete(element);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
`;
|
|
32
|
-
const tridentrcTemplate = (appName, port) => `/** @type {import("@mondaydotcomorg/trident-user-config").MicroFrontendConfig} */
|
|
33
|
-
const config = {
|
|
34
|
-
appName: '${appName}',
|
|
35
|
-
port: ${port},
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
module.exports = config;
|
|
39
|
-
`;
|
|
40
|
-
const dtsFileTemplate = () => `
|
|
41
|
-
declare module '*.module.css' {
|
|
42
|
-
const classNames: {
|
|
43
|
-
[className: string]: string;
|
|
44
|
-
};
|
|
45
|
-
export = classNames;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
declare module '*.module.scss' {
|
|
49
|
-
const classNames: {
|
|
50
|
-
[className: string]: string;
|
|
51
|
-
};
|
|
52
|
-
export = classNames;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
declare module '*.jpg' {
|
|
56
|
-
const e: string;
|
|
57
|
-
export default e;
|
|
58
|
-
}
|
|
59
|
-
declare module '*.jpeg' {
|
|
60
|
-
const e: string;
|
|
61
|
-
export default e;
|
|
62
|
-
}
|
|
63
|
-
declare module '*.png' {
|
|
64
|
-
const e: string;
|
|
65
|
-
export default e;
|
|
66
|
-
}
|
|
67
|
-
declare module '*.riv' {
|
|
68
|
-
const e: string;
|
|
69
|
-
export default e;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
declare module '*.wasm' {
|
|
73
|
-
const e: string;
|
|
74
|
-
export default e;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
declare module '*.html?url' {
|
|
78
|
-
const src: string;
|
|
79
|
-
export default src;
|
|
80
|
-
}
|
|
81
|
-
`;
|
|
82
|
-
const TSCONFIG_TEMPLATE = {
|
|
83
|
-
compilerOptions: {
|
|
84
|
-
target: 'es5',
|
|
85
|
-
lib: ['dom', 'dom.iterable', 'esnext'],
|
|
86
|
-
allowJs: false,
|
|
87
|
-
skipLibCheck: true,
|
|
88
|
-
esModuleInterop: true,
|
|
89
|
-
allowSyntheticDefaultImports: true,
|
|
90
|
-
strict: false,
|
|
91
|
-
module: 'esnext',
|
|
92
|
-
moduleResolution: 'bundler',
|
|
93
|
-
resolveJsonModule: true,
|
|
94
|
-
noImplicitAny: false,
|
|
95
|
-
isolatedModules: true,
|
|
96
|
-
baseUrl: 'src',
|
|
97
|
-
noEmit: true,
|
|
98
|
-
jsx: 'react-jsx',
|
|
99
|
-
},
|
|
100
|
-
include: ['src', '../src'],
|
|
101
|
-
};
|
|
102
5
|
/**
|
|
103
6
|
* Write every generated file under .zth/. Overwrites on every run so the
|
|
104
7
|
* shadow dir is a deterministic function of the CLI version — no drift.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/shadow/scaffold.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/shadow/scaffold.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;GAGG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvG"}
|
package/dist/shadow/scaffold.js
CHANGED
|
@@ -2,120 +2,23 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
const fs = require('fs-extra');
|
|
5
|
+
const mfScaffold = require('@mondaydotcomorg/z2h-shared-utils/mf-scaffold');
|
|
5
6
|
|
|
6
7
|
const _interopDefault = e => e && e.__esModule ? e : { default: e };
|
|
7
8
|
|
|
8
9
|
const path__default = /*#__PURE__*/_interopDefault(path);
|
|
9
10
|
|
|
10
|
-
const WRAPPER_TEMPLATE = `import '@vibe/core/tokens';
|
|
11
|
-
import ConsumerApp from '../../src/index';
|
|
12
|
-
import React from 'react';
|
|
13
|
-
import * as ReactDOM from 'react-dom/client';
|
|
14
|
-
import { ErrorBoundary } from 'react-error-boundary';
|
|
15
|
-
|
|
16
|
-
const roots = new Map<HTMLElement, ReactDOM.Root>();
|
|
17
|
-
|
|
18
|
-
export function init() {}
|
|
19
|
-
|
|
20
|
-
export function render(element: HTMLElement) {
|
|
21
|
-
const root = ReactDOM.createRoot(element);
|
|
22
|
-
root.render(
|
|
23
|
-
<ErrorBoundary fallback={<div style={{ padding: 16 }}>This app crashed.</div>}>
|
|
24
|
-
<ConsumerApp />
|
|
25
|
-
</ErrorBoundary>,
|
|
26
|
-
);
|
|
27
|
-
roots.set(element, root);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function unmount({ element }: { element: HTMLElement }) {
|
|
31
|
-
const root = roots.get(element);
|
|
32
|
-
if (root) {
|
|
33
|
-
root.unmount();
|
|
34
|
-
roots.delete(element);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
`;
|
|
38
|
-
const tridentrcTemplate = (appName, port) => `/** @type {import("@mondaydotcomorg/trident-user-config").MicroFrontendConfig} */
|
|
39
|
-
const config = {
|
|
40
|
-
appName: '${appName}',
|
|
41
|
-
port: ${port},
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
module.exports = config;
|
|
45
|
-
`;
|
|
46
|
-
const dtsFileTemplate = () => `
|
|
47
|
-
declare module '*.module.css' {
|
|
48
|
-
const classNames: {
|
|
49
|
-
[className: string]: string;
|
|
50
|
-
};
|
|
51
|
-
export = classNames;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
declare module '*.module.scss' {
|
|
55
|
-
const classNames: {
|
|
56
|
-
[className: string]: string;
|
|
57
|
-
};
|
|
58
|
-
export = classNames;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
declare module '*.jpg' {
|
|
62
|
-
const e: string;
|
|
63
|
-
export default e;
|
|
64
|
-
}
|
|
65
|
-
declare module '*.jpeg' {
|
|
66
|
-
const e: string;
|
|
67
|
-
export default e;
|
|
68
|
-
}
|
|
69
|
-
declare module '*.png' {
|
|
70
|
-
const e: string;
|
|
71
|
-
export default e;
|
|
72
|
-
}
|
|
73
|
-
declare module '*.riv' {
|
|
74
|
-
const e: string;
|
|
75
|
-
export default e;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
declare module '*.wasm' {
|
|
79
|
-
const e: string;
|
|
80
|
-
export default e;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
declare module '*.html?url' {
|
|
84
|
-
const src: string;
|
|
85
|
-
export default src;
|
|
86
|
-
}
|
|
87
|
-
`;
|
|
88
|
-
const TSCONFIG_TEMPLATE = {
|
|
89
|
-
compilerOptions: {
|
|
90
|
-
target: 'es5',
|
|
91
|
-
lib: ['dom', 'dom.iterable', 'esnext'],
|
|
92
|
-
allowJs: false,
|
|
93
|
-
skipLibCheck: true,
|
|
94
|
-
esModuleInterop: true,
|
|
95
|
-
allowSyntheticDefaultImports: true,
|
|
96
|
-
strict: false,
|
|
97
|
-
module: 'esnext',
|
|
98
|
-
moduleResolution: 'bundler',
|
|
99
|
-
resolveJsonModule: true,
|
|
100
|
-
noImplicitAny: false,
|
|
101
|
-
isolatedModules: true,
|
|
102
|
-
baseUrl: 'src',
|
|
103
|
-
noEmit: true,
|
|
104
|
-
jsx: 'react-jsx',
|
|
105
|
-
},
|
|
106
|
-
include: ['src', '../src'],
|
|
107
|
-
};
|
|
108
11
|
/**
|
|
109
12
|
* Write every generated file under .zth/. Overwrites on every run so the
|
|
110
13
|
* shadow dir is a deterministic function of the CLI version — no drift.
|
|
111
14
|
*/
|
|
112
15
|
async function scaffoldShadow(paths, appName, port) {
|
|
113
16
|
await fs.ensureDir(paths.shadowSrc);
|
|
114
|
-
await fs.writeFile(paths.shadowWrapper, WRAPPER_TEMPLATE);
|
|
115
|
-
await fs.writeFile(paths.shadowTridentrc, tridentrcTemplate(appName, port));
|
|
17
|
+
await fs.writeFile(paths.shadowWrapper, mfScaffold.WRAPPER_TEMPLATE);
|
|
18
|
+
await fs.writeFile(paths.shadowTridentrc, mfScaffold.tridentrcTemplate(appName, port));
|
|
116
19
|
await fs.writeFile(paths.shadowNvmrc, `${process.version}\n`);
|
|
117
|
-
await fs.writeFile(paths.shadowDts, dtsFileTemplate());
|
|
118
|
-
await fs.writeJson(paths.shadowTsconfig, TSCONFIG_TEMPLATE, { spaces: 2 });
|
|
20
|
+
await fs.writeFile(paths.shadowDts, mfScaffold.dtsFileTemplate());
|
|
21
|
+
await fs.writeJson(paths.shadowTsconfig, mfScaffold.TSCONFIG_TEMPLATE, { spaces: 2 });
|
|
119
22
|
// Drop a stub README so contributors who open .zth/ know what they're looking at.
|
|
120
23
|
await fs.writeFile(path__default.default.join(paths.shadowDir, 'README.md'), '# .zth/\nGenerated by z2h-cli. Do not edit — run `z2h-cli build` or `z2h-cli dev` to refresh.\n');
|
|
121
24
|
}
|
|
@@ -57,6 +57,7 @@ const OAUTH_CHANNELS: Record<string, (caller: Caller) => ChannelMap> = {
|
|
|
57
57
|
* setup) is wrong and retrying won't help; `502` means the upstream failed.
|
|
58
58
|
*
|
|
59
59
|
* Each key is wrapped with `z2h.channel.<key>` duration + error metrics, tagged by app.
|
|
60
|
+
* Failures also reach BI generically at the invoke level — see `runner.service.ts`.
|
|
60
61
|
*/
|
|
61
62
|
export function buildChannels(appName: string, caller: Caller, declaredIntegrations: string[]): ChannelMap {
|
|
62
63
|
const channels: ChannelMap = {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { logger } from '@mondaydotcomorg/trident-backend-runtime';
|
|
2
2
|
import { createMondayModel } from '@mondaydotcomorg/kong-ai-gateway-sdk/langchain';
|
|
3
|
+
import { BackendRunnerEvents, trackEvent } from '@mondaydotcomorg/z2h-shared-utils/observability';
|
|
3
4
|
|
|
4
5
|
import { ChannelError } from '../../backend-runner.errors';
|
|
6
|
+
import { BI_SOURCE } from '../../constants';
|
|
5
7
|
import { ConfigurationKey, getConfiguration } from '../../../ignite-service';
|
|
6
8
|
import { LLM_TIMEOUT_MS, withTimeout } from '../with-timeout';
|
|
7
9
|
import type { ChannelMap, Caller } from '../../backend-runner.types';
|
|
@@ -207,6 +209,7 @@ export function makeLlmChannel(appName: string, caller: Caller) {
|
|
|
207
209
|
LLM_TIMEOUT_MS,
|
|
208
210
|
'LLM gateway',
|
|
209
211
|
);
|
|
212
|
+
trackEvent(BackendRunnerEvents.llm.call, { appName, email: caller.email, modelId: model }, BI_SOURCE);
|
|
210
213
|
return { content: extractText(response.content) };
|
|
211
214
|
} catch (err) {
|
|
212
215
|
throw toLlmChannelError(err);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
|
|
3
3
|
import { context, logger } from '@mondaydotcomorg/trident-backend-runtime';
|
|
4
|
+
import { BackendRunnerEvents, trackEvent } from '@mondaydotcomorg/z2h-shared-utils/observability';
|
|
4
5
|
import { getDefaultClient } from 'src/utils/elasticache';
|
|
5
6
|
|
|
6
7
|
import { ChannelError } from '../../backend-runner.errors';
|
|
8
|
+
import { BI_SOURCE } from '../../constants';
|
|
7
9
|
import { TIMEOUT_BACKSTOP_MS, withTimeout } from '../with-timeout';
|
|
8
10
|
import { loadSnowflakeQuery } from './analytical-layer-loader';
|
|
9
11
|
import { guardAndBindQuery, type SnowflakeParamValue } from './query-guard';
|
|
@@ -14,6 +16,7 @@ export const DEFAULT_TIMEOUT_MS = 60_000;
|
|
|
14
16
|
export const MAX_TIMEOUT_MS = 300_000;
|
|
15
17
|
|
|
16
18
|
const CACHE_PATTERN = 'z2h-snowflake-query' as const;
|
|
19
|
+
const LOG_TAG = 'z2h-snowflake';
|
|
17
20
|
|
|
18
21
|
export interface SnowflakeRow {
|
|
19
22
|
[column: string]: unknown;
|
|
@@ -131,8 +134,22 @@ export async function runSnowflakeQuery(
|
|
|
131
134
|
const queryCacheKey = hashQuery(boundSql, params ?? {});
|
|
132
135
|
const queryIdentifier = hashQueryShape(sql);
|
|
133
136
|
const scope = cacheScope(useStateless);
|
|
137
|
+
const startMs = Date.now();
|
|
134
138
|
const cached = scope ? await readCache(queryCacheKey, scope) : null;
|
|
135
139
|
if (cached) {
|
|
140
|
+
const duration_ms = Date.now() - startMs;
|
|
141
|
+
trackEvent(BackendRunnerEvents.snowflake.queryExecuted, { appName, duration_ms, cache_hit: true }, BI_SOURCE);
|
|
142
|
+
logger.info(
|
|
143
|
+
{
|
|
144
|
+
tag: LOG_TAG,
|
|
145
|
+
queryHash: queryCacheKey,
|
|
146
|
+
queryIdentifier,
|
|
147
|
+
duration_ms,
|
|
148
|
+
rowCount: cached.length,
|
|
149
|
+
cache_hit: true,
|
|
150
|
+
},
|
|
151
|
+
'query executed',
|
|
152
|
+
);
|
|
136
153
|
return cached;
|
|
137
154
|
}
|
|
138
155
|
|
|
@@ -164,12 +181,25 @@ export async function runSnowflakeQuery(
|
|
|
164
181
|
'snowflake query',
|
|
165
182
|
);
|
|
166
183
|
} catch (err) {
|
|
184
|
+
// Failure BI tracking already happens generically at the invoke level
|
|
185
|
+
// (runner.service.ts's z2h_backend_invoke_failed, tagged with this
|
|
186
|
+
// ChannelError's `code`) — this stays a log only, for the queryHash/
|
|
187
|
+
// queryIdentifier that generic event can't know about.
|
|
188
|
+
const duration_ms = Date.now() - startMs;
|
|
189
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
190
|
+
logger.error({ tag: LOG_TAG, queryHash: queryCacheKey, queryIdentifier, duration_ms, error }, 'query failed');
|
|
167
191
|
throw toSnowflakeChannelError(err);
|
|
168
192
|
}
|
|
169
193
|
const rows = Array.isArray(data) ? data : [];
|
|
170
194
|
if (scope) {
|
|
171
195
|
await writeCache(queryCacheKey, scope, rows);
|
|
172
196
|
}
|
|
197
|
+
const duration_ms = Date.now() - startMs;
|
|
198
|
+
trackEvent(BackendRunnerEvents.snowflake.queryExecuted, { appName, duration_ms, cache_hit: false }, BI_SOURCE);
|
|
199
|
+
logger.info(
|
|
200
|
+
{ tag: LOG_TAG, queryHash: queryCacheKey, queryIdentifier, duration_ms, rowCount: rows.length, cache_hit: false },
|
|
201
|
+
'query executed',
|
|
202
|
+
);
|
|
173
203
|
return rows;
|
|
174
204
|
}
|
|
175
205
|
|
|
@@ -184,7 +214,7 @@ async function readCache(queryCacheKey: string, scope: string): Promise<Snowflak
|
|
|
184
214
|
const hit = await getDefaultClient().getCompressed(CACHE_PATTERN, { queryHash: queryCacheKey, scope });
|
|
185
215
|
return hit ? (JSON.parse(hit.toString()) as SnowflakeRow[]) : null;
|
|
186
216
|
} catch (err) {
|
|
187
|
-
logger.warn({ tag:
|
|
217
|
+
logger.warn({ tag: LOG_TAG, error: err instanceof Error ? err.message : String(err) }, 'cache read failed');
|
|
188
218
|
return null;
|
|
189
219
|
}
|
|
190
220
|
}
|
|
@@ -193,13 +223,29 @@ async function writeCache(queryCacheKey: string, scope: string, rows: SnowflakeR
|
|
|
193
223
|
try {
|
|
194
224
|
await getDefaultClient().setCompressed(CACHE_PATTERN, { queryHash: queryCacheKey, scope }, JSON.stringify(rows));
|
|
195
225
|
} catch (err) {
|
|
196
|
-
logger.warn(
|
|
197
|
-
{ tag: 'z2h-snowflake', error: err instanceof Error ? err.message : String(err) },
|
|
198
|
-
'cache write failed',
|
|
199
|
-
);
|
|
226
|
+
logger.warn({ tag: LOG_TAG, error: err instanceof Error ? err.message : String(err) }, 'cache write failed');
|
|
200
227
|
}
|
|
201
228
|
}
|
|
202
229
|
|
|
230
|
+
/**
|
|
231
|
+
* The 4xx the analytical layer is known to return. Its own text is fetcher boilerplate
|
|
232
|
+
* ("Fetcher response returned NON-OK status=…") and carries no Snowflake detail, so these
|
|
233
|
+
* say what the app can do instead of forwarding it.
|
|
234
|
+
*/
|
|
235
|
+
const UPSTREAM_4XX: Record<number, { code: string; message: string }> = {
|
|
236
|
+
400: { code: 'snowflake_query_error', message: 'snowflake rejected the query — check the SQL' },
|
|
237
|
+
401: { code: 'snowflake_unauthorized', message: 'snowflake authentication failed' },
|
|
238
|
+
403: { code: 'snowflake_forbidden', message: 'snowflake authorization failed — the caller lacks grants' },
|
|
239
|
+
413: {
|
|
240
|
+
code: 'snowflake_result_too_large',
|
|
241
|
+
message: 'snowflake result too large — add a LIMIT, select fewer columns, or aggregate in SQL',
|
|
242
|
+
},
|
|
243
|
+
429: {
|
|
244
|
+
code: 'snowflake_rate_limited',
|
|
245
|
+
message: 'snowflake rate limit exceeded — too many queries in flight, retry shortly',
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
203
249
|
/**
|
|
204
250
|
* Map an analytical-layer failure to the right HTTP status by cause: the caller
|
|
205
251
|
* lacking Snowflake grants vs. the app's SQL being wrong (compilation/syntax)
|
|
@@ -210,6 +256,37 @@ function toSnowflakeChannelError(err: unknown): ChannelError {
|
|
|
210
256
|
return err;
|
|
211
257
|
}
|
|
212
258
|
const message = err instanceof Error ? err.message : String(err);
|
|
259
|
+
|
|
260
|
+
// The analytical layer rethrows monday-fetch's `HttpFetcherError` untouched, so
|
|
261
|
+
// an HTTP failure arrives with its real status on the error. Read it structurally
|
|
262
|
+
// rather than via `instanceof`: monday-fetch is a transitive dep and node_modules
|
|
263
|
+
// holds several copies, so the prototype chain is not reliably shared. Matches how
|
|
264
|
+
// the llm and monday channels already classify their upstream failures.
|
|
265
|
+
const status = (err as { status?: unknown })?.status;
|
|
266
|
+
if (typeof status === 'number') {
|
|
267
|
+
// Every 4xx the layer reports is about the request the app asked us to make, so it
|
|
268
|
+
// passes through with its own status — the app can act on it and it stays out of our
|
|
269
|
+
// 5xx alerting. Forwarding generically rather than enumerating means a status the
|
|
270
|
+
// layer adds later arrives intact instead of silently becoming a 502.
|
|
271
|
+
//
|
|
272
|
+
// 404 is the exception: it means our own call was misrouted, which the app can do
|
|
273
|
+
// nothing about, and forwarding it would be indistinguishable from
|
|
274
|
+
// BackendNotFoundError's "handler not found".
|
|
275
|
+
if (status >= 400 && status < 500 && status !== 404) {
|
|
276
|
+
const known = UPSTREAM_4XX[status];
|
|
277
|
+
return new ChannelError(
|
|
278
|
+
status,
|
|
279
|
+
known?.message ?? `snowflake rejected the query (${status})`,
|
|
280
|
+
known?.code ?? 'snowflake_request_rejected',
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
// 5xx (and that 404) are genuinely upstream failures. Returning here rather than
|
|
284
|
+
// falling through is deliberate — the heuristics below read the message, and an
|
|
285
|
+
// upstream 500 whose body happens to mention "compilation error" is still a 500.
|
|
286
|
+
return new ChannelError(502, `snowflake query failed: ${message}`, 'snowflake_upstream_error');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// No status: a driver-level error from the Snowflake SDK, classified by message.
|
|
213
290
|
if (/insufficient privileg|not authorized|access denied|permission/i.test(message)) {
|
|
214
291
|
return new ChannelError(403, `snowflake authorization error: ${message}`, 'snowflake_forbidden');
|
|
215
292
|
}
|
|
@@ -62,6 +62,29 @@ export function makeSnowflakeChannel(appName: string) {
|
|
|
62
62
|
* undefined,
|
|
63
63
|
* { timeoutMs: 25_000 },
|
|
64
64
|
* );
|
|
65
|
+
*
|
|
66
|
+
* @throws `400` `snowflake_invalid_argument` — `sql` was not a non-empty string.
|
|
67
|
+
* @throws `400` `snowflake_not_read_only` — the statement is not a SELECT.
|
|
68
|
+
* @throws `400` `snowflake_invalid_sql` — the statement could not be parsed.
|
|
69
|
+
* @throws `400` `snowflake_invalid_binding` — a `:name` placeholder has no matching entry in `params`.
|
|
70
|
+
* @throws `400` `snowflake_invalid_timeout` — `options.timeoutMs` is not a positive number.
|
|
71
|
+
* @throws `400` `snowflake_query_error` — the warehouse rejected the SQL (compilation error, unknown
|
|
72
|
+
* column, missing table). Fix the query; retrying is pointless.
|
|
73
|
+
* @throws `401` `snowflake_unauthorized` — the caller could not be authenticated to Snowflake.
|
|
74
|
+
* @throws `403` `snowflake_forbidden` — the caller is authenticated but lacks grants on what the
|
|
75
|
+
* query reads. Narrow the query or request access to the tables.
|
|
76
|
+
* @throws `413` `snowflake_result_too_large` — the result set is too big to serialize. Add a
|
|
77
|
+
* `LIMIT`, select fewer columns, or aggregate in SQL instead of returning raw rows.
|
|
78
|
+
* @throws `429` `snowflake_rate_limited` — too many queries in flight **for this user**, counted
|
|
79
|
+
* across every Z2H surface they have open. **This one is retryable** and it is the most common
|
|
80
|
+
* failure here. The cap is on concurrency, not on how much you query in total: `await` your
|
|
81
|
+
* queries in sequence, or batch them into one SQL statement, rather than issuing a screenful
|
|
82
|
+
* with `Promise.all`. Retrying immediately without backing off makes it worse for everyone.
|
|
83
|
+
* @throws `4xx` `snowflake_request_rejected` — any other rejection from the analytical layer,
|
|
84
|
+
* forwarded with its own status.
|
|
85
|
+
* @throws `502` `snowflake_upstream_error` — the analytical layer itself failed. Not the app's fault;
|
|
86
|
+
* retry later.
|
|
87
|
+
* @throws `504` `upstream_timeout` — the query exceeded its budget. Narrow it or raise `options.timeoutMs`.
|
|
65
88
|
*/
|
|
66
89
|
'snowflake.query': async ([query, params, options]): Promise<SnowflakeRow[]> => {
|
|
67
90
|
if (typeof query !== 'string' || query.length === 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mondaydotcomorg/z2h-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"bin": "./bin/z2h-cli.js",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
],
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@aws-sdk/client-s3": "^3.706.0",
|
|
54
|
+
"@mondaydotcomorg/z2h-shared-utils": "^0.3.1",
|
|
54
55
|
"chokidar": "^3.6.0",
|
|
55
56
|
"commander": "^12.1.0",
|
|
56
57
|
"cors": "^2.8.6",
|
package/src/shadow/scaffold.ts
CHANGED
|
@@ -1,111 +1,13 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { ensureDir, writeFile, writeJson } from 'fs-extra';
|
|
3
|
+
import {
|
|
4
|
+
WRAPPER_TEMPLATE,
|
|
5
|
+
tridentrcTemplate,
|
|
6
|
+
dtsFileTemplate,
|
|
7
|
+
TSCONFIG_TEMPLATE,
|
|
8
|
+
} from '@mondaydotcomorg/z2h-shared-utils/mf-scaffold';
|
|
3
9
|
import type { ConsumerPaths } from '../types';
|
|
4
10
|
|
|
5
|
-
const WRAPPER_TEMPLATE = `import '@vibe/core/tokens';
|
|
6
|
-
import ConsumerApp from '../../src/index';
|
|
7
|
-
import React from 'react';
|
|
8
|
-
import * as ReactDOM from 'react-dom/client';
|
|
9
|
-
import { ErrorBoundary } from 'react-error-boundary';
|
|
10
|
-
|
|
11
|
-
const roots = new Map<HTMLElement, ReactDOM.Root>();
|
|
12
|
-
|
|
13
|
-
export function init() {}
|
|
14
|
-
|
|
15
|
-
export function render(element: HTMLElement) {
|
|
16
|
-
const root = ReactDOM.createRoot(element);
|
|
17
|
-
root.render(
|
|
18
|
-
<ErrorBoundary fallback={<div style={{ padding: 16 }}>This app crashed.</div>}>
|
|
19
|
-
<ConsumerApp />
|
|
20
|
-
</ErrorBoundary>,
|
|
21
|
-
);
|
|
22
|
-
roots.set(element, root);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function unmount({ element }: { element: HTMLElement }) {
|
|
26
|
-
const root = roots.get(element);
|
|
27
|
-
if (root) {
|
|
28
|
-
root.unmount();
|
|
29
|
-
roots.delete(element);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
`;
|
|
33
|
-
|
|
34
|
-
const tridentrcTemplate = (appName: string, port: number): string =>
|
|
35
|
-
`/** @type {import("@mondaydotcomorg/trident-user-config").MicroFrontendConfig} */
|
|
36
|
-
const config = {
|
|
37
|
-
appName: '${appName}',
|
|
38
|
-
port: ${port},
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
module.exports = config;
|
|
42
|
-
`;
|
|
43
|
-
|
|
44
|
-
const dtsFileTemplate = (): string =>
|
|
45
|
-
`
|
|
46
|
-
declare module '*.module.css' {
|
|
47
|
-
const classNames: {
|
|
48
|
-
[className: string]: string;
|
|
49
|
-
};
|
|
50
|
-
export = classNames;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
declare module '*.module.scss' {
|
|
54
|
-
const classNames: {
|
|
55
|
-
[className: string]: string;
|
|
56
|
-
};
|
|
57
|
-
export = classNames;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
declare module '*.jpg' {
|
|
61
|
-
const e: string;
|
|
62
|
-
export default e;
|
|
63
|
-
}
|
|
64
|
-
declare module '*.jpeg' {
|
|
65
|
-
const e: string;
|
|
66
|
-
export default e;
|
|
67
|
-
}
|
|
68
|
-
declare module '*.png' {
|
|
69
|
-
const e: string;
|
|
70
|
-
export default e;
|
|
71
|
-
}
|
|
72
|
-
declare module '*.riv' {
|
|
73
|
-
const e: string;
|
|
74
|
-
export default e;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
declare module '*.wasm' {
|
|
78
|
-
const e: string;
|
|
79
|
-
export default e;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
declare module '*.html?url' {
|
|
83
|
-
const src: string;
|
|
84
|
-
export default src;
|
|
85
|
-
}
|
|
86
|
-
`;
|
|
87
|
-
|
|
88
|
-
const TSCONFIG_TEMPLATE = {
|
|
89
|
-
compilerOptions: {
|
|
90
|
-
target: 'es5',
|
|
91
|
-
lib: ['dom', 'dom.iterable', 'esnext'],
|
|
92
|
-
allowJs: false,
|
|
93
|
-
skipLibCheck: true,
|
|
94
|
-
esModuleInterop: true,
|
|
95
|
-
allowSyntheticDefaultImports: true,
|
|
96
|
-
strict: false,
|
|
97
|
-
module: 'esnext',
|
|
98
|
-
moduleResolution: 'bundler',
|
|
99
|
-
resolveJsonModule: true,
|
|
100
|
-
noImplicitAny: false,
|
|
101
|
-
isolatedModules: true,
|
|
102
|
-
baseUrl: 'src',
|
|
103
|
-
noEmit: true,
|
|
104
|
-
jsx: 'react-jsx',
|
|
105
|
-
},
|
|
106
|
-
include: ['src', '../src'],
|
|
107
|
-
};
|
|
108
|
-
|
|
109
11
|
/**
|
|
110
12
|
* Write every generated file under .zth/. Overwrites on every run so the
|
|
111
13
|
* shadow dir is a deterministic function of the CLI version — no drift.
|