@dbx-tools/genie 0.6.61 → 0.6.63

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 CHANGED
@@ -142,6 +142,14 @@ const questions = space.genieSampleQuestions(genieSpace);
142
142
  metadata by default. `space.genieSampleQuestions()` extracts curated starter
143
143
  questions and returns `[]` when none are configured.
144
144
 
145
+ The serialized blob needs `Can Edit` on the space, while `Can Run` is enough for
146
+ title and description. When the workspace rejects the serialized request for
147
+ lack of permission, `getGenieSpace()` retries once without it and logs
148
+ `serialized-space:forbidden`, so an app service principal holding only `Can Run`
149
+ still resolves the space and simply reports no starter questions. Every other
150
+ failure - a cancelled request, a missing space, a retry that also fails - is
151
+ thrown.
152
+
145
153
  ## Options
146
154
 
147
155
  `chat.GenieChatOptions` is shared by both drivers:
@@ -10,6 +10,14 @@
10
10
  * workspace client's raw `apiClient` since the typed request shape has no flag
11
11
  * for it.
12
12
  *
13
+ * The serialized blob is also more privileged than the listing surface: the
14
+ * workspace API requires `Can Edit` on the space to return it, while `Can Run`
15
+ * is enough for title / description. A caller that only holds `Can Run` - an
16
+ * app service principal granted just enough to ask questions is the common case
17
+ * - would otherwise lose the whole space lookup to a `PERMISSION_DENIED` it
18
+ * cannot act on, so the fetch degrades to the unserialized request instead of
19
+ * failing (see {@link getGenieSpace}).
20
+ *
13
21
  * @module
14
22
  */
15
23
  import { WorkspaceClient } from "@databricks/sdk-experimental";
