@serverkgg/bridge 0.0.8 → 0.0.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.
Files changed (48) hide show
  1. package/package.json +3 -3
  2. package/src/cli/validate.ts +1 -0
  3. package/src/context/bridgeContext.ts +10 -0
  4. package/src/context/index.ts +1 -0
  5. package/src/core/bridgeDownloadError.ts +10 -0
  6. package/src/core/bridgeHold.ts +12 -0
  7. package/src/core/bridgeKind.ts +2 -0
  8. package/src/core/bridgeUserError.test.ts +23 -0
  9. package/src/core/bridgeUserError.ts +12 -0
  10. package/src/core/index.ts +3 -0
  11. package/src/manifest/index.ts +1 -0
  12. package/src/manifest/manifest.test.ts +85 -0
  13. package/src/manifest/manifest.ts +21 -0
  14. package/src/manifest/presence.ts +48 -0
  15. package/src/manifest/primitives.ts +6 -0
  16. package/src/modules/bridge.ts +45 -2
  17. package/src/modules/bridgeDriver.ts +2 -0
  18. package/src/modules/bridgeModule.ts +25 -3
  19. package/src/panel/bridgeDetail.ts +49 -0
  20. package/src/panel/bridgeLayout.ts +1 -0
  21. package/src/panel/bridgePanel.ts +30 -0
  22. package/src/panel/bridgeUploadMode.ts +4 -0
  23. package/src/panel/index.ts +2 -0
  24. package/src/protocol/bridgeEgress.ts +8 -1
  25. package/src/protocol/bridgeEvents.ts +4 -0
  26. package/src/protocol/bridgeFrame.ts +28 -1
  27. package/src/protocol/bridgeLimits.ts +10 -0
  28. package/src/runtime/bridgeActions.ts +7 -0
  29. package/src/runtime/bridgeDriver.test.ts +477 -2
  30. package/src/runtime/bridgeDriver.ts +116 -3
  31. package/src/runtime/bridgeEgress.ts +3 -0
  32. package/src/runtime/bridgeEvents.ts +7 -5
  33. package/src/runtime/bridgeFiles.test.ts +625 -15
  34. package/src/runtime/bridgeFiles.ts +152 -45
  35. package/src/runtime/bridgeInstall.test.ts +118 -0
  36. package/src/runtime/bridgeInstall.ts +55 -0
  37. package/src/runtime/bridgeNet.ts +32 -3
  38. package/src/runtime/bridgePending.test.ts +148 -0
  39. package/src/runtime/bridgePending.ts +43 -0
  40. package/src/runtime/bridgeRegistry.test.ts +134 -0
  41. package/src/runtime/bridgeRegistry.ts +26 -4
  42. package/src/runtime/bridgeServer.test.ts +338 -0
  43. package/src/runtime/bridgeServer.ts +25 -3
  44. package/src/runtime/bridgeSupervisor.ts +18 -0
  45. package/src/runtime/bridgeZip.ts +179 -58
  46. package/src/runtime/serve.ts +64 -4
  47. package/src/wire/panel.test.ts +166 -0
  48. package/src/wire/panel.ts +32 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@serverkgg/bridge",
3
- "version": "0.0.8",
4
- "description": "Serverk game bridge — driver runtime, wire protocol, manifest schema, and maintainer CLI",
3
+ "version": "0.0.9",
4
+ "description": "Serverk game bridge and driver runtime, wire protocol, manifest schema, and maintainer CLI",
5
5
  "homepage": "https://serverk.gg",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
7
7
  "repository": {
@@ -29,6 +29,6 @@
29
29
  "test": "bun test --parallel"
30
30
  },
31
31
  "dependencies": {
32
- "zod": "4.4.3"
32
+ "zod": "4.5.4"
33
33
  }
34
34
  }
