@flytedesk/app-kit 4.0.0 → 6.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 (45) hide show
  1. package/dist/auth/plugin.js +22 -2
  2. package/dist/auth/plugin.js.map +1 -1
  3. package/dist/auth/testing/fake-idp.d.ts +9 -0
  4. package/dist/auth/testing/fake-idp.js +33 -5
  5. package/dist/auth/testing/fake-idp.js.map +1 -1
  6. package/dist/bigquery/client.d.ts +4 -4
  7. package/dist/bigquery/client.js +2 -2
  8. package/dist/bigquery/client.js.map +1 -1
  9. package/dist/bigquery/errors.d.ts +19 -0
  10. package/dist/bigquery/errors.js +41 -0
  11. package/dist/bigquery/errors.js.map +1 -1
  12. package/dist/bigquery/index.d.ts +3 -3
  13. package/dist/bigquery/index.js +1 -1
  14. package/dist/bigquery/index.js.map +1 -1
  15. package/dist/bigquery/load.d.ts +25 -7
  16. package/dist/bigquery/load.js +61 -31
  17. package/dist/bigquery/load.js.map +1 -1
  18. package/dist/bigquery/query.js +14 -1
  19. package/dist/bigquery/query.js.map +1 -1
  20. package/dist/bigquery/schema.d.ts +5 -0
  21. package/dist/bigquery/schema.js +35 -0
  22. package/dist/bigquery/schema.js.map +1 -0
  23. package/dist/bigquery/types.d.ts +57 -17
  24. package/dist/chat/index.d.ts +52 -21
  25. package/dist/chat/index.js +50 -20
  26. package/dist/chat/index.js.map +1 -1
  27. package/dist/chat/mcp/entry.d.ts +37 -0
  28. package/dist/chat/mcp/entry.js +135 -0
  29. package/dist/chat/mcp/entry.js.map +1 -0
  30. package/dist/cli/is-running-as-main.d.ts +1 -0
  31. package/dist/cli/is-running-as-main.js +54 -0
  32. package/dist/cli/is-running-as-main.js.map +1 -0
  33. package/dist/cli/sync-engine.d.ts +1 -32
  34. package/dist/cli/sync-engine.js +7 -45
  35. package/dist/cli/sync-engine.js.map +1 -1
  36. package/dist/flags/evaluate.d.ts +43 -0
  37. package/dist/flags/evaluate.js +64 -0
  38. package/dist/flags/evaluate.js.map +1 -0
  39. package/dist/flags/index.d.ts +22 -6
  40. package/dist/flags/index.js +21 -6
  41. package/dist/flags/index.js.map +1 -1
  42. package/dist/flags/plugin.js +73 -7
  43. package/dist/flags/plugin.js.map +1 -1
  44. package/dist/flags/types.d.ts +25 -0
  45. package/package.json +3 -2
@@ -1,61 +1,91 @@
1
1
  /**
2
- * Load-job capability (AK-14 follow-on, MP-200/PLN-2): truncate-and-reload a
3
- * BigQuery table from an array of rows, with retry-with-backoff limited to
2
+ * Load-job capability (AK-14 follow-on, MP-200/PLN-2): loads a stream of rows
3
+ * into a BigQuery table in one load job, with retry-with-backoff limited to
4
4
  * BigQuery's own rate-limit/quota class of error — generalized out of
5
5
  * media-planner's `apps/api/src/lib/bigqueryLoad.ts` (via flytedesk-id's
6
6
  * verbatim port at `src/lib/bigqueryLoad.ts`, MP-200), which this module
7
- * replaces. Callers needing a periodic full-refresh load (e.g. the IPEDS
8
- * institutions loader) use this instead of keeping their own copy of the
9
- * write-stream + retry plumbing.
7
+ * replaces.
8
+ *
9
+ * Memory is bounded by `chunkBytes`, never by the row count (AK-28): rows are
10
+ * pulled from the caller's source one at a time, serialized into NDJSON chunks
11
+ * of about `chunkBytes`, and piped into the load job's write stream with
12
+ * `stream.pipeline`, which stops pulling from the source whenever the write
13
+ * stream's buffer is full and resumes on its `drain`. A caller that reads its
14
+ * rows page by page (a keyset-paged or cursor read) can therefore load any
15
+ * number of rows in a fixed amount of memory.
10
16
  */
11
- import { BigQueryReadError, classifyBigQueryError, isRetryableBigQueryError } from "./errors.js";
17
+ import { once } from "node:events";
18
+ import { pipeline } from "node:stream/promises";
19
+ import { BigQueryReadError, classifyBigQueryError, isRetryableBigQueryError, } from "./errors.js";
12
20
  const DEFAULT_ATTEMPTS = 5;
13
21
  const DEFAULT_INITIAL_DELAY_MS = 1_000;
