@agnocon/piece-bika 0.1.7

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 (52) hide show
  1. package/LICENSE.MIT-AP +24 -0
  2. package/dist/index.d.ts +4 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +44 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/lib/actions/create-record.d.ts +14 -0
  7. package/dist/lib/actions/create-record.d.ts.map +1 -0
  8. package/dist/lib/actions/create-record.js +51 -0
  9. package/dist/lib/actions/create-record.js.map +1 -0
  10. package/dist/lib/actions/delete-record.d.ts +12 -0
  11. package/dist/lib/actions/delete-record.d.ts.map +1 -0
  12. package/dist/lib/actions/delete-record.js +40 -0
  13. package/dist/lib/actions/delete-record.js.map +1 -0
  14. package/dist/lib/actions/find-record.d.ts +12 -0
  15. package/dist/lib/actions/find-record.d.ts.map +1 -0
  16. package/dist/lib/actions/find-record.js +39 -0
  17. package/dist/lib/actions/find-record.js.map +1 -0
  18. package/dist/lib/actions/find-records.d.ts +14 -0
  19. package/dist/lib/actions/find-records.d.ts.map +1 -0
  20. package/dist/lib/actions/find-records.js +58 -0
  21. package/dist/lib/actions/find-records.js.map +1 -0
  22. package/dist/lib/actions/update-record.d.ts +15 -0
  23. package/dist/lib/actions/update-record.d.ts.map +1 -0
  24. package/dist/lib/actions/update-record.js +53 -0
  25. package/dist/lib/actions/update-record.js.map +1 -0
  26. package/dist/lib/auth.d.ts +4 -0
  27. package/dist/lib/auth.d.ts.map +1 -0
  28. package/dist/lib/auth.js +45 -0
  29. package/dist/lib/auth.js.map +1 -0
  30. package/dist/lib/common/client.d.ts +63 -0
  31. package/dist/lib/common/client.d.ts.map +1 -0
  32. package/dist/lib/common/client.js +91 -0
  33. package/dist/lib/common/client.js.map +1 -0
  34. package/dist/lib/common/constants.d.ts +28 -0
  35. package/dist/lib/common/constants.d.ts.map +1 -0
  36. package/dist/lib/common/constants.js +10 -0
  37. package/dist/lib/common/constants.js.map +1 -0
  38. package/dist/lib/common/index.d.ts +17 -0
  39. package/dist/lib/common/index.d.ts.map +1 -0
  40. package/dist/lib/common/index.js +235 -0
  41. package/dist/lib/common/index.js.map +1 -0
  42. package/package.json +46 -0
  43. package/src/index.ts +41 -0
  44. package/src/lib/actions/create-record.ts +61 -0
  45. package/src/lib/actions/delete-record.ts +44 -0
  46. package/src/lib/actions/find-record.ts +44 -0
  47. package/src/lib/actions/find-records.ts +62 -0
  48. package/src/lib/actions/update-record.ts +65 -0
  49. package/src/lib/auth.ts +40 -0
  50. package/src/lib/common/client.ts +147 -0
  51. package/src/lib/common/constants.ts +33 -0
  52. package/src/lib/common/index.ts +250 -0