@@ -229,6 +229,7 @@ const validateGamePackage = async (source: PackageSource, manifest: Manifest, di
229
229
  console.log(
230
230
  ` driver: ${Object.keys(driver.modules ?? {}).length} panel module(s), ${driver.panel?.tabs.length ?? 0} tab(s)`,
231
231
  );
232
+ console.log(` pending: ${driver.pending ? `declared (${driver.pending.extensions.length} extension(s))` : "none"}`);
232
233
  reportEvents(driver);
233
234
  reportActionTargets(driver);
234
235
  };
@@ -1,4 +1,5 @@
1
1
  import type { BridgeStream, BridgeValues } from "../core";
2
+ import type { BridgeHttpMethod } from "../protocol";
2
3
 
3
4
  export interface BridgeServer {
4
5
  code: string;
@@ -15,16 +16,22 @@ export interface BridgeFileEntry {
15
16
  modifiedAt: string;
16
17
  }
17
18
 
19
+ export type BridgeDigestAlgorithm = "sha1" | "sha256" | "sha512";
20
+
18
21
  export interface BridgeDownloadOptions {
19
22
  digest?: string;
20
23
  sizeBytes?: number;
21
24
  cache?: boolean;
25
+ quiet?: boolean;
26
+ timeoutMs?: number;
22
27
  }
23
28
 
24
29
  export interface BridgeExtractOptions {
25
30
  extensions?: string[];
26
31
  tree?: boolean;
27
32
  select?: string;
33
+ selects?: string[];
34
+ exclude?: string[];
28
35
  }
29
36
 
30
37
  export interface BridgeFiles {
@@ -36,6 +43,7 @@ export interface BridgeFiles {
36
43
  ensure(...paths: string[]): Promise<void>;
37
44
  exists(path: string): Promise<boolean>;
38
45
  size(path: string): Promise<number>;
46
+ digest(path: string, algorithm: BridgeDigestAlgorithm): Promise<string>;
39
47
  download(path: string, url: string, options?: BridgeDownloadOptions): Promise<void>;
40
48
  extract(path: string, destination: string, options?: BridgeExtractOptions): Promise<string[]>;
41
49
  }
@@ -57,6 +65,8 @@ export interface BridgeCodecs {
57
65
  }
58
66
 
59
67
  export interface BridgeRequestOptions {
68
+ httpMethod?: BridgeHttpMethod;
69
+ body?: string;
60
70
  headers?: Record<string, string>;
61
71
  timeoutMs?: number;
62
72
  cache?: boolean;
@@ -1 +1,2 @@
1
+ export { BridgeHttpMethod } from "../protocol";
1
2
  export * from "./bridgeContext";
@@ -0,0 +1,10 @@
1
+ export class BridgeDownloadError extends Error {
2
+ readonly status: number | null;
3
+
4
+ constructor(status: number | null, message: string) {
5
+ super(message);
6
+
7
+ this.name = "BridgeDownloadError";
8
+ this.status = status;
9
+ }
10
+ }
@@ -0,0 +1,12 @@
1
+ export enum BridgeHoldReason {
2
+ PendingFiles = "pending-files",
3
+ }
4
+
5
+ export interface BridgeInstallHold {
6
+ reason: BridgeHoldReason;
7
+ count: number;
8
+ }
9
+
10
+ export interface BridgeInstallOutcome {
11
+ hold: BridgeInstallHold | null;
12
+ }
@@ -4,11 +4,13 @@ export enum BridgeKind {
4
4
  Backup = "backup",
5
5
  Catalog = "catalog",
6
6
  Collection = "collection",
7
+ Detail = "detail",
7
8
  Terminal = "terminal",
8
9
  Events = "events",
9
10
  Install = "install",
10
11
  Lifecycle = "lifecycle",
11
12
  Options = "options",
13
+ Pending = "pending",
12
14
  Preview = "preview",
13
15
  Query = "query",
14
16
  Settings = "settings",
@@ -0,0 +1,23 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { Bridge } from "../modules";
3
+ import { BridgeUserError } from "./bridgeUserError";
4
+
5
+ describe("BridgeUserError", () => {
6
+ const text = {
7
+ ar: "هذا مو الملف اللي ننتظره",
8
+ en: "this is not a file we are waiting for",
9
+ };
10
+
11
+ test("reads as english in a log line and keeps both languages for the player", () => {
12
+ const error = new BridgeUserError(text);
13
+
14
+ expect(error).toBeInstanceOf(Error);
15
+ expect(error.name).toBe("BridgeUserError");
16
+ expect(error.message).toBe(text.en);
17
+ expect(error.text).toEqual(text);
18
+ });
19
+
20
+ test("is the class a driver reaches through the published namespace", () => {
21
+ expect(new Bridge.UserError(text)).toBeInstanceOf(BridgeUserError);
22
+ });
23
+ });
@@ -0,0 +1,12 @@
1
+ import type { LocalizedText } from "./bridgeValue";
2
+
3
+ export class BridgeUserError extends Error {
4
+ readonly text: LocalizedText;
5
+
6
+ constructor(text: LocalizedText) {
7
+ super(text.en);
8
+
9
+ this.name = "BridgeUserError";
10
+ this.text = text;
11
+ }
12
+ }
package/src/core/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from "./bridgeAction";
2
+ export * from "./bridgeDownloadError";
2
3
  export * from "./bridgeFailure";
4
+ export * from "./bridgeHold";
3
5
  export * from "./bridgeId";
4
6
  export * from "./bridgeKind";
5
7
  export * from "./bridgeNetError";
@@ -7,4 +9,5 @@ export * from "./bridgePort";
7
9
  export * from "./bridgeRuntime";
8
10
  export * from "./bridgeSecretError";
9
11
  export * from "./bridgeStream";
12
+ export * from "./bridgeUserError";
10
13
  export * from "./bridgeValue";
@@ -6,5 +6,6 @@ export * from "./image";
6
6
  export * from "./jsonSchema";
7
7
  export * from "./localized";
8
8
  export * from "./manifest";
9
+ export * from "./presence";
9
10
  export * from "./primitives";
10
11
  export * from "./source";
@@ -586,3 +586,88 @@ describe("packageSchema", () => {
586
586
  expect(result.error && z.prettifyError(result.error)).toContain('tag "start" is not declared in guideTags');
587
587
  });
588
588
  });
589
+
590
+ describe("presence", () => {
591
+ test("is optional, so a game that declares none parses", () => {
592
+ const result = manifestSchema.safeParse(base());
593
+
594
+ expect(result.success).toBe(true);
595
+ expect(result.success && result.data.presence).toBeUndefined();
596
+ });
597
+
598
+ test("defaults the name key to player and the field list to empty", () => {
599
+ const result = manifestSchema.safeParse({
600
+ ...base(),
601
+ presence: {},
602
+ });
603
+
604
+ expect(result.success).toBe(true);
605
+ expect(result.success && result.data.presence?.name).toBe("player");
606
+ expect(result.success && result.data.presence?.fields).toEqual([]);
607
+ });
608
+
609
+ test("accepts an avatar template and localized fields", () => {
610
+ const result = manifestSchema.safeParse({
611
+ ...base(),
612
+ presence: {
613
+ name: "player",
614
+ id: "uuid",
615
+ avatar: "https://mc-heads.net/avatar/{skin}/128",
616
+ fields: [
617
+ {
618
+ key: "platform",
619
+ label: text("Platform"),
620
+ },
621
+ ],
622
+ },
623
+ });
624
+
625
+ expect(result.success).toBe(true);
626
+ expect(result.success && result.data.presence?.fields[0]?.inline).toBe(true);
627
+ });
628
+
629
+ test("refuses an avatar template that is not https", () => {
630
+ const result = manifestSchema.safeParse({
631
+ ...base(),
632
+ presence: {
633
+ avatar: "http://mc-heads.net/avatar/{skin}/128",
634
+ },
635
+ });
636
+
637
+ expect(result.success).toBe(false);
638
+ expect(result.error && z.prettifyError(result.error)).toContain("must be an https url template");
639
+ });
640
+
641
+ test("refuses a payload key that could never be a capture group", () => {
642
+ const result = manifestSchema.safeParse({
643
+ ...base(),
644
+ presence: {
645
+ name: "player name",
646
+ },
647
+ });
648
+
649
+ expect(result.success).toBe(false);
650
+ expect(result.error && z.prettifyError(result.error)).toContain("must be a valid capture group name");
651
+ });
652
+
653
+ test("refuses the same field declared twice", () => {
654
+ const result = manifestSchema.safeParse({
655
+ ...base(),
656
+ presence: {
657
+ fields: [
658
+ {
659
+ key: "platform",
660
+ label: text("Platform"),
661
+ },
662
+ {
663
+ key: "platform",
664
+ label: text("Platform again"),
665
+ },
666
+ ],
667
+ },
668
+ });
669
+
670
+ expect(result.success).toBe(false);
671
+ expect(result.error && z.prettifyError(result.error)).toContain('"platform" is declared more than once');
672
+ });
673
+ });
@@ -6,6 +6,7 @@ import { GameCategory } from "./category";
6
6
  import { type GuideEntry, type GuideTag, guideEntrySchema, guideTagSchema } from "./guide";
7
7
  import { IMAGE_NAMESPACE } from "./image";
8
8
  import { localizedText } from "./localized";
9
+ import { type ManifestPresence, manifestPresenceSchema } from "./presence";
9
10
  import {
10
11
  HOST_RANGE_MAX_SIZE,
11
12
  zAssetPath,
@@ -205,6 +206,24 @@ const duplicateIssues = <Item>(
205
206
  }));
206
207
  };
