@thatopen/services 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +62 -62
  2. package/dist/built-in/index.d.ts +66 -41
  3. package/dist/cli/lib/revit-addin.d.ts +8 -0
  4. package/dist/cli.js +154 -13
  5. package/dist/core/client.d.ts +6 -0
  6. package/dist/core/platform-client.d.ts +139 -0
  7. package/dist/core/platform-client.live.test.d.ts +1 -0
  8. package/dist/core/platform-client.notifications.test.d.ts +1 -0
  9. package/dist/index.cjs.js +2 -2
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.es.js +635 -445
  12. package/dist/types/notifications.d.ts +54 -0
  13. package/dist/vite-env.d.ts +1 -1
  14. package/docs/ai-quickstart.md +120 -1
  15. package/docs/cli/create.md +29 -29
  16. package/docs/cli/local-server.md +38 -38
  17. package/docs/cli/login.md +32 -32
  18. package/docs/cli/publish.md +31 -31
  19. package/docs/cli/run.md +26 -26
  20. package/docs/cli/serve.md +43 -43
  21. package/docs/cli/swap.md +30 -30
  22. package/docs/flow-plugin-guide.md +26 -0
  23. package/docs/revit-collab-quickstart.md +26 -17
  24. package/docs/rhino-interop-quickstart.md +74 -7
  25. package/package.json +1 -1
  26. package/resources/AGENTS.md +40 -40
  27. package/src/built-in/CDEManager/example.ts +214 -214
  28. package/src/built-in/CDEManager/examples/FileEditing/example.ts +226 -226
  29. package/src/built-in/CDEManager/examples/FileMoves/example.ts +186 -186
  30. package/src/built-in/CDEManager/examples/FileOperations/example.ts +295 -295
  31. package/src/built-in/CDEManager/examples/FileTransfers/example.ts +210 -210
  32. package/src/built-in/CDEManager/examples/FolderManagement/example.ts +230 -230
  33. package/src/built-in/CDEManager/examples/MetadataSchema/example.ts +230 -230
  34. package/src/built-in/CDEManager/examples/MetadataViews/example.ts +166 -166
  35. package/src/built-in/CDEManager/examples/ProjectConfig/example.ts +177 -177
  36. package/src/built-in/UIManager/src/app/example.ts +97 -97
  37. package/src/built-in/UIManager/src/app/examples/AppState/example.ts +140 -140
  38. package/src/built-in/UIManager/src/app/examples/Banner/example.ts +159 -159
  39. package/src/built-in/UIManager/src/app/examples/Contexts/example.ts +123 -123
  40. package/src/built-in/UIManager/src/app/examples/FileDrop/example.ts +124 -124
  41. package/src/built-in/UIManager/src/app/examples/FloatingLayout/example.ts +90 -90
  42. package/src/built-in/UIManager/src/app/examples/Modal/example.ts +163 -163
  43. package/src/built-in/UIManager/src/app/examples/Notifications/example.ts +133 -133
  44. package/src/built-in/UIManager/src/app/examples/ProjectFileStorage/example.ts +150 -150
  45. package/src/built-in/UIManager/src/app/examples/Sidebar/example.ts +102 -102
  46. package/src/built-in/UIManager/src/clashes-panel/example.ts +183 -183
  47. package/src/built-in/UIManager/src/git-history-panel/example.ts +70 -70
  48. package/src/built-in/UIManager/src/models-list/example.ts +122 -122
  49. package/src/built-in/UIManager/src/topics-panel/example.ts +170 -170
  50. package/src/built-in/index.ts +66 -41
  51. package/src/cli/templates/app/index.html +35 -35
  52. package/src/cli/templates/app/package.json +32 -32
  53. package/src/cli/templates/app/src/bim-components/CloudRunner/index.ts +95 -95
  54. package/src/cli/templates/app/src/bim-components/CloudRunner/src/index.ts +1 -1
  55. package/src/cli/templates/app/src/bim-components/CloudRunner/src/types.ts +5 -5
  56. package/src/cli/templates/app/src/bim-components/index.ts +1 -1
  57. package/src/cli/templates/app/src/globals.ts +1 -1
  58. package/src/cli/templates/app/src/main.history.ts +191 -0
  59. package/src/cli/templates/app/src/ui-components/app-info-section/index.ts +26 -26
  60. package/src/cli/templates/app/src/ui-components/app-info-section/src/index.ts +1 -1
  61. package/src/cli/templates/app/src/ui-components/app-info-section/src/types.ts +15 -15
  62. package/src/cli/templates/app/src/ui-components/cloud-runner-section/index.ts +37 -37
  63. package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/index.ts +1 -1
  64. package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/types.ts +14 -14
  65. package/src/cli/templates/app/src/ui-components/index.ts +2 -2
  66. package/src/cli/templates/app/tsconfig.json +16 -16
  67. package/src/cli/templates/app/vite.config.js +57 -57
  68. package/src/cli/templates/cloud-component/README.md +205 -205
  69. package/src/cli/templates/cloud-component/_thatopen +5 -5
  70. package/src/cli/templates/cloud-component/declarations.json +4 -4
  71. package/src/cli/templates/cloud-component/package.json +21 -21
  72. package/src/cli/templates/cloud-component/src/main.ts +70 -70
  73. package/src/cli/templates/cloud-component/tsconfig.json +16 -16
  74. package/src/cli/templates/cloud-component/vite.config.js +46 -46
  75. package/src/cli/templates/shared/AGENTS.md +8 -8
  76. package/src/cli/templates/shared/CLAUDE.md +1 -1
  77. package/src/cli/templates/shared/_gitignore +5 -5
