@ifc-lite/flow-nodes 0.3.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/aps-client.d.ts +55 -0
- package/dist/aps-client.d.ts.map +1 -0
- package/dist/aps-client.js +194 -0
- package/dist/aps-client.js.map +1 -0
- package/dist/aps-nodes.d.ts +3 -0
- package/dist/aps-nodes.d.ts.map +1 -0
- package/dist/aps-nodes.js +192 -0
- package/dist/aps-nodes.js.map +1 -0
- package/dist/aps-table.d.ts +24 -0
- package/dist/aps-table.d.ts.map +1 -0
- package/dist/aps-table.js +123 -0
- package/dist/aps-table.js.map +1 -0
- package/dist/base64.d.ts +8 -0
- package/dist/base64.d.ts.map +1 -0
- package/dist/base64.js +59 -0
- package/dist/base64.js.map +1 -0
- package/dist/bcf-client.d.ts +36 -0
- package/dist/bcf-client.d.ts.map +1 -0
- package/dist/bcf-client.js +108 -0
- package/dist/bcf-client.js.map +1 -0
- package/dist/bcf-nodes.d.ts +3 -0
- package/dist/bcf-nodes.d.ts.map +1 -0
- package/dist/bcf-nodes.js +181 -0
- package/dist/bcf-nodes.js.map +1 -0
- package/dist/documents-fetch.d.ts +23 -0
- package/dist/documents-fetch.d.ts.map +1 -0
- package/dist/documents-fetch.js +48 -0
- package/dist/documents-fetch.js.map +1 -0
- package/dist/documents-nodes.d.ts +5 -0
- package/dist/documents-nodes.d.ts.map +1 -0
- package/dist/documents-nodes.js +167 -0
- package/dist/documents-nodes.js.map +1 -0
- package/dist/host.d.ts +28 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js.map +1 -1
- package/dist/http-request-node.d.ts +9 -0
- package/dist/http-request-node.d.ts.map +1 -0
- package/dist/http-request-node.js +109 -0
- package/dist/http-request-node.js.map +1 -0
- package/dist/index.d.ts +20 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -5
- package/dist/index.js.map +1 -1
- package/dist/model-nodes.d.ts.map +1 -1
- package/dist/model-nodes.js +11 -6
- package/dist/model-nodes.js.map +1 -1
- package/dist/open-model-node.d.ts +3 -0
- package/dist/open-model-node.d.ts.map +1 -0
- package/dist/open-model-node.js +47 -0
- package/dist/open-model-node.js.map +1 -0
- package/dist/script-node.d.ts.map +1 -1
- package/dist/script-node.js +55 -21
- package/dist/script-node.js.map +1 -1
- package/dist/secrets.d.ts +88 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +202 -0
- package/dist/secrets.js.map +1 -0
- package/dist/speckle/client.d.ts +52 -0
- package/dist/speckle/client.d.ts.map +1 -0
- package/dist/speckle/client.js +153 -0
- package/dist/speckle/client.js.map +1 -0
- package/dist/speckle/geometry.d.ts +37 -0
- package/dist/speckle/geometry.d.ts.map +1 -0
- package/dist/speckle/geometry.js +126 -0
- package/dist/speckle/geometry.js.map +1 -0
- package/dist/speckle/mapping.d.ts +62 -0
- package/dist/speckle/mapping.d.ts.map +1 -0
- package/dist/speckle/mapping.js +208 -0
- package/dist/speckle/mapping.js.map +1 -0
- package/dist/speckle/parameters.d.ts +49 -0
- package/dist/speckle/parameters.d.ts.map +1 -0
- package/dist/speckle/parameters.js +95 -0
- package/dist/speckle/parameters.js.map +1 -0
- package/dist/speckle/refusals.d.ts +45 -0
- package/dist/speckle/refusals.d.ts.map +1 -0
- package/dist/speckle/refusals.js +40 -0
- package/dist/speckle/refusals.js.map +1 -0
- package/dist/speckle/units.d.ts +13 -0
- package/dist/speckle/units.d.ts.map +1 -0
- package/dist/speckle/units.js +59 -0
- package/dist/speckle/units.js.map +1 -0
- package/dist/speckle/url.d.ts +35 -0
- package/dist/speckle/url.d.ts.map +1 -0
- package/dist/speckle/url.js +48 -0
- package/dist/speckle/url.js.map +1 -0
- package/dist/speckle-receive-node.d.ts +9 -0
- package/dist/speckle-receive-node.d.ts.map +1 -0
- package/dist/speckle-receive-node.js +180 -0
- package/dist/speckle-receive-node.js.map +1 -0
- package/dist/xlsx-nodes.d.ts.map +1 -1
- package/dist/xlsx-nodes.js +1 -40
- package/dist/xlsx-nodes.js.map +1 -1
- package/package.json +13 -11
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-model-node.d.ts","sourceRoot":"","sources":["../src/open-model-node.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAkC,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7E,eAAO,MAAM,aAAa,EAAE,WA2B3B,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* `model.openFromSource` — open a file a previous node fetched (typically
|
|
6
|
+
* `documents.download`) as a model, through the host's own load path
|
|
7
|
+
* (`FlowHost.openModel`): the viewer's canonical `addModel`/`loadFile`
|
|
8
|
+
* pipeline, or the CLI's / MCP's headless loader. The node never parses
|
|
9
|
+
* the bytes itself, so no second ingest pipeline exists.
|
|
10
|
+
*
|
|
11
|
+
* The `modelId` output is what sequences the rest of the graph: wire it
|
|
12
|
+
* into `model.select` / `model.byType`'s `modelId` input so those reads
|
|
13
|
+
* run after the model is open and address it.
|
|
14
|
+
*/
|
|
15
|
+
import { fromBase64 } from './base64.js';
|
|
16
|
+
import { requireCapability, SCALAR_ITEM } from './host.js';
|
|
17
|
+
export const openModelNode = {
|
|
18
|
+
type: 'model.openFromSource',
|
|
19
|
+
title: 'Open model from source',
|
|
20
|
+
category: 'model',
|
|
21
|
+
doc: 'Opens a downloaded IFC file (base64 `data`, e.g. from Download document) as a model on the host. Wire `modelId` into a model read node to query the opened model.',
|
|
22
|
+
inputs: [
|
|
23
|
+
{ name: 'data', type: SCALAR_ITEM },
|
|
24
|
+
{ name: 'name', type: SCALAR_ITEM, optional: true, nullable: true },
|
|
25
|
+
],
|
|
26
|
+
outputs: [{ name: 'modelId', type: SCALAR_ITEM }],
|
|
27
|
+
params: [{ name: 'name', kind: 'string', default: 'model.ifc', doc: 'File name, used when the `name` input is not wired; its extension picks the format.' }],
|
|
28
|
+
capabilities: ['model.create'],
|
|
29
|
+
requires: { backend: ['openModel'] },
|
|
30
|
+
// Loading a model changes what every later read sees, so it is a write
|
|
31
|
+
// (never memoised, and it advances the cache's write generation).
|
|
32
|
+
writes: 'model',
|
|
33
|
+
volatile: true,
|
|
34
|
+
run: async (ctx, i, p) => {
|
|
35
|
+
requireCapability(ctx, 'model.create');
|
|
36
|
+
const open = ctx.host.openModel;
|
|
37
|
+
if (!open)
|
|
38
|
+
throw new Error('model.openFromSource: this host cannot open models');
|
|
39
|
+
const bytes = fromBase64(typeof i.data === 'string' ? i.data : '');
|
|
40
|
+
if (bytes.byteLength === 0)
|
|
41
|
+
throw new Error('model.openFromSource: "data" is empty');
|
|
42
|
+
const name = typeof i.name === 'string' && i.name.length > 0 ? i.name : typeof p.name === 'string' && p.name.length > 0 ? p.name : 'model.ifc';
|
|
43
|
+
const { modelId } = await open.call(ctx.host, bytes, name);
|
|
44
|
+
return { modelId };
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=open-model-node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-model-node.js","sourceRoot":"","sources":["../src/open-model-node.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAoB,MAAM,WAAW,CAAC;AAE7E,MAAM,CAAC,MAAM,aAAa,GAAgB;IACxC,IAAI,EAAE,sBAAsB;IAC5B,KAAK,EAAE,wBAAwB;IAC/B,QAAQ,EAAE,OAAO;IACjB,GAAG,EAAE,mKAAmK;IACxK,MAAM,EAAE;QACN,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE;QACnC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;KACpE;IACD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IACjD,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,qFAAqF,EAAE,CAAC;IAC5J,YAAY,EAAE,CAAC,cAAc,CAAC;IAC9B,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE;IACpC,uEAAuE;IACvE,kEAAkE;IAClE,MAAM,EAAE,OAAO;IACf,QAAQ,EAAE,IAAI;IACd,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;QACvB,iBAAiB,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QACvC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;QAChC,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACjF,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACnE,IAAI,KAAK,CAAC,UAAU,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QACrF,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC;QAC/I,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QAC3D,OAAO,EAAE,OAAO,EAAE,CAAC;IACrB,CAAC;CACF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"script-node.d.ts","sourceRoot":"","sources":["../src/script-node.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"script-node.d.ts","sourceRoot":"","sources":["../src/script-node.ts"],"names":[],"mappings":"AA8BA,OAAO,EAAmD,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AA0H9F,eAAO,MAAM,UAAU,EAAE,WAgBxB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,WAsB5B,CAAC"}
|
package/dist/script-node.js
CHANGED
|
@@ -17,38 +17,57 @@
|
|
|
17
17
|
* no `bcf`, `ids`, `spatial`); that difference is documented, not papered
|
|
18
18
|
* over. Sandbox permissions are derived from the graph's grants: mutation
|
|
19
19
|
* is only enabled when a `model.mutate` grant exists.
|
|
20
|
+
*
|
|
21
|
+
* A lane whose code made a network request calls `ctx.markVolatile()`, so
|
|
22
|
+
* the scheduler does not memoise that run: rerunning the graph fetches
|
|
23
|
+
* again instead of replaying a stale response. A script that never calls
|
|
24
|
+
* `bim.network.fetch` stays memoisable even in a graph holding a grant.
|
|
20
25
|
*/
|
|
21
26
|
import { createSandbox } from '@ifc-lite/sandbox';
|
|
22
27
|
import { ANY_ITEM, ANY_LIST, requireCapability } from './host.js';
|
|
23
28
|
/**
|
|
24
|
-
* One sandbox per (BimContext, permission set), kept for the
|
|
25
|
-
* the context: QuickJS module init is the expensive part, and a
|
|
26
|
-
* several Script nodes (or a re-run) must not pay it per lane.
|
|
27
|
-
* rather than through `bim.sandbox` because that path
|
|
28
|
-
* `@ifc-lite/sandbox` relative to the SDK package, which
|
|
29
|
-
* install cannot resolve headlessly.
|
|
29
|
+
* One sandbox per (BimContext, transport, permission set), kept for the
|
|
30
|
+
* lifetime of the context: QuickJS module init is the expensive part, and a
|
|
31
|
+
* graph with several Script nodes (or a re-run) must not pay it per lane.
|
|
32
|
+
* Created here rather than through `bim.sandbox` because that path
|
|
33
|
+
* dynamically imports `@ifc-lite/sandbox` relative to the SDK package, which
|
|
34
|
+
* a pnpm-isolated install cannot resolve headlessly.
|
|
30
35
|
*/
|
|
31
36
|
const sandboxes = new WeakMap();
|
|
32
|
-
function sandboxFor(bim, permissions, timeoutMs) {
|
|
33
|
-
const key = JSON.stringify({ permissions, timeoutMs });
|
|
37
|
+
function sandboxFor(bim, permissions, timeoutMs, networkGrants, transport) {
|
|
38
|
+
const key = JSON.stringify({ permissions, timeoutMs, networkGrants });
|
|
34
39
|
let perContext = sandboxes.get(bim);
|
|
35
40
|
if (!perContext) {
|
|
36
41
|
perContext = new Map();
|
|
37
42
|
sandboxes.set(bim, perContext);
|
|
38
43
|
}
|
|
39
|
-
let
|
|
40
|
-
if (!
|
|
44
|
+
let perTransport = perContext.get(transport);
|
|
45
|
+
if (!perTransport) {
|
|
46
|
+
perTransport = new Map();
|
|
47
|
+
perContext.set(transport, perTransport);
|
|
48
|
+
}
|
|
49
|
+
const cache = perTransport;
|
|
50
|
+
let entry = cache.get(key);
|
|
51
|
+
if (!entry) {
|
|
52
|
+
const counting = (url, init) => {
|
|
53
|
+
created.requests += 1;
|
|
54
|
+
return transport ? transport(url, init) : fetch(url, init);
|
|
55
|
+
};
|
|
41
56
|
// A rejected creation (a wasm load failure, a transient resource limit)
|
|
42
57
|
// must not be cached: every later lane would await the same rejection for
|
|
43
58
|
// the lifetime of the context, turning one hiccup into a dead node.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
const sandbox = createSandbox(bim, {
|
|
60
|
+
permissions, limits: { timeoutMs }, network: { grants: networkGrants, transport: counting },
|
|
61
|
+
}).catch((err) => {
|
|
62
|
+
if (cache.get(key) === created)
|
|
63
|
+
cache.delete(key);
|
|
47
64
|
throw err;
|
|
48
65
|
});
|
|
49
|
-
|
|
66
|
+
const created = { sandbox, requests: 0 };
|
|
67
|
+
entry = created;
|
|
68
|
+
cache.set(key, entry);
|
|
50
69
|
}
|
|
51
|
-
return
|
|
70
|
+
return entry;
|
|
52
71
|
}
|
|
53
72
|
/** Evaluate `params.code` with `inputs` in scope and return its last expression. */
|
|
54
73
|
async function evaluate(ctx, inputs, params) {
|
|
@@ -57,6 +76,7 @@ async function evaluate(ctx, inputs, params) {
|
|
|
57
76
|
// read the model through a capability the graph never had.
|
|
58
77
|
requireCapability(ctx, 'model.read');
|
|
59
78
|
const grants = ctx.host.grants;
|
|
79
|
+
const networkGrants = ctx.host.networkGrants ?? [];
|
|
60
80
|
const has = (scope, action) => !grants || grants.some((g) => g.scope === scope && g.action === action);
|
|
61
81
|
const permissions = {
|
|
62
82
|
query: true,
|
|
@@ -67,6 +87,9 @@ async function evaluate(ctx, inputs, params) {
|
|
|
67
87
|
export: has('export', 'create'),
|
|
68
88
|
lens: true,
|
|
69
89
|
files: true,
|
|
90
|
+
// `bim.network` exists only when the graph holds a `network.fetch` grant;
|
|
91
|
+
// every call is still checked against those grants (#5446 review).
|
|
92
|
+
network: networkGrants.some((g) => g.scope === 'network' && g.action === 'fetch'),
|
|
70
93
|
};
|
|
71
94
|
const injected = JSON.stringify({ a: inputs.a ?? null, b: inputs.b ?? null, c: inputs.c ?? null });
|
|
72
95
|
// Every lane shares one sandbox, and QuickJS evaluates a program in the
|
|
@@ -79,12 +102,23 @@ async function evaluate(ctx, inputs, params) {
|
|
|
79
102
|
// `inputs` in scope, and the last expression as the value (`eval` returns
|
|
80
103
|
// its program's completion value, which a function body would not).
|
|
81
104
|
const code = `(function (inputs) { return eval(${JSON.stringify(String(params.code ?? ''))}); })(${injected})`;
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
105
|
+
const entry = sandboxFor(ctx.host.bim, permissions, Number(params.timeoutMs) || 30_000, networkGrants, ctx.host.networkTransport);
|
|
106
|
+
const sandbox = await entry.sandbox;
|
|
107
|
+
const requestsBefore = entry.requests;
|
|
108
|
+
let result;
|
|
109
|
+
try {
|
|
110
|
+
// Plain JavaScript by contract: skipping the TypeScript strip avoids
|
|
111
|
+
// initialising esbuild-wasm, which only works in the browser. It is also
|
|
112
|
+
// what keeps the wrapper above honest — the source reaches `eval`
|
|
113
|
+
// verbatim, not as something a transpiler rewrote.
|
|
114
|
+
result = await sandbox.eval(code, { typescript: false });
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
// In `finally`: a lane that fetched and then threw yields null under
|
|
118
|
+
// lifting, and that null must not be memoised either.
|
|
119
|
+
if (entry.requests !== requestsBefore)
|
|
120
|
+
ctx.markVolatile?.();
|
|
121
|
+
}
|
|
88
122
|
for (const entry of result.logs) {
|
|
89
123
|
const level = entry.level === 'error' ? 'error' : entry.level === 'warn' ? 'warn' : 'info';
|
|
90
124
|
ctx.log(level, entry.args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' '));
|
package/dist/script-node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"script-node.js","sourceRoot":"","sources":["../src/script-node.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D
|
|
1
|
+
{"version":3,"file":"script-node.js","sourceRoot":"","sources":["../src/script-node.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secrets from the host environment (#5167 phase 3.5).
|
|
3
|
+
*
|
|
4
|
+
* A node param references a secret with `{{secret:NAME}}` inside a
|
|
5
|
+
* string value (or a string nested inside a `json`-kind param, e.g. a
|
|
6
|
+
* header map). The grammar itself is recognised by `@ifc-lite/flow`'s
|
|
7
|
+
* `referencedSecrets` (availability needs it too); this module is the one
|
|
8
|
+
* place declared-vs-referenced is cross-checked and `process.env` is read —
|
|
9
|
+
* shared by `packages/cli/src/commands/flow.ts` (`flow run`) and
|
|
10
|
+
* `packages/mcp/src/tools/flow.ts` (`run_flow`), the only two callers
|
|
11
|
+
* allowed to read `process.env` for a flow graph. The viewer never calls
|
|
12
|
+
* this module: `HostFeatures.secrets` is always the empty set there, so
|
|
13
|
+
* a graph referencing a secret shows `unavailable` before it runs (see
|
|
14
|
+
* `availability.ts`), not this module's validation error.
|
|
15
|
+
*
|
|
16
|
+
* Two checks run BEFORE any node executes:
|
|
17
|
+
* 1. every `{{secret:NAME}}` reference must be covered by a declared
|
|
18
|
+
* `secret.read:NAME` capability in the graph — an undeclared
|
|
19
|
+
* reference is a validation error, not a silently-empty string;
|
|
20
|
+
* 2. every declared-and-referenced secret must actually be set in the
|
|
21
|
+
* environment — a referenced-but-unset secret is also a validation
|
|
22
|
+
* error, not an empty string interpolated into a request.
|
|
23
|
+
*
|
|
24
|
+
* Redaction (`buildRedactionMap` / `redactDeep`) is a separate, later
|
|
25
|
+
* step the caller applies at the OUTER boundary — right before writing
|
|
26
|
+
* to stdout, an MCP response, or a log sink — so a secret value that
|
|
27
|
+
* reaches a remote response body (a server echoing back an Authorization
|
|
28
|
+
* header) and comes back into the run's own output is still caught, not
|
|
29
|
+
* just the point where `{{secret:NAME}}` was first substituted. See the
|
|
30
|
+
* module doc trade-offs below on the minimum-length cutoff.
|
|
31
|
+
*/
|
|
32
|
+
import { referencedSecrets, type FlowDocument } from '@ifc-lite/flow';
|
|
33
|
+
export { referencedSecrets };
|
|
34
|
+
export interface SecretValidationError {
|
|
35
|
+
readonly nodeId: string;
|
|
36
|
+
readonly param: string;
|
|
37
|
+
readonly name: string;
|
|
38
|
+
readonly reason: 'undeclared' | 'unset';
|
|
39
|
+
readonly message: string;
|
|
40
|
+
}
|
|
41
|
+
/** Every `secret:NAME` the graph's capabilities declare (from `secret.read:<NAME>` grants). */
|
|
42
|
+
export declare function declaredSecrets(doc: FlowDocument): ReadonlySet<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Validate every `{{secret:NAME}}` reference against the graph's declared
|
|
45
|
+
* capabilities AND the environment, before any node runs. Returns an empty
|
|
46
|
+
* array when the graph is clean (including "no secrets used at all").
|
|
47
|
+
*/
|
|
48
|
+
export declare function validateSecretReferences(doc: FlowDocument, env: Readonly<Record<string, string | undefined>>): SecretValidationError[];
|
|
49
|
+
/** Resolve every declared-and-referenced secret's value from `env`. Call only after `validateSecretReferences` returns no errors. */
|
|
50
|
+
export declare function resolveSecretValues(doc: FlowDocument, env: Readonly<Record<string, string | undefined>>): ReadonlyMap<string, string>;
|
|
51
|
+
/** Return a new document with every `{{secret:NAME}}` reference in node params replaced by its resolved value. The input document is never mutated. */
|
|
52
|
+
export declare function interpolateSecrets(doc: FlowDocument, values: ReadonlyMap<string, string>): FlowDocument;
|
|
53
|
+
/** Minimum secret length worth redacting (see module doc): shorter values risk mass-redacting incidental substrings of ordinary text. */
|
|
54
|
+
export declare const MIN_REDACTED_SECRET_LENGTH = 6;
|
|
55
|
+
/**
|
|
56
|
+
* Reverse map (value → marker) for every resolved secret at least
|
|
57
|
+
* `MIN_REDACTED_SECRET_LENGTH` characters long. A secret shorter than
|
|
58
|
+
* that is a real gap (documented, not silently accepted): a 4-character
|
|
59
|
+
* token could coincide with ordinary text often enough that scrubbing it
|
|
60
|
+
* everywhere would make legitimate output unreadable, so this module
|
|
61
|
+
* accepts that a very short secret is not redactable by substring match
|
|
62
|
+
* at all.
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildRedactionMap(values: ReadonlyMap<string, string>): ReadonlyMap<string, string>;
|
|
65
|
+
/**
|
|
66
|
+
* Replace every occurrence of a redacted secret value with its marker, in ONE
|
|
67
|
+
* pass, trying the longest value first at each position. Sequential
|
|
68
|
+
* replacement let a shorter secret inside a longer one match first
|
|
69
|
+
* (`abcdef` inside `abcdefghi` left `<secret:A>ghi`, exposing B's suffix),
|
|
70
|
+
* and could match inside a marker already written (#5446 review).
|
|
71
|
+
*/
|
|
72
|
+
export declare function redactText(text: string, redaction: ReadonlyMap<string, string>): string;
|
|
73
|
+
/**
|
|
74
|
+
* Walk an arbitrary JSON-ish value (a run summary, a log entry, a thrown
|
|
75
|
+
* error's message) and redact every string found, at any depth — this is
|
|
76
|
+
* what catches a secret that travelled through a response body and back
|
|
77
|
+
* into the run's own output, not just the literal param it was
|
|
78
|
+
* interpolated into.
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* The env names a host may report as available secrets: set, and long
|
|
82
|
+
* enough to be redacted. `resolveSecretValues` refuses a shorter value, so
|
|
83
|
+
* counting it as available made `flow validate` pass a graph the run then
|
|
84
|
+
* refused (#5446 review).
|
|
85
|
+
*/
|
|
86
|
+
export declare function usableSecretNames(env: Readonly<Record<string, string | undefined>>): string[];
|
|
87
|
+
export declare function redactDeep<T>(value: T, redaction: ReadonlyMap<string, string>): T;
|
|
88
|
+
//# sourceMappingURL=secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,iBAAiB,EAAqB,KAAK,YAAY,EAAiB,MAAM,gBAAgB,CAAC;AAExG,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAG7B,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,GAAG,EAAE,YAAY,GAAG,WAAW,CAAC,MAAM,CAAC,CAWtE;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,qBAAqB,EAAE,CAyBtI;AAED,qIAAqI;AACrI,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAkBrI;AAaD,uJAAuJ;AACvJ,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAQvG;AAED,yIAAyI;AACzI,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAWlG;AAMD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAKvF;AAED;;;;;;GAMG;AACH;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,MAAM,EAAE,CAI7F;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAejF"}
|
package/dist/secrets.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* Secrets from the host environment (#5167 phase 3.5).
|
|
6
|
+
*
|
|
7
|
+
* A node param references a secret with `{{secret:NAME}}` inside a
|
|
8
|
+
* string value (or a string nested inside a `json`-kind param, e.g. a
|
|
9
|
+
* header map). The grammar itself is recognised by `@ifc-lite/flow`'s
|
|
10
|
+
* `referencedSecrets` (availability needs it too); this module is the one
|
|
11
|
+
* place declared-vs-referenced is cross-checked and `process.env` is read —
|
|
12
|
+
* shared by `packages/cli/src/commands/flow.ts` (`flow run`) and
|
|
13
|
+
* `packages/mcp/src/tools/flow.ts` (`run_flow`), the only two callers
|
|
14
|
+
* allowed to read `process.env` for a flow graph. The viewer never calls
|
|
15
|
+
* this module: `HostFeatures.secrets` is always the empty set there, so
|
|
16
|
+
* a graph referencing a secret shows `unavailable` before it runs (see
|
|
17
|
+
* `availability.ts`), not this module's validation error.
|
|
18
|
+
*
|
|
19
|
+
* Two checks run BEFORE any node executes:
|
|
20
|
+
* 1. every `{{secret:NAME}}` reference must be covered by a declared
|
|
21
|
+
* `secret.read:NAME` capability in the graph — an undeclared
|
|
22
|
+
* reference is a validation error, not a silently-empty string;
|
|
23
|
+
* 2. every declared-and-referenced secret must actually be set in the
|
|
24
|
+
* environment — a referenced-but-unset secret is also a validation
|
|
25
|
+
* error, not an empty string interpolated into a request.
|
|
26
|
+
*
|
|
27
|
+
* Redaction (`buildRedactionMap` / `redactDeep`) is a separate, later
|
|
28
|
+
* step the caller applies at the OUTER boundary — right before writing
|
|
29
|
+
* to stdout, an MCP response, or a log sink — so a secret value that
|
|
30
|
+
* reaches a remote response body (a server echoing back an Authorization
|
|
31
|
+
* header) and comes back into the run's own output is still caught, not
|
|
32
|
+
* just the point where `{{secret:NAME}}` was first substituted. See the
|
|
33
|
+
* module doc trade-offs below on the minimum-length cutoff.
|
|
34
|
+
*/
|
|
35
|
+
import { referencedSecrets, replaceSecretRefs } from '@ifc-lite/flow';
|
|
36
|
+
export { referencedSecrets };
|
|
37
|
+
import { parseCapabilities } from '@ifc-lite/extensions';
|
|
38
|
+
/** Every `secret:NAME` the graph's capabilities declare (from `secret.read:<NAME>` grants). */
|
|
39
|
+
export function declaredSecrets(doc) {
|
|
40
|
+
const parsed = parseCapabilities(doc.capabilities);
|
|
41
|
+
const names = new Set();
|
|
42
|
+
if (!parsed.ok)
|
|
43
|
+
return names;
|
|
44
|
+
for (const cap of parsed.value) {
|
|
45
|
+
if (cap.scope === 'secret' && cap.action === 'read' && cap.target?.segments.length === 1) {
|
|
46
|
+
const seg = cap.target.segments[0];
|
|
47
|
+
if (seg.kind === 'literal')
|
|
48
|
+
names.add(seg.value);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return names;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Validate every `{{secret:NAME}}` reference against the graph's declared
|
|
55
|
+
* capabilities AND the environment, before any node runs. Returns an empty
|
|
56
|
+
* array when the graph is clean (including "no secrets used at all").
|
|
57
|
+
*/
|
|
58
|
+
export function validateSecretReferences(doc, env) {
|
|
59
|
+
const declared = declaredSecrets(doc);
|
|
60
|
+
const errors = [];
|
|
61
|
+
for (const ref of referencedSecrets(doc)) {
|
|
62
|
+
if (!declared.has(ref.name)) {
|
|
63
|
+
errors.push({
|
|
64
|
+
nodeId: ref.nodeId,
|
|
65
|
+
param: ref.param,
|
|
66
|
+
name: ref.name,
|
|
67
|
+
reason: 'undeclared',
|
|
68
|
+
message: `node "${ref.nodeId}" param "${ref.param}" references {{secret:${ref.name}}}, but the graph does not declare "secret.read:${ref.name}"`,
|
|
69
|
+
});
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (env[ref.name] === undefined) {
|
|
73
|
+
errors.push({
|
|
74
|
+
nodeId: ref.nodeId,
|
|
75
|
+
param: ref.param,
|
|
76
|
+
name: ref.name,
|
|
77
|
+
reason: 'unset',
|
|
78
|
+
message: `node "${ref.nodeId}" param "${ref.param}" references {{secret:${ref.name}}}, which is declared but not set in the environment`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return errors;
|
|
83
|
+
}
|
|
84
|
+
/** Resolve every declared-and-referenced secret's value from `env`. Call only after `validateSecretReferences` returns no errors. */
|
|
85
|
+
export function resolveSecretValues(doc, env) {
|
|
86
|
+
const values = new Map();
|
|
87
|
+
for (const ref of referencedSecrets(doc)) {
|
|
88
|
+
const v = env[ref.name];
|
|
89
|
+
if (v === undefined)
|
|
90
|
+
continue;
|
|
91
|
+
// Redaction is a substring scrub, and a very short value cannot be
|
|
92
|
+
// scrubbed without mangling ordinary output — so rather than resolve a
|
|
93
|
+
// secret that would then travel unredacted, refuse it. The message names
|
|
94
|
+
// the variable, never the value.
|
|
95
|
+
if (v.length < MIN_REDACTED_SECRET_LENGTH) {
|
|
96
|
+
throw new Error(`secret ${ref.name} is shorter than ${MIN_REDACTED_SECRET_LENGTH} characters, so it could not be `
|
|
97
|
+
+ 'redacted from run output; refusing to use it');
|
|
98
|
+
}
|
|
99
|
+
values.set(ref.name, v);
|
|
100
|
+
}
|
|
101
|
+
return values;
|
|
102
|
+
}
|
|
103
|
+
function substitute(value, values) {
|
|
104
|
+
if (typeof value === 'string') {
|
|
105
|
+
return replaceSecretRefs(value, (name, whole) => values.get(name) ?? whole);
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(value))
|
|
108
|
+
return value.map((v) => substitute(v, values));
|
|
109
|
+
if (value && typeof value === 'object') {
|
|
110
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, substitute(v, values)]));
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
/** Return a new document with every `{{secret:NAME}}` reference in node params replaced by its resolved value. The input document is never mutated. */
|
|
115
|
+
export function interpolateSecrets(doc, values) {
|
|
116
|
+
if (values.size === 0)
|
|
117
|
+
return doc;
|
|
118
|
+
const nodes = doc.nodes.map((node) => {
|
|
119
|
+
if (!node.params)
|
|
120
|
+
return node;
|
|
121
|
+
const params = Object.fromEntries(Object.entries(node.params).map(([k, v]) => [k, substitute(v, values)]));
|
|
122
|
+
return { ...node, params };
|
|
123
|
+
});
|
|
124
|
+
return { ...doc, nodes };
|
|
125
|
+
}
|
|
126
|
+
/** Minimum secret length worth redacting (see module doc): shorter values risk mass-redacting incidental substrings of ordinary text. */
|
|
127
|
+
export const MIN_REDACTED_SECRET_LENGTH = 6;
|
|
128
|
+
/**
|
|
129
|
+
* Reverse map (value → marker) for every resolved secret at least
|
|
130
|
+
* `MIN_REDACTED_SECRET_LENGTH` characters long. A secret shorter than
|
|
131
|
+
* that is a real gap (documented, not silently accepted): a 4-character
|
|
132
|
+
* token could coincide with ordinary text often enough that scrubbing it
|
|
133
|
+
* everywhere would make legitimate output unreadable, so this module
|
|
134
|
+
* accepts that a very short secret is not redactable by substring match
|
|
135
|
+
* at all.
|
|
136
|
+
*/
|
|
137
|
+
export function buildRedactionMap(values) {
|
|
138
|
+
// Secrets sharing one value share one marker naming all of them, rather
|
|
139
|
+
// than the last silently taking the value over (#5446 review).
|
|
140
|
+
const names = new Map();
|
|
141
|
+
for (const [name, value] of values) {
|
|
142
|
+
if (value.length < MIN_REDACTED_SECRET_LENGTH)
|
|
143
|
+
continue;
|
|
144
|
+
names.set(value, [...(names.get(value) ?? []), name]);
|
|
145
|
+
}
|
|
146
|
+
// Longest value first, so a secret containing another is matched whole.
|
|
147
|
+
const ordered = [...names].sort(([a], [b]) => b.length - a.length);
|
|
148
|
+
return new Map(ordered.map(([value, owners]) => [value, `<secret:${[...owners].sort().join('|')}>`]));
|
|
149
|
+
}
|
|
150
|
+
function escapeRegExp(text) {
|
|
151
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Replace every occurrence of a redacted secret value with its marker, in ONE
|
|
155
|
+
* pass, trying the longest value first at each position. Sequential
|
|
156
|
+
* replacement let a shorter secret inside a longer one match first
|
|
157
|
+
* (`abcdef` inside `abcdefghi` left `<secret:A>ghi`, exposing B's suffix),
|
|
158
|
+
* and could match inside a marker already written (#5446 review).
|
|
159
|
+
*/
|
|
160
|
+
export function redactText(text, redaction) {
|
|
161
|
+
if (redaction.size === 0)
|
|
162
|
+
return text;
|
|
163
|
+
const values = [...redaction.keys()].sort((a, b) => b.length - a.length);
|
|
164
|
+
const pattern = new RegExp(values.map(escapeRegExp).join('|'), 'g');
|
|
165
|
+
return text.replace(pattern, (match) => redaction.get(match) ?? match);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Walk an arbitrary JSON-ish value (a run summary, a log entry, a thrown
|
|
169
|
+
* error's message) and redact every string found, at any depth — this is
|
|
170
|
+
* what catches a secret that travelled through a response body and back
|
|
171
|
+
* into the run's own output, not just the literal param it was
|
|
172
|
+
* interpolated into.
|
|
173
|
+
*/
|
|
174
|
+
/**
|
|
175
|
+
* The env names a host may report as available secrets: set, and long
|
|
176
|
+
* enough to be redacted. `resolveSecretValues` refuses a shorter value, so
|
|
177
|
+
* counting it as available made `flow validate` pass a graph the run then
|
|
178
|
+
* refused (#5446 review).
|
|
179
|
+
*/
|
|
180
|
+
export function usableSecretNames(env) {
|
|
181
|
+
return Object.entries(env)
|
|
182
|
+
.filter(([, value]) => typeof value === 'string' && value.length >= MIN_REDACTED_SECRET_LENGTH)
|
|
183
|
+
.map(([name]) => name);
|
|
184
|
+
}
|
|
185
|
+
export function redactDeep(value, redaction) {
|
|
186
|
+
if (redaction.size === 0)
|
|
187
|
+
return value;
|
|
188
|
+
if (typeof value === 'string')
|
|
189
|
+
return redactText(value, redaction);
|
|
190
|
+
if (Array.isArray(value))
|
|
191
|
+
return value.map((v) => redactDeep(v, redaction));
|
|
192
|
+
// Keys too: a script can return an object keyed by a secret, and
|
|
193
|
+
// `core.groupBy` can group by one (#5446 review).
|
|
194
|
+
if (value instanceof Map) {
|
|
195
|
+
return new Map([...value.entries()].map(([k, v]) => [redactDeep(k, redaction), redactDeep(v, redaction)]));
|
|
196
|
+
}
|
|
197
|
+
if (value && typeof value === 'object') {
|
|
198
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [redactText(k, redaction), redactDeep(v, redaction)]));
|
|
199
|
+
}
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Speckle wire protocol, spoken ONLY through `coreNetworkRequest` (the
|
|
3
|
+
* gated, https-only, exact-host-grant request function every flow node
|
|
4
|
+
* uses). Nothing here calls `fetch`: the transport comes from the host, and
|
|
5
|
+
* the grant check runs before any byte moves.
|
|
6
|
+
*
|
|
7
|
+
* Three requests, the same three Speckle's own object loader makes:
|
|
8
|
+
*
|
|
9
|
+
* POST /graphql version → `referencedObject` (root object id)
|
|
10
|
+
* GET /objects/<project>/<object>/single the root object alone
|
|
11
|
+
* POST /api/getobjects/<project> a batch of objects by id, one `id\tjson` per line
|
|
12
|
+
*
|
|
13
|
+
* The walk is lazy and iterative: a round fetches only the ids the previous
|
|
14
|
+
* round referenced and has not yet seen, so display meshes and their data
|
|
15
|
+
* chunks (the bulk of any commit) are never downloaded — `speckle.receive`
|
|
16
|
+
* does not write them (see `mapping.ts`). A visited set stops cycles and
|
|
17
|
+
* revisits, and `maxObjects` bounds the total, failing loudly when reached.
|
|
18
|
+
*/
|
|
19
|
+
import { type FetchTransport } from '@ifc-lite/sandbox';
|
|
20
|
+
import type { Capability } from '@ifc-lite/extensions';
|
|
21
|
+
import type { SpeckleTarget } from './url.js';
|
|
22
|
+
export type SpeckleObject = Readonly<Record<string, unknown>> & {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
};
|
|
25
|
+
export interface SpeckleClientOptions {
|
|
26
|
+
readonly grants: readonly Capability[];
|
|
27
|
+
readonly transport?: FetchTransport;
|
|
28
|
+
/** Personal access token; sent as `Authorization: Bearer`. Empty = anonymous (public projects). */
|
|
29
|
+
readonly token?: string;
|
|
30
|
+
readonly timeoutMs: number;
|
|
31
|
+
/** Per-response byte cap. A response that hits it fails the receive: a truncated batch is missing objects. */
|
|
32
|
+
readonly maxBytes: number;
|
|
33
|
+
/** Upper bound on the number of objects fetched. */
|
|
34
|
+
readonly maxObjects: number;
|
|
35
|
+
readonly signal?: AbortSignal;
|
|
36
|
+
}
|
|
37
|
+
/** Keys whose values are display geometry: never followed, never fetched. */
|
|
38
|
+
export declare const DISPLAY_KEYS: ReadonlySet<string>;
|
|
39
|
+
/** The root object id a target points at, and the version it came from (if any). */
|
|
40
|
+
export declare function resolveRoot(opts: SpeckleClientOptions, target: SpeckleTarget): Promise<{
|
|
41
|
+
objectId: string;
|
|
42
|
+
versionId?: string;
|
|
43
|
+
}>;
|
|
44
|
+
/** Every `referencedId` in an object's own JSON tree, except under display keys. */
|
|
45
|
+
export declare function referencedIds(value: unknown, out?: Set<string>): Set<string>;
|
|
46
|
+
/**
|
|
47
|
+
* Fetch the root object and everything it references (display geometry
|
|
48
|
+
* excepted), keyed by id. Throws when an id the graph references is not
|
|
49
|
+
* returned by the server, or when `maxObjects` is reached.
|
|
50
|
+
*/
|
|
51
|
+
export declare function fetchObjectGraph(opts: SpeckleClientOptions, target: SpeckleTarget, rootId: string): Promise<Map<string, SpeckleObject>>;
|
|
52
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/speckle/client.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAA0C,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAChG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAExF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC;IACpC,mGAAmG;IACnG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,8GAA8G;IAC9G,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,6EAA6E;AAC7E,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,MAAM,CAA6E,CAAC;AA4C3H,oFAAoF;AACpF,wBAAsB,WAAW,CAAC,IAAI,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAwBtI;AAOD,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,GAAE,GAAG,CAAC,MAAM,CAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAgBvF;AA0BD;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CA0B7I"}
|