207
208
 
209
+ const presenceIssues = (presence: ManifestPresence | undefined): CheckIssue[] => {
210
+ if (!presence) {
211
+ return [];
212
+ }
213
+
214
+ return duplicateIssues(
215
+ presence.fields,
216
+ (field) => field.key,
217
+ (index) => [
218
+ "presence",
219
+ "fields",
220
+ index,
221
+ "key",
222
+ ],
223
+ (key) => `"${key}" is declared more than once`,
224
+ );
225
+ };
226
+
208
227
  const hostRangesOverlap = (a: ManifestPort, b: ManifestPort) => {
209
228
  return a.hostRange.min <= b.hostRange.max && b.hostRange.min <= a.hostRange.max;
210
229
  };
@@ -355,6 +374,7 @@ export const manifestSchema = z
355
374
  reset: manifestResetSchema.prefault({}),
356
375
  secrets: z.array(manifestSecretSchema).max(16).default([]),
357
376
  files: manifestFilesSchema.prefault({}),
377
+ presence: manifestPresenceSchema.optional(),
358
378
  })
359
379
  .check((ctx) => {
360
380
  if (ctx.issues.length > 0) {
@@ -410,6 +430,7 @@ export const manifestSchema = z
410
430
  (key) => `"${key}" already has a note`,
411
431
  ),
412
432
  ...guideIssues(manifest.guideTags, manifest.guides),
433
+ ...presenceIssues(manifest.presence),
413
434
  );
414
435
  });