@@ -0,0 +1,62 @@
1
+ import {
2
+ PiecePropValueSchema,
3
+ Property,
4
+ createAction,
5
+ } from '@agnocon/pieces-framework';
6
+ import { BikaCommon, makeClient } from '../common';
7
+ import { BikaAuth } from '../auth';
8
+ import { prepareQuery } from '../common/client';
9
+
10
+ export const findRecordsAction = createAction({
11
+ auth: BikaAuth,
12
+ name: 'bika_find_records',
13
+ displayName: 'Find Records',
14
+ description: 'Finds records in database.',
15
+ audience: 'both',
16
+ aiMetadata: { description: 'Lists records from a Bika.ai database, optionally narrowed by a filter expression (Bika filter-query-language); with no filter it returns all records up to the configured limits. Use to search or page through a table when you need multiple matching rows rather than a single known ID. Read-only and idempotent.', idempotent: true },
17
+ props: {
18
+ space_id: BikaCommon.space_id,
19
+ database_id: BikaCommon.database_id,
20
+ maxRecords: Property.Number({
21
+ displayName: 'Max Records',
22
+ description: 'How many records are returned in total.',
23
+ required: false,
24
+ }),
25
+ pageSize: Property.Number({
26
+ displayName: 'Page Size',
27
+ description: 'How many records are returned per page (max 1000).',
28
+ required: false,
29
+ }),
30
+ filter: Property.LongText({
31
+ displayName: 'Filter',
32
+ description:
33
+ 'The filter to apply to the records (see https://bika.ai/help/guide/developer/filter-query-language).',
34
+ required: false,
35
+ }),
36
+ },
37
+ async run(context) {
38
+ const databaseId = context.propsValue.database_id;
39
+ const spaceId = context.propsValue.space_id;
40
+ const maxRecords = context.propsValue.maxRecords;
41
+ const pageSize = context.propsValue.pageSize ?? 100;
42
+ const filter = context.propsValue.filter;
43
+
44
+ const client = makeClient(
45
+ context.auth.props,
46
+ );
47
+ const response: any = await client.listRecords(
48
+ spaceId,
49
+ databaseId,
50
+ prepareQuery({
51
+ pageSize,
52
+ maxRecords,
53
+ filter,
54
+ })
55
+ );
56
+
57
+ if (!response.success) {
58
+ throw new Error(JSON.stringify(response, undefined, 2));
59
+ }
60
+ return response;
61
+ },
62
+ });
@@ -0,0 +1,65 @@
1
+ import {
2
+ DynamicPropsValue,
3
+ PiecePropValueSchema,
4
+ Property,
5
+ createAction,
6
+ } from '@agnocon/pieces-framework';
7
+ import { BikaCommon, createNewFields, makeClient } from '../common';
8
+ import { BikaAuth } from '../auth';
9
+
10
+ export const updateRecordAction = createAction({
11
+ auth: BikaAuth,
12
+ name: 'bika_update_record',
13
+ displayName: 'Update Record',
14
+ description: 'Updates an existing record in database.',
15
+ audience: 'both',
16
+ aiMetadata: { description: 'Updates the fields of an existing Bika.ai record identified by its record ID, within a given space and database. Use when modifying a known record; provide only the fields to change (read-only field types are ignored). Idempotent: repeating with the same input leaves the record in the same state.', idempotent: true },
17
+ props: {
18
+ space_id: BikaCommon.space_id,
19
+ database_id: BikaCommon.database_id,
20
+ recordId: Property.ShortText({
21
+ displayName: 'Record ID',
22
+ description: 'The ID of the record to update.',
23
+ required: true,
24
+ }),
25
+ fields: BikaCommon.fields,
26
+ },
27
+ async run(context) {
28
+ const auth = context.auth;
29
+ const databaseId = context.propsValue.database_id;
30
+ const spaceId = context.propsValue.space_id;
31
+ const recordId = context.propsValue.recordId;
32
+ const dynamicFields: DynamicPropsValue = context.propsValue.fields;
33
+ const fields: {
34
+ [n: string]: any;
35
+ } = {};
36
+
37
+ const props = Object.entries(dynamicFields);
38
+ for (const [propertyKey, propertyValue] of props) {
39
+ if (propertyValue !== undefined && propertyValue !== '') {
40
+ fields[propertyKey] = propertyValue;
41
+ }
42
+ }
43
+
44
+ const newFields: Record<string, unknown> = await createNewFields(
45
+ auth,
46
+ spaceId,
47
+ databaseId,
48
+ fields,
49
+ );
50
+
51
+ const client = makeClient(context.auth.props);
52
+
53
+ const response: any = await client.updateRecord(spaceId, databaseId, recordId, {
54
+ fields: {
55
+ ...newFields,
56
+ },
57
+ });
58
+
59
+ if (!response.success) {
60
+ throw new Error(JSON.stringify(response, undefined, 2));
61
+ }
62
+
63
+ return response;
64
+ },
65
+ });
@@ -0,0 +1,40 @@
1
+ import { PieceAuth, PiecePropValueSchema } from '@agnocon/pieces-framework';
2
+ import { makeClient } from './common';
3
+
4
+ export const BikaAuth = PieceAuth.CustomAuth({
5
+ required: true,
6
+ description: `
7
+ To obtain your Bika token, follow these steps:
8
+
9
+ 1. Log in to your Bika account.
10
+ 2. Visit https://bika.com.
11
+ 3. Click on your profile picture (Bottom left).
12
+ 4. Click on "My Settings".
13
+ 5. Click on "Developer".
14
+ 6. Click on "Generate new token".
15
+ 7. Copy the token.
16
+ `,
17
+ props: {
18
+ token: PieceAuth.SecretText({
19
+ displayName: 'Token',
20
+ description: 'The token of the Bika account',
21
+ required: true,
22
+ }),
23
+ },
24
+ validate: async ({ auth }) => {
25
+ try {
26
+ const client = makeClient(
27
+ auth as PiecePropValueSchema<typeof BikaAuth>
28
+ );
29
+ await client.listSpaces();
30
+ return {
31
+ valid: true,
32
+ };
33
+ } catch (e) {
34
+ return {
35
+ valid: false,
36
+ error: 'Invalid Token.',
37
+ };
38
+ }
39
+ },
40
+ });
@@ -0,0 +1,147 @@
1
+ import {
2
+ HttpMessageBody,
3
+ HttpMethod,
4
+ QueryParams,
5
+ AuthenticationType,
6
+ httpClient,
7
+ } from '@agnocon/pieces-common';
8
+ import { BikaFieldType } from './constants';
9
+
10
+ function emptyValueFilter(
11
+ accessor: (key: string) => any
12
+ ): (key: string) => boolean {
13
+ return (key: string) => {
14
+ const val = accessor(key);
15
+ return (
16
+ val !== null &&
17
+ val !== undefined &&
18
+ (typeof val != 'string' || val.length > 0)
19
+ );
20
+ };
21
+ }
22
+
23
+ export function prepareQuery(request?: Record<string, any>): QueryParams {
24
+ const params: QueryParams = {};
25
+ if (!request) return params;
26
+ Object.keys(request)
27
+ .filter(emptyValueFilter((k) => request[k]))
28
+ .forEach((k: string) => {
29
+ params[k] = (request as Record<string, any>)[k].toString();
30
+ });
31
+ return params;
32
+ }
33
+
34
+
35
+ export class BikaClient {
36
+ constructor( private token: string, private bikaUrl = "https://bika.ai") {}
37
+
38
+ async makeRequest<T extends HttpMessageBody>(
39
+ method: HttpMethod,
40
+ resourceUri: string,
41
+ query?: QueryParams,
42
+ body: any | undefined = undefined
43
+ ): Promise<T> {
44
+ const baseUrl = this.bikaUrl.replace(/\/$/, '');
45
+
46
+ const res = await httpClient.sendRequest<T>({
47
+ method: method,
48
+ url: `${baseUrl}/api/openapi/bika` + resourceUri,
49
+ authentication: {
50
+ type: AuthenticationType.BEARER_TOKEN,
51
+ token: this.token,
52
+ },
53
+ queryParams: query,
54
+ body: body,
55
+ });
56
+
57
+ return res.body;
58
+ }
59
+
60
+ async listSpaces() {
61
+ return await this.makeRequest<{
62
+ data: {
63
+ id: string;
64
+ name: string;
65
+ }[];
66
+ }>(HttpMethod.GET, '/v1/spaces');
67
+ }
68
+ async listDatabases(space_id: string) {
69
+ return await this.makeRequest<{
70
+ data: {
71
+ id: string;
72
+ name: string;
73
+ }[];
74
+ }>(HttpMethod.GET, `/v1/spaces/${space_id}/nodes`);
75
+ }
76
+
77
+ async getDatabaseFields(space_id: string, database_id: string) {
78
+ return await this.makeRequest<{
79
+ data: {
80
+ id: string;
81
+ name: string;
82
+ type: BikaFieldType;
83
+ desc: string;
84
+ property?: {
85
+ format?: string;
86
+ defaultValue?: string;
87
+ options?: {
88
+ name: string;
89
+ id?: string;
90
+ }[];
91
+ };
92
+ }[];
93
+ }>(HttpMethod.GET, `/v1/spaces/${space_id}/resources/databases/${database_id}/fields`);
94
+ }
95
+
96
+ async createRecord(space_id: string, database_id: string, request: object) {
97
+ return await this.makeRequest(
98
+ HttpMethod.POST,
99
+ `/v2/spaces/${space_id}/resources/databases/${database_id}/records`,
100
+ undefined,
101
+ request
102
+ );
103
+ }
104
+ async deleteRecord(space_id: string, database_id: string, record_id: string) {
105
+ return await this.makeRequest(
106
+ HttpMethod.DELETE,
107
+ `/v2/spaces/${space_id}/resources/databases/${database_id}/records/${record_id}`
108
+ );
109
+ }
110
+ async updateRecord(space_id: string, database_id: string, record_id: string, request: object) {
111
+ return await this.makeRequest(
112
+ HttpMethod.PUT,
113
+ `/v2/spaces/${space_id}/resources/databases/${database_id}/records/${record_id}`,
114
+ undefined,
115
+ request
116
+ );
117
+ }
118
+
119
+ async findRecord(space_id: string, database_id: string, record_id: string, query?: QueryParams) {
120
+ return await this.makeRequest<{
121
+ data: {
122
+ total: number;
123
+ records: {
124
+ recordId: string;
125
+ createdAt: number;
126
+ updatedAt: number;
127
+ fields: Record<string, unknown>;
128
+ }[];
129
+ };
130
+ }>(HttpMethod.GET, `/v2/spaces/${space_id}/resources/databases/${database_id}/records/${record_id}`, query);
131
+ }
132
+
133
+
134
+ async listRecords(space_id: string, database_id: string, query?: QueryParams) {
135
+ return await this.makeRequest<{
136
+ data: {
137
+ total: number;
138
+ records: {
139
+ recordId: string;
140
+ createdAt: number;
141
+ updatedAt: number;
142
+ fields: Record<string, unknown>;
143
+ }[];
144
+ };
145
+ }>(HttpMethod.GET, `/v2/spaces/${space_id}/resources/databases/${database_id}/records`, query);
146
+ }
147
+ }
@@ -0,0 +1,33 @@
1
+ export const enum BikaFieldType {
2
+ SINGLE_TEXT = 'SINGLE_TEXT',
3
+ LINK = 'LINK',
4
+ LOOKUP = 'LOOKUP',
5
+ SINGLE_SELECT = 'SINGLE_SELECT',
6
+ MEMBER = 'MEMBER',
7
+ DATETIME = 'DATETIME',
8
+ NUMBER = 'NUMBER',
9
+ FORMULA = 'FORMULA',
10
+ LONG_TEXT = 'LONG_TEXT',
11
+ MULTI_SELECT = 'MULTI_SELECT',
12
+ CURRENCY = 'CURRENCY',
13
+ PERCENT = 'PERCENT',
14
+ RATING = 'RATING',
15
+ CHECKBOX = 'CHECKBOX',
16
+ URL = 'URL',
17
+ PHONE = 'PHONE',
18
+ EMAIL = 'EMAIL',
19
+ AUTONUMBER = 'AUTONUMBER',
20
+ CREATED_BY = 'CREATED_BY',
21
+ LAST_MODIFIED_BY = 'LAST_MODIFIED_BY',
22
+ CASCADER = 'CASCADER',
23
+ CREATED_TIME = 'CREATED_TIME',
24
+ LAST_MODIFIED_TIME = 'LAST_MODIFIED_TIME',
25
+ ATTACHMENT = 'ATTACHMENT',
26
+ }
27
+
28
+ export const BikaNumericFieldTypes = [
29
+ BikaFieldType.NUMBER,
30
+ BikaFieldType.RATING,
31
+ BikaFieldType.CURRENCY,
32
+ BikaFieldType.PERCENT,
33
+ ];
@@ -0,0 +1,250 @@
1
+ import { AppConnectionValueForAuthProperty, DynamicPropsValue, PiecePropValueSchema, Property } from '@agnocon/pieces-framework';
2
+ import { BikaAuth } from '../auth';
3
+ import { BikaClient } from './client';
4
+ import { BikaFieldType, BikaNumericFieldTypes } from './constants';
5
+
6
+ export function makeClient(auth: PiecePropValueSchema<typeof BikaAuth>) {
7
+ const client = new BikaClient(auth.token);
8
+ return client;
9
+ }
10
+
11
+ export const BikaCommon = {
12
+ space_id: Property.Dropdown({
13
+ auth: BikaAuth,
14
+ displayName: 'Space',
15
+ required: true,
16
+ refreshers: [],
17
+ options: async ({ auth }) => {
18
+ if (!auth) {
19
+ return {
20
+ disabled: true,
21
+ options: [],
22
+ placeholder: 'Connect your account first.',
23
+ };
24
+ }
25
+ const client = makeClient(auth.props);
26
+ const res = await client.listSpaces();
27
+ return {
28
+ disabled: false,
29
+ options: res.data.map((space) => {
30
+ return {
31
+ label: space.name,
32
+ value: space.id,
33
+ };
34
+ }),
35
+ };
36
+ },
37
+ }),
38
+ database_id: Property.Dropdown({
39
+ auth: BikaAuth,
40
+ displayName: 'Database',
41
+ required: true,
42
+ refreshers: ['space_id'],
43
+ options: async ({ auth, space_id }) => {
44
+ if (!auth || !space_id) {
45
+ return {
46
+ disabled: true,
47
+ options: [],
48
+ placeholder: 'Connect your account first and select space.',
49
+ };
50
+ }
51
+ const client = makeClient(auth.props);
52
+ const res = await client.listDatabases(space_id as string);
53
+
54
+ return {
55
+ disabled: false,
56
+ options: res.data.map((database) => {
57
+ return {
58
+ label: database.name,
59
+ value: database.id,
60
+ };
61
+ }),
62
+ };
63
+ },
64
+ }),
65
+ fields: Property.DynamicProperties({
66
+ auth: BikaAuth,
67
+ displayName: 'Fields',
68
+ description: 'The fields to add to the record.',
69
+ required: true,
70
+ refreshers: ['auth', 'space_id', 'database_id'],
71
+ props: async ({ auth, space_id, database_id }) => {
72
+ if(!auth || !space_id || !database_id) return {};
73
+
74
+ const client = makeClient(auth.props);
75
+ const res = await client.getDatabaseFields(space_id as unknown as string, database_id as unknown as string);
76
+
77
+ const props: DynamicPropsValue = {};
78
+
79
+ for (const field of res.data) {
80
+ if (
81
+ ![
82
+ BikaFieldType.AUTONUMBER,
83
+ BikaFieldType.CASCADER,
84
+ BikaFieldType.CREATED_BY,
85
+ BikaFieldType.CREATED_TIME,
86
+ BikaFieldType.FORMULA,
87
+ BikaFieldType.LAST_MODIFIED_BY,
88
+ BikaFieldType.LAST_MODIFIED_TIME,
89
+ BikaFieldType.LOOKUP,
90
+ ].includes(field.type)
91
+ ) {
92
+ switch (field.type) {
93
+ case BikaFieldType.ATTACHMENT:
94
+ props[field.name] = Property.File({
95
+ displayName: field.name,
96
+ required: false,
97
+ });
98
+ break;
99
+ case BikaFieldType.CHECKBOX:
100
+ props[field.name] = Property.Checkbox({
101
+ displayName: field.name,
102
+ required: false,
103
+ });
104
+ break;
105
+ case BikaFieldType.CURRENCY:
106
+ case BikaFieldType.NUMBER:
107
+ case BikaFieldType.PERCENT:
108
+ case BikaFieldType.RATING:
109
+ props[field.name] = Property.Number({
110
+ displayName: field.name,
111
+ required: false,
112
+ });
113
+ break;
114
+ case BikaFieldType.DATETIME:
115
+ props[field.name] = Property.DateTime({
116
+ displayName: field.name,
117
+ required: false,
118
+ });
119
+ break;
120
+ case BikaFieldType.EMAIL:
121
+ case BikaFieldType.PHONE:
122
+ case BikaFieldType.SINGLE_TEXT:
123
+ case BikaFieldType.URL:
124
+ props[field.name] = Property.ShortText({
125
+ displayName: field.name,
126
+ required: false,
127
+ });
128
+ break;
129
+ case BikaFieldType.LONG_TEXT:
130
+ props[field.name] = Property.LongText({
131
+ displayName: field.name,
132
+ required: false,
133
+ });
134
+ break;
135
+ case BikaFieldType.MULTI_SELECT:
136
+ props[field.name] = Property.StaticMultiSelectDropdown({
137
+ displayName: field.name,
138
+ required: false,
139
+ options: {
140
+ options: field.property?.options?.map((option) => ({
141
+ label: option.name,
142
+ value: option.name,
143
+ })) || [],
144
+ },
145
+ });
146
+ break;
147
+ case BikaFieldType.SINGLE_SELECT:
148
+ props[field.name] = Property.StaticDropdown({
149
+ displayName: field.name,
150
+ required: false,
151
+ options: {
152
+ options: field.property?.options?.map((option) => ({
153
+ label: option.name,
154
+ value: option.name,
155
+ })) || [],
156
+ },
157
+ });
158
+ break;
159
+ case BikaFieldType.MEMBER:
160
+ props[field.name] = Property.StaticMultiSelectDropdown({
161
+ displayName: field.name,
162
+ required: false,
163
+ options: {
164
+ options:
165
+ field.property?.options?.map((option) => {
166
+ return {
167
+ label: option.name,
168
+ value: option.id,
169
+ };
170
+ }) || [],
171
+ },
172
+ });
173
+ break;
174
+ case BikaFieldType.LINK:
175
+ props[field.name] = Property.Array({
176
+ displayName: field.name,
177
+ required: false,
178
+ });
179
+ break;
180
+ }
181
+ }
182
+ }
183
+ return props;
184
+ },
185
+ }),
186
+ };
187
+
188
+ export async function createNewFields(
189
+ auth: AppConnectionValueForAuthProperty<typeof BikaAuth>,
190
+ space_id: string,
191
+ database_id: string,
192
+ fields: Record<string, unknown>,
193
+ ) {
194
+ if (!auth) return fields;
195
+ if (!database_id) return fields;
196
+
197
+ const newFields: Record<string, unknown> = {};
198
+
199
+ const client = makeClient(auth.props);
200
+ const res = await client.getDatabaseFields(space_id,database_id);
201
+
202
+ for(const field of res.data) {
203
+ if (
204
+ [
205
+ BikaFieldType.AUTONUMBER,
206
+ BikaFieldType.CASCADER,
207
+ BikaFieldType.CREATED_BY,
208
+ BikaFieldType.CREATED_TIME,
209
+ BikaFieldType.FORMULA,
210
+ BikaFieldType.LAST_MODIFIED_TIME,
211
+ BikaFieldType.LAST_MODIFIED_BY,
212
+ BikaFieldType.LOOKUP,
213
+ ].includes(field.type) || !(field.name in fields)
214
+ ) {
215
+ continue; // Skip irrelevant or missing fields
216
+ }
217
+
218
+ const key = field.name;
219
+
220
+ // Handle numeric fields
221
+ if(BikaNumericFieldTypes.includes(field.type))
222
+ {
223
+ newFields[key] = Number(fields[key]);
224
+ }
225
+ // Handle member fields
226
+ else if(field.type === BikaFieldType.MEMBER)
227
+ {
228
+ newFields[key] = field.property?.options?.filter(
229
+ (member) => member.id === `${fields[key]}`,
230
+ );
231
+ }
232
+ // Handle multi-select and two-way-link fields
233
+ else if([BikaFieldType.MULTI_SELECT].includes(field.type))
234
+ {
235
+ if(!Array.isArray(fields[key]) || (fields[key] as Array<unknown>).length === 0)
236
+ {
237
+ continue; // Skip empty fields
238
+ }
239
+ newFields[key] = fields[key];
240
+ }
241
+ // Handle all other fields
242
+ else
243
+ {
244
+ newFields[key] = fields[key];
245
+ }
246
+
247
+
248
+ }
249
+ return newFields;
250
+ }