22
+ const DEFAULT_CHUNK_BYTES = 1024 * 1024;
14
23
  const DEFAULT_SOURCE_FORMAT = "NEWLINE_DELIMITED_JSON";
15
24
  function defaultSleep(ms) {
16
25
  return new Promise((resolve) => setTimeout(resolve, ms));
17
26
  }
18
- async function runLoadJob(bq, options, rows) {
27
+ /** Serializes `rows` into NDJSON, one `Buffer` of about `chunkBytes` at a
28
+ * time, counting rows into `counter` as they are consumed. */
29
+ async function* ndjsonChunks(rows, chunkBytes, counter) {
30
+ let lines = [];
31
+ let size = 0;
32
+ for await (const row of rows) {
33
+ const line = `${JSON.stringify(row)}\n`;
34
+ lines.push(line);
35
+ size += Buffer.byteLength(line);
36
+ counter.rows += 1;
37
+ if (size >= chunkBytes) {
38
+ yield Buffer.from(lines.join(""));
39
+ lines = [];
40
+ size = 0;
41
+ }
42
+ }
43
+ if (lines.length > 0)
44
+ yield Buffer.from(lines.join(""));
45
+ }
46
+ async function runLoadJob(bq, options, source) {
19
47
  const table = bq.dataset(options.datasetId).table(options.tableId);
20
48
  const createWriteStream = table.createWriteStream;
21
49
  if (!createWriteStream) {
22
50
  throw new BigQueryReadError(`dataset(${options.datasetId}).table(${options.tableId}) does not implement createWriteStream — this BigQueryLike was not constructed for load jobs`);
23
51
  }
24
- const ndjson = rows.map((row) => `${JSON.stringify(row)}\n`);
25
- await new Promise((resolve, reject) => {
26
- const stream = createWriteStream.call(table, {
27
- sourceFormat: options.sourceFormat ?? DEFAULT_SOURCE_FORMAT,
28
- schema: options.schema,
29
- writeDisposition: options.writeDisposition,
30
- createDisposition: options.createDisposition,
31
- labels: options.labels,
32
- jobId: options.jobId,
33
- });
34
- stream.on("error", reject);
35
- stream.on("complete", () => resolve());
36
- for (const line of ndjson)
37
- stream.write(line);
38
- stream.end();
52
+ const stream = createWriteStream.call(table, {
53
+ sourceFormat: options.sourceFormat ?? DEFAULT_SOURCE_FORMAT,
54
+ schema: options.schema,
55
+ writeDisposition: options.writeDisposition,
56
+ createDisposition: options.createDisposition,
57
+ labels: options.labels,
58
+ jobId: options.jobId,
39
59
  });
60
+ const counter = { rows: 0 };
61
+ // The load is done only when BigQuery reports the job "complete" AND the
62
+ // pipeline has flushed every chunk. `once` rejects on the stream's "error",
63
+ // which is how a failed job surfaces; `pipeline` rejects (and destroys the
64
+ // stream, aborting the upload) when the source itself throws.
65
+ await Promise.all([
66
+ once(stream, "complete"),
67
+ pipeline(ndjsonChunks(source(), options.chunkBytes ?? DEFAULT_CHUNK_BYTES, counter), stream),
68
+ ]);
69
+ return { rowCount: counter.rows };
40
70
  }
41
71
  /**
42
- * Loads `rows` into `datasetId.tableId` in one load job, retrying with
43
- * exponential backoff ONLY when the failure is BigQuery's
72
+ * Streams the rows `source` yields into `datasetId.tableId` in one load job,
73
+ * retrying with exponential backoff ONLY when the failure is BigQuery's
44
74
  * rateLimitExceeded/quotaExceeded class of error (see
45
- * `isRetryableBigQueryError`). Any other error (bad schema, auth failure,
46
- * etc.) is classified and rethrown on the first attempt.
75
+ * `isRetryableBigQueryError`). Any other error (bad schema, auth failure, a
76
+ * throw from the source, etc.) is classified and rethrown on the first
77
+ * attempt. Each attempt calls `source` afresh — see `LoadRowSource`.
47
78
  *
48
- * Pass `writeDisposition: "WRITE_TRUNCATE"` for a full-refresh reload (the
49
- * IPEDS institutions loader's use case); omit it for an append load.
79
+ * Pass `writeDisposition: "WRITE_TRUNCATE"` for a full-refresh reload; omit it
80
+ * for an append load.
50
81
  */
51
- export async function loadRowsWithRetry(bq, options, rows) {
82
+ export async function loadRowsWithRetry(bq, options, source) {
52
83
  const attempts = options.attempts ?? DEFAULT_ATTEMPTS;
53
84
  const initialDelayMs = options.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
54
85
  const sleep = options.sleep ?? defaultSleep;
55
86
  for (let attempt = 1;; attempt += 1) {
56
87
  try {
57
- await runLoadJob(bq, options, rows);
58
- return;
88
+ return await runLoadJob(bq, options, source);
59
89
  }
60
90
  catch (err) {
61
91
  if (attempt >= attempts || !isRetryableBigQueryError(err)) {
@@ -1 +1 @@
1
- {"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/bigquery/load.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/bigquery/load.ts"],"names":[],"mappings":"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"}
@@ -4,8 +4,9 @@
4
4
  * (AK-11). Inventory-specific SQL stays in media-planner; this module only
5
5
  * ever takes SQL + params from the caller and runs it.
6
6
  */
7
- import { BadQueryError, classifyBigQueryError } from "./errors.js";
7
+ import { BadQueryError, classifyBigQueryError, isJobTimeoutError, JobTimeoutError } from "./errors.js";
8
8
  import { DryRunBudgetExceededError } from "./errors.js";
9
+ import { normalizeResultSchema } from "./schema.js";
9
10
  /** Runs one page of a parameterised, read-only query. Always bind
10
11
  * user-supplied values through `options.params`/`options.types` — never
11
12
  * string-interpolate them into `options.sql`.
@@ -34,9 +35,13 @@ export async function runQuery(bq, options) {
34
35
  maximumBytesBilled: options.maximumBytesBilled !== undefined
35
36
  ? String(options.maximumBytesBilled)
36
37
  : undefined,
38
+ jobTimeoutMs: options.jobTimeoutMs,
37
39
  });
38
40
  }
39
41
  catch (err) {
42
+ if (options.jobTimeoutMs !== undefined && isJobTimeoutError(err)) {
43
+ throw new JobTimeoutError(options.jobTimeoutMs, err);
44
+ }
40
45
  throw classifyBigQueryError(err);
41
46
  }
42
47
  }
@@ -52,6 +57,9 @@ export async function runQuery(bq, options) {
52
57
  };
53
58
  }
54
59
  catch (err) {
60
+ if (options.jobTimeoutMs !== undefined && isJobTimeoutError(err)) {
61
+ throw new JobTimeoutError(options.jobTimeoutMs, err);
62
+ }
55
63
  throw classifyBigQueryError(err);
56
64
  }
57
65
  }
@@ -87,9 +95,13 @@ export async function estimateQueryBytes(bq, options) {
87
95
  types: options.types,
88
96
  location: options.location,
89
97
  dryRun: true,
98
+ jobTimeoutMs: options.jobTimeoutMs,
90
99
  });
91
100
  }
92
101
  catch (err) {
102
+ if (options.jobTimeoutMs !== undefined && isJobTimeoutError(err)) {
103
+ throw new JobTimeoutError(options.jobTimeoutMs, err);
104
+ }
93
105
  throw classifyBigQueryError(err);
94
106
  }
95
107
  const stats = metadata.statistics?.query;
@@ -98,6 +110,7 @@ export async function estimateQueryBytes(bq, options) {
98
110
  cacheHit: stats?.cacheHit ?? false,
99
111
  statementType: stats?.statementType,
100
112
  referencedTables: stats?.referencedTables,
113
+ schema: normalizeResultSchema(stats?.schema),
101
114
  };
102
115
  if (options.maxBytesBilled !== undefined &&
103
116
  estimate.totalBytesProcessed > options.maxBytesBilled) {
@@ -1 +1 @@
1
- {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/bigquery/query.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/bigquery/query.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { BigQueryRawSchema, BigQueryResultSchemaField } from "./types.js";
2
+ /** `undefined` when BigQuery sent no schema at all; `[]` when it sent one with
3
+ * no fields. Throws `MalformedResultSchemaError` for a field lacking a
4
+ * non-empty `name` or `type`, at any nesting depth. */
5
+ export declare function normalizeResultSchema(schema: BigQueryRawSchema | undefined): BigQueryResultSchemaField[] | undefined;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Parses BigQuery's raw dry-run result schema (every property optional, as the
3
+ * real SDK types it) into the normalised `BigQueryResultSchemaField[]` callers
4
+ * get. Parse at the boundary: a field missing `name` or `type` is a typed error
5
+ * here rather than a silent `undefined` downstream (AK-31).
6
+ */
7
+ import { MalformedResultSchemaError } from "./errors.js";
8
+ /** `undefined` when BigQuery sent no schema at all; `[]` when it sent one with
9
+ * no fields. Throws `MalformedResultSchemaError` for a field lacking a
10
+ * non-empty `name` or `type`, at any nesting depth. */
11
+ export function normalizeResultSchema(schema) {
12
+ if (schema === undefined)
13
+ return undefined;
14
+ return normalizeFields(schema.fields ?? [], "");
15
+ }
16
+ function normalizeFields(fields, parentPath) {
17
+ return fields.map((field, index) => normalizeField(field, index, parentPath));
18
+ }
19
+ function normalizeField(field, index, parentPath) {
20
+ const { name, type } = field;
21
+ if (!name) {
22
+ throw new MalformedResultSchemaError(`${parentPath}fields[${index}]`, "name");
23
+ }
24
+ const path = `${parentPath}${name}`;
25
+ if (!type) {
26
+ throw new MalformedResultSchemaError(path, "type");
27
+ }
28
+ const normalized = { name, type };
29
+ if (field.mode !== undefined)
30
+ normalized.mode = field.mode;
31
+ if (field.fields !== undefined)
32
+ normalized.fields = normalizeFields(field.fields, `${path}.`);
33
+ return normalized;
34
+ }
35
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/bigquery/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAOzD;;wDAEwD;AACxD,MAAM,UAAU,qBAAqB,CACnC,MAAqC;IAErC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC3C,OAAO,eAAe,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,eAAe,CACtB,MAAgC,EAChC,UAAkB;IAElB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;AAChF,CAAC;AAED,SAAS,cAAc,CACrB,KAA6B,EAC7B,KAAa,EACb,UAAkB;IAElB,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IAC7B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,0BAA0B,CAAC,GAAG,UAAU,UAAU,KAAK,GAAG,EAAE,MAAM,CAAC,CAAC;IAChF,CAAC;IACD,MAAM,IAAI,GAAG,GAAG,UAAU,GAAG,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,0BAA0B,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACrD,CAAC;IACD,MAAM,UAAU,GAA8B,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7D,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAAE,UAAU,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IAC3D,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;QAAE,UAAU,CAAC,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC;IAC9F,OAAO,UAAU,CAAC;AACpB,CAAC"}
@@ -11,6 +11,7 @@
11
11
  * `@google-cloud/bigquery` calls it made; inventory-specific SQL stays behind
12
12
  * in media-planner.
13
13
  */
14
+ import type { Writable } from "node:stream";
14
15
  /** One structured error entry, same shape the Google APIs client library
15
16
  * attaches to a rejected job promise's `.errors` array. */
16
17
  export interface BigQueryApiErrorDetail {
@@ -31,6 +32,20 @@ export interface BigQueryTableReference {
31
32
  datasetId?: string;
32
33
  tableId?: string;
33
34
  }
35
+ /** A result-schema field exactly as BigQuery's job statistics report it: the
36
+ * real SDK's `ITableFieldSchema`, where every property is optional. Only the
37
+ * raw boundary speaks this shape; callers get `BigQueryResultSchemaField`
38
+ * (name and type guaranteed) from `estimateQueryBytes`. */
39
+ export interface BigQueryRawSchemaField {
40
+ name?: string;
41
+ type?: string;
42
+ mode?: string;
43
+ fields?: BigQueryRawSchemaField[];
44
+ }
45
+ /** `IJobStatistics2.schema`, an `ITableSchema`. */
46
+ export interface BigQueryRawSchema {
47
+ fields?: BigQueryRawSchemaField[];
48
+ }
34
49
  export interface BigQueryJobStatistics {
35
50
  query?: {
36
51
  totalBytesProcessed?: string | number;
@@ -41,6 +56,9 @@ export interface BigQueryJobStatistics {
41
56
  statementType?: string;
42
57
  /** Every table the query touches — `IJobStatistics2.referencedTables`. */
43
58
  referencedTables?: BigQueryTableReference[];
59
+ /** The result schema BigQuery computed for the (dry-run) query —
60
+ * `IJobStatistics2.schema`, an `ITableSchema` (`{ fields }`). */
61
+ schema?: BigQueryRawSchema;
44
62
  };
45
63
  }
46
64
  export interface BigQueryJobMetadata {
@@ -91,6 +109,10 @@ export interface BigQueryCreateQueryJobOptions {
91
109
  * than run. Distinct from `EstimateQueryBytesOptions.maxBytesBilled`
92
110
  * (a pre-flight dry-run check this module enforces client-side). */
93
111
  maximumBytesBilled?: string;
112
+ /** BigQuery's own `configuration.jobTimeoutMs` — the job fails with
113
+ * `stopped` state once it runs longer than this, wall-clock, from job
114
+ * creation. The SDK converts this number to the wire's decimal string. */
115
+ jobTimeoutMs?: number;
94
116
  }
95
117
  export interface BigQueryExtractOptions {
96
118
  /** `@google-cloud/bigquery`'s own extract-job vocabulary — note this is
@@ -109,24 +131,26 @@ export interface GcsFileLike {
109
131
  readonly name: string;
110
132
  };
111
133
  }
112
- /** Structural subset of Node's own Writable that `Table#createWriteStream`
113
- * needs to expose — the "error"/"complete" events a load-job caller pipes
114
- * rows into and waits on. */
115
- export interface BigQueryWriteStreamLike {
116
- on(event: "error", listener: (err: unknown) => void): this;
117
- on(event: "complete", listener: () => void): this;
118
- write(chunk: string): boolean;
119
- end(): void;
120
- }
121
134
  /** One BigQuery table-schema field, as `createWriteStream`'s `schema` option
122
- * (and the REST API's `tables.insert`) accepts it. `fields` recurses for
123
- * RECORD/STRUCT columns. */
135
+ * (and the REST API's `tables.insert`) accepts it: load-job INPUT, where the
136
+ * caller must state `name` and `type`. `fields` recurses for RECORD/STRUCT
137
+ * columns. The schema BigQuery reports back is `BigQueryResultSchemaField`. */
124
138
  export interface BigQuerySchemaField {
125
139
  name: string;
126
140
  type: string;
127
141
  mode?: "NULLABLE" | "REQUIRED" | "REPEATED";
128
142
  fields?: BigQuerySchemaField[];
129
143
  }
144
+ /** One column of a query's result schema, normalised from BigQuery's own
145
+ * dry-run statistics: `name` and `type` are guaranteed (a field missing
146
+ * either is rejected with `MalformedResultSchemaError`); `mode` is BigQuery's
147
+ * string as reported. `fields` recurses for RECORD/STRUCT columns. */
148
+ export interface BigQueryResultSchemaField {
149
+ name: string;
150
+ type: string;
151
+ mode?: string;
152
+ fields?: BigQueryResultSchemaField[];
153
+ }
130
154
  export interface BigQueryLoadOptions {
131
155
  sourceFormat?: "NEWLINE_DELIMITED_JSON" | "CSV" | "AVRO" | "PARQUET";
132
156
  /** Full destination-table schema. Required when the table doesn't already
@@ -143,12 +167,15 @@ export interface BigQueryLoadOptions {
143
167
  }
144
168
  export interface BigQueryTableLike {
145
169
  createExtractJob(destination: GcsFileLike, options?: BigQueryExtractOptions): Promise<[BigQueryJobLike, BigQueryJobMetadata]>;
146
- /** Opens a load-job write stream — the caller pipes newline-delimited rows
147
- * (or other `sourceFormat`) into it, then awaits the stream's "complete"/
148
- * "error" event. Mirrors `@google-cloud/bigquery`'s `Table#createWriteStream`.
149
- * Optional: read-only callers (query/extract) never need it — only
150
- * `loadRowsWithRetry` (./load.js) does, and it fails loudly if missing. */
151
- createWriteStream?(options: BigQueryLoadOptions): BigQueryWriteStreamLike;
170
+ /** Opens a load-job write stream — a real Node `Writable`, since the loader
171
+ * pipes NDJSON into it with `stream.pipeline` and relies on its
172
+ * backpressure (`write()` returning false, then "drain"). The stream emits
173
+ * "complete" once BigQuery finishes the job, or "error" if the job fails.
174
+ * Mirrors `@google-cloud/bigquery`'s `Table#createWriteStream`, which
175
+ * returns a `Writable`. Optional: read-only callers (query/extract) never
176
+ * need it — only `loadRowsWithRetry` (./load.js) does, and it fails loudly
177
+ * if missing. */
178
+ createWriteStream?(options: BigQueryLoadOptions): Writable;
152
179
  }
153
180
  export interface BigQueryDatasetLike {
154
181
  table(id: string): BigQueryTableLike;
@@ -187,6 +214,11 @@ export interface RunQueryOptions {
187
214
  * belt to that check's suspenders, since data can grow between a dry
188
215
  * run and the real job, or a caller can skip the dry run entirely. */
189
216
  maximumBytesBilled?: number;
217
+ /** Fails the job with a typed `JobTimeoutError` once it runs longer than
218
+ * this many milliseconds, wall-clock from job creation — set on the job's
219
+ * own `configuration.jobTimeoutMs` (BigQuery enforces it server-side),
220
+ * not a client-side `Promise.race`. */
221
+ jobTimeoutMs?: number;
190
222
  }
191
223
  export interface QueryPage<T = Record<string, unknown>> {
192
224
  rows: T[];
@@ -202,6 +234,10 @@ export interface EstimateQueryBytesOptions {
202
234
  /** When given, `estimateQueryBytes` throws `DryRunBudgetExceededError`
203
235
  * instead of returning once the estimate exceeds this many bytes. */
204
236
  maxBytesBilled?: number;
237
+ /** Same `configuration.jobTimeoutMs` enforcement as `RunQueryOptions` —
238
+ * applies to the dry-run job itself, not the (never-run) query it
239
+ * estimates. */
240
+ jobTimeoutMs?: number;
205
241
  }
206
242
  export interface DryRunEstimate {
207
243
  totalBytesProcessed: number;
@@ -216,6 +252,10 @@ export interface DryRunEstimate {
216
252
  * checks every entry against its own read-only table allowlist before
217
253
  * ever submitting the real job. */
218
254
  referencedTables?: BigQueryTableReference[];
255
+ /** BigQuery's own result schema for the query, typed directly from the
256
+ * dry-run job's `statistics.query.schema` — never inferred client-side.
257
+ * `undefined` only when BigQuery's dry-run statistics omitted it. */
258
+ schema?: BigQueryResultSchemaField[];
219
259
  }
220
260
  export interface ExtractTableToGCSInput {
221
261
  datasetId: string;
@@ -41,32 +41,63 @@
41
41
  * async (request) => recordReply(request.body),
42
42
  * );
43
43
  *
44
- * Usage — the MCP server script the dispatched agent's clean room actually runs
45
- * (typically `dist/chat/mcp/entry.js`, wired through a `.danxbot/config/mcp-servers/
46
- * *.yml`):
44
+ * Usage — the MCP server the dispatched agent's clean room actually runs (AK-34): a
45
+ * `.danxbot/config/mcp-servers/*.yml` launches the PUBLISHED `app-kit-chat-mcp` bin
46
+ * directly, no vendored copy and no local install in the app's own repo. This
47
+ * package publishes FIVE bins and none is named `app-kit`, so `npx` needs
48
+ * `--package=` to say which package to fetch, separately from which bin to run —
49
+ * `npx -y @flytedesk/app-kit@<v> app-kit-chat-mcp` fails ("could not determine
50
+ * executable to run"); this is the form that actually works (verified against a real
51
+ * `npm pack` tarball run via `npx --package=<tgz>` from a clean directory with no
52
+ * `node_modules` — see `mcp/entry.test.ts`):
47
53
  *
48
- * import {
49
- * createChatMcpProtocol,
50
- * createChatMcpStdioServer,
51
- * createReplyTool,
52
- * } from "@flytedesk/app-kit/chat";
54
+ * server:
55
+ * command: npx
56
+ * args:
57
+ * - "-y"
58
+ * - "--package=@flytedesk/app-kit@6.0.0"
59
+ * - "app-kit-chat-mcp"
60
+ * - "--tools"
61
+ * - "${DANX_REPO_ROOT}/packages/audience-chat-mcp/src/chatTools.mjs"
53
62
  *
54
- * const protocol = createChatMcpProtocol({
55
- * serverName: "sms-app-chat",
56
- * tools: [
57
- * createReplyTool({ description: "...", deliver: postReplyToApi }),
58
- * // The extension point: register whatever app-specific tools the agent
59
- * // needs — each is just { name, description, inputSchema, handler }.
60
- * { name: "audience_validate", description: "...", inputSchema: {...}, handler: validateAudience },
61
- * ],
62
- * });
63
- * createChatMcpStdioServer({ protocol }).start();
63
+ * `--tools <path>` MUST be absolute — danxbot's cwd for this process is its own
64
+ * clean room, never assumed to equal the app's repo root, hence the
65
+ * `${DANX_REPO_ROOT}`-prefixed path above rather than a bare relative one.
66
+ *
67
+ * The path names the ONE thing this package cannot supply itself: the app's own
68
+ * tools module. That module must NOT `import` anything from `@flytedesk/app-kit` —
69
+ * a bare package import cannot resolve from danxbot's unbuilt dispatch clone (no
70
+ * `node_modules`). Instead, `app-kit-chat-mcp` INJECTS the pieces the module needs as
71
+ * a `ChatMcpKit` argument, and the module returns a (optionally wrapped)
72
+ * `ChatMcpProtocol` rather than a plain options bag:
73
+ *
74
+ * // chatTools.mjs — the app's own file, never vendored, never copied, never
75
+ * // imports @flytedesk/app-kit as a VALUE (a type-only `// @ts-check` JSDoc
76
+ * // reference to BuildChatServer/ChatMcpKit is fine — see mcp/entry.ts's header)
77
+ * export default function buildChatServer(kit) {
78
+ * return kit.createChatMcpProtocol({
79
+ * serverName: "sms-app-chat",
80
+ * tools: [
81
+ * kit.createReplyTool({ description: "...", deliver: postReplyToApi }),
82
+ * // The extension point: register whatever app-specific tools the agent
83
+ * // needs — each is just { name, description, inputSchema, handler }.
84
+ * { name: "audience_validate", description: "...", inputSchema: {...}, handler: validateAudience },
85
+ * ],
86
+ * });
87
+ * }
88
+ *
89
+ * `app-kit-chat-mcp` (`src/chat/mcp/entry.ts`, published as `dist/chat/mcp/entry.js`)
90
+ * loads that module, calls its default export with the kit, and starts the stdio
91
+ * loop on whatever `ChatMcpProtocol` it returns — see `entry.ts`'s own doc comment
92
+ * for the full contract, why it stays dependency-free, and how a consumer (e.g.
93
+ * media-planner's MP-169 `loggedProtocol`) wraps `handleMessage` before returning.
64
94
  */
65
- export { createDanxbotLauncher, DanxbotError, isFailedJobStatus, isTerminalJobStatus } from "./launcher.js";
66
- export { callbackSecretMatches, requireCallbackSecret } from "./callback-secret.js";
95
+ export { createDanxbotLauncher, DanxbotError, isFailedJobStatus, isTerminalJobStatus, } from "./launcher.js";
96
+ export { callbackSecretMatches, requireCallbackSecret, } from "./callback-secret.js";
67
97
  export { createChatMcpProtocol, createReplyTool, parseReplyArguments, FALLBACK_PROTOCOL_VERSION, JSON_RPC, } from "./mcp-protocol.js";
68
98
  export { createChatMcpStdioServer } from "./mcp-server.js";
69
99
  export { parseChatEnv, ChatEnvError } from "./env.js";
70
100
  export type { AppRegisteredTool, ChatEnv, ChatToolCallResult, ChatToolContent, DanxbotDispatchResult, DanxbotJobStatus, DanxbotLauncher, DanxbotLauncherOptions, } from "./types.js";
71
101
  export type { ChatMcpProtocol, ChatMcpProtocolOptions, ChatReplyPayload, ParseReplyArgumentsOptions, ReplyDeliverResult, ReplyToolOptions, } from "./mcp-protocol.js";
72
- export type { ChatMcpStdioServer, ChatMcpStdioServerOptions } from "./mcp-server.js";
102
+ export type { ChatMcpStdioServer, ChatMcpStdioServerOptions, } from "./mcp-server.js";
103
+ export type { BuildChatServer, ChatMcpKit } from "./mcp/entry.js";
@@ -41,29 +41,59 @@
41
41
  * async (request) => recordReply(request.body),
42
42
  * );
43
43
  *
44
- * Usage — the MCP server script the dispatched agent's clean room actually runs
45
- * (typically `dist/chat/mcp/entry.js`, wired through a `.danxbot/config/mcp-servers/
46
- * *.yml`):
44
+ * Usage — the MCP server the dispatched agent's clean room actually runs (AK-34): a
45
+ * `.danxbot/config/mcp-servers/*.yml` launches the PUBLISHED `app-kit-chat-mcp` bin
46
+ * directly, no vendored copy and no local install in the app's own repo. This
47
+ * package publishes FIVE bins and none is named `app-kit`, so `npx` needs
48
+ * `--package=` to say which package to fetch, separately from which bin to run —
49
+ * `npx -y @flytedesk/app-kit@<v> app-kit-chat-mcp` fails ("could not determine
50
+ * executable to run"); this is the form that actually works (verified against a real
51
+ * `npm pack` tarball run via `npx --package=<tgz>` from a clean directory with no
52
+ * `node_modules` — see `mcp/entry.test.ts`):
47
53
  *
48
- * import {
49
- * createChatMcpProtocol,
50
- * createChatMcpStdioServer,
51
- * createReplyTool,
52
- * } from "@flytedesk/app-kit/chat";
54
+ * server:
55
+ * command: npx
56
+ * args:
57
+ * - "-y"
58
+ * - "--package=@flytedesk/app-kit@6.0.0"
59
+ * - "app-kit-chat-mcp"
60
+ * - "--tools"
61
+ * - "${DANX_REPO_ROOT}/packages/audience-chat-mcp/src/chatTools.mjs"
53
62
  *
54
- * const protocol = createChatMcpProtocol({
55
- * serverName: "sms-app-chat",
56
- * tools: [
57
- * createReplyTool({ description: "...", deliver: postReplyToApi }),
58
- * // The extension point: register whatever app-specific tools the agent
59
- * // needs — each is just { name, description, inputSchema, handler }.
60
- * { name: "audience_validate", description: "...", inputSchema: {...}, handler: validateAudience },
61
- * ],
62
- * });
63
- * createChatMcpStdioServer({ protocol }).start();
63
+ * `--tools <path>` MUST be absolute — danxbot's cwd for this process is its own
64
+ * clean room, never assumed to equal the app's repo root, hence the
65
+ * `${DANX_REPO_ROOT}`-prefixed path above rather than a bare relative one.
66
+ *
67
+ * The path names the ONE thing this package cannot supply itself: the app's own
68
+ * tools module. That module must NOT `import` anything from `@flytedesk/app-kit` —
69
+ * a bare package import cannot resolve from danxbot's unbuilt dispatch clone (no
70
+ * `node_modules`). Instead, `app-kit-chat-mcp` INJECTS the pieces the module needs as
71
+ * a `ChatMcpKit` argument, and the module returns a (optionally wrapped)
72
+ * `ChatMcpProtocol` rather than a plain options bag:
73
+ *
74
+ * // chatTools.mjs — the app's own file, never vendored, never copied, never
75
+ * // imports @flytedesk/app-kit as a VALUE (a type-only `// @ts-check` JSDoc
76
+ * // reference to BuildChatServer/ChatMcpKit is fine — see mcp/entry.ts's header)
77
+ * export default function buildChatServer(kit) {
78
+ * return kit.createChatMcpProtocol({
79
+ * serverName: "sms-app-chat",
80
+ * tools: [
81
+ * kit.createReplyTool({ description: "...", deliver: postReplyToApi }),
82
+ * // The extension point: register whatever app-specific tools the agent
83
+ * // needs — each is just { name, description, inputSchema, handler }.
84
+ * { name: "audience_validate", description: "...", inputSchema: {...}, handler: validateAudience },
85
+ * ],
86
+ * });
87
+ * }
88
+ *
89
+ * `app-kit-chat-mcp` (`src/chat/mcp/entry.ts`, published as `dist/chat/mcp/entry.js`)
90
+ * loads that module, calls its default export with the kit, and starts the stdio
91
+ * loop on whatever `ChatMcpProtocol` it returns — see `entry.ts`'s own doc comment
92
+ * for the full contract, why it stays dependency-free, and how a consumer (e.g.
93
+ * media-planner's MP-169 `loggedProtocol`) wraps `handleMessage` before returning.
64
94
  */
65
- export { createDanxbotLauncher, DanxbotError, isFailedJobStatus, isTerminalJobStatus } from "./launcher.js";
66
- export { callbackSecretMatches, requireCallbackSecret } from "./callback-secret.js";
95
+ export { createDanxbotLauncher, DanxbotError, isFailedJobStatus, isTerminalJobStatus, } from "./launcher.js";
96
+ export { callbackSecretMatches, requireCallbackSecret, } from "./callback-secret.js";
67
97
  export { createChatMcpProtocol, createReplyTool, parseReplyArguments, FALLBACK_PROTOCOL_VERSION, JSON_RPC, } from "./mcp-protocol.js";
68
98
  export { createChatMcpStdioServer } from "./mcp-server.js";
69
99
  export { parseChatEnv, ChatEnvError } from "./env.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/chat/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAC5G,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,yBAAyB,EACzB,QAAQ,GACT,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/chat/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6FG;AACH,OAAO,EACL,qBAAqB,EACrB,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,yBAAyB,EACzB,QAAQ,GACT,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import { createChatMcpProtocol, createReplyTool, parseReplyArguments, type ChatMcpProtocol } from "../mcp-protocol.js";
3
+ /**
4
+ * Everything a `--tools` module needs, injected rather than imported — see this
5
+ * file's own doc comment for why a runtime `import "@flytedesk/app-kit/chat"` from
6
+ * the module itself cannot work in danxbot's unbuilt dispatch clone.
7
+ */
8
+ export interface ChatMcpKit {
9
+ createReplyTool: typeof createReplyTool;
10
+ parseReplyArguments: typeof parseReplyArguments;
11
+ createChatMcpProtocol: typeof createChatMcpProtocol;
12
+ }
13
+ /** The contract a `--tools` module's default export must satisfy: given the kit,
14
+ * return a (possibly wrapped) `ChatMcpProtocol`. */
15
+ export type BuildChatServer = (kit: ChatMcpKit) => ChatMcpProtocol | Promise<ChatMcpProtocol>;
16
+ export declare class ChatMcpEntryError extends Error {
17
+ }
18
+ export interface ChatMcpEntryArgs {
19
+ /** Path to the app's tools module. MUST be absolute in real dispatch use (see this
20
+ * file's own doc comment) — resolved against `process.cwd()` only as a fallback
21
+ * for a genuinely relative path, which `path.resolve` leaves untouched when the
22
+ * input is already absolute. */
23
+ toolsPath: string;
24
+ }
25
+ /** Parse argv into `{toolsPath}`. Exported for direct unit testing. */
26
+ export declare function parseArgs(argv: string[]): ChatMcpEntryArgs;
27
+ /**
28
+ * Dynamically `import()` the app's tools module and validate its default export.
29
+ * Exported for direct unit testing (no process spawn needed to exercise the loading
30
+ * and validation logic — see entry.test.ts's own split between a fast in-process
31
+ * suite for this function and real stdio/npx spawn suites for the whole process).
32
+ */
33
+ export declare function loadBuildChatServer(toolsPath: string): Promise<BuildChatServer>;
34
+ /** Run the server: load the tools module, build the protocol (via the injected
35
+ * kit), and start stdio. Exported for direct unit testing of argument/validation
36
+ * failures without a process spawn. */
37
+ export declare function main(argv: string[]): Promise<void>;