415
436
 
@@ -0,0 +1,48 @@
1
+ import { z } from "zod";
2
+ import { localizedText } from "./localized";
3
+ import { zPayloadKey } from "./primitives";
4
+
5
+ export const PRESENCE_FIELDS_LIMIT = 6;
6
+
7
+ export const PRESENCE_AVATAR_LENGTH_LIMIT = 300;
8
+
9
+ export const manifestPresenceFieldSchema = z.object({
10
+ key: zPayloadKey,
11
+ label: localizedText(32),
12
+ inline: z.boolean().default(true),
13
+ });
14
+
15
+ export type ManifestPresenceField = z.infer<typeof manifestPresenceFieldSchema>;
16
+
17
+ export const manifestPresenceSchema = z.object({
18
+ name: zPayloadKey.default("player"),
19
+ id: zPayloadKey.optional(),
20
+ avatar: z
21
+ .string()
22
+ .max(PRESENCE_AVATAR_LENGTH_LIMIT)
23
+ .regex(/^https:\/\//, "must be an https url template")
24
+ .optional(),
25
+ fields: z.array(manifestPresenceFieldSchema).max(PRESENCE_FIELDS_LIMIT).default([]),
26
+ });
27
+
28
+ export type ManifestPresence = z.infer<typeof manifestPresenceSchema>;
29
+
30
+ export const PRESENCE_PLACEHOLDER = /\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
31
+
32
+ export const presencePlaceholders = (template: string): string[] => {
33
+ PRESENCE_PLACEHOLDER.lastIndex = 0;
34
+
35
+ const names: string[] = [];
36
+
37
+ let match = PRESENCE_PLACEHOLDER.exec(template);
38
+
39
+ while (match) {
40
+ if (match[1]) {
41
+ names.push(match[1]);
42
+ }
43
+
44
+ match = PRESENCE_PLACEHOLDER.exec(template);
45
+ }
46
+
47
+ return names;
48
+ };
@@ -26,6 +26,12 @@ export const zEnvKey = z
26
26
  .max(32)
27
27
  .regex(/^[A-Z][A-Z0-9_]*$/, "must be SCREAMING_SNAKE_CASE, starting with a letter");
28
28
 
29
+ export const zPayloadKey = z
30
+ .string()
31
+ .min(1)
32
+ .max(64)
33
+ .regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "must be a valid capture group name, starting with a letter or underscore");
34
+
29
35
  export const zImageReference = z.templateLiteral([
30
36
  `${IMAGE_NAMESPACE}/`,
31
37
  zSlug,
@@ -1,6 +1,29 @@
1
1
  import type { BridgeContext, BridgeExecResult, BridgeFileEntry, BridgeReply } from "../context";
2
- import type { BridgeOption, BridgeRow, BridgeValue, BridgeValues, LocalizedText } from "../core";
3
- import type { BridgeField, BridgePanel, BridgeSection, BridgeSectionAction, BridgeTab } from "../panel";
2
+ import type {
3
+ BridgeInstallHold,
4
+ BridgeInstallOutcome,
5
+ BridgeOption,
6
+ BridgeRow,
7
+ BridgeValue,
8
+ BridgeValues,
9
+ LocalizedText,
10
+ } from "../core";
11
+ import { BridgeUserError } from "../core";
12
+ import type {
13
+ BridgeDetail,
14
+ BridgeDetailBadge,
15
+ BridgeDetailLink,
16
+ BridgeDetailOutcome,
17
+ BridgeDetailStat,
18
+ BridgeField,
19
+ BridgeFormClear,
20
+ BridgePanel,
21
+ BridgeSection,
22
+ BridgeSectionAction,
23
+ BridgeTab,
24
+ BridgeTabRef,
25
+ BridgeUploadMode,
26
+ } from "../panel";
4
27
  import type {
5
28
  BridgeCatalogEntry,
6
29
  BridgeCatalogFacet,
@@ -9,6 +32,7 @@ import type {
9
32
  BridgeCatalogProvider,
10
33
  BridgeCatalogQuery,
11
34
  } from "../protocol/bridgeCatalog";
35
+ import type { BridgePendingFile } from "../protocol/bridgeFrame";
12
36
  import type { BridgePreview } from "../protocol/bridgePreview";
13
37
  import type { BridgeTerminalArg, BridgeTerminalCommand, BridgeTerminalRule } from "../terminal";
14
38
  import type { BridgeDriver } from "./bridgeDriver";
@@ -18,11 +42,14 @@ import type {
18
42
  BridgeBackupModule,
19
43
  BridgeCatalogModule,
20
44
  BridgeCollectionModule,
45
+ BridgeDetailAction,
46
+ BridgeDetailModule,
21
47
  BridgeEventPattern,
22
48
  BridgeEventsModule,
23
49
  BridgeInstallModule,
24
50
  BridgeLifecycleModule,
25
51
  BridgeOptionsModule,
52
+ BridgePendingModule,
26
53
  BridgePreviewModule,
27
54
  BridgeQueryModule,
28
55
  BridgeRowAction,
@@ -45,17 +72,29 @@ export namespace Bridge {
45
72
  export type CatalogQuery = BridgeCatalogQuery;
46
73
  export type Collection<Row extends BridgeRow = BridgeRow> = BridgeCollectionModule<Row>;
47
74
  export type Context = BridgeContext;
75
+ export type Detail = BridgeDetailModule;
76
+ export type DetailAction = BridgeDetailAction;
77
+ export type DetailBadge = BridgeDetailBadge;
78
+ export type DetailLink = BridgeDetailLink;
79
+ export type DetailOutcome = BridgeDetailOutcome;
80
+ export type DetailResult = BridgeDetail;
81
+ export type DetailStat = BridgeDetailStat;
48
82
  export type Driver = BridgeDriver;
49
83
  export type EventPattern = BridgeEventPattern;
50
84
  export type Events = BridgeEventsModule;
51
85
  export type ExecResult = BridgeExecResult;
52
86
  export type Field = BridgeField;
53
87
  export type FileEntry = BridgeFileEntry;
88
+ export type FormClear = BridgeFormClear;
54
89
  export type Install = BridgeInstallModule;
90
+ export type InstallHold = BridgeInstallHold;
91
+ export type InstallOutcome = BridgeInstallOutcome;
55
92
  export type Lifecycle = BridgeLifecycleModule;
56
93
  export type Option = BridgeOption;
57
94
  export type Options = BridgeOptionsModule;
58
95
  export type Panel = BridgePanel;
96
+ export type Pending = BridgePendingModule;
97
+ export type PendingFile = BridgePendingFile;
59
98
  export type Preview = BridgePreviewModule;
60
99
  export type PreviewResult = BridgePreview;
61
100
  export type Query = BridgeQueryModule;
@@ -72,6 +111,10 @@ export namespace Bridge {
72
111
  export type TerminalCommand = BridgeTerminalCommand;
73
112
  export type TerminalRule = BridgeTerminalRule;
74
113
  export type Tab = BridgeTab;
114
+ export type TabRef = BridgeTabRef;
115
+ export type UploadMode = BridgeUploadMode;
116
+ export const UserError = BridgeUserError;
117
+ export type UserError = BridgeUserError;
75
118
  export type Text = LocalizedText;
76
119
  export type Value = BridgeValue;
77
120
  export type Values = BridgeValues;
@@ -6,6 +6,7 @@ import type {
6
6
  BridgeInstallModule,
7
7
  BridgeLifecycleModule,
8
8
  BridgePanelModule,
9
+ BridgePendingModule,
9
10
  BridgeQueryModule,
10
11
  BridgeTerminalModule,
11
12
  } from "./bridgeModule";
@@ -17,6 +18,7 @@ export interface BridgeDriver {
17
18
  query?: BridgeQueryModule;
18
19
  backup?: BridgeBackupModule;
19
20
  announce?: BridgeAnnounceModule;
21
+ pending?: BridgePendingModule;
20
22
  terminal?: BridgeTerminalModule;
21
23
  panel?: BridgePanel;
22
24
  modules?: Record<string, BridgePanelModule>;
@@ -1,7 +1,8 @@
1
1
  import type { BridgeContext } from "../context";
2
- import type { BridgeKind, BridgeOption, BridgeRow, BridgeStream, BridgeValues } from "../core";
2
+ import type { BridgeInstallOutcome, BridgeKind, BridgeOption, BridgeRow, BridgeStream, BridgeValues } from "../core";
3
+ import type { BridgeDetail, BridgeDetailOutcome } from "../panel";
3
4
  import type { BridgeCatalogEntry, BridgeCatalogPage, BridgeCatalogQuery } from "../protocol/bridgeCatalog";
4
- import type { BridgeDescribeResult } from "../protocol/bridgeFrame";
5
+ import type { BridgeDescribeResult, BridgePendingFile } from "../protocol/bridgeFrame";
5
6
  import type { BridgePreview } from "../protocol/bridgePreview";
6
7
  import type { BridgeTerminalCommand, BridgeTerminalRule } from "../terminal";
7
8
 
@@ -9,7 +10,7 @@ export type BridgeDescription = Omit<BridgeDescribeResult, "supported">;
9
10
 
10
11
  export interface BridgeInstallModule {
11
12
  kind: BridgeKind.Install;
12
- run(context: BridgeContext): Promise<void>;
13
+ run(context: BridgeContext): Promise<BridgeInstallOutcome | undefined>;
13
14
  describe?(context: BridgeContext): Promise<BridgeDescription>;
14
15
  }
15
16
 
@@ -34,6 +35,14 @@ export interface BridgeBackupModule {
34
35
  release?(context: BridgeContext): Promise<void>;
35
36
  }
36
37
 
38
+ export interface BridgePendingModule {
39
+ kind: BridgeKind.Pending;
40
+ staging: string;
41
+ extensions: string[];
42
+ list(context: BridgeContext): Promise<BridgePendingFile[]>;
43
+ add(context: BridgeContext, input: string): Promise<void>;
44
+ }
45
+
37
46
  export interface BridgeSample {
38
47
  online: number | null;
39
48
  max: number | null;
@@ -49,6 +58,7 @@ export interface BridgeEventPattern {
49
58
  match: RegExp;
50
59
  emit: string;
51
60
  stream?: BridgeStream;
61
+ payload?: BridgeValues;
52
62
  }
53
63
 
54
64
  export interface BridgeEventsModule {
@@ -104,6 +114,16 @@ export interface BridgeCollectionModule<Row extends BridgeRow = BridgeRow> {
104
114
  actions?: Record<string, BridgeRowAction<Row>>;
105
115
  }
106
116
 
117
+ export type BridgeDetailAction = (context: BridgeContext, args: BridgeValues) => Promise<BridgeDetailOutcome | null>;
118
+
119
+ export interface BridgeDetailModule {
120
+ kind: BridgeKind.Detail;
121
+ requiresRunning?: boolean;
122
+ refreshSeconds?: number;
123
+ read(context: BridgeContext): Promise<BridgeDetail | null>;
124
+ actions?: Record<string, BridgeDetailAction>;
125
+ }
126
+
107
127
  export interface BridgeCatalogModule {
108
128
  kind: BridgeKind.Catalog;
109
129
  pageSize?: number;
@@ -118,6 +138,7 @@ export type BridgePanelModule =
118
138
  | BridgeActionsModule
119
139
  | BridgeCatalogModule
120
140
  | BridgeCollectionModule
141
+ | BridgeDetailModule
121
142
  | BridgeOptionsModule
122
143
  | BridgePreviewModule
123
144
  | BridgeSettingsModule;
@@ -130,4 +151,5 @@ export type BridgeModule =
130
151
  | BridgeInstallModule
131
152
  | BridgeLifecycleModule
132
153
  | BridgePanelModule
154
+ | BridgePendingModule
133
155
  | BridgeQueryModule;
@@ -0,0 +1,49 @@
1
+ import type { BridgeValue, LocalizedText } from "../core";
2
+
3
+ export enum BridgeDetailTone {
4
+ Neutral = "neutral",
5
+ Success = "success",
6
+ Warning = "warning",
7
+ Danger = "danger",
8
+ }
9
+
10
+ export enum BridgeDetailFormat {
11
+ Bytes = "bytes",
12
+ Date = "date",
13
+ Number = "number",
14
+ Text = "text",
15
+ }
16
+
17
+ export interface BridgeDetailBadge {
18
+ label: LocalizedText;
19
+ tone: BridgeDetailTone;
20
+ }
21
+
22
+ export interface BridgeDetailStat {
23
+ key: string;
24
+ label: LocalizedText;
25
+ value: BridgeValue;
26
+ format: BridgeDetailFormat;
27
+ }
28
+
29
+ export interface BridgeDetailLink {
30
+ label: LocalizedText;
31
+ url: string;
32
+ }
33
+
34
+ export interface BridgeDetail {
35
+ id: string;
36
+ title: string;
37
+ subtitle: string | null;
38
+ description: string | null;
39
+ image: string | null;
40
+ badges: BridgeDetailBadge[];
41
+ stats: BridgeDetailStat[];
42
+ links: BridgeDetailLink[];
43
+ stale: boolean;
44
+ actions: string[];
45
+ }
46
+
47
+ export interface BridgeDetailOutcome {
48
+ variables: Record<string, string> | null;
49
+ }
@@ -2,6 +2,7 @@ export enum BridgeLayout {
2
2
  Actions = "actions",
3
3
  Cards = "cards",
4
4
  Catalog = "catalog",
5
+ Detail = "detail",
5
6
  Form = "form",
6
7
  Table = "table",
7
8
  }
@@ -5,6 +5,7 @@ import type { BridgeControl } from "./bridgeControl";
5
5
  import type { BridgeFormTarget } from "./bridgeFormTarget";
6
6
  import type { BridgeIcon } from "./bridgeIcon";
7
7
  import type { BridgeLayout } from "./bridgeLayout";
8
+ import type { BridgeUploadMode } from "./bridgeUploadMode";
8
9
 
9
10
  export interface BridgeVisibleWhen {
10
11
  variable: string;
@@ -44,6 +45,16 @@ export interface BridgeSectionAction {
44
45
  fields?: BridgeField[];
45
46
  }
46
47
 
48
+ export interface BridgeTabRef {
49
+ tab: string;
50
+ label: LocalizedText;
51
+ }
52
+
53
+ export interface BridgeFormClear {
54
+ variable: string;
55
+ warning: LocalizedText;
56
+ }
57
+
47
58
  export interface BridgeSectionAdd {
48
59
  label: LocalizedText;
49
60
  placeholder?: string;
@@ -53,6 +64,7 @@ export interface BridgeSectionUpload {
53
64
  label: LocalizedText;
54
65
  extensions: string[];
55
66
  staging: string;
67
+ mode?: BridgeUploadMode;
56
68
  }
57
69
 
58
70
  export interface BridgeActionsSection {
@@ -78,6 +90,7 @@ export interface BridgeFormSection {
78
90
  confirm?: BridgeConfirm;
79
91
  confirmText?: LocalizedText;
80
92
  fields: BridgeField[];
93
+ clears?: BridgeFormClear[];
81
94
  visibleWhen?: BridgeVisibleWhen;
82
95
  access?: BridgeAccess;
83
96
  }
@@ -130,10 +143,27 @@ export interface BridgeCatalogSection {
130
143
  access?: BridgeAccess;
131
144
  }
132
145
 
146
+ export interface BridgeDetailSection {
147
+ layout: BridgeLayout.Detail;
148
+ id: string;
149
+ title?: LocalizedText;
150
+ module: string;
151
+ actions?: BridgeSectionAction[];
152
+ reinstall?: boolean;
153
+ variable?: string;
154
+ confirm?: BridgeConfirm;
155
+ confirmText?: LocalizedText;
156
+ related?: BridgeTabRef;
157
+ empty?: LocalizedText;
158
+ visibleWhen?: BridgeVisibleWhen;
159
+ access?: BridgeAccess;
160
+ }
161
+
133
162
  export type BridgeSection =
134
163
  | BridgeActionsSection
135
164
  | BridgeCardsSection
136
165
  | BridgeCatalogSection
166
+ | BridgeDetailSection
137
167
  | BridgeFormSection
138
168
  | BridgeTableSection;
139
169