@budibase/frontend-core 3.12.13 → 3.12.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@budibase/frontend-core",
3
- "version": "3.12.13",
3
+ "version": "3.12.14",
4
4
  "description": "Budibase frontend core libraries used in builder and client",
5
5
  "author": "Budibase",
6
6
  "license": "MPL-2.0",
@@ -17,5 +17,5 @@
17
17
  "shortid": "2.2.15",
18
18
  "socket.io-client": "^4.7.5"
19
19
  },
20
- "gitHead": "3b8f23325e52fad7328a51218d61c171232d9e02"
20
+ "gitHead": "dbe8dd62a6714a1bf0768542434fdeca2f81052b"
21
21
  }
package/src/api/agents.ts CHANGED
@@ -6,11 +6,18 @@ import {
6
6
  ChatAgentResponse,
7
7
  CreateToolSourceRequest,
8
8
  FetchAgentHistoryResponse,
9
+ LLMStreamChunk,
9
10
  } from "@budibase/types"
11
+
10
12
  import { BaseAPIClient } from "./types"
11
13
 
12
14
  export interface AgentEndpoints {
13
15
  agentChat: (chat: AgentChat) => Promise<ChatAgentResponse>
16
+ agentChatStream: (
17
+ chat: AgentChat,
18
+ onChunk: (chunk: LLMStreamChunk) => void,
19
+ onError?: (error: Error) => void
20
+ ) => Promise<void>
14
21
 
15
22
  removeChat: (historyId: string) => Promise<void>
16
23
  fetchChats: () => Promise<FetchAgentHistoryResponse>
@@ -32,6 +39,67 @@ export const buildAgentEndpoints = (API: BaseAPIClient): AgentEndpoints => ({
32
39
  })
33
40
  },
34
41
 
