datagrok-tools 6.7.7 → 6.7.9

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.
@@ -3,13 +3,14 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.NodeUsersDataSource = exports.NodeTablesDataSource = exports.NodeSharesDataSource = exports.NodePackagesDataSource = exports.NodeHttpDataSource = exports.NodeGroupsDataSource = exports.NodeFuncsDataSource = exports.NodeFilesDataSource = exports.NodeDomainsDataSource = exports.NodeDapi = exports.NodeConnectionsDataSource = exports.NodeApiClient = exports.InternalDataSource = void 0;
6
+ exports.UUID_RE = exports.NodeUsersDataSource = exports.NodeTablesDataSource = exports.NodeSharesDataSource = exports.NodePackagesDataSource = exports.NodeHttpDataSource = exports.NodeGroupsDataSource = exports.NodeFuncsDataSource = exports.NodeFilesDataSource = exports.NodeDomainsDataSource = exports.NodeDapi = exports.NodeConnectionsDataSource = exports.NodeApiClient = exports.InternalDataSource = void 0;
7
7
  exports.apiPath = apiPath;
8
8
  exports.buildQuery = buildQuery;
9
9
  exports.ensureBodyId = ensureBodyId;
10
10
  exports.mapPositionalParams = mapPositionalParams;
11
11
  exports.parseDomainAddress = parseDomainAddress;
12
12
  exports.throwIfApiError = throwIfApiError;
13
+ exports.tryParseJson = tryParseJson;
13
14
  var _crypto = require("crypto");
14
15
  var _keypair = require("./keypair");
15
16
  /// Docs: [Grok Dapi](/docs/plans/grok-dapi/)
@@ -142,7 +143,7 @@ class NodeApiClient {
142
143
  return null;
143
144
  }
144
145
  const ct = res.headers.get('content-type') ?? '';
145
- return throwIfApiError(ct.includes('application/json') ? await res.json() : await res.text());
146
+ return throwIfApiError(ct.includes('application/json') ? await res.json() : await res.text(), res.headers);
146
147
  }
147
148
  get(path) {
148
149
  return this.request('GET', path);
@@ -170,7 +171,7 @@ class NodeApiClient {
170
171
  }, false, BYTES_TIMEOUT_MS);
171
172
  if (!res.ok) await throwHttpError(res);
172
173
  const ct = res.headers.get('content-type') ?? '';
173
- return throwIfApiError(ct.includes('application/json') ? await res.json() : await res.text());
174
+ return throwIfApiError(ct.includes('application/json') ? await res.json() : await res.text(), res.headers);
174
175
  }
175
176
 
176
177
  /** POST a JSON body and read the response as raw bytes (d42 query results). */