@@ -27,6 +35,10 @@ export interface GetGenieSpaceOptions {
27
35
  * Request the `serialized_space` blob (catalogs, tables, sample questions,
28
36
  * prompts). Defaults to `true` - the only reason to skip it is when the
29
37
  * caller just needs title / description and wants the smaller payload.
38
+ *
39
+ * Requesting it is best-effort: the blob needs `Can Edit` on the space, so a
40
+ * caller without it gets the unserialized space back rather than an error,
41
+ * and {@link genieSampleQuestions} then reports no suggestions.
30
42
  */
31
43
  serialized?: boolean;
32
44
  /**
@@ -41,6 +53,13 @@ export interface GetGenieSpaceOptions {
41
53
  * through the raw `apiClient`, then validates the response against
42
54
  * {@link GenieSpaceSchema} (unknown fields like `etag` /
43
55
  * `parent_path` are stripped).
56
+ *
57
+ * When the serialized request is rejected for lack of permission (`403`, or a
58
+ * `PERMISSION_DENIED` / `Can Edit` message - the workspace API gates the blob
59
+ * behind `Can Edit`), it retries once without the flag so the caller still gets
60
+ * the space. Any other failure, and a retry that fails too, is rethrown: a
61
+ * cancelled request or a missing space must not look like an unserialized
62
+ * space.
44
63
  */
45
64
  export declare function getGenieSpace(spaceId: string, options?: GetGenieSpaceOptions): Promise<GenieSpace>;
46
65
  /**
package/lib/src/space.js CHANGED
@@ -10,11 +10,19 @@
10
10
  * workspace client's raw `apiClient` since the typed request shape has no flag
11
11
  * for it.
12
12
  *
13
+ * The serialized blob is also more privileged than the listing surface: the
14
+ * workspace API requires `Can Edit` on the space to return it, while `Can Run`
15
+ * is enough for title / description. A caller that only holds `Can Run` - an
16
+ * app service principal granted just enough to ask questions is the common case
17
+ * - would otherwise lose the whole space lookup to a `PERMISSION_DENIED` it
18
+ * cannot act on, so the fetch degrades to the unserialized request instead of
19
+ * failing (see {@link getGenieSpace}).
20
+ *
13
21
  * @module
14
22
  */
15
23
  import { WorkspaceClient } from "@databricks/sdk-experimental";
16
24
  import { databricks } from "@dbx-tools/appkit";
17
- import { json, log, object, string } from "@dbx-tools/shared-core";
25
+ import { error, json, log, object, string } from "@dbx-tools/shared-core";
18
26
  import { genieModel } from "@dbx-tools/shared-genie";
19
27
  const logger = log.logger("genie/space");
20
28
  /**
@@ -23,20 +31,52 @@ const logger = log.logger("genie/space");
23
31
  * through the raw `apiClient`, then validates the response against
24
32
  * {@link GenieSpaceSchema} (unknown fields like `etag` /
25
33
  * `parent_path` are stripped).
34
+ *
35
+ * When the serialized request is rejected for lack of permission (`403`, or a
36
+ * `PERMISSION_DENIED` / `Can Edit` message - the workspace API gates the blob
37
+ * behind `Can Edit`), it retries once without the flag so the caller still gets
38
+ * the space. Any other failure, and a retry that fails too, is rethrown: a
39
+ * cancelled request or a missing space must not look like an unserialized
40
+ * space.
26
41
  */
27
42
  export async function getGenieSpace(spaceId, options) {
28
43
  const client = options?.workspaceClient ?? new WorkspaceClient({});
29
44
  const serialized = options?.serialized !== false;
30
45
  const ctx = options?.context ? databricks.toContext(options.context) : undefined;
31
- const raw = await client.apiClient.request({
46
+ const request = (includeSerialized) => client.apiClient.request({
32
47
  path: `/api/2.0/genie/spaces/${encodeURIComponent(spaceId)}`,
33
48
  method: "GET",
34
- query: serialized ? { include_serialized_space: true } : {},
49
+ query: includeSerialized ? { include_serialized_space: true } : {},
35
50
  headers: new Headers(),
36
51
  raw: false,
37
52
  }, ctx);
53
+ let raw;
54
+ try {
55
+ raw = await request(serialized);
56
+ }
57
+ catch (err) {
58
+ if (!serialized || !isSerializedSpaceForbidden(err))
59
+ throw err;
60
+ logger.debug("serialized-space:forbidden", {
61
+ spaceId,
62
+ error: error.errorMessage(err),
63
+ });
64
+ raw = await request(false);
65
+ }
38
66
  return genieModel.GenieSpaceSchema.parse(raw);
39
67
  }
68
+ /**
69
+ * True when a failed space request was rejected for lack of permission, the one
70
+ * failure the unserialized retry can recover from. Matches on `403` and on the
71
+ * `PERMISSION_DENIED` / `Can Edit` wording the workspace API returns, since the
72
+ * SDK surfaces the error code without always carrying a status.
73
+ */
74
+ function isSerializedSpaceForbidden(err) {
75
+ const context = error.errorContext(err);
76
+ return (context.hasStatusCode(403) ||
77
+ context.hasMessage("permission denied") ||
78
+ context.hasMessage("can edit"));
79
+ }
40
80
  /**
41
81
  * Extract the curated starter questions an author configured on a Genie space.
42
82
  * Reads `serialized_space -> config.sample_questions[*].question`. Returns `[]`
@@ -66,4 +106,4 @@ export function genieSampleQuestions(space) {
66
106
  .distinct(),
67
107
  ];
68
108
  }
69
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BhY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc3BhY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUVILE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUMvRCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDL0MsT0FBTyxFQUFFLElBQUksRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ25FLE9BQU8sRUFBRSxVQUFVLEVBQW1CLE1BQU0seUJBQXlCLENBQUM7QUFFdEUsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxhQUFhLENBQUMsQ0FBQztBQXVCekM7Ozs7OztHQU1HO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxhQUFhLENBQ2pDLE9BQWUsRUFDZixPQUE4QjtJQUU5QixNQUFNLE1BQU0sR0FBRyxPQUFPLEVBQUUsZUFBZSxJQUFJLElBQUksZUFBZSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ25FLE1BQU0sVUFBVSxHQUFHLE9BQU8sRUFBRSxVQUFVLEtBQUssS0FBSyxDQUFDO0lBQ2pELE1BQU0sR0FBRyxHQUFHLE9BQU8sRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7SUFDakYsTUFBTSxHQUFHLEdBQUcsTUFBTSxNQUFNLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FDeEM7UUFDRSxJQUFJLEVBQUUseUJBQXlCLGtCQUFrQixDQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQzVELE1BQU0sRUFBRSxLQUFLO1FBQ2IsS0FBSyxFQUFFLFVBQVUsQ0FBQyxDQUFDLENBQUMsRUFBRSx3QkFBd0IsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRTtRQUMzRCxPQUFPLEVBQUUsSUFBSSxPQUFPLEVBQUU7UUFDdEIsR0FBRyxFQUFFLEtBQUs7S0FDWCxFQUNELEdBQUcsQ0FDSixDQUFDO0lBQ0YsT0FBTyxVQUFVLENBQUMsZ0JBQWdCLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0FBQ2hELENBQUM7QUFZRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxVQUFVLG9CQUFvQixDQUFDLEtBQWlCO0lBQ3BELE1BQU0sVUFBVSxHQUFHLEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQztJQUMxQyxJQUFJLENBQUMsVUFBVTtRQUFFLE9BQU8sRUFBRSxDQUFDO0lBQzNCLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDdEMsSUFBSSxNQUFNLEtBQUssU0FBUyxFQUFFLENBQUM7UUFDekIsTUFBTSxDQUFDLElBQUksQ0FBQyw4QkFBOEIsRUFBRSxFQUFFLE9BQU8sRUFBRSxLQUFLLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztRQUN6RSxPQUFPLEVBQUUsQ0FBQztJQUNaLENBQUM7SUFDRCxNQUFNLGVBQWUsR0FBSSxNQUE2RCxFQUFFLE1BQU07UUFDNUYsRUFBRSxnQkFBZ0IsQ0FBQztJQUNyQixJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxlQUFlLENBQUM7UUFBRSxPQUFPLEVBQUUsQ0FBQztJQUUvQyxPQUFPO1FBQ0wsR0FBRyxNQUFNO2FBQ04sUUFBUSxDQUFDLGVBQTZDLENBQUM7YUFDdkQsR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUMsYUFBYSxDQUFDLEtBQUssRUFBRSxRQUFRLENBQUMsQ0FBQzthQUNyRCxPQUFPLEVBQUU7YUFDVCxRQUFRLEVBQUU7S0FDZCxDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuaWUgc3BhY2UgbWV0YWRhdGEgaGVscGVycy5cbiAqXG4gKiBGZXRjaGVzIGEgR2VuaWUgc3BhY2UncyBkZWZpbml0aW9uIChpbmNsdWRpbmcgdGhlIG9wdC1pbiBgc2VyaWFsaXplZF9zcGFjZWBcbiAqIGJsb2IpIGFuZCBleHRyYWN0cyB0aGUgY3VyYXRlZCBzdGFydGVyIHF1ZXN0aW9ucyBhbiBhdXRob3IgY29uZmlndXJlZCBvbiB0aGVcbiAqIHNwYWNlLiBUaGUgdHlwZWQgU0RLIGBjbGllbnQuZ2VuaWUuZ2V0U3BhY2VgIG9ubHkgcmV0dXJucyB0aGVcbiAqIGRpcmVjdG9yeS1saXN0aW5nIHN1cmZhY2UgKGB0aXRsZWAgLyBgZGVzY3JpcHRpb25gIC8gYHdhcmVob3VzZV9pZGApOyB0aGVcbiAqIHNhbXBsZSBxdWVzdGlvbnMgbGl2ZSBpbnNpZGUgYHNlcmlhbGl6ZWRfc3BhY2VgLCB3aGljaCB0aGUgUkVTVCBBUEkgcmV0dXJuc1xuICogb25seSB3aGVuIGBpbmNsdWRlX3NlcmlhbGl6ZWRfc3BhY2U9dHJ1ZWAuIFdlIGhpdCB0aGF0IGVuZHBvaW50IHRocm91Z2ggdGhlXG4gKiB3b3Jrc3BhY2UgY2xpZW50J3MgcmF3IGBhcGlDbGllbnRgIHNpbmNlIHRoZSB0eXBlZCByZXF1ZXN0IHNoYXBlIGhhcyBubyBmbGFnXG4gKiBmb3IgaXQuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IFdvcmtzcGFjZUNsaWVudCB9IGZyb20gXCJAZGF0YWJyaWNrcy9zZGstZXhwZXJpbWVudGFsXCI7XG5pbXBvcnQgeyBkYXRhYnJpY2tzIH0gZnJvbSBcIkBkYngtdG9vbHMvYXBwa2l0XCI7XG5pbXBvcnQgeyBqc29uLCBsb2csIG9iamVjdCwgc3RyaW5nIH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWNvcmVcIjtcbmltcG9ydCB7IGdlbmllTW9kZWwsIHR5cGUgR2VuaWVTcGFjZSB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1nZW5pZVwiO1xuXG5jb25zdCBsb2dnZXIgPSBsb2cubG9nZ2VyKFwiZ2VuaWUvc3BhY2VcIik7XG5cbi8qKiBPcHRpb25zIGZvciB7QGxpbmsgZ2V0R2VuaWVTcGFjZX0uICovXG5leHBvcnQgaW50ZXJmYWNlIEdldEdlbmllU3BhY2VPcHRpb25zIHtcbiAgLyoqXG4gICAqIEV4cGxpY2l0IGBXb3Jrc3BhY2VDbGllbnRgLiBEZWZhdWx0cyB0byBhIGZyZXNoIGBuZXcgV29ya3NwYWNlQ2xpZW50KHt9KWBcbiAgICogKGVudi12YXIgYXV0aCkuIFNlcnZlciBjYWxsZXJzIHNob3VsZCBwYXNzIHRoZWlyIE9CTy1zY29wZWQgY2xpZW50IHNvIHRoZVxuICAgKiBsb29rdXAgcnVucyBhcyB0aGUgdXNlci5cbiAgICovXG4gIHdvcmtzcGFjZUNsaWVudD86IFdvcmtzcGFjZUNsaWVudDtcbiAgLyoqXG4gICAqIFJlcXVlc3QgdGhlIGBzZXJpYWxpemVkX3NwYWNlYCBibG9iIChjYXRhbG9ncywgdGFibGVzLCBzYW1wbGUgcXVlc3Rpb25zLFxuICAgKiBwcm9tcHRzKS4gRGVmYXVsdHMgdG8gYHRydWVgIC0gdGhlIG9ubHkgcmVhc29uIHRvIHNraXAgaXQgaXMgd2hlbiB0aGVcbiAgICogY2FsbGVyIGp1c3QgbmVlZHMgdGl0bGUgLyBkZXNjcmlwdGlvbiBhbmQgd2FudHMgdGhlIHNtYWxsZXIgcGF5bG9hZC5cbiAgICovXG4gIHNlcmlhbGl6ZWQ/OiBib29sZWFuO1xuICAvKipcbiAgICogRXh0ZXJuYWwgY2FuY2VsbGF0aW9uLiBBY2NlcHRzIGEgV0hBVFdHIGBBYm9ydFNpZ25hbGAgb3IgYSBmdWxseS1idWlsdCBTREtcbiAgICogYENvbnRleHRgIChzZWUgYGRhdGFicmlja3MuQ29udGV4dExpa2VgKS5cbiAgICovXG4gIGNvbnRleHQ/OiBkYXRhYnJpY2tzLkNvbnRleHRMaWtlO1xufVxuXG4vKipcbiAqIEZldGNoIGEgR2VuaWUgc3BhY2UgYnkgaWQsIG9wdGlvbmFsbHkgaW5jbHVkaW5nIGl0cyBzZXJpYWxpemVkIGRlZmluaXRpb24uXG4gKiBIaXRzIGBHRVQgL2FwaS8yLjAvZ2VuaWUvc3BhY2VzLzxpZD5gIHdpdGggYGluY2x1ZGVfc2VyaWFsaXplZF9zcGFjZT10cnVlYFxuICogdGhyb3VnaCB0aGUgcmF3IGBhcGlDbGllbnRgLCB0aGVuIHZhbGlkYXRlcyB0aGUgcmVzcG9uc2UgYWdhaW5zdFxuICoge0BsaW5rIEdlbmllU3BhY2VTY2hlbWF9ICh1bmtub3duIGZpZWxkcyBsaWtlIGBldGFnYCAvXG4gKiBgcGFyZW50X3BhdGhgIGFyZSBzdHJpcHBlZCkuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXRHZW5pZVNwYWNlKFxuICBzcGFjZUlkOiBzdHJpbmcsXG4gIG9wdGlvbnM/OiBHZXRHZW5pZVNwYWNlT3B0aW9ucyxcbik6IFByb21pc2U8R2VuaWVTcGFjZT4ge1xuICBjb25zdCBjbGllbnQgPSBvcHRpb25zPy53b3Jrc3BhY2VDbGllbnQgPz8gbmV3IFdvcmtzcGFjZUNsaWVudCh7fSk7XG4gIGNvbnN0IHNlcmlhbGl6ZWQgPSBvcHRpb25zPy5zZXJpYWxpemVkICE9PSBmYWxzZTtcbiAgY29uc3QgY3R4ID0gb3B0aW9ucz8uY29udGV4dCA/IGRhdGFicmlja3MudG9Db250ZXh0KG9wdGlvbnMuY29udGV4dCkgOiB1bmRlZmluZWQ7XG4gIGNvbnN0IHJhdyA9IGF3YWl0IGNsaWVudC5hcGlDbGllbnQucmVxdWVzdChcbiAgICB7XG4gICAgICBwYXRoOiBgL2FwaS8yLjAvZ2VuaWUvc3BhY2VzLyR7ZW5jb2RlVVJJQ29tcG9uZW50KHNwYWNlSWQpfWAsXG4gICAgICBtZXRob2Q6IFwiR0VUXCIsXG4gICAgICBxdWVyeTogc2VyaWFsaXplZCA/IHsgaW5jbHVkZV9zZXJpYWxpemVkX3NwYWNlOiB0cnVlIH0gOiB7fSxcbiAgICAgIGhlYWRlcnM6IG5ldyBIZWFkZXJzKCksXG4gICAgICByYXc6IGZhbHNlLFxuICAgIH0sXG4gICAgY3R4LFxuICApO1xuICByZXR1cm4gZ2VuaWVNb2RlbC5HZW5pZVNwYWNlU2NoZW1hLnBhcnNlKHJhdyk7XG59XG5cbi8qKlxuICogT25lIGVudHJ5IGluIGEgc2VyaWFsaXplZCBzcGFjZSdzIGBjb25maWcuc2FtcGxlX3F1ZXN0aW9uc2AuIFRoZVxuICogYXV0aG9yLWZhY2luZyBmaWVsZCBpcyBgcXVlc3Rpb25gLCB3aGljaCB0aGUgd2lyZSBmb3JtYXQgbW9kZWxzIGFzIGEgc3RyaW5nXG4gKiBhcnJheSAoYSBzaW5nbGUgbXVsdGktbGluZSBxdWVzdGlvbiBpcyBzcGxpdCBhY3Jvc3MgZW50cmllcyk7IHdlIHRyZWF0IHRoZVxuICogZmlyc3Qgbm9uLWVtcHR5IGVudHJ5IGFzIHRoZSBkaXNwbGF5YWJsZSBxdWVzdGlvbiB0ZXh0LlxuICovXG5pbnRlcmZhY2UgU2VyaWFsaXplZFNhbXBsZVF1ZXN0aW9uIHtcbiAgcXVlc3Rpb24/OiB1bmtub3duO1xufVxuXG4vKipcbiAqIEV4dHJhY3QgdGhlIGN1cmF0ZWQgc3RhcnRlciBxdWVzdGlvbnMgYW4gYXV0aG9yIGNvbmZpZ3VyZWQgb24gYSBHZW5pZSBzcGFjZS5cbiAqIFJlYWRzIGBzZXJpYWxpemVkX3NwYWNlIC0+IGNvbmZpZy5zYW1wbGVfcXVlc3Rpb25zWypdLnF1ZXN0aW9uYC4gUmV0dXJucyBgW11gXG4gKiB3aGVuIHRoZSBzcGFjZSBjYXJyaWVzIG5vIHNlcmlhbGl6ZWQgYmxvYiwgdGhlIGJsb2IgaXMgdW5wYXJzZWFibGUsIG9yIG5vXG4gKiBzYW1wbGUgcXVlc3Rpb25zIGFyZSBjb25maWd1cmVkIC0gc28gYSBtaXNzaW5nIG9yIG1pc2NvbmZpZ3VyZWQgc3BhY2VcbiAqIGRlZ3JhZGVzIHRvIFwibm8gc3VnZ2VzdGlvbnNcIiByYXRoZXIgdGhhbiB0aHJvd2luZy4gT3JkZXIgaXMgcHJlc2VydmVkICh0aGVcbiAqIGF1dGhvcidzIG9yZGVyaW5nKSBhbmQgZHVwbGljYXRlcyBhcmUgZHJvcHBlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdlbmllU2FtcGxlUXVlc3Rpb25zKHNwYWNlOiBHZW5pZVNwYWNlKTogc3RyaW5nW10ge1xuICBjb25zdCBzZXJpYWxpemVkID0gc3BhY2Uuc2VyaWFsaXplZF9zcGFjZTtcbiAgaWYgKCFzZXJpYWxpemVkKSByZXR1cm4gW107XG4gIGNvbnN0IHBhcnNlZCA9IGpzb24ucGFyc2Uoc2VyaWFsaXplZCk7XG4gIGlmIChwYXJzZWQgPT09IHVuZGVmaW5lZCkge1xuICAgIGxvZ2dlci53YXJuKFwic2VyaWFsaXplZC1zcGFjZTpwYXJzZS1lcnJvclwiLCB7IHNwYWNlSWQ6IHNwYWNlLnNwYWNlX2lkIH0pO1xuICAgIHJldHVybiBbXTtcbiAgfVxuICBjb25zdCBzYW1wbGVRdWVzdGlvbnMgPSAocGFyc2VkIGFzIHsgY29uZmlnPzogeyBzYW1wbGVfcXVlc3Rpb25zPzogdW5rbm93biB9IH0gfCBudWxsKT8uY29uZmlnXG4gICAgPy5zYW1wbGVfcXVlc3Rpb25zO1xuICBpZiAoIUFycmF5LmlzQXJyYXkoc2FtcGxlUXVlc3Rpb25zKSkgcmV0dXJuIFtdO1xuXG4gIHJldHVybiBbXG4gICAgLi4ub2JqZWN0XG4gICAgICAuc2VxdWVuY2Uoc2FtcGxlUXVlc3Rpb25zIGFzIFNlcmlhbGl6ZWRTYW1wbGVRdWVzdGlvbltdKVxuICAgICAgLm1hcCgoZW50cnkpID0+IHN0cmluZy5maXJzdE5vbkVtcHR5KGVudHJ5Py5xdWVzdGlvbikpXG4gICAgICAubm9uTnVsbCgpXG4gICAgICAuZGlzdGluY3QoKSxcbiAgXTtcbn1cbiJdfQ==
109
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BhY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc3BhY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXFCRztBQUVILE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUMvRCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDL0MsT0FBTyxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUMxRSxPQUFPLEVBQUUsVUFBVSxFQUFtQixNQUFNLHlCQUF5QixDQUFDO0FBRXRFLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsYUFBYSxDQUFDLENBQUM7QUEyQnpDOzs7Ozs7Ozs7Ozs7O0dBYUc7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLGFBQWEsQ0FDakMsT0FBZSxFQUNmLE9BQThCO0lBRTlCLE1BQU0sTUFBTSxHQUFHLE9BQU8sRUFBRSxlQUFlLElBQUksSUFBSSxlQUFlLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDbkUsTUFBTSxVQUFVLEdBQUcsT0FBTyxFQUFFLFVBQVUsS0FBSyxLQUFLLENBQUM7SUFDakQsTUFBTSxHQUFHLEdBQUcsT0FBTyxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztJQUNqRixNQUFNLE9BQU8sR0FBRyxDQUFDLGlCQUEwQixFQUFvQixFQUFFLENBQy9ELE1BQU0sQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUN0QjtRQUNFLElBQUksRUFBRSx5QkFBeUIsa0JBQWtCLENBQUMsT0FBTyxDQUFDLEVBQUU7UUFDNUQsTUFBTSxFQUFFLEtBQUs7UUFDYixLQUFLLEVBQUUsaUJBQWlCLENBQUMsQ0FBQyxDQUFDLEVBQUUsd0JBQXdCLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUU7UUFDbEUsT0FBTyxFQUFFLElBQUksT0FBTyxFQUFFO1FBQ3RCLEdBQUcsRUFBRSxLQUFLO0tBQ1gsRUFDRCxHQUFHLENBQ0osQ0FBQztJQUVKLElBQUksR0FBWSxDQUFDO0lBQ2pCLElBQUksQ0FBQztRQUNILEdBQUcsR0FBRyxNQUFNLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUNsQyxDQUFDO0lBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztRQUNiLElBQUksQ0FBQyxVQUFVLElBQUksQ0FBQywwQkFBMEIsQ0FBQyxHQUFHLENBQUM7WUFBRSxNQUFNLEdBQUcsQ0FBQztRQUMvRCxNQUFNLENBQUMsS0FBSyxDQUFDLDRCQUE0QixFQUFFO1lBQ3pDLE9BQU87WUFDUCxLQUFLLEVBQUUsS0FBSyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUM7U0FDL0IsQ0FBQyxDQUFDO1FBQ0gsR0FBRyxHQUFHLE1BQU0sT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzdCLENBQUM7SUFDRCxPQUFPLFVBQVUsQ0FBQyxnQkFBZ0IsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7QUFDaEQsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsU0FBUywwQkFBMEIsQ0FBQyxHQUFZO0lBQzlDLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxZQUFZLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDeEMsT0FBTyxDQUNMLE9BQU8sQ0FBQyxhQUFhLENBQUMsR0FBRyxDQUFDO1FBQzFCLE9BQU8sQ0FBQyxVQUFVLENBQUMsbUJBQW1CLENBQUM7UUFDdkMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxVQUFVLENBQUMsQ0FDL0IsQ0FBQztBQUNKLENBQUM7QUFZRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxVQUFVLG9CQUFvQixDQUFDLEtBQWlCO0lBQ3BELE1BQU0sVUFBVSxHQUFHLEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQztJQUMxQyxJQUFJLENBQUMsVUFBVTtRQUFFLE9BQU8sRUFBRSxDQUFDO0lBQzNCLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDdEMsSUFBSSxNQUFNLEtBQUssU0FBUyxFQUFFLENBQUM7UUFDekIsTUFBTSxDQUFDLElBQUksQ0FBQyw4QkFBOEIsRUFBRSxFQUFFLE9BQU8sRUFBRSxLQUFLLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztRQUN6RSxPQUFPLEVBQUUsQ0FBQztJQUNaLENBQUM7SUFDRCxNQUFNLGVBQWUsR0FBSSxNQUE2RCxFQUFFLE1BQU07UUFDNUYsRUFBRSxnQkFBZ0IsQ0FBQztJQUNyQixJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxlQUFlLENBQUM7UUFBRSxPQUFPLEVBQUUsQ0FBQztJQUUvQyxPQUFPO1FBQ0wsR0FBRyxNQUFNO2FBQ04sUUFBUSxDQUFDLGVBQTZDLENBQUM7YUFDdkQsR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUMsYUFBYSxDQUFDLEtBQUssRUFBRSxRQUFRLENBQUMsQ0FBQzthQUNyRCxPQUFPLEVBQUU7YUFDVCxRQUFRLEVBQUU7S0FDZCxDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuaWUgc3BhY2UgbWV0YWRhdGEgaGVscGVycy5cbiAqXG4gKiBGZXRjaGVzIGEgR2VuaWUgc3BhY2UncyBkZWZpbml0aW9uIChpbmNsdWRpbmcgdGhlIG9wdC1pbiBgc2VyaWFsaXplZF9zcGFjZWBcbiAqIGJsb2IpIGFuZCBleHRyYWN0cyB0aGUgY3VyYXRlZCBzdGFydGVyIHF1ZXN0aW9ucyBhbiBhdXRob3IgY29uZmlndXJlZCBvbiB0aGVcbiAqIHNwYWNlLiBUaGUgdHlwZWQgU0RLIGBjbGllbnQuZ2VuaWUuZ2V0U3BhY2VgIG9ubHkgcmV0dXJucyB0aGVcbiAqIGRpcmVjdG9yeS1saXN0aW5nIHN1cmZhY2UgKGB0aXRsZWAgLyBgZGVzY3JpcHRpb25gIC8gYHdhcmVob3VzZV9pZGApOyB0aGVcbiAqIHNhbXBsZSBxdWVzdGlvbnMgbGl2ZSBpbnNpZGUgYHNlcmlhbGl6ZWRfc3BhY2VgLCB3aGljaCB0aGUgUkVTVCBBUEkgcmV0dXJuc1xuICogb25seSB3aGVuIGBpbmNsdWRlX3NlcmlhbGl6ZWRfc3BhY2U9dHJ1ZWAuIFdlIGhpdCB0aGF0IGVuZHBvaW50IHRocm91Z2ggdGhlXG4gKiB3b3Jrc3BhY2UgY2xpZW50J3MgcmF3IGBhcGlDbGllbnRgIHNpbmNlIHRoZSB0eXBlZCByZXF1ZXN0IHNoYXBlIGhhcyBubyBmbGFnXG4gKiBmb3IgaXQuXG4gKlxuICogVGhlIHNlcmlhbGl6ZWQgYmxvYiBpcyBhbHNvIG1vcmUgcHJpdmlsZWdlZCB0aGFuIHRoZSBsaXN0aW5nIHN1cmZhY2U6IHRoZVxuICogd29ya3NwYWNlIEFQSSByZXF1aXJlcyBgQ2FuIEVkaXRgIG9uIHRoZSBzcGFjZSB0byByZXR1cm4gaXQsIHdoaWxlIGBDYW4gUnVuYFxuICogaXMgZW5vdWdoIGZvciB0aXRsZSAvIGRlc2NyaXB0aW9uLiBBIGNhbGxlciB0aGF0IG9ubHkgaG9sZHMgYENhbiBSdW5gIC0gYW5cbiAqIGFwcCBzZXJ2aWNlIHByaW5jaXBhbCBncmFudGVkIGp1c3QgZW5vdWdoIHRvIGFzayBxdWVzdGlvbnMgaXMgdGhlIGNvbW1vbiBjYXNlXG4gKiAtIHdvdWxkIG90aGVyd2lzZSBsb3NlIHRoZSB3aG9sZSBzcGFjZSBsb29rdXAgdG8gYSBgUEVSTUlTU0lPTl9ERU5JRURgIGl0XG4gKiBjYW5ub3QgYWN0IG9uLCBzbyB0aGUgZmV0Y2ggZGVncmFkZXMgdG8gdGhlIHVuc2VyaWFsaXplZCByZXF1ZXN0IGluc3RlYWQgb2ZcbiAqIGZhaWxpbmcgKHNlZSB7QGxpbmsgZ2V0R2VuaWVTcGFjZX0pLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBXb3Jrc3BhY2VDbGllbnQgfSBmcm9tIFwiQGRhdGFicmlja3Mvc2RrLWV4cGVyaW1lbnRhbFwiO1xuaW1wb3J0IHsgZGF0YWJyaWNrcyB9IGZyb20gXCJAZGJ4LXRvb2xzL2FwcGtpdFwiO1xuaW1wb3J0IHsgZXJyb3IsIGpzb24sIGxvZywgb2JqZWN0LCBzdHJpbmcgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHsgZ2VuaWVNb2RlbCwgdHlwZSBHZW5pZVNwYWNlIH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWdlbmllXCI7XG5cbmNvbnN0IGxvZ2dlciA9IGxvZy5sb2dnZXIoXCJnZW5pZS9zcGFjZVwiKTtcblxuLyoqIE9wdGlvbnMgZm9yIHtAbGluayBnZXRHZW5pZVNwYWNlfS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgR2V0R2VuaWVTcGFjZU9wdGlvbnMge1xuICAvKipcbiAgICogRXhwbGljaXQgYFdvcmtzcGFjZUNsaWVudGAuIERlZmF1bHRzIHRvIGEgZnJlc2ggYG5ldyBXb3Jrc3BhY2VDbGllbnQoe30pYFxuICAgKiAoZW52LXZhciBhdXRoKS4gU2VydmVyIGNhbGxlcnMgc2hvdWxkIHBhc3MgdGhlaXIgT0JPLXNjb3BlZCBjbGllbnQgc28gdGhlXG4gICAqIGxvb2t1cCBydW5zIGFzIHRoZSB1c2VyLlxuICAgKi9cbiAgd29ya3NwYWNlQ2xpZW50PzogV29ya3NwYWNlQ2xpZW50O1xuICAvKipcbiAgICogUmVxdWVzdCB0aGUgYHNlcmlhbGl6ZWRfc3BhY2VgIGJsb2IgKGNhdGFsb2dzLCB0YWJsZXMsIHNhbXBsZSBxdWVzdGlvbnMsXG4gICAqIHByb21wdHMpLiBEZWZhdWx0cyB0byBgdHJ1ZWAgLSB0aGUgb25seSByZWFzb24gdG8gc2tpcCBpdCBpcyB3aGVuIHRoZVxuICAgKiBjYWxsZXIganVzdCBuZWVkcyB0aXRsZSAvIGRlc2NyaXB0aW9uIGFuZCB3YW50cyB0aGUgc21hbGxlciBwYXlsb2FkLlxuICAgKlxuICAgKiBSZXF1ZXN0aW5nIGl0IGlzIGJlc3QtZWZmb3J0OiB0aGUgYmxvYiBuZWVkcyBgQ2FuIEVkaXRgIG9uIHRoZSBzcGFjZSwgc28gYVxuICAgKiBjYWxsZXIgd2l0aG91dCBpdCBnZXRzIHRoZSB1bnNlcmlhbGl6ZWQgc3BhY2UgYmFjayByYXRoZXIgdGhhbiBhbiBlcnJvcixcbiAgICogYW5kIHtAbGluayBnZW5pZVNhbXBsZVF1ZXN0aW9uc30gdGhlbiByZXBvcnRzIG5vIHN1Z2dlc3Rpb25zLlxuICAgKi9cbiAgc2VyaWFsaXplZD86IGJvb2xlYW47XG4gIC8qKlxuICAgKiBFeHRlcm5hbCBjYW5jZWxsYXRpb24uIEFjY2VwdHMgYSBXSEFUV0cgYEFib3J0U2lnbmFsYCBvciBhIGZ1bGx5LWJ1aWx0IFNES1xuICAgKiBgQ29udGV4dGAgKHNlZSBgZGF0YWJyaWNrcy5Db250ZXh0TGlrZWApLlxuICAgKi9cbiAgY29udGV4dD86IGRhdGFicmlja3MuQ29udGV4dExpa2U7XG59XG5cbi8qKlxuICogRmV0Y2ggYSBHZW5pZSBzcGFjZSBieSBpZCwgb3B0aW9uYWxseSBpbmNsdWRpbmcgaXRzIHNlcmlhbGl6ZWQgZGVmaW5pdGlvbi5cbiAqIEhpdHMgYEdFVCAvYXBpLzIuMC9nZW5pZS9zcGFjZXMvPGlkPmAgd2l0aCBgaW5jbHVkZV9zZXJpYWxpemVkX3NwYWNlPXRydWVgXG4gKiB0aHJvdWdoIHRoZSByYXcgYGFwaUNsaWVudGAsIHRoZW4gdmFsaWRhdGVzIHRoZSByZXNwb25zZSBhZ2FpbnN0XG4gKiB7QGxpbmsgR2VuaWVTcGFjZVNjaGVtYX0gKHVua25vd24gZmllbGRzIGxpa2UgYGV0YWdgIC9cbiAqIGBwYXJlbnRfcGF0aGAgYXJlIHN0cmlwcGVkKS5cbiAqXG4gKiBXaGVuIHRoZSBzZXJpYWxpemVkIHJlcXVlc3QgaXMgcmVqZWN0ZWQgZm9yIGxhY2sgb2YgcGVybWlzc2lvbiAoYDQwM2AsIG9yIGFcbiAqIGBQRVJNSVNTSU9OX0RFTklFRGAgLyBgQ2FuIEVkaXRgIG1lc3NhZ2UgLSB0aGUgd29ya3NwYWNlIEFQSSBnYXRlcyB0aGUgYmxvYlxuICogYmVoaW5kIGBDYW4gRWRpdGApLCBpdCByZXRyaWVzIG9uY2Ugd2l0aG91dCB0aGUgZmxhZyBzbyB0aGUgY2FsbGVyIHN0aWxsIGdldHNcbiAqIHRoZSBzcGFjZS4gQW55IG90aGVyIGZhaWx1cmUsIGFuZCBhIHJldHJ5IHRoYXQgZmFpbHMgdG9vLCBpcyByZXRocm93bjogYVxuICogY2FuY2VsbGVkIHJlcXVlc3Qgb3IgYSBtaXNzaW5nIHNwYWNlIG11c3Qgbm90IGxvb2sgbGlrZSBhbiB1bnNlcmlhbGl6ZWRcbiAqIHNwYWNlLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZ2V0R2VuaWVTcGFjZShcbiAgc3BhY2VJZDogc3RyaW5nLFxuICBvcHRpb25zPzogR2V0R2VuaWVTcGFjZU9wdGlvbnMsXG4pOiBQcm9taXNlPEdlbmllU3BhY2U+IHtcbiAgY29uc3QgY2xpZW50ID0gb3B0aW9ucz8ud29ya3NwYWNlQ2xpZW50ID8/IG5ldyBXb3Jrc3BhY2VDbGllbnQoe30pO1xuICBjb25zdCBzZXJpYWxpemVkID0gb3B0aW9ucz8uc2VyaWFsaXplZCAhPT0gZmFsc2U7XG4gIGNvbnN0IGN0eCA9IG9wdGlvbnM/LmNvbnRleHQgPyBkYXRhYnJpY2tzLnRvQ29udGV4dChvcHRpb25zLmNvbnRleHQpIDogdW5kZWZpbmVkO1xuICBjb25zdCByZXF1ZXN0ID0gKGluY2x1ZGVTZXJpYWxpemVkOiBib29sZWFuKTogUHJvbWlzZTx1bmtub3duPiA9PlxuICAgIGNsaWVudC5hcGlDbGllbnQucmVxdWVzdChcbiAgICAgIHtcbiAgICAgICAgcGF0aDogYC9hcGkvMi4wL2dlbmllL3NwYWNlcy8ke2VuY29kZVVSSUNvbXBvbmVudChzcGFjZUlkKX1gLFxuICAgICAgICBtZXRob2Q6IFwiR0VUXCIsXG4gICAgICAgIHF1ZXJ5OiBpbmNsdWRlU2VyaWFsaXplZCA/IHsgaW5jbHVkZV9zZXJpYWxpemVkX3NwYWNlOiB0cnVlIH0gOiB7fSxcbiAgICAgICAgaGVhZGVyczogbmV3IEhlYWRlcnMoKSxcbiAgICAgICAgcmF3OiBmYWxzZSxcbiAgICAgIH0sXG4gICAgICBjdHgsXG4gICAgKTtcblxuICBsZXQgcmF3OiB1bmtub3duO1xuICB0cnkge1xuICAgIHJhdyA9IGF3YWl0IHJlcXVlc3Qoc2VyaWFsaXplZCk7XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGlmICghc2VyaWFsaXplZCB8fCAhaXNTZXJpYWxpemVkU3BhY2VGb3JiaWRkZW4oZXJyKSkgdGhyb3cgZXJyO1xuICAgIGxvZ2dlci5kZWJ1ZyhcInNlcmlhbGl6ZWQtc3BhY2U6Zm9yYmlkZGVuXCIsIHtcbiAgICAgIHNwYWNlSWQsXG4gICAgICBlcnJvcjogZXJyb3IuZXJyb3JNZXNzYWdlKGVyciksXG4gICAgfSk7XG4gICAgcmF3ID0gYXdhaXQgcmVxdWVzdChmYWxzZSk7XG4gIH1cbiAgcmV0dXJuIGdlbmllTW9kZWwuR2VuaWVTcGFjZVNjaGVtYS5wYXJzZShyYXcpO1xufVxuXG4vKipcbiAqIFRydWUgd2hlbiBhIGZhaWxlZCBzcGFjZSByZXF1ZXN0IHdhcyByZWplY3RlZCBmb3IgbGFjayBvZiBwZXJtaXNzaW9uLCB0aGUgb25lXG4gKiBmYWlsdXJlIHRoZSB1bnNlcmlhbGl6ZWQgcmV0cnkgY2FuIHJlY292ZXIgZnJvbS4gTWF0Y2hlcyBvbiBgNDAzYCBhbmQgb24gdGhlXG4gKiBgUEVSTUlTU0lPTl9ERU5JRURgIC8gYENhbiBFZGl0YCB3b3JkaW5nIHRoZSB3b3Jrc3BhY2UgQVBJIHJldHVybnMsIHNpbmNlIHRoZVxuICogU0RLIHN1cmZhY2VzIHRoZSBlcnJvciBjb2RlIHdpdGhvdXQgYWx3YXlzIGNhcnJ5aW5nIGEgc3RhdHVzLlxuICovXG5mdW5jdGlvbiBpc1NlcmlhbGl6ZWRTcGFjZUZvcmJpZGRlbihlcnI6IHVua25vd24pOiBib29sZWFuIHtcbiAgY29uc3QgY29udGV4dCA9IGVycm9yLmVycm9yQ29udGV4dChlcnIpO1xuICByZXR1cm4gKFxuICAgIGNvbnRleHQuaGFzU3RhdHVzQ29kZSg0MDMpIHx8XG4gICAgY29udGV4dC5oYXNNZXNzYWdlKFwicGVybWlzc2lvbiBkZW5pZWRcIikgfHxcbiAgICBjb250ZXh0Lmhhc01lc3NhZ2UoXCJjYW4gZWRpdFwiKVxuICApO1xufVxuXG4vKipcbiAqIE9uZSBlbnRyeSBpbiBhIHNlcmlhbGl6ZWQgc3BhY2UncyBgY29uZmlnLnNhbXBsZV9xdWVzdGlvbnNgLiBUaGVcbiAqIGF1dGhvci1mYWNpbmcgZmllbGQgaXMgYHF1ZXN0aW9uYCwgd2hpY2ggdGhlIHdpcmUgZm9ybWF0IG1vZGVscyBhcyBhIHN0cmluZ1xuICogYXJyYXkgKGEgc2luZ2xlIG11bHRpLWxpbmUgcXVlc3Rpb24gaXMgc3BsaXQgYWNyb3NzIGVudHJpZXMpOyB3ZSB0cmVhdCB0aGVcbiAqIGZpcnN0IG5vbi1lbXB0eSBlbnRyeSBhcyB0aGUgZGlzcGxheWFibGUgcXVlc3Rpb24gdGV4dC5cbiAqL1xuaW50ZXJmYWNlIFNlcmlhbGl6ZWRTYW1wbGVRdWVzdGlvbiB7XG4gIHF1ZXN0aW9uPzogdW5rbm93bjtcbn1cblxuLyoqXG4gKiBFeHRyYWN0IHRoZSBjdXJhdGVkIHN0YXJ0ZXIgcXVlc3Rpb25zIGFuIGF1dGhvciBjb25maWd1cmVkIG9uIGEgR2VuaWUgc3BhY2UuXG4gKiBSZWFkcyBgc2VyaWFsaXplZF9zcGFjZSAtPiBjb25maWcuc2FtcGxlX3F1ZXN0aW9uc1sqXS5xdWVzdGlvbmAuIFJldHVybnMgYFtdYFxuICogd2hlbiB0aGUgc3BhY2UgY2FycmllcyBubyBzZXJpYWxpemVkIGJsb2IsIHRoZSBibG9iIGlzIHVucGFyc2VhYmxlLCBvciBub1xuICogc2FtcGxlIHF1ZXN0aW9ucyBhcmUgY29uZmlndXJlZCAtIHNvIGEgbWlzc2luZyBvciBtaXNjb25maWd1cmVkIHNwYWNlXG4gKiBkZWdyYWRlcyB0byBcIm5vIHN1Z2dlc3Rpb25zXCIgcmF0aGVyIHRoYW4gdGhyb3dpbmcuIE9yZGVyIGlzIHByZXNlcnZlZCAodGhlXG4gKiBhdXRob3IncyBvcmRlcmluZykgYW5kIGR1cGxpY2F0ZXMgYXJlIGRyb3BwZWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZW5pZVNhbXBsZVF1ZXN0aW9ucyhzcGFjZTogR2VuaWVTcGFjZSk6IHN0cmluZ1tdIHtcbiAgY29uc3Qgc2VyaWFsaXplZCA9IHNwYWNlLnNlcmlhbGl6ZWRfc3BhY2U7XG4gIGlmICghc2VyaWFsaXplZCkgcmV0dXJuIFtdO1xuICBjb25zdCBwYXJzZWQgPSBqc29uLnBhcnNlKHNlcmlhbGl6ZWQpO1xuICBpZiAocGFyc2VkID09PSB1bmRlZmluZWQpIHtcbiAgICBsb2dnZXIud2FybihcInNlcmlhbGl6ZWQtc3BhY2U6cGFyc2UtZXJyb3JcIiwgeyBzcGFjZUlkOiBzcGFjZS5zcGFjZV9pZCB9KTtcbiAgICByZXR1cm4gW107XG4gIH1cbiAgY29uc3Qgc2FtcGxlUXVlc3Rpb25zID0gKHBhcnNlZCBhcyB7IGNvbmZpZz86IHsgc2FtcGxlX3F1ZXN0aW9ucz86IHVua25vd24gfSB9IHwgbnVsbCk/LmNvbmZpZ1xuICAgID8uc2FtcGxlX3F1ZXN0aW9ucztcbiAgaWYgKCFBcnJheS5pc0FycmF5KHNhbXBsZVF1ZXN0aW9ucykpIHJldHVybiBbXTtcblxuICByZXR1cm4gW1xuICAgIC4uLm9iamVjdFxuICAgICAgLnNlcXVlbmNlKHNhbXBsZVF1ZXN0aW9ucyBhcyBTZXJpYWxpemVkU2FtcGxlUXVlc3Rpb25bXSlcbiAgICAgIC5tYXAoKGVudHJ5KSA9PiBzdHJpbmcuZmlyc3ROb25FbXB0eShlbnRyeT8ucXVlc3Rpb24pKVxuICAgICAgLm5vbk51bGwoKVxuICAgICAgLmRpc3RpbmN0KCksXG4gIF07XG59XG4iXX0=
package/package.json CHANGED
@@ -28,9 +28,9 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@databricks/sdk-experimental": "^0.17.0",
31
- "@dbx-tools/appkit": "0.6.61",
32
- "@dbx-tools/shared-core": "0.6.61",
33
- "@dbx-tools/shared-genie": "0.6.61"
31
+ "@dbx-tools/appkit": "0.6.63",
32
+ "@dbx-tools/shared-core": "0.6.63",
33
+ "@dbx-tools/shared-genie": "0.6.63"
34
34
  },
35
35
  "main": "./lib/index.js",
36
36
  "license": "UNLICENSED",
@@ -46,7 +46,7 @@
46
46
  "./package.json": "./package.json"
47
47
  }
48
48
  },
49
- "version": "0.6.61",
49
+ "version": "0.6.63",
50
50
  "types": "./lib/index.d.ts",
51
51
  "type": "module",
52
52
  "exports": {
package/src/space.ts CHANGED
@@ -10,12 +10,20 @@
10
10
  * workspace client's raw `apiClient` since the typed request shape has no flag
11
11
  * for it.
12
12
  *
13
+ * The serialized blob is also more privileged than the listing surface: the
14
+ * workspace API requires `Can Edit` on the space to return it, while `Can Run`
15
+ * is enough for title / description. A caller that only holds `Can Run` - an
16
+ * app service principal granted just enough to ask questions is the common case
17
+ * - would otherwise lose the whole space lookup to a `PERMISSION_DENIED` it
18
+ * cannot act on, so the fetch degrades to the unserialized request instead of
19
+ * failing (see {@link getGenieSpace}).
20
+ *
13
21
  * @module
14
22
  */
15
23
 
16
24
  import { WorkspaceClient } from "@databricks/sdk-experimental";
17
25
  import { databricks } from "@dbx-tools/appkit";
18
- import { json, log, object, string } from "@dbx-tools/shared-core";
26
+ import { error, json, log, object, string } from "@dbx-tools/shared-core";
19
27
  import { genieModel, type GenieSpace } from "@dbx-tools/shared-genie";
20
28
 
21
29
  const logger = log.logger("genie/space");
@@ -32,6 +40,10 @@ export interface GetGenieSpaceOptions {
32
40
  * Request the `serialized_space` blob (catalogs, tables, sample questions,
33
41
  * prompts). Defaults to `true` - the only reason to skip it is when the
34
42
  * caller just needs title / description and wants the smaller payload.
43
+ *
44
+ * Requesting it is best-effort: the blob needs `Can Edit` on the space, so a
45
+ * caller without it gets the unserialized space back rather than an error,
46
+ * and {@link genieSampleQuestions} then reports no suggestions.
35
47
  */
36
48
  serialized?: boolean;
37
49
  /**
@@ -47,6 +59,13 @@ export interface GetGenieSpaceOptions {
47
59
  * through the raw `apiClient`, then validates the response against
48
60
  * {@link GenieSpaceSchema} (unknown fields like `etag` /
49
61
  * `parent_path` are stripped).
62
+ *
63
+ * When the serialized request is rejected for lack of permission (`403`, or a
64
+ * `PERMISSION_DENIED` / `Can Edit` message - the workspace API gates the blob
65
+ * behind `Can Edit`), it retries once without the flag so the caller still gets
66
+ * the space. Any other failure, and a retry that fails too, is rethrown: a
67
+ * cancelled request or a missing space must not look like an unserialized
68
+ * space.
50
69
  */
51
70
  export async function getGenieSpace(
52
71
  spaceId: string,
@@ -55,19 +74,47 @@ export async function getGenieSpace(
55
74
  const client = options?.workspaceClient ?? new WorkspaceClient({});
56
75
  const serialized = options?.serialized !== false;
57
76
  const ctx = options?.context ? databricks.toContext(options.context) : undefined;
58
- const raw = await client.apiClient.request(
59
- {
60
- path: `/api/2.0/genie/spaces/${encodeURIComponent(spaceId)}`,
61
- method: "GET",
62
- query: serialized ? { include_serialized_space: true } : {},
63
- headers: new Headers(),
64
- raw: false,
65
- },
66
- ctx,
67
- );
77
+ const request = (includeSerialized: boolean): Promise<unknown> =>
78
+ client.apiClient.request(
79
+ {
80
+ path: `/api/2.0/genie/spaces/${encodeURIComponent(spaceId)}`,
81
+ method: "GET",
82
+ query: includeSerialized ? { include_serialized_space: true } : {},
83
+ headers: new Headers(),
84
+ raw: false,
85
+ },
86
+ ctx,
87
+ );
88
+
89
+ let raw: unknown;
90
+ try {
91
+ raw = await request(serialized);
92
+ } catch (err) {
93
+ if (!serialized || !isSerializedSpaceForbidden(err)) throw err;
94
+ logger.debug("serialized-space:forbidden", {
95
+ spaceId,
96
+ error: error.errorMessage(err),
97
+ });
98
+ raw = await request(false);
99
+ }
68
100
  return genieModel.GenieSpaceSchema.parse(raw);
69
101
  }
70
102
 
103
+ /**
104
+ * True when a failed space request was rejected for lack of permission, the one
105
+ * failure the unserialized retry can recover from. Matches on `403` and on the
106
+ * `PERMISSION_DENIED` / `Can Edit` wording the workspace API returns, since the
107
+ * SDK surfaces the error code without always carrying a status.
108
+ */
109
+ function isSerializedSpaceForbidden(err: unknown): boolean {
110
+ const context = error.errorContext(err);
111
+ return (
112
+ context.hasStatusCode(403) ||
113
+ context.hasMessage("permission denied") ||
114
+ context.hasMessage("can edit")
115
+ );
116
+ }
117
+
71
118
  /**
72
119
  * One entry in a serialized space's `config.sample_questions`. The
73
120
  * author-facing field is `question`, which the wire format models as a string