@hypequery/cli 1.26.0 → 1.28.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.
- package/README.md +14 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +9 -2
- package/dist/commands/deploy.d.ts +6 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +48 -2
- package/dist/commands/deployment.d.ts.map +1 -1
- package/dist/commands/deployment.js +11 -0
- package/dist/commands/generate-client.d.ts +13 -1
- package/dist/commands/generate-client.d.ts.map +1 -1
- package/dist/commands/generate-client.js +41 -7
- package/dist/generators/client-contract/render.d.ts +7 -1
- package/dist/generators/client-contract/render.d.ts.map +1 -1
- package/dist/generators/client-contract/render.js +5 -3
- package/dist/generators/dataset-generator.d.ts.map +1 -1
- package/dist/generators/dataset-generator.js +77 -68
- package/dist/templates/cloud.d.ts.map +1 -1
- package/dist/templates/cloud.js +3 -0
- package/dist/utils/clickhouse-dimension-type.d.ts +27 -0
- package/dist/utils/clickhouse-dimension-type.d.ts.map +1 -0
- package/dist/utils/clickhouse-dimension-type.js +61 -0
- package/dist/utils/clickhouse-type-utils.d.ts.map +1 -1
- package/dist/utils/clickhouse-type-utils.js +9 -1
- package/dist/utils/client-contract-file.d.ts +5 -0
- package/dist/utils/client-contract-file.d.ts.map +1 -1
- package/dist/utils/client-contract-file.js +49 -5
- package/dist/utils/cloud-client-contract.d.ts +20 -0
- package/dist/utils/cloud-client-contract.d.ts.map +1 -0
- package/dist/utils/cloud-client-contract.js +52 -0
- package/dist/utils/deployment-upload.d.ts +6 -2
- package/dist/utils/deployment-upload.d.ts.map +1 -1
- package/dist/utils/deployment-upload.js +12 -2
- package/dist/utils/draft-deployment.d.ts +16 -0
- package/dist/utils/draft-deployment.d.ts.map +1 -0
- package/dist/utils/draft-deployment.js +35 -0
- package/dist/utils/field-namer.d.ts +10 -0
- package/dist/utils/field-namer.d.ts.map +1 -0
- package/dist/utils/field-namer.js +20 -0
- package/dist/utils/json-pointer.d.ts +3 -0
- package/dist/utils/json-pointer.d.ts.map +1 -0
- package/dist/utils/json-pointer.js +4 -0
- package/dist/utils/live-deployment.d.ts +1 -1
- package/dist/utils/live-deployment.d.ts.map +1 -1
- package/dist/utils/read-capped.d.ts +6 -0
- package/dist/utils/read-capped.d.ts.map +1 -0
- package/dist/utils/read-capped.js +36 -0
- package/dist/utils/shell-quote.d.ts +6 -0
- package/dist/utils/shell-quote.d.ts.map +1 -0
- package/dist/utils/shell-quote.js +7 -0
- package/dist/utils/telemetry/catalog.d.ts +1 -1
- package/dist/utils/telemetry/domains.d.ts +6 -6
- package/dist/utils/telemetry/domains.js +6 -6
- package/dist/utils/tenant-key-candidates.d.ts +19 -0
- package/dist/utils/tenant-key-candidates.d.ts.map +1 -0
- package/dist/utils/tenant-key-candidates.js +63 -0
- package/package.json +7 -7
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** A dataset dimension type the scaffolder can emit. */
|
|
2
|
+
export type ScaffoldDimensionType = 'string' | 'number' | 'boolean' | 'timestamp';
|
|
3
|
+
export interface NamedTupleElement {
|
|
4
|
+
name: string;
|
|
5
|
+
type: ScaffoldDimensionType;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The value type behind `Nullable`, `LowCardinality` and
|
|
9
|
+
* `SimpleAggregateFunction(fn, T)` wrappers; none changes what a value is.
|
|
10
|
+
*/
|
|
11
|
+
export declare function unwrapClickHouseType(type: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Maps a ClickHouse column type to a dataset dimension type, or null when no
|
|
14
|
+
* scalar dimension type describes its values (Array, Tuple, Map, geo, JSON,
|
|
15
|
+
* Interval, ...). The outer type name is matched exactly: a substring match
|
|
16
|
+
* reads `Point` or `Array(Int32)` as numbers. Mirrors Cloud's `protocolTypeOf`
|
|
17
|
+
* so a table scaffolds with the same field types in both.
|
|
18
|
+
*/
|
|
19
|
+
export declare function clickHouseDimensionType(type: string): ScaffoldDimensionType | null;
|
|
20
|
+
/**
|
|
21
|
+
* The scalar elements of a named `Tuple(name T, ...)` column, each of which can
|
|
22
|
+
* be its own dimension through `tupleElement`. Null for any other type,
|
|
23
|
+
* including unnamed tuples, whose positions carry no meaning to name a field
|
|
24
|
+
* after. Elements without a scalar type or a plain identifier name are left out.
|
|
25
|
+
*/
|
|
26
|
+
export declare function namedTupleElements(type: string): NamedTupleElement[] | null;
|
|
27
|
+
//# sourceMappingURL=clickhouse-dimension-type.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clickhouse-dimension-type.d.ts","sourceRoot":"","sources":["../../src/utils/clickhouse-dimension-type.ts"],"names":[],"mappings":"AAEA,wDAAwD;AACxD,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;AAElF,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,qBAAqB,CAAC;CAC7B;AAID;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASzD;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAOlF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE,GAAG,IAAI,CAgB3E"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { matchTypeCall } from './clickhouse-type-utils.js';
|
|
2
|
+
const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
3
|
+
/**
|
|
4
|
+
* The value type behind `Nullable`, `LowCardinality` and
|
|
5
|
+
* `SimpleAggregateFunction(fn, T)` wrappers; none changes what a value is.
|
|
6
|
+
*/
|
|
7
|
+
export function unwrapClickHouseType(type) {
|
|
8
|
+
let current = type.trim();
|
|
9
|
+
const simple = /^SimpleAggregateFunction\(\s*[A-Za-z_][A-Za-z0-9_]*\s*,\s*(.+)\)$/s.exec(current);
|
|
10
|
+
if (simple)
|
|
11
|
+
current = simple[1].trim();
|
|
12
|
+
for (;;) {
|
|
13
|
+
const wrapped = /^(?:Nullable|LowCardinality)\((.*)\)$/s.exec(current);
|
|
14
|
+
if (!wrapped)
|
|
15
|
+
return current;
|
|
16
|
+
current = wrapped[1].trim();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Maps a ClickHouse column type to a dataset dimension type, or null when no
|
|
21
|
+
* scalar dimension type describes its values (Array, Tuple, Map, geo, JSON,
|
|
22
|
+
* Interval, ...). The outer type name is matched exactly: a substring match
|
|
23
|
+
* reads `Point` or `Array(Int32)` as numbers. Mirrors Cloud's `protocolTypeOf`
|
|
24
|
+
* so a table scaffolds with the same field types in both.
|
|
25
|
+
*/
|
|
26
|
+
export function clickHouseDimensionType(type) {
|
|
27
|
+
const value = unwrapClickHouseType(type);
|
|
28
|
+
if (/^(?:U?Int\d+|Float\d+|Decimal\d*)(?:\(.*\))?$/s.test(value))
|
|
29
|
+
return 'number';
|
|
30
|
+
if (/^(?:Date|Date32|DateTime|DateTime64)(?:\(.*\))?$/s.test(value))
|
|
31
|
+
return 'timestamp';
|
|
32
|
+
if (value === 'Bool')
|
|
33
|
+
return 'boolean';
|
|
34
|
+
if (/^(?:String|FixedString|UUID|Enum\d*|IPv4|IPv6)(?:\(.*\))?$/s.test(value))
|
|
35
|
+
return 'string';
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The scalar elements of a named `Tuple(name T, ...)` column, each of which can
|
|
40
|
+
* be its own dimension through `tupleElement`. Null for any other type,
|
|
41
|
+
* including unnamed tuples, whose positions carry no meaning to name a field
|
|
42
|
+
* after. Elements without a scalar type or a plain identifier name are left out.
|
|
43
|
+
*/
|
|
44
|
+
export function namedTupleElements(type) {
|
|
45
|
+
const parts = matchTypeCall(unwrapClickHouseType(type), 'Tuple');
|
|
46
|
+
if (!parts?.length)
|
|
47
|
+
return null;
|
|
48
|
+
const elements = [];
|
|
49
|
+
for (const part of parts) {
|
|
50
|
+
const named = /^(\S+)\s+(.+)$/s.exec(part);
|
|
51
|
+
// Element types never contain top-level whitespace before their first
|
|
52
|
+
// `(`, so a part without a name-then-space prefix is an unnamed element.
|
|
53
|
+
if (!named || named[1].includes('('))
|
|
54
|
+
return null;
|
|
55
|
+
const elementType = clickHouseDimensionType(named[2]);
|
|
56
|
+
if (IDENTIFIER.test(named[1]) && elementType) {
|
|
57
|
+
elements.push({ name: named[1], type: elementType });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return elements;
|
|
61
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clickhouse-type-utils.d.ts","sourceRoot":"","sources":["../../src/utils/clickhouse-type-utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"clickhouse-type-utils.d.ts","sourceRoot":"","sources":["../../src/utils/clickhouse-type-utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAqDzD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG3E;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAGzE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,UASpD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,WAE7C"}
|
|
@@ -3,10 +3,18 @@ export function splitTopLevelArgs(value) {
|
|
|
3
3
|
let current = '';
|
|
4
4
|
let depth = 0;
|
|
5
5
|
let quote = null;
|
|
6
|
+
let escaped = false;
|
|
6
7
|
for (const char of value) {
|
|
7
8
|
if (quote) {
|
|
8
9
|
current += char;
|
|
9
|
-
|
|
10
|
+
// ClickHouse escapes a quote inside a literal with a backslash (`'can\'t'`).
|
|
11
|
+
if (escaped) {
|
|
12
|
+
escaped = false;
|
|
13
|
+
}
|
|
14
|
+
else if (char === '\\') {
|
|
15
|
+
escaped = true;
|
|
16
|
+
}
|
|
17
|
+
else if (char === quote) {
|
|
10
18
|
quote = null;
|
|
11
19
|
}
|
|
12
20
|
continue;
|
|
@@ -3,4 +3,9 @@ export declare function checkClientOutputPath(filePath: string): Promise<void>;
|
|
|
3
3
|
/** Bound allocation before reading; O_NOFOLLOW also refuses a swapped final symlink. */
|
|
4
4
|
export declare function readClientArtifact(filePath: string, maxBytes?: number): Promise<string>;
|
|
5
5
|
export declare function readClientOutput(filePath: string): Promise<string | undefined>;
|
|
6
|
+
/**
|
|
7
|
+
* Say where a client contract is invalid. An unknown field usually means the
|
|
8
|
+
* contract was exported by a newer hypequery than this CLI understands.
|
|
9
|
+
*/
|
|
10
|
+
export declare function describeClientContractError(error: unknown): unknown;
|
|
6
11
|
//# sourceMappingURL=client-contract-file.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-contract-file.d.ts","sourceRoot":"","sources":["../../src/utils/client-contract-file.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client-contract-file.d.ts","sourceRoot":"","sources":["../../src/utils/client-contract-file.ts"],"names":[],"mappings":"AAMA,qFAAqF;AACrF,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB3E;AAsBD,wFAAwF;AACxF,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,SAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBhG;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAMpF;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAQnE"}
|
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import { constants } from 'node:fs';
|
|
2
|
-
import { lstat, open } from 'node:fs/promises';
|
|
2
|
+
import { lstat, open, realpath, stat } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { ProtocolClientContractError } from '@hypequery/protocol';
|
|
5
|
+
import { pointer } from './json-pointer.js';
|
|
4
6
|
/** Refuse filesystem indirection for generated output and its existing ancestors. */
|
|
5
7
|
export async function checkClientOutputPath(filePath) {
|
|
6
|
-
|
|
8
|
+
const target = path.resolve(filePath);
|
|
9
|
+
let current = target;
|
|
7
10
|
while (true) {
|
|
8
11
|
try {
|
|
9
12
|
const info = await lstat(current);
|
|
10
|
-
if (info.isSymbolicLink()
|
|
11
|
-
throw
|
|
13
|
+
if (info.isSymbolicLink())
|
|
14
|
+
throw await throughLink(filePath, target, current);
|
|
15
|
+
if (current === target ? !info.isFile() : !info.isDirectory()) {
|
|
16
|
+
throw new Error(current === target
|
|
17
|
+
? `Client output ${filePath} must be a regular file.`
|
|
18
|
+
: `Client output ${filePath} must be a regular file under ordinary directories: ${current} is not a directory.`);
|
|
12
19
|
}
|
|
13
20
|
}
|
|
14
21
|
catch (error) {
|
|
15
|
-
|
|
22
|
+
// Missing, or below a file: the ancestor that exists is checked next.
|
|
23
|
+
const code = error.code;
|
|
24
|
+
if (code !== 'ENOENT' && code !== 'ENOTDIR')
|
|
16
25
|
throw error;
|
|
17
26
|
}
|
|
18
27
|
const parent = path.dirname(current);
|
|
@@ -21,6 +30,27 @@ export async function checkClientOutputPath(filePath) {
|
|
|
21
30
|
current = parent;
|
|
22
31
|
}
|
|
23
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Name the link, and the same output without it, e.g. /tmp → /private/tmp on macOS.
|
|
35
|
+
* The path is suggested only when the link resolves to what that position needs:
|
|
36
|
+
* a directory above the output, a regular file at the output itself.
|
|
37
|
+
*/
|
|
38
|
+
async function throughLink(filePath, target, link) {
|
|
39
|
+
const message = link === target
|
|
40
|
+
? `Client output ${filePath} must be a regular file, not a symbolic link.`
|
|
41
|
+
: `Client output ${filePath} must be a regular file under ordinary directories: ${link} is a symbolic link.`;
|
|
42
|
+
try {
|
|
43
|
+
const real = await realpath(link);
|
|
44
|
+
const info = await stat(real);
|
|
45
|
+
if (link === target ? !info.isFile() : !info.isDirectory())
|
|
46
|
+
return new Error(message);
|
|
47
|
+
const resolved = path.join(real, path.relative(link, target));
|
|
48
|
+
return new Error(`${message} Use the path it points to instead: ${resolved}`);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return new Error(message);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
24
54
|
/** Bound allocation before reading; O_NOFOLLOW also refuses a swapped final symlink. */
|
|
25
55
|
export async function readClientArtifact(filePath, maxBytes = 1_048_576) {
|
|
26
56
|
const handle = await open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
@@ -56,3 +86,17 @@ export async function readClientOutput(filePath) {
|
|
|
56
86
|
throw error;
|
|
57
87
|
}
|
|
58
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Say where a client contract is invalid. An unknown field usually means the
|
|
91
|
+
* contract was exported by a newer hypequery than this CLI understands.
|
|
92
|
+
*/
|
|
93
|
+
export function describeClientContractError(error) {
|
|
94
|
+
if (!(error instanceof ProtocolClientContractError))
|
|
95
|
+
return error;
|
|
96
|
+
const at = error.field === undefined ? error.path : pointer(error.path, error.field);
|
|
97
|
+
const where = at ? ` at ${at}` : '';
|
|
98
|
+
const hint = error.code === 'HQ_CLIENT_UNKNOWN_FIELD'
|
|
99
|
+
? ' The contract may come from a newer hypequery than this CLI; upgrade @hypequery/cli.'
|
|
100
|
+
: '';
|
|
101
|
+
return new Error(`${error.message.replace(/\.$/, '')}${where} (${error.code}).${hint}`, { cause: error });
|
|
102
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ProtocolDeploymentReleaseTarget } from '@hypequery/protocol';
|
|
2
|
+
import { type LiveDeploymentFetch } from './live-deployment.js';
|
|
3
|
+
/**
|
|
4
|
+
* The origin a generated client calls. Refused when Cloud did not say, when it
|
|
5
|
+
* is not https (loopback aside), or when it carries a path: the client resolves
|
|
6
|
+
* its absolute endpoint paths against the origin, so a prefix would be dropped.
|
|
7
|
+
*/
|
|
8
|
+
export declare function clientOrigin(baseUrl: string | undefined): string;
|
|
9
|
+
/**
|
|
10
|
+
* The client contract of the target's live release, as Cloud serves it beside
|
|
11
|
+
* the deployment endpoint. Returned as text: the caller decodes and verifies it
|
|
12
|
+
* exactly as it would a contract file.
|
|
13
|
+
*/
|
|
14
|
+
export declare function fetchCloudClientContract(input: {
|
|
15
|
+
readonly endpoint: string;
|
|
16
|
+
readonly token: string;
|
|
17
|
+
readonly target: ProtocolDeploymentReleaseTarget;
|
|
18
|
+
readonly fetch?: LiveDeploymentFetch;
|
|
19
|
+
}): Promise<string>;
|
|
20
|
+
//# sourceMappingURL=cloud-client-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-client-contract.d.ts","sourceRoot":"","sources":["../../src/utils/cloud-client-contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAW,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAOzE;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAehE;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,KAAK,EAAE;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,KAAK,CAAC,EAAE,mBAAmB,CAAC;CACtC,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBlB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { liveUrl } from './live-deployment.js';
|
|
2
|
+
import { readCapped } from './read-capped.js';
|
|
3
|
+
const MAX_CONTRACT_BYTES = 1_048_576;
|
|
4
|
+
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);
|
|
5
|
+
/**
|
|
6
|
+
* The origin a generated client calls. Refused when Cloud did not say, when it
|
|
7
|
+
* is not https (loopback aside), or when it carries a path: the client resolves
|
|
8
|
+
* its absolute endpoint paths against the origin, so a prefix would be dropped.
|
|
9
|
+
*/
|
|
10
|
+
export function clientOrigin(baseUrl) {
|
|
11
|
+
if (baseUrl === undefined) {
|
|
12
|
+
throw new Error('Cloud did not say where this release is served, so the client would have no base URL. Retry, or update Cloud.');
|
|
13
|
+
}
|
|
14
|
+
const url = new URL(baseUrl);
|
|
15
|
+
if (url.protocol !== 'https:' && !(url.protocol === 'http:' && LOOPBACK_HOSTS.has(url.hostname))) {
|
|
16
|
+
throw new Error(`Cloud serves this release from ${url.origin}, which is not https.`);
|
|
17
|
+
}
|
|
18
|
+
if (url.pathname !== '/' || url.search || url.hash) {
|
|
19
|
+
throw new Error(`Cloud serves this release under ${baseUrl} because an endpoint path collides with a reserved Cloud path (such as /mcp). `
|
|
20
|
+
+ 'Rename that endpoint path and deploy again; generated clients call the customer origin directly.');
|
|
21
|
+
}
|
|
22
|
+
return url.origin;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The client contract of the target's live release, as Cloud serves it beside
|
|
26
|
+
* the deployment endpoint. Returned as text: the caller decodes and verifies it
|
|
27
|
+
* exactly as it would a contract file.
|
|
28
|
+
*/
|
|
29
|
+
export async function fetchCloudClientContract(input) {
|
|
30
|
+
const url = liveUrl(input.endpoint, input.target, 'client-contract');
|
|
31
|
+
if (!url) {
|
|
32
|
+
throw new Error('The deployment endpoint is not a Cloud submissions URL, so it serves no client contract.');
|
|
33
|
+
}
|
|
34
|
+
const response = await (input.fetch ?? fetch)(url, {
|
|
35
|
+
method: 'GET',
|
|
36
|
+
headers: { Accept: 'application/json', Authorization: `Bearer ${input.token}` },
|
|
37
|
+
redirect: 'error',
|
|
38
|
+
signal: AbortSignal.timeout(30_000),
|
|
39
|
+
});
|
|
40
|
+
const target = `${input.target.project} / ${input.target.environment}`;
|
|
41
|
+
if (response.status === 401 || response.status === 403) {
|
|
42
|
+
throw new Error(`Cloud refused the client contract for ${target}. Run \`hypequery login\` again.`);
|
|
43
|
+
}
|
|
44
|
+
if (response.status === 404) {
|
|
45
|
+
throw new Error(`Cloud has no client contract for ${target}: publish a release first, `
|
|
46
|
+
+ 'or this Cloud version does not serve client contracts yet.');
|
|
47
|
+
}
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw new Error(`Cloud could not return the client contract for ${target} (HTTP ${response.status}).`);
|
|
50
|
+
}
|
|
51
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(await readCapped(response, MAX_CONTRACT_BYTES));
|
|
52
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { ReadableStream } from 'node:stream/web';
|
|
2
2
|
import { type PreparedProtocolDeploymentReleaseEnvelope } from '@hypequery/protocol';
|
|
3
3
|
import { type VerifiedDeploymentBundle } from './deployment-bundle.js';
|
|
4
|
-
import type { DeploymentSubmissionResponse } from '@hypequery/deployment';
|
|
5
|
-
|
|
4
|
+
import type { DeploymentSubmissionResponse as BaseDeploymentSubmissionResponse } from '@hypequery/deployment';
|
|
5
|
+
import { type DraftDeploymentResult } from './draft-deployment.js';
|
|
6
|
+
export type DeploymentSubmissionResponse = BaseDeploymentSubmissionResponse & {
|
|
7
|
+
readonly draft?: DraftDeploymentResult;
|
|
8
|
+
};
|
|
6
9
|
export type DeploymentUploadErrorCode = 'HQ_UPLOAD_CONFIGURATION' | 'HQ_UPLOAD_IDENTITY_MISMATCH' | 'HQ_UPLOAD_BUNDLE_CHANGED' | 'HQ_UPLOAD_NETWORK' | 'HQ_UPLOAD_REJECTED' | 'HQ_UPLOAD_INVALID_RESPONSE';
|
|
7
10
|
export declare class DeploymentUploadError extends Error {
|
|
8
11
|
readonly code: DeploymentUploadErrorCode;
|
|
@@ -29,6 +32,7 @@ export interface HttpDeploymentUploadTransportOptions {
|
|
|
29
32
|
readonly token: string;
|
|
30
33
|
readonly expectedActivationRevision?: string | null;
|
|
31
34
|
readonly replaceRestored?: boolean;
|
|
35
|
+
readonly draft?: string;
|
|
32
36
|
readonly timeoutMs?: number;
|
|
33
37
|
readonly fetch?: DeploymentFetch;
|
|
34
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deployment-upload.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-upload.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAIL,KAAK,yCAAyC,EAC/C,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"deployment-upload.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-upload.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAIL,KAAK,yCAAyC,EAC/C,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,4BAA4B,IAAI,gCAAgC,EAAE,MAAM,uBAAuB,CAAC;AAC9G,OAAO,EAA8D,KAAK,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE/H,MAAM,MAAM,4BAA4B,GAAG,gCAAgC,GAAG;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,qBAAqB,CAAC;CACxC,CAAC;AAQF,MAAM,MAAM,yBAAyB,GACjC,yBAAyB,GACzB,6BAA6B,GAC7B,0BAA0B,GAC1B,mBAAmB,GACnB,oBAAoB,GACpB,4BAA4B,CAAC;AAEjC,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEb,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAM9E;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,MAAM,MAAM,eAAe,GAAG,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,mBAAmB,KACtB,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAErC,MAAM,WAAW,oCAAoC;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,0BAA0B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpD,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,CACJ,MAAM,EAAE,wBAAwB,EAChC,OAAO,EAAE,yCAAyC,GACjD,OAAO,CAAC,4BAA4B,CAAC,CAAC;CAC1C;AAoXD,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,oCAAoC,GAC5C,yBAAyB,CAqG3B"}
|
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { prepareProtocolDeploymentBundleManifest, prepareProtocolDeploymentReleaseEnvelope, } from '@hypequery/protocol';
|
|
5
5
|
import { DEPLOYMENT_BUNDLE_MANIFEST, } from './deployment-bundle.js';
|
|
6
6
|
import { logger } from './logger.js';
|
|
7
|
+
import { parseDraftDeploymentResult, validateDraftDeploymentOptions } from './draft-deployment.js';
|
|
7
8
|
const SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
8
9
|
const MAX_RESPONSE_BYTES = 64 * 1024;
|
|
9
10
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
@@ -249,15 +250,18 @@ function exactRecord(value, fields) {
|
|
|
249
250
|
}
|
|
250
251
|
return record;
|
|
251
252
|
}
|
|
252
|
-
function validateSubmissionResponse(input, bundleIdentity, releaseIdentity, status) {
|
|
253
|
+
function validateSubmissionResponse(input, bundleIdentity, releaseIdentity, status, expectedDraft) {
|
|
253
254
|
const value = exactRecord(input, [
|
|
254
255
|
'kind',
|
|
255
256
|
'version',
|
|
256
257
|
'status',
|
|
257
258
|
'releaseIdentity',
|
|
258
259
|
'bundleIdentity',
|
|
260
|
+
...(expectedDraft ? ['draft'] : []),
|
|
259
261
|
]);
|
|
262
|
+
const draft = expectedDraft && value ? parseDraftDeploymentResult(value.draft, expectedDraft) : undefined;
|
|
260
263
|
if (!value
|
|
264
|
+
|| (expectedDraft && !draft)
|
|
261
265
|
|| value.kind !== 'hypequery-deployment-submission'
|
|
262
266
|
|| value.version !== 1
|
|
263
267
|
|| (value.status !== 'accepted' && value.status !== 'already-exists')
|
|
@@ -275,6 +279,7 @@ function validateSubmissionResponse(input, bundleIdentity, releaseIdentity, stat
|
|
|
275
279
|
status: value.status,
|
|
276
280
|
releaseIdentity: value.releaseIdentity,
|
|
277
281
|
bundleIdentity: value.bundleIdentity,
|
|
282
|
+
...(draft ? { draft } : {}),
|
|
278
283
|
});
|
|
279
284
|
}
|
|
280
285
|
function rejectedMessage(response, body) {
|
|
@@ -319,6 +324,10 @@ function nestedUploadError(input) {
|
|
|
319
324
|
}
|
|
320
325
|
const defaultFetch = async (input, init) => fetch(input, init);
|
|
321
326
|
export function createHttpDeploymentUploadTransport(options) {
|
|
327
|
+
validateDraftDeploymentOptions(options);
|
|
328
|
+
if (options.draft !== undefined && options.expectedActivationRevision !== undefined) {
|
|
329
|
+
configurationError('Draft uploads cannot specify an expected Production activation revision.');
|
|
330
|
+
}
|
|
322
331
|
const endpoint = endpointUrl(options.endpoint);
|
|
323
332
|
const token = bearerToken(options.token);
|
|
324
333
|
const timeoutMs = timeout(options.timeoutMs);
|
|
@@ -347,6 +356,7 @@ export function createHttpDeploymentUploadTransport(options) {
|
|
|
347
356
|
'Idempotency-Key': preparedRelease.identity,
|
|
348
357
|
'X-HypeQuery-Bundle-Identity': manifest.identity,
|
|
349
358
|
'X-HypeQuery-Release-Identity': preparedRelease.identity,
|
|
359
|
+
...(options.draft !== undefined ? { 'X-HypeQuery-Draft-Name': options.draft } : {}),
|
|
350
360
|
...(options.expectedActivationRevision !== undefined
|
|
351
361
|
? {
|
|
352
362
|
'X-HypeQuery-Expected-Activation-Revision': options.expectedActivationRevision ?? 'none',
|
|
@@ -387,7 +397,7 @@ export function createHttpDeploymentUploadTransport(options) {
|
|
|
387
397
|
catch {
|
|
388
398
|
throw new DeploymentUploadError('HQ_UPLOAD_INVALID_RESPONSE', 'Deployment service returned invalid JSON.', response.status);
|
|
389
399
|
}
|
|
390
|
-
return validateSubmissionResponse(input, manifest.identity, preparedRelease.identity, response.status);
|
|
400
|
+
return validateSubmissionResponse(input, manifest.identity, preparedRelease.identity, response.status, options.draft !== undefined ? { name: options.draft, endpoint, project: preparedRelease.release.target.project } : undefined);
|
|
391
401
|
},
|
|
392
402
|
};
|
|
393
403
|
return Object.freeze(transport);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface DraftDeploymentResult {
|
|
2
|
+
readonly draftId: string;
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly stagingUrl: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function validateDraftDeploymentOptions(options: {
|
|
7
|
+
draft?: string;
|
|
8
|
+
replaceRestored?: boolean;
|
|
9
|
+
}): void;
|
|
10
|
+
/** Accept only the requested draft and a staging endpoint on the deployment service. */
|
|
11
|
+
export declare function parseDraftDeploymentResult(input: unknown, expected: {
|
|
12
|
+
name: string;
|
|
13
|
+
endpoint: string;
|
|
14
|
+
project: string;
|
|
15
|
+
}): DraftDeploymentResult | undefined;
|
|
16
|
+
//# sourceMappingURL=draft-deployment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft-deployment.d.ts","sourceRoot":"","sources":["../../src/utils/draft-deployment.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,GAAG,IAAI,CAQP;AAED,wFAAwF;AACxF,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC5D,qBAAqB,GAAG,SAAS,CAmBnC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export function validateDraftDeploymentOptions(options) {
|
|
2
|
+
if (options.draft === undefined)
|
|
3
|
+
return;
|
|
4
|
+
if (options.draft.length > 64 || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(options.draft)) {
|
|
5
|
+
throw new Error('--draft must be a lowercase name with letters, numbers and single hyphens (max 64 characters).');
|
|
6
|
+
}
|
|
7
|
+
if (options.replaceRestored) {
|
|
8
|
+
throw new Error('--draft cannot be combined with --replace-restored; draft uploads do not replace Production.');
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** Accept only the requested draft and a staging endpoint on the deployment service. */
|
|
12
|
+
export function parseDraftDeploymentResult(input, expected) {
|
|
13
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input)
|
|
14
|
+
|| Object.getPrototypeOf(input) !== Object.prototype)
|
|
15
|
+
return undefined;
|
|
16
|
+
const value = input;
|
|
17
|
+
if (Object.keys(value).sort().join(',') !== 'draftId,name,stagingUrl'
|
|
18
|
+
|| typeof value.draftId !== 'string'
|
|
19
|
+
|| !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(value.draftId)
|
|
20
|
+
|| value.name !== expected.name
|
|
21
|
+
|| typeof value.stagingUrl !== 'string')
|
|
22
|
+
return undefined;
|
|
23
|
+
let url;
|
|
24
|
+
try {
|
|
25
|
+
url = new URL(value.stagingUrl, expected.endpoint);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
if (url.origin !== new URL(expected.endpoint).origin || url.username || url.password
|
|
31
|
+
|| url.search || url.hash
|
|
32
|
+
|| url.pathname !== `/api/gateway/${encodeURIComponent(expected.project)}/draft/${value.draftId}`)
|
|
33
|
+
return undefined;
|
|
34
|
+
return Object.freeze({ draftId: value.draftId, name: expected.name, stagingUrl: url.href });
|
|
35
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hands out the field names of one generated dataset. A field's name is also
|
|
3
|
+
* its SQL alias, and ClickHouse resolves an alias before a column of the same
|
|
4
|
+
* name, so a field may take a source column's name only when it reads exactly
|
|
5
|
+
* that column; otherwise every expression reading that column would read the
|
|
6
|
+
* aliased field instead. A name that is taken, or is another column's name,
|
|
7
|
+
* gets the lowest free numeric suffix (`fareTotal2`).
|
|
8
|
+
*/
|
|
9
|
+
export declare function createFieldNamer(columnNames: readonly string[]): (base: string, ownColumn?: string) => string;
|
|
10
|
+
//# sourceMappingURL=field-namer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-namer.d.ts","sourceRoot":"","sources":["../../src/utils/field-namer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,IAGrD,MAAM,MAAM,EAAE,YAAY,MAAM,KAAG,MAAM,CAQlD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hands out the field names of one generated dataset. A field's name is also
|
|
3
|
+
* its SQL alias, and ClickHouse resolves an alias before a column of the same
|
|
4
|
+
* name, so a field may take a source column's name only when it reads exactly
|
|
5
|
+
* that column; otherwise every expression reading that column would read the
|
|
6
|
+
* aliased field instead. A name that is taken, or is another column's name,
|
|
7
|
+
* gets the lowest free numeric suffix (`fareTotal2`).
|
|
8
|
+
*/
|
|
9
|
+
export function createFieldNamer(columnNames) {
|
|
10
|
+
const columns = new Set(columnNames);
|
|
11
|
+
const taken = new Set();
|
|
12
|
+
return (base, ownColumn) => {
|
|
13
|
+
let name = base;
|
|
14
|
+
for (let suffix = 2; taken.has(name) || (columns.has(name) && name !== ownColumn); suffix += 1) {
|
|
15
|
+
name = `${base}${suffix}`;
|
|
16
|
+
}
|
|
17
|
+
taken.add(name);
|
|
18
|
+
return name;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-pointer.d.ts","sourceRoot":"","sources":["../../src/utils/json-pointer.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEzD"}
|
|
@@ -31,7 +31,7 @@ export type LiveDeploymentFetch = typeof fetch;
|
|
|
31
31
|
* the endpoint is not a Cloud submissions URL. Refuses anything but https (or
|
|
32
32
|
* loopback http) so a credential is never sent in the clear.
|
|
33
33
|
*/
|
|
34
|
-
export declare function liveUrl(endpoint: string, target: ProtocolDeploymentReleaseTarget, resource: 'state' | 'source' | 'endpoints'): string | undefined;
|
|
34
|
+
export declare function liveUrl(endpoint: string, target: ProtocolDeploymentReleaseTarget, resource: 'state' | 'source' | 'endpoints' | 'client-contract'): string | undefined;
|
|
35
35
|
export declare function fetchLiveDeployment(input: {
|
|
36
36
|
readonly endpoint: string;
|
|
37
37
|
readonly token: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-deployment.d.ts","sourceRoot":"","sources":["../../src/utils/live-deployment.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,+BAA+B,EACrC,MAAM,qBAAqB,CAAC;AAO7B,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,wBAAwB,EAAE,CAAC;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAClB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;QACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;KACzB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,IAAI,GAAG;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;QACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QAC3B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;QAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC;KACxC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,OAAO,KAAK,CAAC;AAE/C;;;;GAIG;AACH,wBAAgB,OAAO,CACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,+BAA+B,EACvC,QAAQ,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"live-deployment.d.ts","sourceRoot":"","sources":["../../src/utils/live-deployment.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,+BAA+B,EACrC,MAAM,qBAAqB,CAAC;AAO7B,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,wBAAwB,EAAE,CAAC;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAClB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;QACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;KACzB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,IAAI,GAAG;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;QACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QAC3B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;QAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC;KACxC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,OAAO,KAAK,CAAC;AAE/C;;;;GAIG;AACH,wBAAgB,OAAO,CACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,+BAA+B,EACvC,QAAQ,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,iBAAiB,GAC7D,MAAM,GAAG,SAAS,CAkBpB;AAsJD,wBAAsB,mBAAmB,CAAC,KAAK,EAAE;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,QAAQ,CAAC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,mBAAmB,CAAC;CACtC,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAuCtC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A response body, refused once it passes `maxBytes`: by the declared length
|
|
3
|
+
* before reading, and while streaming when no length is declared.
|
|
4
|
+
*/
|
|
5
|
+
export declare function readCapped(response: Response, maxBytes: number): Promise<Uint8Array>;
|
|
6
|
+
//# sourceMappingURL=read-capped.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-capped.d.ts","sourceRoot":"","sources":["../../src/utils/read-capped.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA6B1F"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A response body, refused once it passes `maxBytes`: by the declared length
|
|
3
|
+
* before reading, and while streaming when no length is declared.
|
|
4
|
+
*/
|
|
5
|
+
export async function readCapped(response, maxBytes) {
|
|
6
|
+
const tooLarge = () => new Error(`The response exceeds ${maxBytes} bytes.`);
|
|
7
|
+
const declared = Number(response.headers.get('content-length'));
|
|
8
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
9
|
+
// Release the connection rather than leave the body unread.
|
|
10
|
+
await response.body?.cancel();
|
|
11
|
+
throw tooLarge();
|
|
12
|
+
}
|
|
13
|
+
if (!response.body)
|
|
14
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
15
|
+
const reader = response.body.getReader();
|
|
16
|
+
const chunks = [];
|
|
17
|
+
let total = 0;
|
|
18
|
+
for (;;) {
|
|
19
|
+
const { done, value } = await reader.read();
|
|
20
|
+
if (done)
|
|
21
|
+
break;
|
|
22
|
+
total += value.byteLength;
|
|
23
|
+
if (total > maxBytes) {
|
|
24
|
+
await reader.cancel();
|
|
25
|
+
throw tooLarge();
|
|
26
|
+
}
|
|
27
|
+
chunks.push(value);
|
|
28
|
+
}
|
|
29
|
+
const bytes = new Uint8Array(total);
|
|
30
|
+
let offset = 0;
|
|
31
|
+
for (const chunk of chunks) {
|
|
32
|
+
bytes.set(chunk, offset);
|
|
33
|
+
offset += chunk.byteLength;
|
|
34
|
+
}
|
|
35
|
+
return bytes;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-quote.d.ts","sourceRoot":"","sources":["../../src/utils/shell-quote.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One argument as a POSIX shell word, for commands printed for the user to
|
|
3
|
+
* copy. Plain words stay as they are; anything else is single-quoted.
|
|
4
|
+
*/
|
|
5
|
+
export function shellQuote(value) {
|
|
6
|
+
return /^[A-Za-z0-9_./:=@%+-]+$/.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
|
|
7
|
+
}
|
|
@@ -625,7 +625,7 @@ export declare const EVENT_CATALOG: {
|
|
|
625
625
|
readonly flags_used: {
|
|
626
626
|
readonly kind: "enum_list";
|
|
627
627
|
readonly description: string;
|
|
628
|
-
readonly values: readonly ("--path" | "--style" | "--database" | "--chdb-path" | "--auth" | "--all-tables" | "--tables" | "--exclude-tables" | "--no-example" | "--no-interactive" | "--force" | "--skip-connection" | "--port" | "--hostname" | "--no-watch" | "--no-cache" | "--cache" | "--redis-url" | "--open" | "--cors" | "--quiet" | "--tenant" | "--self-test" | "--url" | "--client-contract-url" | "--output" | "--tenant-column" | "--check" | "--diff" | "--cloud
|
|
628
|
+
readonly values: readonly ("--path" | "--style" | "--database" | "--chdb-path" | "--auth" | "--all-tables" | "--tables" | "--exclude-tables" | "--no-example" | "--no-interactive" | "--force" | "--skip-connection" | "--port" | "--hostname" | "--no-watch" | "--no-cache" | "--cache" | "--redis-url" | "--open" | "--cors" | "--quiet" | "--tenant" | "--self-test" | "--url" | "--client-contract-url" | "--output" | "--tenant-column" | "--check" | "--diff" | "--cloud" | "--project" | "--environment" | "--endpoint" | "--cloud-url" | "--bundle-output" | "--release-output" | "--no-source" | "--replace-restored" | "--mcp-config" | "--draft" | "--client" | "--release" | "--no-telemetry" | "--help" | "--version")[];
|
|
629
629
|
};
|
|
630
630
|
readonly outcome: {
|
|
631
631
|
readonly kind: "enum";
|