@@ -236,13 +237,15 @@ async function throwHttpError(res) {
236
237
  * a JSON string when the handler returned text. Every response passes through here so a
237
238
  * missing entity, a rejected save or an unknown function throws instead of printing as data.
238
239
  */
239
- function throwIfApiError(payload) {
240
+ /** A user error answers HTTP 200 with the ApiError in the body and its status in the `api-error-code` header. */
241
+ function throwIfApiError(payload, headers) {
240
242
  const parsed = typeof payload === 'string' ? tryParseJson(payload) : payload;
241
243
  if (parsed?.['#type'] === 'ApiError') {
244
+ const code = headers?.get('api-error-code');
242
245
  const err = {
243
246
  error: parsed.message ?? 'Request failed',
244
247
  source: 'Server',
245
- errorCode: parsed.errorCode,
248
+ errorCode: parsed.errorCode ?? (code ? Number(code) : undefined),
246
249
  stackTrace: parsed.stackTrace,
247
250
  body: parsed
248
251
  };
@@ -388,7 +391,7 @@ class InternalDataSource {
388
391
  }
389
392
  }
390
393
  exports.InternalDataSource = InternalDataSource;
391
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
394
+ const UUID_RE = exports.UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
392
395
  class NodeGroupsDataSource extends NodeHttpDataSource {
393
396
  constructor(client) {
394
397
  super(client, 'groups', '/groups');
@@ -936,6 +939,10 @@ function parseDomainAddress(s, opts = {}) {
936
939
  if (opts.table === false && address.table) throw new Error(`'${str}' names a table; this command needs a schema`);
937
940
  return address;
938
941
  }
942
+ /** Every literal a `/domains/{schema}/{table}/{id}` route registers at the id position, the dot
943
+ * segments a proxy folds away, and the u2 draft sentinel — the Dart `DomainRowKey.reserved` list. */
944
+ const RESERVED_ROW_KEYS = ['access', 'aggregate', 'audit', 'batch', 'delete', 'facets', 'filters', 'query', 'update', 'version', 'watch', 'new', '.', '..'];
945
+
939
946
  /**
940
947
  * Client for entity-mapped domain tables (`/domains/...`, DomainsRouter). Rows are plain
941
948
  * JSON objects; the server validates, permission-checks and audits every write. Errors
@@ -950,6 +957,13 @@ class NodeDomainsDataSource {
950
957
  return `/domains/${encodeURIComponent(schema)}/${encodeURIComponent(table)}`;
951
958
  }
952
959
 
960
+ /** The row-id segment as the Dart `DomainRowKey` spells it (grok_shared domain_manifest_rules.dart):
961
+ * escaped, then percent-encoded once more so an external key's own `%2C` survives the proxy. */
962
+ row(schema, table, id) {
963
+ const escaped = RESERVED_ROW_KEYS.includes(id) || id.startsWith('~') ? `~${id}` : id;
964
+ return `${this.rows(schema, table)}/${encodeURIComponent(escaped)}`;
965
+ }
966
+
953
967
  /** Registered schemas with their tables (`GET /domains/schemas`); [text] is a smart filter. */
954
968
  schemas(text) {
955
969
  return this.client.get(`/domains/schemas${buildQuery({
@@ -1001,7 +1015,7 @@ class NodeDomainsDataSource {
1001
1015
  })}`);
1002
1016
  }
1003
1017
  rowAudit(schema, table, id) {
1004
- return this.client.get(`${this.rows(schema, table)}/${encodeURIComponent(id)}/audit`);
1018
+ return this.client.get(`${this.row(schema, table, id)}/audit`);
1005
1019
  }
1006
1020
  grants(entityId) {
1007
1021
  return this.client.get(`/domains/grants/${encodeURIComponent(entityId)}`);
@@ -1053,7 +1067,7 @@ class NodeDomainsDataSource {
1053
1067
  /** One row, or null when it does not exist or is not visible (the server answers 404). */
1054
1068
  async getRow(schema, table, id) {
1055
1069
  try {
1056
- return await this.client.get(`${this.rows(schema, table)}/${encodeURIComponent(id)}`);
1070
+ return await this.client.get(this.row(schema, table, id));
1057
1071
  } catch (err) {
1058
1072
  if (err?.apiError?.errorCode === 404) return null;
1059
1073
  throw err;
@@ -1073,10 +1087,10 @@ class NodeDomainsDataSource {
1073
1087
  values
1074
1088
  };
1075
1089
  if (version !== undefined) body.version = version;
1076
- return this.client.request('PATCH', `${this.rows(schema, table)}/${encodeURIComponent(id)}`, body);
1090
+ return this.client.request('PATCH', this.row(schema, table, id), body);
1077
1091
  }
1078
1092
  deleteRow(schema, table, id) {
1079
- return this.client.del(`${this.rows(schema, table)}/${encodeURIComponent(id)}`);
1093
+ return this.client.del(this.row(schema, table, id));
1080
1094
  }
1081
1095
 
1082
1096
  /** Soft-deletes up to [limit] (≤1000) matching rows in one transaction; `{deleted, hasMore}`. */
@@ -1138,6 +1152,9 @@ class NodeDapi {
1138
1152
  get reports() {
1139
1153
  return this.internal('/reports');
1140
1154
  }
1155
+ get problems() {
1156
+ return this.internal('/problems');
1157
+ }
1141
1158
  get files() {
1142
1159
  return new NodeFilesDataSource(this.client);
1143
1160
  }
@@ -16,6 +16,36 @@
16
16
  "type": "string",
17
17
  "description": "Manifest version (semver recommended). Recorded in the registry on deploy."
18
18
  },
19
+ "storage": {
20
+ "type": "object",
21
+ "description": "Where the rows live. Absent = domain: the platform creates and owns the tables. external: the tables already exist in the database of the named platform connection and are read through it (no row security, soft delete, audit, relations, constraints, property schemas or extensions; read-only unless 'writable'); every table then declares its remote primary key as businessKey, and 'table'/'column' carry the remote names when they differ from the declared ones.",
22
+ "required": ["kind"],
23
+ "additionalProperties": false,
24
+ "properties": {
25
+ "kind": {"enum": ["domain", "external"]},
26
+ "connection": {
27
+ "type": "string",
28
+ "minLength": 1,
29
+ "description": "external: the qualified name of the platform connection to read through ('Package:Connection'); required."
30
+ },
31
+ "schema": {
32
+ "$ref": "#/definitions/remoteIdentifier",
33
+ "description": "external: the schema inside the remote database (e.g. 'public', 'dbo'); required."
34
+ },
35
+ "catalog": {
36
+ "$ref": "#/definitions/remoteIdentifier",
37
+ "description": "external: the catalog/database, for targets that have one."
38
+ },
39
+ "writable": {
40
+ "type": "boolean",
41
+ "default": false,
42
+ "description": "external: rows may be inserted, patched and deleted through the connection (one remote transaction per request, keyed by businessKey; on ClickHouse, which commits every statement, a request is one multi-row INSERT and patch/delete/upsert are refused). The publisher must hold Query, AddRows, ChangeValues and RemoveRows on the connection; a table opts out with its own 'writable': false."
43
+ }
44
+ },
45
+ "if": {"properties": {"kind": {"const": "external"}}},
46
+ "then": {"required": ["connection", "schema"]},
47
+ "else": {"properties": {"connection": false, "schema": false, "catalog": false, "writable": false}}
48
+ },
19
49
  "extensible": {
20
50
  "type": "object",
21
51
  "description": "Opt-in for user extensions of this schema. Users holding the Extend permission on the schema entity may then add their own tables; per-table column extension is opted in with the table's own 'extensible' flag. User objects are retained across plugin upgrades and never appear in generated typed clients.",
@@ -64,11 +94,26 @@
64
94
  {"not": {"type": "string", "pattern": "^x_"}}
65
95
  ]
66
96
  },
97
+ "remoteIdentifier": {
98
+ "description": "A name inside the remote database, verbatim (spaces allowed, the connector quotes it); it may not contain brackets, quotes, backslashes or dots.",
99
+ "type": "string",
100
+ "minLength": 1,
101
+ "maxLength": 256,
102
+ "pattern": "^[^\\[\\]\"'`\\\\.]+$"
103
+ },
67
104
  "table": {
68
105
  "type": "object",
69
106
  "required": ["columns"],
70
107
  "additionalProperties": false,
71
108
  "properties": {
109
+ "table": {
110
+ "$ref": "#/definitions/remoteIdentifier",
111
+ "description": "External storage only: the remote table's name when it differs from the declared one (e.g. 'Order Details')."
112
+ },
113
+ "writable": {
114
+ "type": "boolean",
115
+ "description": "Writable external storage only: false keeps this table read-only while the rest of the binding accepts writes."
116
+ },
72
117
  "securityMode": {
73
118
  "enum": ["table", "master", "row"],
74
119
  "default": "table",
@@ -263,7 +308,11 @@
263
308
  "properties": {
264
309
  "type": {
265
310
  "enum": ["string", "int", "float", "bool", "datetime", "string_list", "ref", "file", "json", "user", "group"],
266
- "description": "Property type; ref = FK to another table in this manifest; user/group = aliases of ref Core.users / ref Core.groups (semantic type User / Group); file = a file://<connection>/<path> string into platform file storage (semantic type File); json = a jsonb-backed opaque object (filters, sorts, facets and aggregates refuse it)."
311
+ "description": "Property type; ref = FK to another table in this manifest; user/group = aliases of ref Core.users / ref Core.groups (semantic type User / Group); file = a file://<connection>/<path> string into platform file storage (semantic type File); json = a jsonb-backed opaque object (filters, sorts, facets and aggregates refuse it). An external storage admits string, int, float, bool, datetime and same-manifest ref only."
312
+ },
313
+ "column": {
314
+ "$ref": "#/definitions/remoteIdentifier",
315
+ "description": "External storage only: the remote column's name when it differs from the declared one (e.g. 'OrderID')."
267
316
  },
268
317
  "required": {"type": "boolean", "default": false},
269
318
  "unique": {
@@ -1,4 +1,4 @@
1
- import {domains} from '@datagrok-libraries/u2/src/dg/index.js';
1
+ import {domains} from '@datagrok-libraries/ems/src/dg';
2
2
 
3
3
  // the skins of everything the u2 domain stack renders
4
- import '@datagrok-libraries/u2/src/dg/domain/styles.js';
4
+ import '@datagrok-libraries/ems/src/dg/styles.js';
package/package.json CHANGED
@@ -4,9 +4,12 @@
4
4
  "type": "git",
5
5
  "url": "https://github.com/datagrok-ai/public.git"
6
6
  },
7
- "version": "6.7.7",
7
+ "version": "6.7.9",
8
8
  "description": "Utility to upload and publish packages to Datagrok",
9
9
  "homepage": "https://github.com/datagrok-ai/public/tree/master/tools#readme",
10
+ "engines": {
11
+ "node": ">=22.12.0"
12
+ },
10
13
  "dependencies": {
11
14
  "@babel/parser": "^7.29.7",
12
15
  "@babel/runtime": "^7.29.7",
@@ -24,7 +27,7 @@
24
27
  "minimist": "^1.2.8",
25
28
  "node-fetch": "^2.7.0",
26
29
  "papaparse": "^5.5.3",
27
- "puppeteer": "^24.15.0",
30
+ "puppeteer": "^25.14.0",
28
31
  "ts-morph": "^28.0.0",
29
32
  "@babel/generator": "^7.29.0",
30
33
  "datagrok-api": "^1.27.12"
@@ -90,7 +93,7 @@
90
93
  "update:ivp-parser": "esbuild plugins/ivp-parser.entry.mjs --bundle --format=cjs --platform=node --alias:diff-grok=../libraries/compute-utils/node_modules/diff-grok --outfile=plugins/ivp-parser.bundle.cjs",
91
94
  "debug-source-map": "node build.js --source-maps",
92
95
  "test": "vitest run --project unit",
93
- "test:server": "vitest run --project unit bin/__tests__/node-dapi bin/__tests__/server bin/__tests__/migrate bin/__tests__/keypair",
96
+ "test:server": "vitest run --project unit bin/__tests__/node-dapi bin/__tests__/server bin/__tests__/migrate bin/__tests__/keypair bin/__tests__/grok-cli",
94
97
  "test:watch": "vitest --project unit",
95
98
  "test:integration": "vitest run --project integration",
96
99
  "test:all": "vitest run"