package/dist/cli.js CHANGED
@@ -4205,8 +4205,8 @@ class BaseXHR extends Polling {
4205
4205
  /**
4206
4206
  * Sends data.
4207
4207
  *
4208
- * @param {String} data to send.
4209
- * @param {Function} called upon flush.
4208
+ * @param {String} data - data to send.
4209
+ * @param {Function} fn - called upon flush.
4210
4210
  * @private
4211
4211
  */
4212
4212
  doWrite(data, fn) {
@@ -4959,7 +4959,7 @@ class SocketWithoutUpgrade extends Emitter {
4959
4959
  /**
4960
4960
  * Sends a packet.
4961
4961
  *
4962
- * @param {String} type: packet type.
4962
+ * @param {String} type - packet type.
4963
4963
  * @param {String} data.
4964
4964
  * @param {Object} options.
4965
4965
  * @param {Function} fn - callback function.
@@ -5194,11 +5194,12 @@ class SocketWithUpgrade extends SocketWithoutUpgrade {
5194
5194
  }
5195
5195
  let Socket$1 = class Socket extends SocketWithUpgrade {
5196
5196
  constructor(uri, opts = {}) {
5197
- const o = typeof uri === "object" ? uri : opts;
5197
+ const isOptionsOnly = typeof uri === "object";
5198
+ const o = isOptionsOnly ? { ...uri } : { ...opts };
5198
5199
  if (!o.transports || o.transports && typeof o.transports[0] === "string") {
5199
5200
  o.transports = (o.transports || ["polling", "websocket", "webtransport"]).map((transportName) => transports[transportName]).filter((t) => !!t);
5200
5201
  }
5201
- super(uri, o);
5202
+ super(isOptionsOnly ? o : uri, o);
5202
5203
  }
5203
5204
  };
5204
5205
  function url(uri, path2 = "", loc) {
@@ -5419,12 +5420,13 @@ class Encoder {
5419
5420
  class Decoder extends Emitter {
5420
5421
  /**
5421
5422
  * Decoder constructor
5422
- *
5423
- * @param {function} reviver - custom reviver to pass down to JSON.stringify
5424
5423
  */
5425
- constructor(reviver) {
5424
+ constructor(opts) {
5426
5425
  super();
5427
- this.reviver = reviver;
5426
+ this.opts = Object.assign({
5427
+ reviver: void 0,
5428
+ maxAttachments: 10
5429
+ }, typeof opts === "function" ? { reviver: opts } : opts);
5428
5430
  }
5429
5431
  /**
5430
5432
  * Decodes an encoded packet string into packet JSON.
@@ -5484,7 +5486,13 @@ class Decoder extends Emitter {
5484
5486
  if (buf != Number(buf) || str.charAt(i) !== "-") {
5485
5487
  throw new Error("Illegal attachments");
5486
5488
  }
5487
- p.attachments = Number(buf);
5489
+ const n = Number(buf);
5490
+ if (!isInteger(n) || n < 0) {
5491
+ throw new Error("Illegal attachments");
5492
+ } else if (n > this.opts.maxAttachments) {
5493
+ throw new Error("too many attachments");
5494
+ }
5495
+ p.attachments = n;
5488
5496
  }
5489
5497
  if ("/" === str.charAt(i + 1)) {
5490
5498
  const start = i + 1;
@@ -5525,7 +5533,7 @@ class Decoder extends Emitter {
5525
5533
  }
5526
5534
  tryParse(str) {
5527
5535
  try {
5528
- return JSON.parse(str, this.reviver);
5536
+ return JSON.parse(str, this.opts.reviver);
5529
5537
  } catch (e) {
5530
5538
  return false;
5531
5539
  }
@@ -5587,6 +5595,9 @@ class BinaryReconstructor {
5587
5595
  this.buffers = [];
5588
5596
  }
5589
5597
  }
5598
+ const isInteger = Number.isInteger || function(value2) {
5599
+ return typeof value2 === "number" && isFinite(value2) && Math.floor(value2) === value2;
5600
+ };
5590
5601
  function isObject(value2) {
5591
5602
  return Object.prototype.toString.call(value2) === "[object Object]";
5592
5603
  }
@@ -6942,6 +6953,14 @@ class EngineServicesClient {
6942
6953
  * the new token is picked up on every request — expired tokens no
6943
6954
  * longer stick around.
6944
6955
  */
6956
+ /**
6957
+ * Socket origin without namespace or query, for gateways other than the
6958
+ * execution one. `wsUrl` already carries a token that may be stale when a
6959
+ * provider is in play, so callers append their own.
6960
+ */
6961
+ get socketOrigin() {
6962
+ return this.wsUrl.split("?")[0];
6963
+ }
6945
6964
  async resolveAccessToken() {
6946
6965
  return this.accessToken;
6947
6966
  }
@@ -8120,7 +8139,7 @@ const libVersion = (() => {
8120
8139
  }
8121
8140
  })();
8122
8141
  const templatesDir = require$$2.join(__dirname, "..", "src", "cli", "templates");
8123
- const createCommand = new Command2("create").argument("<project-name>", 'Name of the project to create (use "." for current directory)').option("-t, --template <template>", `Template (${TEMPLATES.join(", ")})`, "app").option("--beta", "Use beta engine libraries (@thatopen-platform/*-beta)").description("Scaffold a new ThatOpen app or cloud component project").action(async (projectName, opts) => {
8142
+ const createCommand = new Command2("create").argument("<project-name>", 'Name of the project to create (use "." for current directory)').option("-t, --template <template>", `Template (${TEMPLATES.join(", ")})`, "app").option("--beta", "Use beta engine libraries (@thatopen-platform/*-beta)").option("--history", "App only: include the revit-flow commit history panel, already wired").description("Scaffold a new ThatOpen app or cloud component project").action(async (projectName, opts) => {
8124
8143
  const template = opts.template;
8125
8144
  if (!TEMPLATES.includes(template)) {
8126
8145
  console.error(`Unknown template "${opts.template}". Available: ${TEMPLATES.join(", ")}`);
@@ -8159,6 +8178,16 @@ The templates currently require the beta engine libraries. Public (non-beta) eng
8159
8178
  if (isCloud) {
8160
8179
  require$$3.renameSync(require$$2.join(targetDir, "_thatopen"), require$$2.join(targetDir, ".thatopen"));
8161
8180
  }
8181
+ if (!isCloud) {
8182
+ const variant = require$$2.join(targetDir, "src", "main.history.ts");
8183
+ if (require$$3.existsSync(variant)) {
8184
+ if (opts.history) require$$3.renameSync(variant, require$$2.join(targetDir, "src", "main.ts"));
8185
+ else require$$3.rmSync(variant);
8186
+ }
8187
+ } else if (opts.history) {
8188
+ console.error("--history applies to the app template only.");
8189
+ process.exit(1);
8190
+ }
8162
8191
  const pkgPath = require$$2.join(targetDir, "package.json");
8163
8192
  const pkgContent = require$$3.readFileSync(pkgPath, "utf-8").replace(/\{\{PROJECT_NAME\}\}/g, packageName).replace(/\{\{VERSION\}\}/g, libVersion).replace(/"@thatopen\/services": "file:[^"]*"/, `"@thatopen/services": "^${libVersion}"`);
8164
8193
  require$$3.writeFileSync(pkgPath, pkgContent);
@@ -8216,7 +8245,7 @@ Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/js
8216
8245
  JSZip uses the library pako released under the MIT license :
8217
8246
  https://github.com/nodeca/pako/blob/main/LICENSE
8218
8247
  */
8219
- (function(module2, exports$1) {
8248
+ (function(module2, exports) {
8220
8249
  !function(e) {
8221
8250
  module2.exports = e();
8222
8251
  }(function() {
@@ -11733,6 +11762,38 @@ function discoveryFile() {
11733
11762
  const appData = process.env.APPDATA || require$$2.join(node_os.homedir(), "AppData", "Roaming");
11734
11763
  return require$$2.join(appData, "ThatOpen", "revit-addin.json");
11735
11764
  }
11765
+ function tryDiscoverAddin() {
11766
+ const file = discoveryFile();
11767
+ if (!require$$3.existsSync(file)) return null;
11768
+ try {
11769
+ return JSON.parse(require$$3.readFileSync(file, "utf-8"));
11770
+ } catch {
11771
+ return null;
11772
+ }
11773
+ }
11774
+ async function addinAlive() {
11775
+ const info = tryDiscoverAddin();
11776
+ if (!info) return false;
11777
+ try {
11778
+ const res = await fetch(`http://127.0.0.1:${info.port}/status`, {
11779
+ method: "POST",
11780
+ headers: { "Content-Type": "application/json", "X-RevitFlow-Token": info.token },
11781
+ body: "{}",
11782
+ signal: AbortSignal.timeout(4e3)
11783
+ });
11784
+ return res.ok;
11785
+ } catch {
11786
+ return false;
11787
+ }
11788
+ }
11789
+ async function waitForAddin(timeoutMs) {
11790
+ const until = Date.now() + timeoutMs;
11791
+ while (Date.now() < until) {
11792
+ if (await addinAlive()) return true;
11793
+ await new Promise((r) => setTimeout(r, 2e3));
11794
+ }
11795
+ return false;
11796
+ }
11736
11797
  function discoverAddin() {
11737
11798
  const file = discoveryFile();
11738
11799
  if (!require$$3.existsSync(file)) {
@@ -11789,6 +11850,28 @@ async function configureAddin(apiUrl, accessToken) {
11789
11850
  key: accessToken
11790
11851
  });
11791
11852
  }
11853
+ function isRevitRunning() {
11854
+ if (process.platform !== "win32") return false;
11855
+ try {
11856
+ return require$$1.execSync('tasklist /FI "IMAGENAME eq Revit.exe" /NH', { encoding: "utf-8" }).toLowerCase().includes("revit.exe");
11857
+ } catch {
11858
+ return false;
11859
+ }
11860
+ }
11861
+ function startRevit() {
11862
+ const roots = ["C:\\Program Files\\Autodesk"];
11863
+ for (const root of roots) {
11864
+ if (!require$$3.existsSync(root)) continue;
11865
+ for (const dir of require$$3.readdirSync(root)) {
11866
+ if (!/^Revit \d{4}$/.test(dir)) continue;
11867
+ const exe = require$$2.join(root, dir, "Revit.exe");
11868
+ if (!require$$3.existsSync(exe)) continue;
11869
+ require$$1.spawn(exe, [], { detached: true, stdio: "ignore" }).unref();
11870
+ return;
11871
+ }
11872
+ }
11873
+ console.log(" Could not find Revit.exe. Start Revit yourself and this will pick it up.");
11874
+ }
11792
11875
  const REVIT_ADDIN_PACKAGE = "@thatopen-platform/flow-revit";
11793
11876
  const revitCommand = new Command2("revit").description(
11794
11877
  "Revit collaboration — drive the That Open Revit add-in from the CLI"
@@ -11815,6 +11898,64 @@ revitCommand.command("inspect").description("Check whether a .rvt is already a w
11815
11898
  console.log("Ask the user for consent, then run publish-central with --convert.");
11816
11899
  }
11817
11900
  });
11901
+ revitCommand.command("share").description("Share a .rvt with your team: install if needed, publish the central, open your local").requiredOption("--file <path>", "Absolute path to the .rvt on this machine").requiredOption("--project <id>", "Platform project id (or the dashboard URL)").option("--doc <name>", "Short name for the shared central (default: from the file name)").action(async (opts) => {
11902
+ const cfg = requireResolvedConfig();
11903
+ const file = require$$2.resolve(opts.file);
11904
+ if (!require$$3.existsSync(file)) {
11905
+ console.error(`No such file: ${file}`);
11906
+ process.exit(1);
11907
+ }
11908
+ const project = opts.project.match(/projects\/([a-f0-9]{24})/i)?.[1] ?? opts.project;
11909
+ const doc = opts.doc ?? require$$2.basename(file, ".rvt").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
11910
+ console.log(`Sharing "${require$$2.basename(file)}" as "${doc}" in project ${project}.
11911
+ `);
11912
+ if (!await addinAlive()) {
11913
+ if (!isRevitRunning()) {
11914
+ console.log("[1/5] Installing the Revit add-in...");
11915
+ const client = new EngineServicesClient(cfg.accessToken, cfg.apiUrl);
11916
+ const { dir, cleanup } = await fetchPluginPackage(client, REVIT_ADDIN_PACKAGE, "latest");
11917
+ try {
11918
+ const installer = require$$2.join(dir, "install.ps1");
11919
+ if (!require$$3.existsSync(installer)) throw new Error("The package has no install.ps1.");
11920
+ require$$1.execFileSync("powershell", ["-ExecutionPolicy", "Bypass", "-File", installer], {
11921
+ stdio: "inherit"
11922
+ });
11923
+ } finally {
11924
+ cleanup();
11925
+ }
11926
+ console.log("[2/5] Starting Revit...");
11927
+ startRevit();
11928
+ } else {
11929
+ console.log("[1/5] Waiting for Revit...");
11930
+ }
11931
+ if (!await waitForAddin(3e5)) {
11932
+ console.error(
11933
+ '\nRevit did not report in. If it is open, the add-in may not be installed:\n close Revit, run "thatopen revit install", and start it again.'
11934
+ );
11935
+ process.exit(1);
11936
+ }
11937
+ } else {
11938
+ console.log("[1/5] Revit is open and the add-in is answering.");
11939
+ }
11940
+ await configureAddin(cfg.apiUrl, cfg.accessToken);
11941
+ console.log("[3/5] Checking the file...");
11942
+ const info = await callAddin("inspect", { file });
11943
+ console.log(
11944
+ info.isCentral ? " Already a workshared central. Publishing a copy of it." : " Not a central yet. Converting a COPY. Your file is not modified."
11945
+ );
11946
+ console.log("[4/5] Publishing to the platform (Revit is saving the central, this takes a bit)...");
11947
+ const pub = await callAddin("publish-central", { project, doc, file, convert: true });
11948
+ console.log("[5/5] Creating and opening your local...");
11949
+ const joined = await callAddin("join", { project, doc });
11950
+ console.log(`
11951
+ Shared. Central "${doc}" is v${pub.version} in project ${project}.`);
11952
+ console.log(`Your local, now open in Revit:
11953
+ ${joined.local}`);
11954
+ console.log(`
11955
+ Teammates join with:
11956
+ thatopen revit join --project ${project} --doc ${doc}`);
11957
+ console.log(`From here, Revit's own Synchronize with Central publishes to the team.`);
11958
+ });
11818
11959
  revitCommand.command("publish-central").description("Turn a .rvt into a shared central and upload it to a project").requiredOption("--project <id>", "Platform project id").requiredOption("--doc <name>", "A short name for this central (e.g. tower-central)").requiredOption("--file <path>", "Absolute path to the source .rvt on this machine").option("--convert", "Allow converting a non-central .rvt into one (on a copy; original untouched)").action(async (opts) => {
11819
11960
  await connect();
11820
11961
  console.log(`Publishing "${opts.file}" as central "${opts.doc}"...`);
@@ -207,6 +207,12 @@ export declare class EngineServicesClient {
207
207
  * the new token is picked up on every request — expired tokens no
208
208
  * longer stick around.
209
209
  */
210
+ /**
211
+ * Socket origin without namespace or query, for gateways other than the
212
+ * execution one. `wsUrl` already carries a token that may be stale when a
213
+ * provider is in play, so callers append their own.
214
+ */
215
+ protected get socketOrigin(): string;
210
216
  protected resolveAccessToken(): Promise<string>;
211
217
  /**
212
218
  * Protected extension hook for subclasses (e.g. `PlatformClient`) that
@@ -1,6 +1,33 @@
1
1
  import { EngineServicesClient, EngineServicesClientProps } from './client';
2
2
  import { Project, ProjectData } from '../types/projects';
3
+ import { MarkNotificationsReadResultDto, NotificationPageDto, NotificationSubscriptionView } from '../types/notifications';
3
4
 
5
+ /**
6
+ * What arrived on the socket. One callback for all three because a bell
7
+ * reacts the same way to each: refresh the badge, and the list if open.
8
+ *
9
+ * `allRead` is one event for the whole sweep rather than one per
10
+ * notification, so do not expect an id on it.
11
+ */
12
+ export type LiveNotificationEvent = {
13
+ type: 'created';
14
+ id: string;
15
+ } | {
16
+ type: 'read';
17
+ id: string;
18
+ } | {
19
+ type: 'allRead';
20
+ batch: number;
21
+ };
22
+ /** Per-automation opt-in. `failures` skips started events entirely. */
23
+ export type NotificationSubscriptionFilterInput = 'all' | 'failures';
24
+ export interface NotificationSubscriptionInput {
25
+ filter?: NotificationSubscriptionFilterInput;
26
+ /** Overrides the account-level channel setting for this automation only. */
27
+ channels?: {
28
+ email?: boolean;
29
+ };
30
+ }
4
31
  /** Scope by which a permission was granted (or `'none'` if denied). */
5
32
  export type PermissionScope = 'global' | 'project' | 'entity' | 'none';
6
33
  /** Result of a single permission check. */
@@ -110,4 +137,116 @@ export declare class PlatformClient extends EngineServicesClient {
110
137
  * single round-trip; results come back in the same order as `checks`.
111
138
  */
112
139
  checkPermissionBatch(checks: PermissionCheckEntry[]): Promise<PermissionCheckResult[]>;
140
+ /**
141
+ * Lists the signed-in user's notifications, newest first.
142
+ *
143
+ * Scoped to whoever the bearer token belongs to — an app cannot read
144
+ * anyone else's. Muted notifications are included: muting silences the
145
+ * badge and the delivery channels, it does not hide the record.
146
+ *
147
+ * Paginate by passing the previous response's `nextCursor` back in; it is
148
+ * opaque, so do not build one by hand. A null `nextCursor` means the last
149
+ * page.
150
+ *
151
+ * @example Walk every page:
152
+ * ```ts
153
+ * let cursor: string | undefined;
154
+ * do {
155
+ * const page = await client.getNotifications({ cursor });
156
+ * render(page.items);
157
+ * cursor = page.nextCursor ?? undefined;
158
+ * } while (cursor);
159
+ * ```
160
+ */
161
+ getNotifications(params?: {
162
+ cursor?: string;
163
+ limit?: number;
164
+ }): Promise<NotificationPageDto>;
165
+ /**
166
+ * Number of unread notifications, excluding muted ones. This is the bell
167
+ * badge count, so it is cheap to poll.
168
+ */
169
+ getUnreadNotificationCount(): Promise<number>;
170
+ /**
171
+ * Marks specific notifications as read. Ids the caller does not own are
172
+ * ignored rather than rejected, so `updated` can be lower than the number
173
+ * passed in.
174
+ */
175
+ markNotificationsRead(notificationIds: string[]): Promise<MarkNotificationsReadResultDto>;
176
+ /** Marks every unread notification as read in one call. */
177
+ markAllNotificationsRead(): Promise<MarkNotificationsReadResultDto>;
178
+ /**
179
+ * The automations this user has subscribed to. Nobody is subscribed by
180
+ * default, so an empty list is the normal state.
181
+ */
182
+ getNotificationSubscriptions(): Promise<NotificationSubscriptionView[]>;
183
+ /**
184
+ * Removes this user's subscription to one automation.
185
+ *
186
+ * Unlike subscribing, which happens through the project routes, this works
187
+ * for any automation the user is subscribed to and keeps working after the
188
+ * automation itself is gone — opting out must never be the thing that
189
+ * fails.
190
+ */
191
+ unsubscribeFromAutomation(hookId: string): Promise<{
192
+ unsubscribed: boolean;
193
+ }>;
194
+ /**
195
+ * Subscribes the signed-in user to one project automation's runs.
196
+ *
197
+ * Nobody is subscribed by default, so this is what makes an automation
198
+ * produce notifications for this user at all. Subscribing again is
199
+ * harmless: it updates the existing subscription rather than duplicating.
200
+ *
201
+ * Read-level access to the project is enough. Someone who can see an
202
+ * automation can follow it without being able to change it.
203
+ *
204
+ * @example Follow only the failures, and email me about them:
205
+ * ```ts
206
+ * await client.subscribeToAutomation(projectId, hookId, {
207
+ * filter: 'failures',
208
+ * channels: { email: true },
209
+ * });
210
+ * ```
211
+ */
212
+ subscribeToAutomation(projectId: string, hookId: string, input?: NotificationSubscriptionInput): Promise<{
213
+ subscribed: true;
214
+ }>;
215
+ /**
216
+ * Changes an existing subscription. Only the fields passed are touched, so
217
+ * sending `channels` alone leaves the filter as it was.
218
+ *
219
+ * Throws if the user is not subscribed; use {@link subscribeToAutomation}
220
+ * to create one.
221
+ */
222
+ updateAutomationSubscription(projectId: string, hookId: string, changes: NotificationSubscriptionInput): Promise<{
223
+ updated: true;
224
+ }>;
225
+ /**
226
+ * Listens for this user's notifications in real time.
227
+ *
228
+ * Unlike {@link EngineServicesClient.onExecutionProgress}, which follows one
229
+ * execution and closes when it ends, this stays connected for the session:
230
+ * the server puts the socket in a room for the signed-in account and pushes
231
+ * anything addressed to them.
232
+ *
233
+ * The events carry ids rather than the notifications themselves, so treat
234
+ * them as a signal to refresh. That keeps a burst cheap and means the
235
+ * server is never the source of a stale render.
236
+ *
237
+ * Requires a user JWT. An API access token is rejected by the gateway,
238
+ * because it identifies a token rather than a person.
239
+ *
240
+ * @returns a function that disconnects. Call it on unmount.
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * const stop = await client.onNotification((event) => {
245
+ * if (event.type === 'created') refreshBell();
246
+ * });
247
+ * // later
248
+ * stop();
249
+ * ```
250
+ */
251
+ onNotification(onEvent: (event: LiveNotificationEvent) => void): Promise<() => void>;
113
252
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};