@base44-preview/sdk 0.8.26-pr.169.4ce3986 → 0.8.27-pr.171.bb56537
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/dist/client.js +10 -8
- package/dist/modules/connectors.types.d.ts +77 -69
- package/dist/modules/entities.js +14 -0
- package/dist/modules/entities.types.d.ts +13 -0
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -112,6 +112,16 @@ export function createClient(config) {
|
|
|
112
112
|
appBaseUrl: normalizedAppBaseUrl,
|
|
113
113
|
serverUrl,
|
|
114
114
|
});
|
|
115
|
+
// Apply the access token before any module that may issue authenticated
|
|
116
|
+
// requests during construction (notably analytics, which fires an init
|
|
117
|
+
// event whose flush calls auth.me()). Without this, the first User/me
|
|
118
|
+
// request is built before setToken runs and goes out unauthenticated.
|
|
119
|
+
if (typeof window !== "undefined") {
|
|
120
|
+
const accessToken = token || getAccessToken();
|
|
121
|
+
if (accessToken) {
|
|
122
|
+
userAuthModule.setToken(accessToken);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
115
125
|
const userModules = {
|
|
116
126
|
entities: createEntitiesModule({
|
|
117
127
|
axios: axiosClient,
|
|
@@ -189,14 +199,6 @@ export function createClient(config) {
|
|
|
189
199
|
}
|
|
190
200
|
},
|
|
191
201
|
};
|
|
192
|
-
// Always try to get token from localStorage or URL parameters
|
|
193
|
-
if (typeof window !== "undefined") {
|
|
194
|
-
// Get token from URL or localStorage
|
|
195
|
-
const accessToken = token || getAccessToken();
|
|
196
|
-
if (accessToken) {
|
|
197
|
-
userModules.auth.setToken(accessToken);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
202
|
// If authentication is required, verify token and redirect to login if needed
|
|
201
203
|
if (requiresAuth && typeof window !== "undefined") {
|
|
202
204
|
// We perform this check asynchronously to not block client creation
|
|
@@ -33,27 +33,44 @@ export interface ConnectorConnectionResponse {
|
|
|
33
33
|
connectionConfig: Record<string, string> | null;
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
* Connection details for an app
|
|
36
|
+
* Connection details for an app user connector.
|
|
37
37
|
*/
|
|
38
38
|
export interface AppUserConnectorConnectionResponse {
|
|
39
|
-
/** The OAuth access token for the
|
|
39
|
+
/** The OAuth access token for the app user's connection. */
|
|
40
40
|
accessToken: string;
|
|
41
41
|
/** Key-value configuration for the connection, or `null` if the connector does not provide one. */
|
|
42
42
|
connectionConfig: Record<string, string> | null;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* Connectors module for managing
|
|
45
|
+
* Connectors module for managing OAuth tokens for external services.
|
|
46
46
|
*
|
|
47
|
-
*
|
|
47
|
+
* Unlike the {@link IntegrationsModule | integrations} module that provides pre-built functions, connectors give you raw OAuth tokens so you can call external service APIs directly. Use this when you need custom API interactions that the pre-built integrations do not cover.
|
|
48
48
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
49
|
+
* There are two connector types, depending on whether the token is shared across the app or specific to each user:
|
|
50
|
+
*
|
|
51
|
+
* - **[Shared connectors](#shared-connectors):** A single OAuth token shared by all app users. Best for shared service accounts.
|
|
52
|
+
* - **[App user connectors](#app-user-connectors):** Each app user has their own OAuth token. Best for actions that need to happen as the individual user.
|
|
53
|
+
*
|
|
54
|
+
* ## Shared connectors
|
|
55
|
+
*
|
|
56
|
+
* All app users share a single OAuth token. Use this for shared accounts. For example, posting to a company Slack channel or reading from a shared Google Calendar. To use a shared connector:
|
|
57
|
+
*
|
|
58
|
+
* 1. Connect the external service account in the app's Integration settings or using the [`connectors push`](/developers/references/cli/commands/connectors-push) CLI command.
|
|
59
|
+
* 2. In a backend function, call {@linkcode getConnection | getConnection()} using the service role client (`base44.asServiceRole.connectors`) with an [integration type](#available-connectors) string to retrieve the shared OAuth token.
|
|
60
|
+
* 3. Use the returned `accessToken` to call the external service's API directly. Some connectors also return a `connectionConfig` with additional values such as a subdomain for building the API URL.
|
|
61
|
+
*
|
|
62
|
+
* ## App user connectors
|
|
63
|
+
*
|
|
64
|
+
* Each signed-in app user has their own OAuth token. Use this when each user needs to act as themselves. For example, sending emails from their Gmail account or posting to their personal LinkedIn. To use an app user connector:
|
|
65
|
+
*
|
|
66
|
+
* 1. Register OAuth credentials for the service in Workspace Settings to get a **connector ID**. This requires workspace admin access.
|
|
67
|
+
* 2. From the frontend, call [connectAppUser()](#connectappuser) with the connector ID to get an authorization URL, then redirect the app user to that URL to complete the OAuth flow.
|
|
68
|
+
* 3. In a backend function, call {@linkcode getCurrentAppUserConnection | getCurrentAppUserConnection()} using the service role client (`base44.asServiceRole.connectors`) with the connector ID to retrieve the app user's token.
|
|
69
|
+
* 4. Use the returned `accessToken` to call the external service's API directly. Some connectors also return a `connectionConfig` with additional values such as a subdomain for building the API URL.
|
|
53
70
|
*
|
|
54
71
|
* ## Available connectors
|
|
55
72
|
*
|
|
56
|
-
* All connectors
|
|
73
|
+
* All connectors listed below support both shared and app user connections. For shared connectors, pass the integration type string to {@linkcode getConnection | getConnection()}. For app user connectors, register the connector in Workspace Settings and use the connector ID with {@linkcode getCurrentAppUserConnection | getCurrentAppUserConnection()}.
|
|
57
74
|
*
|
|
58
75
|
* | Service | Type identifier |
|
|
59
76
|
* |---|---|
|
|
@@ -81,7 +98,7 @@ export interface AppUserConnectorConnectionResponse {
|
|
|
81
98
|
* | Google Tasks | `googletasks` |
|
|
82
99
|
* | HubSpot | `hubspot` |
|
|
83
100
|
* | Hugging Face | `hugging_face` |
|
|
84
|
-
* | Instagram | `instagram` |
|
|
101
|
+
* | Instagram Business | `instagram` |
|
|
85
102
|
* | Linear | `linear` |
|
|
86
103
|
* | LinkedIn | `linkedin` |
|
|
87
104
|
* | Microsoft Teams | `microsoft_teams` |
|
|
@@ -104,13 +121,9 @@ export interface AppUserConnectorConnectionResponse {
|
|
|
104
121
|
* - **Scopes and permissions**: {@link https://docs.base44.com/Integrations/gmail-connector#gmail-scopes-and-permissions | Gmail}, {@link https://docs.base44.com/Integrations/linkedin-connector#linkedin-scopes-and-permissions | LinkedIn}, {@link https://docs.base44.com/Integrations/slack-connector#slack-scopes-and-permissions | Slack}, {@link https://docs.base44.com/Integrations/github-connector#github-scopes-and-permissions | GitHub}
|
|
105
122
|
* - **Slack connector types**: {@link https://docs.base44.com/Integrations/slack-connector#about-the-slack-connectors | About the Slack connectors} explains the difference between `slack` and `slackbot`
|
|
106
123
|
*
|
|
107
|
-
* ## Authentication Modes
|
|
108
|
-
*
|
|
109
|
-
* This module is only available to use with a client in service role authentication mode, which means it can only be used in backend environments.
|
|
110
|
-
*
|
|
111
124
|
* ## Dynamic Types
|
|
112
125
|
*
|
|
113
|
-
* If you're working in a TypeScript project, you can generate types from your app's connector configurations to get autocomplete on integration type names when calling
|
|
126
|
+
* If you're working in a TypeScript project, you can generate types from your app's connector configurations to get autocomplete on integration type names when calling {@link getConnection}. See the [Dynamic Types](/developers/references/sdk/getting-started/dynamic-types) guide to get started.
|
|
114
127
|
*/
|
|
115
128
|
export interface ConnectorsModule {
|
|
116
129
|
/**
|
|
@@ -118,9 +131,8 @@ export interface ConnectorsModule {
|
|
|
118
131
|
*
|
|
119
132
|
* @deprecated Use {@link getConnection} instead.
|
|
120
133
|
*
|
|
121
|
-
* Returns the OAuth token string for an external service
|
|
122
|
-
*
|
|
123
|
-
* and can be used to make authenticated API calls to that external service on behalf of the app.
|
|
134
|
+
* Returns the OAuth token string for an external service connected to the app.
|
|
135
|
+
* This token represents the connected account and can be used to make authenticated API calls to that external service on behalf of the app.
|
|
124
136
|
*
|
|
125
137
|
* @param integrationType - The type of integration, such as `'googlecalendar'`, `'slack'`, `'slackbot'`, `'github'`, or `'discord'`. See [Available connectors](#available-connectors) for the full list.
|
|
126
138
|
* @returns Promise resolving to the access token string.
|
|
@@ -135,11 +147,11 @@ export interface ConnectorsModule {
|
|
|
135
147
|
* const timeMin = new Date().toISOString();
|
|
136
148
|
* const url = `https://www.googleapis.com/calendar/v3/calendars/primary/events?maxResults=10&orderBy=startTime&singleEvents=true&timeMin=${timeMin}`;
|
|
137
149
|
*
|
|
138
|
-
* const
|
|
150
|
+
* const response = await fetch(url, {
|
|
139
151
|
* headers: { 'Authorization': `Bearer ${googleToken}` }
|
|
140
152
|
* });
|
|
141
153
|
*
|
|
142
|
-
* const events = await
|
|
154
|
+
* const events = await response.json();
|
|
143
155
|
* ```
|
|
144
156
|
*
|
|
145
157
|
* @example
|
|
@@ -151,11 +163,11 @@ export interface ConnectorsModule {
|
|
|
151
163
|
* // List all public and private channels
|
|
152
164
|
* const url = 'https://slack.com/api/conversations.list?types=public_channel,private_channel&limit=100';
|
|
153
165
|
*
|
|
154
|
-
* const
|
|
166
|
+
* const response = await fetch(url, {
|
|
155
167
|
* headers: { 'Authorization': `Bearer ${slackToken}` }
|
|
156
168
|
* });
|
|
157
169
|
*
|
|
158
|
-
* const data = await
|
|
170
|
+
* const data = await response.json();
|
|
159
171
|
* ```
|
|
160
172
|
*
|
|
161
173
|
* @example
|
|
@@ -183,7 +195,9 @@ export interface ConnectorsModule {
|
|
|
183
195
|
*/
|
|
184
196
|
getAccessToken(integrationType: ConnectorIntegrationType): Promise<string>;
|
|
185
197
|
/**
|
|
186
|
-
* Retrieves the OAuth access token and connection configuration for a specific [external integration type](#available-connectors).
|
|
198
|
+
* Retrieves the shared OAuth access token and connection configuration for a [shared connector](#shared-connectors) to a specific [external integration type](#available-connectors).
|
|
199
|
+
*
|
|
200
|
+
* Use this when a single shared account is connected and all app users access the same token. For per-user tokens, use [`getCurrentAppUserConnection()`](#getcurrentappuserconnection) instead.
|
|
187
201
|
*
|
|
188
202
|
* Some connectors require connection-specific parameters to build API calls.
|
|
189
203
|
* In such cases, the returned `connectionConfig` is an object with the additional parameters. If there are no extra parameters needed for the connection, the `connectionConfig` is `null`.
|
|
@@ -197,17 +211,13 @@ export interface ConnectorsModule {
|
|
|
197
211
|
* @example
|
|
198
212
|
* ```typescript
|
|
199
213
|
* // Google Calendar connection
|
|
200
|
-
* // Get Google Calendar OAuth token and fetch upcoming events
|
|
201
214
|
* const { accessToken } = await base44.asServiceRole.connectors.getConnection('googlecalendar');
|
|
202
215
|
*
|
|
203
|
-
* const
|
|
204
|
-
* const url = `https://www.googleapis.com/calendar/v3/calendars/primary/events?maxResults=10&orderBy=startTime&singleEvents=true&timeMin=${timeMin}`;
|
|
205
|
-
*
|
|
206
|
-
* const calendarResponse = await fetch(url, {
|
|
216
|
+
* const response = await fetch('https://www.googleapis.com/calendar/v3/users/me/calendarList', {
|
|
207
217
|
* headers: { Authorization: `Bearer ${accessToken}` }
|
|
208
218
|
* });
|
|
209
219
|
*
|
|
210
|
-
* const
|
|
220
|
+
* const { items } = await response.json();
|
|
211
221
|
* ```
|
|
212
222
|
*
|
|
213
223
|
* @example
|
|
@@ -218,11 +228,11 @@ export interface ConnectorsModule {
|
|
|
218
228
|
*
|
|
219
229
|
* const url = 'https://slack.com/api/conversations.list?types=public_channel,private_channel&limit=100';
|
|
220
230
|
*
|
|
221
|
-
* const
|
|
231
|
+
* const response = await fetch(url, {
|
|
222
232
|
* headers: { Authorization: `Bearer ${accessToken}` }
|
|
223
233
|
* });
|
|
224
234
|
*
|
|
225
|
-
* const data = await
|
|
235
|
+
* const data = await response.json();
|
|
226
236
|
* ```
|
|
227
237
|
*
|
|
228
238
|
* @example
|
|
@@ -242,75 +252,73 @@ export interface ConnectorsModule {
|
|
|
242
252
|
*/
|
|
243
253
|
getConnection(integrationType: ConnectorIntegrationType): Promise<ConnectorConnectionResponse>;
|
|
244
254
|
/**
|
|
245
|
-
*
|
|
246
|
-
*
|
|
255
|
+
* @internal
|
|
247
256
|
* @deprecated Use {@link getCurrentAppUserConnection} instead.
|
|
257
|
+
*/
|
|
258
|
+
getCurrentAppUserAccessToken(connectorId: string): Promise<string>;
|
|
259
|
+
/**
|
|
260
|
+
* Retrieves the OAuth access token and connection configuration for an [app user connector](#app-user-connectors).
|
|
248
261
|
*
|
|
249
|
-
*
|
|
250
|
-
* for the specified connector.
|
|
262
|
+
* The token returned is specific to the app user making the current request. For this to work, the SDK client must know which app user to act on behalf of. Use {@linkcode createClientFromRequest | createClientFromRequest()} in a Base44 backend function to create such a client. It reads the app user's JWT from the incoming request and attaches it automatically so the runtime can resolve the correct user's connection.
|
|
251
263
|
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
264
|
+
* The connector must be registered in Workspace Settings with OAuth credentials before this method can return a connection. The app user must also have completed the OAuth flow using [connectAppUser()](#connectappuser).
|
|
265
|
+
*
|
|
266
|
+
* @param connectorId - The ID of the app user connector configured in your workspace. This is not the integration type string. You can find it on the connector's settings page in Workspace Settings.
|
|
267
|
+
* @returns Promise resolving to an {@link AppUserConnectorConnectionResponse} with `accessToken` and `connectionConfig`.
|
|
254
268
|
*
|
|
255
269
|
* @example
|
|
256
270
|
* ```typescript
|
|
257
|
-
* //
|
|
258
|
-
* const
|
|
271
|
+
* // Basic usage
|
|
272
|
+
* const { accessToken } = await base44.asServiceRole.connectors.getCurrentAppUserConnection('abc123def');
|
|
259
273
|
*
|
|
260
274
|
* const response = await fetch('https://www.googleapis.com/calendar/v3/calendars/primary/events', {
|
|
261
|
-
* headers: {
|
|
275
|
+
* headers: { Authorization: `Bearer ${accessToken}` }
|
|
262
276
|
* });
|
|
263
|
-
* ```
|
|
264
|
-
*/
|
|
265
|
-
getCurrentAppUserAccessToken(connectorId: string): Promise<string>;
|
|
266
|
-
/**
|
|
267
|
-
* Retrieves the OAuth access token and connection configuration for an end user's
|
|
268
|
-
* connection to a specific connector.
|
|
269
|
-
*
|
|
270
|
-
* Returns both the OAuth token and any connection-specific configuration that
|
|
271
|
-
* belongs to the currently authenticated end user for the specified connector.
|
|
272
277
|
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
278
|
+
* const data = await response.json();
|
|
279
|
+
* ```
|
|
275
280
|
*
|
|
276
281
|
* @example
|
|
277
282
|
* ```typescript
|
|
278
|
-
* //
|
|
283
|
+
* // Using connectionConfig
|
|
279
284
|
* const { accessToken, connectionConfig } = await base44.asServiceRole.connectors.getCurrentAppUserConnection('abc123def');
|
|
280
285
|
*
|
|
281
|
-
* const
|
|
282
|
-
*
|
|
283
|
-
*
|
|
286
|
+
* const subdomain = connectionConfig?.subdomain;
|
|
287
|
+
* const response = await fetch(
|
|
288
|
+
* `https://${subdomain}.example.com/api/v1/resources`,
|
|
289
|
+
* { headers: { Authorization: `Bearer ${accessToken}` } }
|
|
290
|
+
* );
|
|
291
|
+
*
|
|
292
|
+
* const data = await response.json();
|
|
284
293
|
* ```
|
|
285
294
|
*/
|
|
286
295
|
getCurrentAppUserConnection(connectorId: string): Promise<AppUserConnectorConnectionResponse>;
|
|
287
296
|
}
|
|
288
297
|
/**
|
|
289
|
-
* User-scoped connectors module for managing app
|
|
298
|
+
* User-scoped connectors module for managing app user OAuth connections.
|
|
290
299
|
*
|
|
291
|
-
* This module provides methods for app
|
|
292
|
-
* retrieving the end user's access token, and disconnecting the end user's connection.
|
|
300
|
+
* This module provides methods for app user OAuth flows: initiating an OAuth connection and disconnecting an app user's connection.
|
|
293
301
|
*
|
|
294
302
|
* Unlike {@link ConnectorsModule | ConnectorsModule} which manages app-scoped tokens,
|
|
295
|
-
* this module manages tokens scoped to individual
|
|
296
|
-
* the connector ID
|
|
303
|
+
* this module manages tokens scoped to individual app users. Methods are keyed on
|
|
304
|
+
* the connector ID, not the integration type.
|
|
297
305
|
*
|
|
298
306
|
* Available via `base44.connectors`.
|
|
299
307
|
*/
|
|
300
308
|
export interface UserConnectorsModule {
|
|
301
309
|
/**
|
|
302
|
-
* Initiates the
|
|
310
|
+
* Initiates the OAuth flow for an [app user connector](#app-user-connectors).
|
|
303
311
|
*
|
|
304
|
-
* Returns a redirect URL that the
|
|
312
|
+
* Returns a redirect URL that the app user should be navigated to in order to
|
|
305
313
|
* authenticate with the external service. The scopes and integration type are
|
|
306
|
-
* derived from the connector configuration
|
|
314
|
+
* derived from the connector configuration in the backend.
|
|
307
315
|
*
|
|
308
|
-
* @param connectorId - The connector ID
|
|
316
|
+
* @param connectorId - The ID of the app user connector configured in your workspace. The AI builder inserts this ID into generated code when it sets up the connector flow. You can also retrieve it from the workspace connectors API.
|
|
309
317
|
* @returns Promise resolving to the redirect URL string.
|
|
310
318
|
*
|
|
311
319
|
* @example
|
|
312
320
|
* ```typescript
|
|
313
|
-
* // Start OAuth for the
|
|
321
|
+
* // Start OAuth for the app user
|
|
314
322
|
* const redirectUrl = await base44.connectors.connectAppUser('abc123def');
|
|
315
323
|
*
|
|
316
324
|
* // Redirect the user to the OAuth provider
|
|
@@ -319,17 +327,17 @@ export interface UserConnectorsModule {
|
|
|
319
327
|
*/
|
|
320
328
|
connectAppUser(connectorId: string): Promise<string>;
|
|
321
329
|
/**
|
|
322
|
-
* Disconnects an
|
|
330
|
+
* Disconnects an app user's OAuth connection for an [app user connector](#app-user-connectors).
|
|
323
331
|
*
|
|
324
|
-
* Removes the stored OAuth credentials for the currently authenticated
|
|
332
|
+
* Removes the stored OAuth credentials for the currently authenticated app user's
|
|
325
333
|
* connection to the specified connector.
|
|
326
334
|
*
|
|
327
|
-
* @param connectorId - The connector ID
|
|
335
|
+
* @param connectorId - The ID of the app user connector configured in your workspace. The AI builder inserts this ID into generated code when it sets up the connector flow. You can also retrieve it from the workspace connectors API.
|
|
328
336
|
* @returns Promise resolving when the connection has been removed.
|
|
329
337
|
*
|
|
330
338
|
* @example
|
|
331
339
|
* ```typescript
|
|
332
|
-
* // Disconnect the
|
|
340
|
+
* // Disconnect the app user's connection
|
|
333
341
|
* await base44.connectors.disconnectAppUser('abc123def');
|
|
334
342
|
* ```
|
|
335
343
|
*/
|
package/dist/modules/entities.js
CHANGED
|
@@ -131,10 +131,24 @@ function createEntityHandler(axios, appId, entityName, getSocket) {
|
|
|
131
131
|
const socket = getSocket();
|
|
132
132
|
const unsubscribe = socket.subscribeToRoom(room, {
|
|
133
133
|
update_model: (msg) => {
|
|
134
|
+
var _a;
|
|
134
135
|
const event = parseRealtimeMessage(msg.data);
|
|
135
136
|
if (!event) {
|
|
136
137
|
return;
|
|
137
138
|
}
|
|
139
|
+
// Server signals oversize broadcasts with `_oversize: true` on
|
|
140
|
+
// `data`. The wire payload was slimmed to fit under the realtime
|
|
141
|
+
// transport cap, so big string fields arrive as empty strings (or
|
|
142
|
+
// the whole record collapses to a stub). Surface this to the
|
|
143
|
+
// developer console so they know to fetch the full record on
|
|
144
|
+
// demand (e.g. a follow-up entities.X.get(id) call) instead of
|
|
145
|
+
// rendering the slimmed payload directly. Skip on delete events
|
|
146
|
+
// — the record no longer exists.
|
|
147
|
+
if (event.type !== "delete" && ((_a = event.data) === null || _a === void 0 ? void 0 : _a._oversize)) {
|
|
148
|
+
console.error(`[Base44 SDK] Realtime broadcast for ${entityName}#${event.id} was oversize and got slimmed for transport. ` +
|
|
149
|
+
`Fields >10 KB are empty and the rest of the record may be a stub. ` +
|
|
150
|
+
`Call \`entities.${entityName}.get("${event.id}")\` to fetch the full record.`);
|
|
151
|
+
}
|
|
138
152
|
try {
|
|
139
153
|
callback(event);
|
|
140
154
|
}
|
|
@@ -84,6 +84,19 @@ export interface ImportResult<T = any> {
|
|
|
84
84
|
* ```
|
|
85
85
|
*/
|
|
86
86
|
export type SortField<T> = (keyof T & string) | `+${keyof T & string}` | `-${keyof T & string}`;
|
|
87
|
+
/**
|
|
88
|
+
* Entity filter query type system.
|
|
89
|
+
*
|
|
90
|
+
* `EntityFilterQuery<T>` keeps field names tied to the entity schema while
|
|
91
|
+
* allowing Base44's documented filtering syntax. Each field can use an exact
|
|
92
|
+
* value, `null`, an array shorthand for matching any listed value, or a
|
|
93
|
+
* field-level operator object. Root-level `$and`, `$or`, and `$nor` combine
|
|
94
|
+
* nested filter queries.
|
|
95
|
+
*
|
|
96
|
+
* Operator values are typed from the field they filter where possible. For
|
|
97
|
+
* example, numeric fields accept numeric comparison values, string fields
|
|
98
|
+
* accept `$regex`, and array fields accept `$all` and `$size`.
|
|
99
|
+
*/
|
|
87
100
|
/**
|
|
88
101
|
* Value accepted when filtering an entity field.
|
|
89
102
|
*
|