@mondaydotcomorg/z2h-cli 0.25.4 → 0.25.8
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/auth.d.ts +2 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +2 -1
- package/dist/commands/__tests__/build.test.d.ts +2 -0
- package/dist/commands/__tests__/build.test.d.ts.map +1 -0
- package/dist/commands/backend.d.ts.map +1 -1
- package/dist/commands/backend.js +7 -1
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +10 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +42 -19
- package/dist/commands/grant.d.ts.map +1 -1
- package/dist/commands/grant.js +8 -7
- package/dist/esm/auth.d.ts +2 -1
- package/dist/esm/auth.d.ts.map +1 -1
- package/dist/esm/auth.mjs +2 -1
- package/dist/esm/commands/__tests__/build.test.d.ts +2 -0
- package/dist/esm/commands/__tests__/build.test.d.ts.map +1 -0
- package/dist/esm/commands/backend.d.ts.map +1 -1
- package/dist/esm/commands/backend.mjs +7 -1
- package/dist/esm/commands/build.d.ts.map +1 -1
- package/dist/esm/commands/build.mjs +10 -0
- package/dist/esm/commands/deploy.d.ts.map +1 -1
- package/dist/esm/commands/deploy.mjs +43 -20
- package/dist/esm/commands/grant.d.ts.map +1 -1
- package/dist/esm/commands/grant.mjs +8 -7
- package/dist/esm/index.mjs +41 -140
- package/dist/esm/types.d.ts +23 -0
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/util/__tests__/errors.test.d.ts +2 -0
- package/dist/esm/util/__tests__/errors.test.d.ts.map +1 -0
- package/dist/esm/util/__tests__/run-command.test.d.ts +2 -0
- package/dist/esm/util/__tests__/run-command.test.d.ts.map +1 -0
- package/dist/esm/util/auth/auth-store.d.ts +0 -4
- package/dist/esm/util/auth/auth-store.d.ts.map +1 -1
- package/dist/esm/util/auth/auth-store.mjs +2 -9
- package/dist/esm/util/auth/broker-credential-provider.d.ts.map +1 -1
- package/dist/esm/util/auth/broker-credential-provider.mjs +16 -16
- package/dist/esm/util/broker/app.d.ts.map +1 -1
- package/dist/esm/util/broker/app.mjs +17 -9
- package/dist/esm/util/errors.d.ts +48 -0
- package/dist/esm/util/errors.d.ts.map +1 -0
- package/dist/esm/util/errors.mjs +91 -0
- package/dist/esm/util/git/repo.d.ts +0 -3
- package/dist/esm/util/git/repo.d.ts.map +1 -1
- package/dist/esm/util/git/repo.mjs +4 -9
- package/dist/esm/util/logger.d.ts +4 -1
- package/dist/esm/util/logger.d.ts.map +1 -1
- package/dist/esm/util/logger.mjs +11 -3
- package/dist/esm/util/run-command.d.ts +16 -0
- package/dist/esm/util/run-command.d.ts.map +1 -0
- package/dist/esm/util/run-command.mjs +47 -0
- package/dist/esm/util/s3/upload.d.ts.map +1 -1
- package/dist/esm/util/s3/upload.mjs +22 -16
- package/dist/esm/util/tracker.d.ts +9 -2
- package/dist/esm/util/tracker.d.ts.map +1 -1
- package/dist/esm/util/tracker.mjs +18 -15
- package/dist/index.js +40 -139
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/util/__tests__/errors.test.d.ts +2 -0
- package/dist/util/__tests__/errors.test.d.ts.map +1 -0
- package/dist/util/__tests__/run-command.test.d.ts +2 -0
- package/dist/util/__tests__/run-command.test.d.ts.map +1 -0
- package/dist/util/auth/auth-store.d.ts +0 -4
- package/dist/util/auth/auth-store.d.ts.map +1 -1
- package/dist/util/auth/auth-store.js +2 -10
- package/dist/util/auth/broker-credential-provider.d.ts.map +1 -1
- package/dist/util/auth/broker-credential-provider.js +16 -16
- package/dist/util/broker/app.d.ts.map +1 -1
- package/dist/util/broker/app.js +17 -9
- package/dist/util/errors.d.ts +48 -0
- package/dist/util/errors.d.ts.map +1 -0
- package/dist/util/errors.js +102 -0
- package/dist/util/git/repo.d.ts +0 -3
- package/dist/util/git/repo.d.ts.map +1 -1
- package/dist/util/git/repo.js +4 -10
- package/dist/util/logger.d.ts +4 -1
- package/dist/util/logger.d.ts.map +1 -1
- package/dist/util/logger.js +11 -3
- package/dist/util/run-command.d.ts +16 -0
- package/dist/util/run-command.d.ts.map +1 -0
- package/dist/util/run-command.js +49 -0
- package/dist/util/s3/upload.d.ts.map +1 -1
- package/dist/util/s3/upload.js +22 -16
- package/dist/util/tracker.d.ts +9 -2
- package/dist/util/tracker.d.ts.map +1 -1
- package/dist/util/tracker.js +19 -14
- package/handler-api/backend-runner/channels/channel-map.ts +6 -12
- package/handler-api/backend-runner/channels/channel-telemetry.ts +79 -0
- package/handler-api/backend-runner/channels/llm-channel/llm.channel.ts +3 -2
- package/handler-api/backend-runner/channels/snowflake-channel/run-query.ts +21 -7
- package/handler-api/backend-runner/channels/snowflake-channel/snowflake.channel.ts +14 -2
- package/handler-api/backend-runner/channels/snowflake-channel/stateless-part-b.ts +1 -4
- package/package.json +2 -2
- package/src/auth.ts +2 -1
- package/src/commands/__tests__/backend-deploy.test.ts +18 -0
- package/src/commands/__tests__/build.test.ts +83 -0
- package/src/commands/__tests__/deploy.test.ts +107 -0
- package/src/commands/backend.ts +6 -1
- package/src/commands/build.ts +10 -0
- package/src/commands/deploy.ts +42 -20
- package/src/commands/grant.ts +8 -7
- package/src/index.ts +84 -133
- package/src/types.ts +47 -0
- package/src/util/__tests__/errors.test.ts +90 -0
- package/src/util/__tests__/run-command.test.ts +98 -0
- package/src/util/auth/__tests__/auth-store.test.ts +2 -1
- package/src/util/auth/__tests__/broker-credential-provider.test.ts +44 -8
- package/src/util/auth/auth-store.ts +1 -7
- package/src/util/auth/broker-credential-provider.ts +18 -16
- package/src/util/broker/__tests__/app.test.ts +23 -20
- package/src/util/broker/app.ts +16 -9
- package/src/util/errors.ts +98 -0
- package/src/util/git/__tests__/repo.test.ts +5 -3
- package/src/util/git/repo.ts +3 -9
- package/src/util/logger.ts +16 -3
- package/src/util/run-command.ts +59 -0
- package/src/util/s3/upload.ts +27 -22
- package/src/util/tracker.ts +19 -19
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base for every error the CLI classifies. runCommand in ./run-command.ts reads `reason`
|
|
5
|
+
* and `data` straight off the error, so the throw site is the only place that needs to know
|
|
6
|
+
* why it failed. Nothing re-derives it from the error type at the top level.
|
|
7
|
+
*
|
|
8
|
+
* `exitCode` lets a throw site override the CLI's --help contract, 1 for user-resolvable
|
|
9
|
+
* and 2 for system failures, per error instead of per command. Nothing sets it yet. The
|
|
10
|
+
* command table still pins exit codes, which keeps today's behavior.
|
|
11
|
+
*/
|
|
12
|
+
class Z2hError extends Error {
|
|
13
|
+
reason;
|
|
14
|
+
data;
|
|
15
|
+
exitCode;
|
|
16
|
+
constructor(message, reason, data, exitCode = 1) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.reason = reason;
|
|
19
|
+
this.data = data;
|
|
20
|
+
this.exitCode = exitCode;
|
|
21
|
+
this.name = new.target.name;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Tags a deploy/preview stage failure with a bounded reason code instead of a freeform message. */
|
|
25
|
+
class DeployError extends Z2hError {
|
|
26
|
+
reason;
|
|
27
|
+
constructor(reason, message, data) {
|
|
28
|
+
super(message, reason, data);
|
|
29
|
+
this.reason = reason;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Tags a grant/revoke/transfer-owner failure with a bounded reason code instead of a freeform message.
|
|
34
|
+
* `reason` also accepts a raw HTTP status, which gets mapped to a bounded reason internally.
|
|
35
|
+
*/
|
|
36
|
+
class GrantError extends Z2hError {
|
|
37
|
+
constructor(reason, message, data) {
|
|
38
|
+
super(message, typeof reason === 'number' ? grantReasonForStatus(reason) : reason, data);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** No z2h JWT found — see auth-store.ts for the discovery order (env var, then ~/.zth/auth.json). */
|
|
42
|
+
class Z2hAuthMissingError extends Z2hError {
|
|
43
|
+
authFilePath;
|
|
44
|
+
constructor(authFilePath) {
|
|
45
|
+
super(`No Z2H deploy token found at ${authFilePath}. Please run 'z2h-cli generate-z2h-token' command.`, 'read_auth_token');
|
|
46
|
+
this.authFilePath = authFilePath;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** A git pull diverged or a push was rejected; `reason` distinguishes which stage hit it. */
|
|
50
|
+
class GitConflictError extends Z2hError {
|
|
51
|
+
constructor(message, reason = 'git_error') {
|
|
52
|
+
super(message, reason);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Maps an HTTP status to a bounded grant/revoke/transfer-owner failure reason. */
|
|
56
|
+
function grantReasonForStatus(status) {
|
|
57
|
+
// 401 is an expired/invalid token (re-auth fixes it); 403 is a real permission denial.
|
|
58
|
+
if (status === 401) {
|
|
59
|
+
return 'auth';
|
|
60
|
+
}
|
|
61
|
+
if (status === 403) {
|
|
62
|
+
return 'forbidden';
|
|
63
|
+
}
|
|
64
|
+
if (status === 404) {
|
|
65
|
+
return 'not_found';
|
|
66
|
+
}
|
|
67
|
+
if (status >= 500) {
|
|
68
|
+
return 'server_error';
|
|
69
|
+
}
|
|
70
|
+
return 'unknown';
|
|
71
|
+
}
|
|
72
|
+
/** Tags a failure with `reason`, unless `err` is already classified; `signal` overrides `reason` for a killed child process. */
|
|
73
|
+
function tagDeployError(reason, err) {
|
|
74
|
+
if (err instanceof Z2hError) {
|
|
75
|
+
return err;
|
|
76
|
+
}
|
|
77
|
+
const signal = err instanceof Error ? err.signal : undefined;
|
|
78
|
+
return new DeployError(signal ? 'interrupted' : reason, messageOf(err));
|
|
79
|
+
}
|
|
80
|
+
/** Reads the bounded reason off a classified error; anything unclassified is `unknown`. */
|
|
81
|
+
function reasonOf(err) {
|
|
82
|
+
return err instanceof Z2hError ? err.reason : 'unknown';
|
|
83
|
+
}
|
|
84
|
+
/** Extra telemetry dimensions the throw site attached, if any. */
|
|
85
|
+
function dataOf(err) {
|
|
86
|
+
return err instanceof Z2hError ? err.data ?? {} : {};
|
|
87
|
+
}
|
|
88
|
+
/** Safe message for any thrown value. A non-Error throw must not break the error path. */
|
|
89
|
+
function messageOf(err) {
|
|
90
|
+
return err instanceof Error ? err.message : String(err);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
exports.DeployError = DeployError;
|
|
94
|
+
exports.GitConflictError = GitConflictError;
|
|
95
|
+
exports.GrantError = GrantError;
|
|
96
|
+
exports.Z2hAuthMissingError = Z2hAuthMissingError;
|
|
97
|
+
exports.Z2hError = Z2hError;
|
|
98
|
+
exports.dataOf = dataOf;
|
|
99
|
+
exports.grantReasonForStatus = grantReasonForStatus;
|
|
100
|
+
exports.messageOf = messageOf;
|
|
101
|
+
exports.reasonOf = reasonOf;
|
|
102
|
+
exports.tagDeployError = tagDeployError;
|
package/dist/util/git/repo.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../../src/util/git/repo.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../../src/util/git/repo.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAKD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOzE;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAI3E;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK/D;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5G;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAclE;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B7F;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB3F;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAO7D;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG5D"}
|
package/dist/util/git/repo.js
CHANGED
|
@@ -5,6 +5,7 @@ const fs = require('fs-extra');
|
|
|
5
5
|
const execa = require('execa');
|
|
6
6
|
const constants = require('../../constants.js');
|
|
7
7
|
const util_appName = require('../app-name.js');
|
|
8
|
+
const util_errors = require('../errors.js');
|
|
8
9
|
|
|
9
10
|
const _interopDefault = e => e && e.__esModule ? e : { default: e };
|
|
10
11
|
|
|
@@ -19,12 +20,6 @@ async function execGitLines(args, dir, env) {
|
|
|
19
20
|
.then(r => r.stdout.split('\n').filter(Boolean))
|
|
20
21
|
.catch(() => []);
|
|
21
22
|
}
|
|
22
|
-
class GitConflictError extends Error {
|
|
23
|
-
constructor(message) {
|
|
24
|
-
super(message);
|
|
25
|
-
this.name = 'GitConflictError';
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
23
|
const REQUIRED_GITIGNORE_ENTRIES = ['node_modules/', '.zth/', 'build/'];
|
|
29
24
|
const LOCAL_HOOKS_DIR = '.zth/hooks';
|
|
30
25
|
function defaultS3Remote(app) {
|
|
@@ -98,7 +93,7 @@ async function pullAndResolve(dir, env) {
|
|
|
98
93
|
}
|
|
99
94
|
await safeBackupIfDirty(dir);
|
|
100
95
|
const info = await gatherConflictInfo(dir, gitEnv);
|
|
101
|
-
throw new GitConflictError(`Pull rejected: Git conflict detected: ${JSON.stringify(info)}`);
|
|
96
|
+
throw new util_errors.GitConflictError(`Pull rejected: Git conflict detected: ${JSON.stringify(info)}`);
|
|
102
97
|
}
|
|
103
98
|
}
|
|
104
99
|
/**
|
|
@@ -126,9 +121,9 @@ async function pushToRemote(dir, env) {
|
|
|
126
121
|
}
|
|
127
122
|
if (isRejected) {
|
|
128
123
|
const info = await gatherConflictInfo(dir, gitEnv);
|
|
129
|
-
throw new GitConflictError(`Push rejected: Git conflict detected: ${JSON.stringify(info)}
|
|
124
|
+
throw new util_errors.GitConflictError(`Push rejected: Git conflict detected: ${JSON.stringify(info)}`, 'push_error');
|
|
130
125
|
}
|
|
131
|
-
throw err;
|
|
126
|
+
throw new util_errors.DeployError('push_error', util_errors.messageOf(err));
|
|
132
127
|
}
|
|
133
128
|
}
|
|
134
129
|
async function remoteHead(dir) {
|
|
@@ -183,7 +178,6 @@ async function ensureGitignore(dir) {
|
|
|
183
178
|
await fs__default.default.outputFile(gitignorePath, content + separator + missing.join('\n') + '\n');
|
|
184
179
|
}
|
|
185
180
|
|
|
186
|
-
exports.GitConflictError = GitConflictError;
|
|
187
181
|
exports.cloneRepo = cloneRepo;
|
|
188
182
|
exports.defaultS3Remote = defaultS3Remote;
|
|
189
183
|
exports.getRemoteUrl = getRemoteUrl;
|
package/dist/util/logger.d.ts
CHANGED
|
@@ -17,6 +17,9 @@ export declare function output(human: string, data: unknown): void;
|
|
|
17
17
|
/**
|
|
18
18
|
* Emit a structured failure payload (JSON mode) or a red error line (human mode),
|
|
19
19
|
* then exit with the given code.
|
|
20
|
+
*
|
|
21
|
+
* `track: false` skips the telemetry event but still prints and exits. Commands with no
|
|
22
|
+
* success event pass it, so their failures don't skew per-command error rates.
|
|
20
23
|
*/
|
|
21
|
-
export declare function fail(message: string, payload: Record<string, unknown>, exitCode?: number): Promise<never>;
|
|
24
|
+
export declare function fail(message: string, payload: Record<string, unknown>, exitCode?: number, track?: boolean): Promise<never>;
|
|
22
25
|
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/util/logger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/util/logger.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AAI1C,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAEpD;AAED,wBAAgB,aAAa,IAAI,UAAU,CAE1C;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAItC;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAItC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAI1C;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAMzD;AAED;;;;;;GAMG;AACH,wBAAsB,IAAI,CACxB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,SAAI,EACZ,KAAK,UAAO,GACX,OAAO,CAAC,KAAK,CAAC,CAehB"}
|
package/dist/util/logger.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
|
|
3
3
|
const pc = require('picocolors');
|
|
4
|
+
const observability = require('@mondaydotcomorg/z2h-shared-utils/observability');
|
|
4
5
|
const util_tracker = require('./tracker.js');
|
|
5
6
|
|
|
6
7
|
const _interopDefault = e => e && e.__esModule ? e : { default: e };
|
|
@@ -49,10 +50,15 @@ function output(human, data) {
|
|
|
49
50
|
/**
|
|
50
51
|
* Emit a structured failure payload (JSON mode) or a red error line (human mode),
|
|
51
52
|
* then exit with the given code.
|
|
53
|
+
*
|
|
54
|
+
* `track: false` skips the telemetry event but still prints and exits. Commands with no
|
|
55
|
+
* success event pass it, so their failures don't skew per-command error rates.
|
|
52
56
|
*/
|
|
53
|
-
async function fail(message, payload, exitCode = 1) {
|
|
57
|
+
async function fail(message, payload, exitCode = 1, track = true) {
|
|
58
|
+
// Canonical fields go last so a payload key can never shadow `error`/`action`.
|
|
59
|
+
const { command, ...rest } = payload;
|
|
54
60
|
if (mode === 'json') {
|
|
55
|
-
process.stdout.write(`${JSON.stringify({ ok: false, error: message
|
|
61
|
+
process.stdout.write(`${JSON.stringify({ ok: false, ...payload, error: message })}\n`);
|
|
56
62
|
}
|
|
57
63
|
else {
|
|
58
64
|
process.stderr.write(pc__default.default.red(`[z2h-cli] ${message}\n`));
|
|
@@ -60,7 +66,9 @@ async function fail(message, payload, exitCode = 1) {
|
|
|
60
66
|
process.stderr.write(pc__default.default.dim(` → ${payload.suggestedCommand}\n`));
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
|
-
|
|
69
|
+
if (track) {
|
|
70
|
+
await util_tracker.trackEvent(observability.Z2hCliEvents.error, { ...rest, action: command ?? 'unknown', error: message });
|
|
71
|
+
}
|
|
64
72
|
process.exit(exitCode);
|
|
65
73
|
}
|
|
66
74
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { CommandSpec } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Wraps a command body with the whole telemetry and failure contract so no command
|
|
5
|
+
* hand-rolls it. It times the happy path. On a throw it reads `reason` and `data` straight
|
|
6
|
+
* off the error, defined in ./errors, instead of re-deriving them per command.
|
|
7
|
+
*
|
|
8
|
+
* Returns a commander action handler, chainable straight off `.command(...)` — commander always
|
|
9
|
+
* appends the Command instance as the action handler's last argument, so this splits it back off
|
|
10
|
+
* to read the command's own name/parent instead of asking the caller to pass it in.
|
|
11
|
+
*
|
|
12
|
+
* NoInfer keeps the arg types coming from `fn` alone. A `data` or `label` callback that
|
|
13
|
+
* only needs the first arg must not narrow the handler's own signature.
|
|
14
|
+
*/
|
|
15
|
+
export declare function runCommand<A extends unknown[]>(spec: CommandSpec<NoInfer<A>>, fn: (...args: A) => Promise<unknown>): (...args: [...A, Command]) => Promise<void>;
|
|
16
|
+
//# sourceMappingURL=run-command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-command.d.ts","sourceRoot":"","sources":["../../src/util/run-command.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAW,MAAM,UAAU,CAAC;AAcrD;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,OAAO,EAAE,EAC5C,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAC7B,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,GACnC,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAyB7C"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const util_logger = require('./logger.js');
|
|
4
|
+
const util_tracker = require('./tracker.js');
|
|
5
|
+
const util_errors = require('./errors.js');
|
|
6
|
+
|
|
7
|
+
/** A spec field is a constant, a callback over the args, or absent — in which case `fallback` applies. */
|
|
8
|
+
const resolveSpecField = (field, args, fallback) => {
|
|
9
|
+
if (field === undefined) {
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
return typeof field === 'function' ? field(args) : field;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Wraps a command body with the whole telemetry and failure contract so no command
|
|
16
|
+
* hand-rolls it. It times the happy path. On a throw it reads `reason` and `data` straight
|
|
17
|
+
* off the error, defined in ./errors, instead of re-deriving them per command.
|
|
18
|
+
*
|
|
19
|
+
* Returns a commander action handler, chainable straight off `.command(...)` — commander always
|
|
20
|
+
* appends the Command instance as the action handler's last argument, so this splits it back off
|
|
21
|
+
* to read the command's own name/parent instead of asking the caller to pass it in.
|
|
22
|
+
*
|
|
23
|
+
* NoInfer keeps the arg types coming from `fn` alone. A `data` or `label` callback that
|
|
24
|
+
* only needs the first arg must not narrow the handler's own signature.
|
|
25
|
+
*/
|
|
26
|
+
function runCommand(spec, fn) {
|
|
27
|
+
return async (...allArgs) => {
|
|
28
|
+
const cmd = allArgs[allArgs.length - 1];
|
|
29
|
+
const args = allArgs.slice(0, -1);
|
|
30
|
+
const name = cmd.parent ? `${cmd.parent.name()} ${cmd.name()}` : cmd.name();
|
|
31
|
+
const resolve = (v, fallback) => resolveSpecField(v, args, fallback);
|
|
32
|
+
const track = resolve(spec.track, true);
|
|
33
|
+
const label = resolve(spec.label, name);
|
|
34
|
+
const event = resolve(spec.event, util_tracker.successEvent(name));
|
|
35
|
+
const data = spec.data?.(args) ?? {};
|
|
36
|
+
try {
|
|
37
|
+
await (track ? util_tracker.trackDuration(event, () => fn(...args), data) : fn(...args));
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
const reason = util_errors.reasonOf(err);
|
|
41
|
+
// A deploy/preview that failed because the consumer's own build broke isn't a CLI/infra
|
|
42
|
+
// problem — don't report it at all, not even as `unknown`.
|
|
43
|
+
const shouldTrack = track && reason !== 'build_error';
|
|
44
|
+
await util_logger.fail(`${label} failed: ${util_errors.messageOf(err)}`, { command: label, reason, ...data, ...util_errors.dataOf(err) }, spec.exitCode ?? (err instanceof util_errors.Z2hError ? err.exitCode : 1), shouldTrack);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
exports.runCommand = runCommand;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../../src/util/s3/upload.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../../src/util/s3/upload.ts"],"names":[],"mappings":"AAgBA,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,MAAM,GAAE,MAA0B,GACjC,OAAO,CAAC,MAAM,EAAE,CAAC,CA8BnB"}
|
package/dist/util/s3/upload.js
CHANGED
|
@@ -7,6 +7,7 @@ const mimeTypes = require('mime-types');
|
|
|
7
7
|
const pMap = require('p-map');
|
|
8
8
|
const clientS3 = require('@aws-sdk/client-s3');
|
|
9
9
|
const constants = require('../../constants.js');
|
|
10
|
+
const util_errors = require('../errors.js');
|
|
10
11
|
const util_s3_client = require('./client.js');
|
|
11
12
|
|
|
12
13
|
const _interopDefault = e => e && e.__esModule ? e : { default: e };
|
|
@@ -19,22 +20,27 @@ const pMap__default = /*#__PURE__*/_interopDefault(pMap);
|
|
|
19
20
|
const FOREVER = 'public, immutable';
|
|
20
21
|
const UPLOAD_CONCURRENCY = 5;
|
|
21
22
|
async function uploadDir(localDir, keyPrefix, bucket = constants.BUCKET_WRITE_NAME) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
23
|
+
try {
|
|
24
|
+
const entries = await promises.readdir(localDir, { recursive: true, withFileTypes: true });
|
|
25
|
+
const files = entries.filter(e => e.isFile()).map(e => path__default.default.relative(localDir, path__default.default.join(e.parentPath, e.name)));
|
|
26
|
+
const s3 = util_s3_client.getS3Client();
|
|
27
|
+
await pMap__default.default(files, async (rel) => {
|
|
28
|
+
const body = await fs.readFile(path__default.default.join(localDir, rel));
|
|
29
|
+
const contentType = mimeTypes.lookup(rel) || 'application/octet-stream';
|
|
30
|
+
const key = `${keyPrefix}/${rel.split(path__default.default.sep).join('/')}`;
|
|
31
|
+
await s3.send(new clientS3.PutObjectCommand({
|
|
32
|
+
Bucket: bucket,
|
|
33
|
+
Key: key,
|
|
34
|
+
Body: body,
|
|
35
|
+
ContentType: contentType,
|
|
36
|
+
CacheControl: FOREVER,
|
|
37
|
+
}));
|
|
38
|
+
}, { concurrency: UPLOAD_CONCURRENCY });
|
|
39
|
+
return files.map(f => f.split(path__default.default.sep).join('/'));
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
throw util_errors.tagDeployError('s3_error', err);
|
|
43
|
+
}
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
exports.uploadDir = uploadDir;
|
package/dist/util/tracker.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
export declare
|
|
2
|
-
|
|
1
|
+
export declare const trackEvent: (name: string, data: Record<string, unknown>) => Promise<void>, trackDuration: <T>(name: string, fn: () => Promise<T>, data?: Record<string, unknown>) => Promise<T>;
|
|
2
|
+
/**
|
|
3
|
+
* `z2h_cli_<command>_success` — the success-event name for every command, derived by
|
|
4
|
+
* util/run-command.ts's `runCommand()` so no command has to choose one. Nested commands join
|
|
5
|
+
* their path with `_` (`backend invoke` → `z2h_cli_backend_invoke_success`). The one place a
|
|
6
|
+
* command names its own event, `deploy --preview`, still goes through this function so the
|
|
7
|
+
* convention holds everywhere. See CommandSpec's `event` field in types.ts.
|
|
8
|
+
*/
|
|
9
|
+
export declare function successEvent(command: string): string;
|
|
3
10
|
//# sourceMappingURL=tracker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../src/util/tracker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../src/util/tracker.ts"],"names":[],"mappings":"AAcA,eAAO,MAAQ,UAAU,kEAAE,aAAa,uFAItC,CAAC;AAEH;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpD"}
|
package/dist/util/tracker.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
|
|
3
3
|
const os = require('node:os');
|
|
4
|
+
const node_module = require('node:module');
|
|
5
|
+
const observability = require('@mondaydotcomorg/z2h-shared-utils/observability');
|
|
4
6
|
|
|
7
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
5
8
|
const _interopDefault = e => e && e.__esModule ? e : { default: e };
|
|
6
9
|
|
|
7
10
|
const os__default = /*#__PURE__*/_interopDefault(os);
|
|
@@ -9,22 +12,24 @@ const os__default = /*#__PURE__*/_interopDefault(os);
|
|
|
9
12
|
const DOORMAN_URL = 'https://track-int.bigbrain.me/prod/event';
|
|
10
13
|
const DOORMAN_URL_STAGING = 'https://track-int.bigbrainstaging.me/stage/event';
|
|
11
14
|
const username = os__default.default.userInfo().username;
|
|
15
|
+
const { version: cliVersion } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('util/tracker.js', document.baseURI).href)))('@mondaydotcomorg/z2h-cli/package.json');
|
|
12
16
|
const isStaging = process.env.NODE_ENV === 'staging';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return
|
|
17
|
+
const { trackEvent, trackDuration } = observability.createHttpTracker({
|
|
18
|
+
url: isStaging ? DOORMAN_URL_STAGING : DOORMAN_URL,
|
|
19
|
+
source: 'z2h-cli',
|
|
20
|
+
getContext: () => ({ user_name: username, cli_version: cliVersion }),
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* `z2h_cli_<command>_success` — the success-event name for every command, derived by
|
|
24
|
+
* util/run-command.ts's `runCommand()` so no command has to choose one. Nested commands join
|
|
25
|
+
* their path with `_` (`backend invoke` → `z2h_cli_backend_invoke_success`). The one place a
|
|
26
|
+
* command names its own event, `deploy --preview`, still goes through this function so the
|
|
27
|
+
* convention holds everywhere. See CommandSpec's `event` field in types.ts.
|
|
28
|
+
*/
|
|
29
|
+
function successEvent(command) {
|
|
30
|
+
return `z2h_cli_${command.replace(/[\s-]+/g, '_')}_success`;
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
exports.successEvent = successEvent;
|
|
29
34
|
exports.trackDuration = trackDuration;
|
|
30
35
|
exports.trackEvent = trackEvent;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { logger
|
|
1
|
+
import { logger } from '@mondaydotcomorg/trident-backend-runtime';
|
|
2
2
|
|
|
3
|
+
import { withChannelTelemetry } from './channel-telemetry';
|
|
3
4
|
import { makeDbChannel } from './db-channel/db.channel';
|
|
4
5
|
import { makeLlmChannel } from './llm-channel/llm.channel';
|
|
5
6
|
import { makeMondayChannel } from './monday-channel/monday.channel';
|
|
@@ -56,8 +57,9 @@ const OAUTH_CHANNELS: Record<string, (caller: Caller) => ChannelMap> = {
|
|
|
56
57
|
* `monday_not_connected`, `monday_auth`, … A `4xx` means the handler (or the user's
|
|
57
58
|
* setup) is wrong and retrying won't help; `502` means the upstream failed.
|
|
58
59
|
*
|
|
59
|
-
* Each key
|
|
60
|
-
*
|
|
60
|
+
* Each key emits a unified call counter, duration distribution, and BI event tagged
|
|
61
|
+
* by app, channel, method, and outcome. Uncaught failures also reach BI at the
|
|
62
|
+
* invocation level — see `runner.service.ts`.
|
|
61
63
|
*/
|
|
62
64
|
export function buildChannels(appName: string, caller: Caller, declaredIntegrations: string[]): ChannelMap {
|
|
63
65
|
const channels: ChannelMap = {
|
|
@@ -78,13 +80,5 @@ export function buildChannels(appName: string, caller: Caller, declaredIntegrati
|
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
return Object.fromEntries(
|
|
82
|
-
Object.entries(channels).map(([key, fn]) => [
|
|
83
|
-
key,
|
|
84
|
-
metric.wrapFunctionWithMetrics(fn, {
|
|
85
|
-
customName: `z2h.channel.${key}`,
|
|
86
|
-
tags: { highCardinalityTags: { appName } },
|
|
87
|
-
}),
|
|
88
|
-
]),
|
|
89
|
-
);
|
|
83
|
+
return Object.fromEntries(Object.entries(channels).map(([key, fn]) => [key, withChannelTelemetry(appName, key, fn)]));
|
|
90
84
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { metric } from '@mondaydotcomorg/trident-backend-runtime';
|
|
2
|
+
import { BackendRunnerEvents } from '@mondaydotcomorg/z2h-shared-utils/observability';
|
|
3
|
+
import { trackEvent } from '@mondaydotcomorg/z2h-shared-utils/observability/trident';
|
|
4
|
+
|
|
5
|
+
import { ChannelError } from '../backend-runner.errors';
|
|
6
|
+
import type { ChannelHandler } from '../backend-runner.types';
|
|
7
|
+
import { BI_SOURCE } from '../constants';
|
|
8
|
+
|
|
9
|
+
type ChannelOutcome = 'success' | 'failure';
|
|
10
|
+
|
|
11
|
+
function parseChannelKey(key: string): { channel: string; method: string } {
|
|
12
|
+
const parts = key.split('.');
|
|
13
|
+
if (parts.length !== 2 || parts.some((part) => !part)) {
|
|
14
|
+
throw new Error(`channel key must use <channel>.<method>: ${key}`);
|
|
15
|
+
}
|
|
16
|
+
return { channel: parts[0], method: parts[1] };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
20
|
+
return (
|
|
21
|
+
(typeof value === 'object' || typeof value === 'function') &&
|
|
22
|
+
value !== null &&
|
|
23
|
+
typeof (value as PromiseLike<unknown>).then === 'function'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function recordChannelCall(
|
|
28
|
+
appName: string,
|
|
29
|
+
channel: string,
|
|
30
|
+
method: string,
|
|
31
|
+
outcome: ChannelOutcome,
|
|
32
|
+
startedAt: number,
|
|
33
|
+
error?: unknown,
|
|
34
|
+
): void {
|
|
35
|
+
const durationMs = performance.now() - startedAt;
|
|
36
|
+
const dimensions = { appName, channel, method };
|
|
37
|
+
|
|
38
|
+
metric.increment(BackendRunnerEvents.channel.callMetric, { ...dimensions, outcome });
|
|
39
|
+
metric.distribution(BackendRunnerEvents.channel.durationMetric, durationMs, dimensions);
|
|
40
|
+
void trackEvent(
|
|
41
|
+
BackendRunnerEvents.channel.biEvent,
|
|
42
|
+
{
|
|
43
|
+
...dimensions,
|
|
44
|
+
outcome,
|
|
45
|
+
duration_ms: durationMs,
|
|
46
|
+
...(error instanceof ChannelError ? { error_code: error.code } : {}),
|
|
47
|
+
},
|
|
48
|
+
BI_SOURCE,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function withChannelTelemetry(appName: string, key: string, handler: ChannelHandler): ChannelHandler {
|
|
53
|
+
const { channel, method } = parseChannelKey(key);
|
|
54
|
+
|
|
55
|
+
return (args) => {
|
|
56
|
+
const startedAt = performance.now();
|
|
57
|
+
try {
|
|
58
|
+
const result = handler(args);
|
|
59
|
+
if (isPromiseLike(result)) {
|
|
60
|
+
return Promise.resolve(result).then(
|
|
61
|
+
(value) => {
|
|
62
|
+
recordChannelCall(appName, channel, method, 'success', startedAt);
|
|
63
|
+
return value;
|
|
64
|
+
},
|
|
65
|
+
(error: unknown) => {
|
|
66
|
+
recordChannelCall(appName, channel, method, 'failure', startedAt, error);
|
|
67
|
+
throw error;
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
recordChannelCall(appName, channel, method, 'success', startedAt);
|
|
73
|
+
return result;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
recordChannelCall(appName, channel, method, 'failure', startedAt, error);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { logger } from '@mondaydotcomorg/trident-backend-runtime';
|
|
2
2
|
import { createMondayModel } from '@mondaydotcomorg/kong-ai-gateway-sdk/langchain';
|
|
3
|
-
import { BackendRunnerEvents
|
|
3
|
+
import { BackendRunnerEvents } from '@mondaydotcomorg/z2h-shared-utils/observability';
|
|
4
|
+
import { trackEvent } from '@mondaydotcomorg/z2h-shared-utils/observability/trident';
|
|
4
5
|
|
|
5
6
|
import { ChannelError } from '../../backend-runner.errors';
|
|
6
7
|
import { BI_SOURCE } from '../../constants';
|
|
@@ -209,7 +210,7 @@ export function makeLlmChannel(appName: string, caller: Caller) {
|
|
|
209
210
|
LLM_TIMEOUT_MS,
|
|
210
211
|
'LLM gateway',
|
|
211
212
|
);
|
|
212
|
-
trackEvent(BackendRunnerEvents.llm.call, { appName, email: caller.email, modelId: model }, BI_SOURCE);
|
|
213
|
+
void trackEvent(BackendRunnerEvents.llm.call, { appName, email: caller.email, modelId: model }, BI_SOURCE);
|
|
213
214
|
return { content: extractText(response.content) };
|
|
214
215
|
} catch (err) {
|
|
215
216
|
throw toLlmChannelError(err);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
|
|
3
3
|
import { context, logger } from '@mondaydotcomorg/trident-backend-runtime';
|
|
4
|
-
import { BackendRunnerEvents
|
|
4
|
+
import { BackendRunnerEvents } from '@mondaydotcomorg/z2h-shared-utils/observability';
|
|
5
|
+
import { trackEvent } from '@mondaydotcomorg/z2h-shared-utils/observability/trident';
|
|
5
6
|
import { getDefaultClient } from 'src/utils/elasticache';
|
|
6
7
|
|
|
7
8
|
import { ChannelError } from '../../backend-runner.errors';
|
|
@@ -25,6 +26,18 @@ export interface SnowflakeRow {
|
|
|
25
26
|
export interface RunQueryOptions {
|
|
26
27
|
/** Clamped to MAX_TIMEOUT_MS. Defaults to DEFAULT_TIMEOUT_MS. */
|
|
27
28
|
timeoutMs?: number;
|
|
29
|
+
/** Human-readable label for the pool identifier/logs, e.g. a widget name, instead of the default query-shape hash. Sanitized to `[a-z0-9-_]`, max 64 chars. */
|
|
30
|
+
identifier?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const MAX_IDENTIFIER_LENGTH = 64;
|
|
34
|
+
|
|
35
|
+
function sanitizeIdentifier(raw: string): string {
|
|
36
|
+
return raw
|
|
37
|
+
.toLowerCase()
|
|
38
|
+
.replace(/[^a-z0-9-_]+/g, '-')
|
|
39
|
+
.replace(/^-+|-+$/g, '')
|
|
40
|
+
.slice(0, MAX_IDENTIFIER_LENGTH);
|
|
28
41
|
}
|
|
29
42
|
|
|
30
43
|
function hashQuery(boundSql: string, params: Record<string, SnowflakeParamValue>): string {
|
|
@@ -109,8 +122,8 @@ function resolveTimeout(timeoutMs: number | undefined): number {
|
|
|
109
122
|
*/
|
|
110
123
|
export async function runSnowflakeQuery(
|
|
111
124
|
sql: string,
|
|
125
|
+
appName: string,
|
|
112
126
|
params?: Record<string, SnowflakeParamValue> | null,
|
|
113
|
-
appName?: string,
|
|
114
127
|
options?: RunQueryOptions | null,
|
|
115
128
|
): Promise<SnowflakeRow[]> {
|
|
116
129
|
const timeoutInMs = resolveTimeout(options?.timeoutMs);
|
|
@@ -132,13 +145,14 @@ export async function runSnowflakeQuery(
|
|
|
132
145
|
|
|
133
146
|
// Cache key stays parameter-sensitive; the pool identifier deliberately is not.
|
|
134
147
|
const queryCacheKey = hashQuery(boundSql, params ?? {});
|
|
135
|
-
const
|
|
148
|
+
const sanitizedIdentifier = options?.identifier ? sanitizeIdentifier(options.identifier) : '';
|
|
149
|
+
const queryIdentifier = sanitizedIdentifier || hashQueryShape(sql);
|
|
136
150
|
const scope = cacheScope(useStateless);
|
|
137
151
|
const startMs = Date.now();
|
|
138
152
|
const cached = scope ? await readCache(queryCacheKey, scope) : null;
|
|
139
153
|
if (cached) {
|
|
140
154
|
const duration_ms = Date.now() - startMs;
|
|
141
|
-
trackEvent(BackendRunnerEvents.snowflake.queryExecuted, { appName, duration_ms, cache_hit: true }, BI_SOURCE);
|
|
155
|
+
void trackEvent(BackendRunnerEvents.snowflake.queryExecuted, { appName, duration_ms, cache_hit: true }, BI_SOURCE);
|
|
142
156
|
logger.info(
|
|
143
157
|
{
|
|
144
158
|
tag: LOG_TAG,
|
|
@@ -155,7 +169,7 @@ export async function runSnowflakeQuery(
|
|
|
155
169
|
|
|
156
170
|
const queryOptions = {
|
|
157
171
|
query: boundSql,
|
|
158
|
-
identifier: `z2h-query:${queryIdentifier}`,
|
|
172
|
+
identifier: `z2h-query:${appName}:${queryIdentifier}`,
|
|
159
173
|
fallback: [] as SnowflakeRow[],
|
|
160
174
|
timeoutInMs,
|
|
161
175
|
convertToSingularResult: false,
|
|
@@ -182,7 +196,7 @@ export async function runSnowflakeQuery(
|
|
|
182
196
|
);
|
|
183
197
|
} catch (err) {
|
|
184
198
|
// Failure BI tracking already happens generically at the invoke level
|
|
185
|
-
// (runner.service.ts's
|
|
199
|
+
// (runner.service.ts's BackendRunnerEvents.invoke.biEvent, tagged with this
|
|
186
200
|
// ChannelError's `code`) — this stays a log only, for the queryHash/
|
|
187
201
|
// queryIdentifier that generic event can't know about.
|
|
188
202
|
const duration_ms = Date.now() - startMs;
|
|
@@ -195,7 +209,7 @@ export async function runSnowflakeQuery(
|
|
|
195
209
|
await writeCache(queryCacheKey, scope, rows);
|
|
196
210
|
}
|
|
197
211
|
const duration_ms = Date.now() - startMs;
|
|
198
|
-
trackEvent(BackendRunnerEvents.snowflake.queryExecuted, { appName, duration_ms, cache_hit: false }, BI_SOURCE);
|
|
212
|
+
void trackEvent(BackendRunnerEvents.snowflake.queryExecuted, { appName, duration_ms, cache_hit: false }, BI_SOURCE);
|
|
199
213
|
logger.info(
|
|
200
214
|
{ tag: LOG_TAG, queryHash: queryCacheKey, queryIdentifier, duration_ms, rowCount: rows.length, cache_hit: false },
|
|
201
215
|
'query executed',
|