42
+ agentChatStream: async (chat, onChunk, onError) => {
43
+ const body: ChatAgentRequest = chat
44
+
45
+ try {
46
+ // TODO: add support for streaming into the frontend-core API object
47
+ const response = await fetch("/api/agent/chat/stream", {
48
+ method: "POST",
49
+ headers: {
50
+ "Content-Type": "application/json",
51
+ Accept: "application/json",
52
+ },
53
+ body: JSON.stringify(body),
54
+ credentials: "same-origin",
55
+ })
56
+
57
+ if (!response.ok) {
58
+ throw new Error(`HTTP error! status: ${response.status}`)
59
+ }
60
+
61
+ const reader = response.body?.getReader()
62
+ if (!reader) {
63
+ throw new Error("Failed to get response reader")
64
+ }
65
+
66
+ const decoder = new TextDecoder()
67
+ let buffer = ""
68
+
69
+ while (true) {
70
+ const { done, value } = await reader.read()
71
+
72
+ if (done) break
73
+
74
+ buffer += decoder.decode(value, { stream: true })
75
+
76
+ // Process complete lines
77
+ const lines = buffer.split("\n")
78
+ buffer = lines.pop() || "" // Keep incomplete line in buffer
79
+
80
+ for (const line of lines) {
81
+ if (line.startsWith("data: ")) {
82
+ try {
83
+ const data = line.slice(6) // Remove 'data: ' prefix
84
+ if (data.trim()) {
85
+ const chunk: LLMStreamChunk = JSON.parse(data)
86
+ onChunk(chunk)
87
+ }
88
+ } catch (parseError) {
89
+ console.error("Failed to parse SSE data:", parseError)
90
+ }
91
+ }
92
+ }
93
+ }
94
+ } catch (error: any) {
95
+ if (onError) {
96
+ onError(error)
97
+ } else {
98
+ throw error
99
+ }
100
+ }
101
+ },
102
+
35
103
  removeChat: async (historyId: string) => {
36
104
  return await API.delete({
37
105
  url: `/api/agent/history/${historyId}`,
package/src/api/app.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  GetDiagnosticsResponse,
16
16
  ImportToUpdateAppRequest,
17
17
  ImportToUpdateAppResponse,
18
+ PublishAppRequest,
18
19
  PublishAppResponse,
19
20
  RevertAppClientResponse,
20
21
  RevertAppResponse,
@@ -32,7 +33,10 @@ export interface AppEndpoints {
32
33
  metadata: UpdateAppRequest
33
34
  ) => Promise<UpdateAppResponse>
34
35
  unpublishApp: (appId: string) => Promise<UnpublishAppResponse>
35
- publishAppChanges: (appId: string) => Promise<PublishAppResponse>
36
+ publishAppChanges: (
37
+ appId: string,
38
+ opts?: PublishAppRequest
39
+ ) => Promise<PublishAppResponse>
36
40
  revertAppChanges: (appId: string) => Promise<RevertAppResponse>
37
41
  updateAppClientVersion: (appId: string) => Promise<UpdateAppClientResponse>
38
42
  revertAppClientVersion: (appId: string) => Promise<RevertAppClientResponse>
@@ -86,9 +90,10 @@ export const buildAppEndpoints = (API: BaseAPIClient): AppEndpoints => ({
86
90
  /**
87
91
  * Publishes the current app.
88
92
  */
89
- publishAppChanges: async appId => {
93
+ publishAppChanges: async (appId, opts) => {
90
94
  return await API.post({
91
95
  url: `/api/applications/${appId}/publish`,
96
+ body: opts,
92
97
  })
93
98
  },
94
99
 
package/src/api/auth.ts CHANGED
@@ -12,12 +12,16 @@ import {
12
12
  } from "@budibase/types"
13
13
  import { BaseAPIClient } from "./types"
14
14
 
15
+ export interface ExtendedLoginResponse extends LoginResponse {
16
+ invalidatedSessionCount?: number
17
+ }
18
+
15
19
  export interface AuthEndpoints {
16
20
  logIn: (
17
21
  tenantId: string,
18
22
  username: string,
19
23
  password: string
20
- ) => Promise<LoginResponse>
24
+ ) => Promise<ExtendedLoginResponse>
21
25
  logOut: () => Promise<LogoutResponse>
22
26
  requestForgotPassword: (
23
27
  tenantId: string,
@@ -46,6 +50,18 @@ export const buildAuthEndpoints = (API: BaseAPIClient): AuthEndpoints => ({
46
50
  username,
47
51
  password,
48
52
  },
53
+ parseResponse: async response => {
54
+ const data = (await response.json()) as LoginResponse
55
+ const invalidatedSessionCount = response.headers.get(
56
+ "X-Session-Invalidated-Count"
57
+ )
58
+ return {
59
+ ...data,
60
+ invalidatedSessionCount: invalidatedSessionCount
61
+ ? parseInt(invalidatedSessionCount)
62
+ : 0,
63
+ } as ExtendedLoginResponse
64
+ },
49
65
  })
50
66
  },
51
67
 
package/src/api/index.ts CHANGED
@@ -50,6 +50,7 @@ import { buildAgentEndpoints } from "./agents"
50
50
  import { buildFeatureFlagEndpoints } from "./features"
51
51
  import { buildNavigationEndpoints } from "./navigation"
52
52
  import { buildWorkspaceAppEndpoints } from "./workspaceApps"
53
+ import { buildResourceEndpoints } from "./resource"
53
54
 
54
55
  export type { APIClient } from "./types"
55
56
 
@@ -300,5 +301,6 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => {
300
301
  oauth2: buildOAuth2Endpoints(API),
301
302
  navigation: buildNavigationEndpoints(API),
302
303
  workspaceApp: buildWorkspaceAppEndpoints(API),
304
+ resource: buildResourceEndpoints(API),
303
305
  }
304
306
  }
@@ -0,0 +1,23 @@
1
+ import { ResourceUsageRequest, ResourceUsageResponse } from "@budibase/types"
2
+ import { BaseAPIClient } from "./types"
3
+
4
+ export interface ResourceEndpoints {
5
+ searchForUsage: (body: {
6
+ automationIds?: string[]
7
+ workspaceAppIds?: string[]
8
+ }) => Promise<ResourceUsageResponse>
9
+ }
10
+
11
+ export const buildResourceEndpoints = (
12
+ API: BaseAPIClient
13
+ ): ResourceEndpoints => ({
14
+ searchForUsage: async (body: {
15
+ automationIds?: string[]
16
+ workspaceAppIds?: string[]
17
+ }) => {
18
+ return await API.post<ResourceUsageRequest, ResourceUsageResponse>({
19
+ url: `/api/resources/usage`,
20
+ body,
21
+ })
22
+ },
23
+ })
package/src/api/types.ts CHANGED
@@ -37,6 +37,7 @@ import { ViewV2Endpoints } from "./viewsV2"
37
37
  import { AgentEndpoints } from "./agents"
38
38
  import { NavigationEndpoints } from "./navigation"
39
39
  import { WorkspaceAppEndpoints } from "./workspaceApps"
40
+ import { ResourceEndpoints } from "./resource"
40
41
 
41
42
  export enum HTTPMethod {
42
43
  POST = "POST",
@@ -140,6 +141,7 @@ export type APIClient = BaseAPIClient &
140
141
  UserEndpoints &
141
142
  FeatureFlagEndpoints &
142
143
  ViewEndpoints & {
144
+ resource: ResourceEndpoints
143
145
  rowActions: RowActionEndpoints
144
146
  viewV2: ViewV2Endpoints
145
147
  oauth2: OAuth2Endpoints
@@ -12,7 +12,7 @@
12
12
  export let schema
13
13
  export let maximum
14
14
 
15
- const { API, notifications, props } = getContext("grid")
15
+ const { API, notifications, props, datasource } = getContext("grid")
16
16
  const imageExtensions = ["png", "tiff", "gif", "raw", "jpg", "jpeg"]
17
17
 
18
18
  let isOpen = false
@@ -55,7 +55,13 @@
55
55
  data.append("file", fileList[i])
56
56
  }
57
57
  try {
58
- return await API.uploadBuilderAttachment(data)
58
+ const tableId = $datasource?.tableId || $datasource?.id
59
+ if (tableId) {
60
+ return await API.uploadAttachment(tableId, data)
61
+ } else {
62
+ // Fallback to builder endpoint if no table context
63
+ return await API.uploadBuilderAttachment(data)
64
+ }
59
65
  } catch (error) {
60
66
  $notifications.error(error.message || "Failed to upload attachment")
61
67
  return []
@@ -5,6 +5,7 @@ import {
5
5
  EmptyFilterOption,
6
6
  UIRow,
7
7
  UICondition,
8
+ EXTERNAL_ROW_REV,
8
9
  } from "@budibase/types"
9
10
  import { Store as StoreContext } from "."
10
11
 
@@ -72,9 +73,19 @@ export const initialise = (context: StoreContext) => {
72
73
  const $metadata = get(metadata)
73
74
  let metadataUpdates: Record<string, any> = {}
74
75
  for (let row of $rows) {
75
- if (!row._rev || $metadata[row._id]?.version !== row._rev) {
76
- metadataUpdates[row._id] = evaluateConditions(row, $conditions)
76
+ const changed =
77
+ // No _rev indicates a new row
78
+ !row._rev ||
79
+ // _rev changed since last evaluation
80
+ $metadata[row._id].version !== row._rev ||
81
+ // this is an external row, we have no way to know if it has changed, so
82
+ // we always re-evaluate
83
+ row._rev === EXTERNAL_ROW_REV
84
+ if (!changed) {
85
+ continue
77
86
  }
87
+
88
+ $metadata[row._id] = evaluateConditions(row, $conditions)
78
89
  }
79
90
  if (Object.keys(metadataUpdates).length) {
80
91
  metadata.update(state => ({
@@ -16,3 +16,4 @@ export * from "./table"
16
16
  export * from "./components"
17
17
  export * from "./validation"
18
18
  export * from "./formatting"
19
+ export * from "./login"
@@ -0,0 +1,29 @@
1
+ import { MAX_SESSIONS_PER_USER } from "@budibase/shared-core"
2
+
3
+ const SESSIONS_INVALIDATED_KEY = "bb-sessions-invalidated"
4
+
5
+ // export function checkIfSessionsInvalidatedAndNotify() {
6
+ export function popNumSessionsInvalidated() {
7
+ const invalidatedCount = parseInt(
8
+ localStorage.getItem(SESSIONS_INVALIDATED_KEY) || "0",
9
+ 10
10
+ )
11
+ localStorage.removeItem(SESSIONS_INVALIDATED_KEY)
12
+ return invalidatedCount
13
+ }
14
+
15
+ export function pushNumSessionsInvalidated(num: number) {
16
+ const currentCount = parseInt(
17
+ localStorage.getItem(SESSIONS_INVALIDATED_KEY) || "0",
18
+ 10
19
+ )
20
+ localStorage.setItem(
21
+ SESSIONS_INVALIDATED_KEY,
22
+ (currentCount + num).toString()
23
+ )
24
+ }
25
+
26
+ export function invalidationMessage(num: number) {
27
+ const sessionText = num === 1 ? "session" : "sessions"
28
+ return `You've been logged out of ${num} other ${sessionText} because users are only allowed ${MAX_SESSIONS_PER_USER} active sessions at any one time.`
29
+ }