@softr/sdk 0.0.25 → 0.0.26
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/action.d.ts +195 -0
- package/dist/action.js +21 -0
- package/dist/auth/isomorphic.d.ts +1 -2
- package/dist/auth/permissions.d.ts +7 -2
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +639 -0
- package/dist/codegen/datasource-types.js +471 -0
- package/dist/codegen/softr-env.js +103 -0
- package/dist/codegen/user-groups.js +11 -0
- package/dist/core/integration.js +1 -1
- package/dist/datasource/chart.d.ts +196 -0
- package/dist/datasource/chart.js +289 -0
- package/dist/datasource/datasource.d.ts +16 -0
- package/dist/datasource/datasource.js +17 -0
- package/dist/datasource/fields.d.ts +122 -0
- package/dist/datasource/fields.js +82 -0
- package/dist/datasource/filter.d.ts +30 -0
- package/dist/datasource/filter.js +146 -0
- package/dist/datasource/record.d.ts +138 -0
- package/dist/datasource/record.js +258 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.js +2 -2
- package/dist/package.js +5 -0
- package/dist/routes/visibility.d.ts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -1
- package/skills/softr-sdk/SKILL.md +6 -2
- package/skills/softr-sdk/auth/SKILL.md +1 -1
- package/skills/softr-sdk/current-user/SKILL.md +1 -1
- package/skills/softr-sdk/data-mutations/SKILL.md +1 -1
- package/skills/softr-sdk/integrations/SKILL.md +140 -0
- package/skills/softr-sdk/reading-records/SKILL.md +1 -1
- package/skills/softr-sdk/routes-and-visibility/SKILL.md +1 -1
- package/dist/core/datasource.d.ts +0 -31
- package/dist/core/datasource.js +0 -226
- package/dist/core/query.d.ts +0 -354
- package/dist/core/query.js +0 -528
- package/dist/core/utils.js +0 -14
- package/dist/types.d.ts +0 -128
|
@@ -139,4 +139,4 @@ type VisibilityHandle<R extends RouteVisibility = RouteVisibility> = {
|
|
|
139
139
|
*/
|
|
140
140
|
declare function defineVisibility<const R extends RouteVisibility>(rule: R): VisibilityHandle<R>;
|
|
141
141
|
//#endregion
|
|
142
|
-
export {
|
|
142
|
+
export { defineVisibility, isAccessError, isAlreadySignedInError, isForbiddenError, isUnauthenticatedError };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { auth } from "./auth/server.js";
|
|
2
|
-
import { ds } from "./
|
|
2
|
+
import { ds } from "./datasource/datasource.js";
|
|
3
3
|
import { workflow } from "./core/workflow.js";
|
|
4
4
|
import { integration } from "./core/integration.js";
|
|
5
5
|
import "@tanstack/react-start/server-only";
|
package/dist/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { auth } from "./auth/server.js";
|
|
2
|
-
import { ds } from "./
|
|
2
|
+
import { ds } from "./datasource/datasource.js";
|
|
3
3
|
import { workflow } from "./core/workflow.js";
|
|
4
4
|
import { integration } from "./core/integration.js";
|
|
5
5
|
import "@tanstack/react-start/server-only";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softr/sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"./server": { "types": "./dist/server.d.ts", "import": "./dist/server.js" },
|
|
9
9
|
"./vite": { "types": "./dist/vite.d.ts", "import": "./dist/vite.js" }
|
|
10
10
|
},
|
|
11
|
+
"bin": { "softr-sdk": "./dist/cli.js" },
|
|
11
12
|
"files": ["dist", "skills", "!skills/_artifacts"],
|
|
12
13
|
"publishConfig": { "access": "public" },
|
|
13
14
|
"scripts": {
|
|
@@ -11,7 +11,7 @@ description: >-
|
|
|
11
11
|
metadata:
|
|
12
12
|
type: core
|
|
13
13
|
library: softr-sdk
|
|
14
|
-
library_version: 0.0.
|
|
14
|
+
library_version: 0.0.26
|
|
15
15
|
---
|
|
16
16
|
|
|
17
17
|
# Softr SDK core
|
|
@@ -43,6 +43,7 @@ Each sub-skill stands alone for its task and repeats the boundary above, so most
|
|
|
43
43
|
| Fetch records from a datasource | [reading-records/SKILL.md](./reading-records/SKILL.md) |
|
|
44
44
|
| Create, update or delete a record | [data-mutations/SKILL.md](./data-mutations/SKILL.md) |
|
|
45
45
|
| Sign a visitor in, out, or up | [auth/SKILL.md](./auth/SKILL.md) |
|
|
46
|
+
| Call a connected third-party API (Slack, Stripe, Gmail, …) | [integrations/SKILL.md](./integrations/SKILL.md) |
|
|
46
47
|
|
|
47
48
|
## Quick Decision Tree
|
|
48
49
|
|
|
@@ -62,6 +63,9 @@ Submitting a form, editing a record, or deleting one?
|
|
|
62
63
|
Wiring a login, signup, or sign-out?
|
|
63
64
|
load softr-sdk/auth
|
|
64
65
|
|
|
66
|
+
Calling a connected third-party API (Slack, Stripe, Gmail, Google Calendar, …)?
|
|
67
|
+
load softr-sdk/integrations
|
|
68
|
+
|
|
65
69
|
Writing a server function, loader, or middleware in general?
|
|
66
70
|
not a Softr concern. Use the TanStack Start skills.
|
|
67
71
|
```
|
|
@@ -124,4 +128,4 @@ type User = {
|
|
|
124
128
|
|
|
125
129
|
## Version
|
|
126
130
|
|
|
127
|
-
Targets `@softr/sdk` v0.0.
|
|
131
|
+
Targets `@softr/sdk` v0.0.26.
|
|
@@ -16,7 +16,7 @@ description: >-
|
|
|
16
16
|
metadata:
|
|
17
17
|
type: sub-skill
|
|
18
18
|
library: softr-sdk
|
|
19
|
-
library_version: 0.0.
|
|
19
|
+
library_version: 0.0.26
|
|
20
20
|
---
|
|
21
21
|
|
|
22
22
|
**Boundary.** `auth` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build. `defineVisibility`, the hooks and every type come from `@softr/sdk`. The app is already wired (`src/start.ts`, `src/router.tsx`, `src/routes/__root.tsx`); never add a second registration. Load the core `softr-sdk` skill only if you are unsure this is the right sub-skill.
|
|
@@ -10,7 +10,7 @@ description: >-
|
|
|
10
10
|
metadata:
|
|
11
11
|
type: sub-skill
|
|
12
12
|
library: softr-sdk
|
|
13
|
-
library_version: 0.0.
|
|
13
|
+
library_version: 0.0.26
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
**Boundary.** `useCurrentUser`, `usePermissions` and every type come from `@softr/sdk` and work anywhere; `ds` and `auth` live on `@softr/sdk/server` and run only inside a `createServerFn` handler. The app is already wired — `src/start.ts` registers `softrAuthMiddleware`, `src/routes/__root.tsx` publishes the visitor as route context — never add a second registration. Load the core `softr-sdk` skill only if you are unsure this is the right sub-skill.
|
|
@@ -8,7 +8,7 @@ description: >-
|
|
|
8
8
|
metadata:
|
|
9
9
|
type: sub-skill
|
|
10
10
|
library: softr-sdk
|
|
11
|
-
library_version: 0.0.
|
|
11
|
+
library_version: 0.0.26
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
**Boundary.** `ds` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build. `q` and every type come from `@softr/sdk` and can be imported anywhere. The app is already wired (`src/start.ts`, `src/router.tsx`, `src/routes/__root.tsx`); never add a second registration. Load the core `softr-sdk` skill only if you are unsure this is the right sub-skill.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: integrations
|
|
3
|
+
description: >-
|
|
4
|
+
Call a third-party service connected to the app as an integration: integration.fetch
|
|
5
|
+
from @softr/sdk/server, reading the datasource id and integration type from the
|
|
6
|
+
"Attached data sources" prompt section, looking up the base URL from the reference
|
|
7
|
+
table by integrationType, and constructing the full API URL from base URL plus the
|
|
8
|
+
path and query params the model knows from training. Check res.ok before res.json().
|
|
9
|
+
metadata:
|
|
10
|
+
type: sub-skill
|
|
11
|
+
library: softr-sdk
|
|
12
|
+
library_version: 0.0.26
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
**Boundary.** `integration` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build. Load the core `softr-sdk` skill only if you are unsure this is the right sub-skill.
|
|
16
|
+
|
|
17
|
+
# Integrations
|
|
18
|
+
|
|
19
|
+
`integration.fetch` calls a third-party API through Softr's authenticated proxy. It handles authorization automatically — you never set API keys or auth headers yourself.
|
|
20
|
+
|
|
21
|
+
## Setup
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
25
|
+
import { integration } from '@softr/sdk/server'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Reading connected integrations from the prompt
|
|
29
|
+
|
|
30
|
+
When the user attaches integrations to the chat, they appear in the `## Attached data sources` section of the prompt in this form:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
- "Gmail" — database `<databaseId>`, integration `<integrationId>`, type `GMAIL`
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Use `integrationId` as the first argument to `integration.fetch`. Use `integrationType` (e.g. `GMAIL`) to look up the base URL from the reference table below — then construct the full URL by appending the path and query parameters your training knows for that API.
|
|
37
|
+
|
|
38
|
+
## Core pattern
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const getCalendarEvents = createServerFn().handler(async () => {
|
|
42
|
+
const now = new Date().toISOString()
|
|
43
|
+
const res = await integration.fetch(
|
|
44
|
+
'25d60213-ef4f-4fa0-b094-a46931b2d120',
|
|
45
|
+
`https://www.googleapis.com/calendar/v3/calendars/primary/events?timeMin=${encodeURIComponent(now)}&singleEvents=true&orderBy=startTime&maxResults=10`,
|
|
46
|
+
)
|
|
47
|
+
if (!res.ok) throw new Error(`Integration request failed: ${res.status} ${await res.text()}`)
|
|
48
|
+
return res.json()
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The integration id (`25d60213-…`) is the `integrationId` from the attached data sources section. The base URL (`https://www.googleapis.com`) is looked up from the reference table below by matching `integrationType` (`GOOGLE_CALENDAR`). The path and query params come from your knowledge of the Google Calendar REST API.
|
|
53
|
+
|
|
54
|
+
## POST request
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const sendSlackMessage = createServerFn({ method: 'POST' })
|
|
58
|
+
.validator((data: { channel: string; text: string }) => data)
|
|
59
|
+
.handler(async ({ data }) => {
|
|
60
|
+
const res = await integration.fetch(
|
|
61
|
+
'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
|
62
|
+
'https://slack.com/api/chat.postMessage',
|
|
63
|
+
{
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: { 'Content-Type': 'application/json' },
|
|
66
|
+
body: JSON.stringify({ channel: data.channel, text: data.text }),
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
if (!res.ok) throw new Error(`Slack error: ${res.status} ${await res.text()}`)
|
|
70
|
+
return res.json()
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`integration.fetch` accepts the same `RequestInit` options as the native `fetch`. Do not pass `Authorization` headers — Softr injects the integration's credentials automatically.
|
|
75
|
+
|
|
76
|
+
## Base URLs by integration type
|
|
77
|
+
|
|
78
|
+
Match the `integrationType` from the attached data sources section to find the base URL:
|
|
79
|
+
|
|
80
|
+
| integrationType | Base URL |
|
|
81
|
+
|---|---|
|
|
82
|
+
| ANTHROPIC_AI | `https://api.anthropic.com` |
|
|
83
|
+
| APOLLO | `https://api.apollo.io` |
|
|
84
|
+
| APIFY | `https://api.apify.com` |
|
|
85
|
+
| ASANA | `https://app.asana.com` |
|
|
86
|
+
| ATTIO | `https://api.attio.com` |
|
|
87
|
+
| BASEROW | `https://api.baserow.io` |
|
|
88
|
+
| BITLY | `https://api-ssl.bitly.com` |
|
|
89
|
+
| BREVO | `https://api.brevo.com` |
|
|
90
|
+
| CALCOM | `https://api.cal.com` |
|
|
91
|
+
| CALENDLY | `https://api.calendly.com` |
|
|
92
|
+
| COHERE | `https://api.cohere.com` |
|
|
93
|
+
| DEEPL | `https://api.deepl.com` |
|
|
94
|
+
| DEEP_SEEK_AI | `https://api.deepseek.com` |
|
|
95
|
+
| DOCS_AUTOMATOR | `https://api.docsautomator.co` |
|
|
96
|
+
| DOCUMINT | `https://api.documint.me` |
|
|
97
|
+
| ELASTIC_EMAIL | `https://api.elasticemail.com` |
|
|
98
|
+
| ELEVEN_LABS | `https://api.elevenlabs.io` |
|
|
99
|
+
| FATHOM | `https://api.fathom.ai` |
|
|
100
|
+
| FIRECRAWL | `https://api.firecrawl.dev` |
|
|
101
|
+
| GEMINI_AI | `https://generativelanguage.googleapis.com` |
|
|
102
|
+
| GMAIL | `https://gmail.googleapis.com` |
|
|
103
|
+
| GOOGLE_CALENDAR | `https://www.googleapis.com` |
|
|
104
|
+
| GRANOLA | `https://public-api.granola.ai` |
|
|
105
|
+
| HUNTER | `https://api.hunter.io` |
|
|
106
|
+
| INSTANTLY | `https://api.instantly.ai` |
|
|
107
|
+
| LEMLIST | `https://api.lemlist.com` |
|
|
108
|
+
| LINEAR | `https://api.linear.app` |
|
|
109
|
+
| LINKUP | `https://api.linkup.so` |
|
|
110
|
+
| LOOPS | `https://app.loops.so` |
|
|
111
|
+
| MESSAGE_BIRD | `https://rest.messagebird.com` |
|
|
112
|
+
| MISTRAL_AI | `https://api.mistral.ai` |
|
|
113
|
+
| OPEN_AI | `https://api.openai.com` |
|
|
114
|
+
| OUTLOOK | `https://graph.microsoft.com` |
|
|
115
|
+
| PADDLE | `https://api.paddle.com` |
|
|
116
|
+
| PINECONE | `https://api.pinecone.io` |
|
|
117
|
+
| PIPELINE_CRM | `https://api.pipelinecrm.com` |
|
|
118
|
+
| POSTMARK | `https://api.postmarkapp.com` |
|
|
119
|
+
| REPLICATE | `https://api.replicate.com` |
|
|
120
|
+
| RESEND | `https://api.resend.com` |
|
|
121
|
+
| SLACK | `https://slack.com/api` |
|
|
122
|
+
| SMARTLEAD | `https://server.smartlead.ai` |
|
|
123
|
+
| STRIPE | `https://api.stripe.com` |
|
|
124
|
+
| TELEGRAM | `https://api.telegram.org` |
|
|
125
|
+
| TINYURL | `https://api.tinyurl.com` |
|
|
126
|
+
| TWILIO | `https://api.twilio.com` |
|
|
127
|
+
| XERO | `https://api.xero.com` |
|
|
128
|
+
| ZOOM | `https://api.zoom.us` |
|
|
129
|
+
|
|
130
|
+
## When the service is not a Softr integration
|
|
131
|
+
|
|
132
|
+
If the API the user needs is not in the table above, call it directly from a server function using the native `fetch`. The user supplies their own credentials and the call lives entirely in the server function handler.
|
|
133
|
+
|
|
134
|
+
## Failure modes
|
|
135
|
+
|
|
136
|
+
**Calling from client code.** `integration` is server-only. Importing it into a component or calling it outside a `createServerFn` handler fails the build or has no request context to act on.
|
|
137
|
+
|
|
138
|
+
**Setting Authorization headers.** Softr injects credentials automatically. A manually set `Authorization` header overrides the injected one and the request is rejected by the third-party API.
|
|
139
|
+
|
|
140
|
+
**Not checking `res.ok`.** `integration.fetch` returns the proxied response as-is. A 4xx or 5xx from the third-party API is not thrown — call `res.ok` or `res.status` before calling `res.json()`.
|
|
@@ -10,7 +10,7 @@ description: >-
|
|
|
10
10
|
metadata:
|
|
11
11
|
type: sub-skill
|
|
12
12
|
library: softr-sdk
|
|
13
|
-
library_version: 0.0.
|
|
13
|
+
library_version: 0.0.26
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
**Boundary.** `ds` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler or a server-side route hook — importing it into a component fails the build. `q` and every type come from `@softr/sdk` and can be imported anywhere. The app is already wired (`src/start.ts`, `src/router.tsx`, `src/routes/__root.tsx`); never add a second registration. Load the core `softr-sdk` skill only if you are unsure this is the right sub-skill.
|
|
@@ -12,7 +12,7 @@ description: >-
|
|
|
12
12
|
metadata:
|
|
13
13
|
type: sub-skill
|
|
14
14
|
library: softr-sdk
|
|
15
|
-
library_version: 0.0.
|
|
15
|
+
library_version: 0.0.26
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
**Boundary.** `defineVisibility`, the error predicates and every type come from `@softr/sdk` and work anywhere; `ds` and `auth` live on `@softr/sdk/server` and run only inside a `createServerFn` handler. The app is already wired — `src/start.ts` registers `softrAuthMiddleware`, `src/routes/__root.tsx` publishes the visitor as route context — never add a second registration. Load the core `softr-sdk` skill only if you are unsure this is the right sub-skill.
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { ChartOptions, ChartResult, CreateOptions, GetOptions, ListOptions, ListResult, MetricOptions, MetricResult, RecordFieldsFor, SoftrRecord, UpdateOptions } from "../types.js";
|
|
2
|
-
|
|
3
|
-
//#region src/core/datasource.d.ts
|
|
4
|
-
declare function listRecords<Id extends string, Sel extends Record<string, string> = Record<never, never>>(datasourceId: Id, options?: Omit<ListOptions, 'select'> & {
|
|
5
|
-
select?: Sel;
|
|
6
|
-
}): Promise<ListResult<SoftrRecord<RecordFieldsFor<Id, Sel>>>>;
|
|
7
|
-
declare function getRecord<Id extends string, Sel extends Record<string, string> = Record<never, never>>(datasourceId: Id, options: Omit<GetOptions, 'select'> & {
|
|
8
|
-
select?: Sel;
|
|
9
|
-
}): Promise<SoftrRecord<RecordFieldsFor<Id, Sel>> | null>;
|
|
10
|
-
declare function createRecord<Id extends string, Sel extends Record<string, string> = Record<never, never>>(datasourceId: Id, options: Omit<CreateOptions, 'select' | 'data'> & {
|
|
11
|
-
select?: Sel;
|
|
12
|
-
data: Partial<RecordFieldsFor<Id, Sel>>;
|
|
13
|
-
}): Promise<SoftrRecord<RecordFieldsFor<Id, Sel>>>;
|
|
14
|
-
declare function updateRecord<Id extends string, Sel extends Record<string, string> = Record<never, never>>(datasourceId: Id, options: Omit<UpdateOptions, 'select' | 'data'> & {
|
|
15
|
-
select?: Sel;
|
|
16
|
-
data: Partial<RecordFieldsFor<Id, Sel>>;
|
|
17
|
-
}): Promise<SoftrRecord<RecordFieldsFor<Id, Sel>>>;
|
|
18
|
-
declare function deleteRecord(datasourceId: string, id: string): Promise<void>;
|
|
19
|
-
declare function getMetric(datasourceId: string, options: MetricOptions): Promise<MetricResult>;
|
|
20
|
-
declare function getChart(datasourceId: string, options: ChartOptions): Promise<ChartResult>;
|
|
21
|
-
declare const ds: {
|
|
22
|
-
list: typeof listRecords;
|
|
23
|
-
get: typeof getRecord;
|
|
24
|
-
create: typeof createRecord;
|
|
25
|
-
update: typeof updateRecord;
|
|
26
|
-
delete: typeof deleteRecord;
|
|
27
|
-
metric: typeof getMetric;
|
|
28
|
-
chart: typeof getChart;
|
|
29
|
-
};
|
|
30
|
-
//#endregion
|
|
31
|
-
export { ds };
|
package/dist/core/datasource.js
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import { buildFilterCriteria, buildOrderBy, dealiasFilterCriteria, dealiasOrderBy } from "./query.js";
|
|
2
|
-
import { panic } from "../assertions.js";
|
|
3
|
-
import { readUserTimezone } from "../cookies.js";
|
|
4
|
-
import { sessionConfig } from "../auth/config.js";
|
|
5
|
-
import { readSessionToken } from "../auth/session.js";
|
|
6
|
-
import { assertResponseOk } from "./utils.js";
|
|
7
|
-
|
|
8
|
-
//#region src/core/datasource.ts
|
|
9
|
-
const MAX_RECORD_COUNT = 100;
|
|
10
|
-
const COUNT_CNT = "COUNT_CNT";
|
|
11
|
-
const dsUrl = (datasourceId) => {
|
|
12
|
-
const datasourceApiUrl = process.env.SOFTR_DATASOURCE_API_URL;
|
|
13
|
-
if (!datasourceApiUrl) panic("SOFTR_DATASOURCE_API_URL is not set.");
|
|
14
|
-
const { applicationId } = sessionConfig();
|
|
15
|
-
return `${withoutTrailingSlash(datasourceApiUrl)}/v1/datasource/vibe/applications/${applicationId}/datasources/${datasourceId}`;
|
|
16
|
-
};
|
|
17
|
-
async function listRecords(datasourceId, options = {}) {
|
|
18
|
-
const { select, where, orderBy } = options;
|
|
19
|
-
const count = Math.min(options.limit ?? 20, MAX_RECORD_COUNT);
|
|
20
|
-
const offset = options.offset ?? null;
|
|
21
|
-
const filterCriteria = where ? dealiasFilterCriteria(buildFilterCriteria(where), select ?? {}) : {};
|
|
22
|
-
const sortingOption = orderBy ? dealiasOrderBy(buildOrderBy(orderBy), select ?? {}) : void 0;
|
|
23
|
-
const res = await fetch(`${dsUrl(datasourceId)}/records`, {
|
|
24
|
-
method: "POST",
|
|
25
|
-
headers: {
|
|
26
|
-
"Content-Type": "application/json",
|
|
27
|
-
"X-User-Timezone": options.timeZone ?? readUserTimezone() ?? "UTC",
|
|
28
|
-
"Authorization": `Bearer ${readSessionToken()}`
|
|
29
|
-
},
|
|
30
|
-
body: JSON.stringify({
|
|
31
|
-
options: { userLocale: "en-US" },
|
|
32
|
-
filterCriteria,
|
|
33
|
-
sortingOption,
|
|
34
|
-
pagingOption: {
|
|
35
|
-
offset,
|
|
36
|
-
count
|
|
37
|
-
}
|
|
38
|
-
})
|
|
39
|
-
});
|
|
40
|
-
await assertResponseOk(res, `Softr datasource list failed for "${datasourceId}"`);
|
|
41
|
-
const payload = await res.json();
|
|
42
|
-
const nextOffset = payload.offset < payload.total ? payload.offset : null;
|
|
43
|
-
return {
|
|
44
|
-
records: payload.items.map((item) => ({
|
|
45
|
-
id: item.id,
|
|
46
|
-
fields: select ? aliasFields(item.fields, select) : item.fields
|
|
47
|
-
})),
|
|
48
|
-
total: payload.total,
|
|
49
|
-
nextOffset
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
async function getRecord(datasourceId, options) {
|
|
53
|
-
if (options.id !== void 0) {
|
|
54
|
-
if (!options.id) throw new Error(`Softr datasource get: id must not be empty`);
|
|
55
|
-
const res = await fetch(`${dsUrl(datasourceId)}/records/${options.id}`, {
|
|
56
|
-
method: "POST",
|
|
57
|
-
headers: {
|
|
58
|
-
"Content-Type": "application/json",
|
|
59
|
-
"X-User-Timezone": options.timeZone ?? readUserTimezone() ?? "UTC",
|
|
60
|
-
"Authorization": `Bearer ${readSessionToken()}`
|
|
61
|
-
},
|
|
62
|
-
body: JSON.stringify({ options: { userLocale: "en-US" } })
|
|
63
|
-
});
|
|
64
|
-
await assertResponseOk(res, `Softr datasource get failed for "${datasourceId}"`);
|
|
65
|
-
const contentLength = res.headers.get("Content-Length");
|
|
66
|
-
if (contentLength && parseInt(contentLength, 10) === 0) return null;
|
|
67
|
-
const record = await res.json();
|
|
68
|
-
return {
|
|
69
|
-
id: record.id,
|
|
70
|
-
fields: options.select ? aliasFields(record.fields, options.select) : record.fields
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
return (await listRecords(datasourceId, {
|
|
74
|
-
where: options.where,
|
|
75
|
-
select: options.select,
|
|
76
|
-
limit: 1,
|
|
77
|
-
timeZone: options.timeZone
|
|
78
|
-
})).records[0] ?? null;
|
|
79
|
-
}
|
|
80
|
-
async function createRecord(datasourceId, options) {
|
|
81
|
-
const rawFields = options.select ? dealiasData(options.data, options.select) : options.data;
|
|
82
|
-
const res = await fetch(`${dsUrl(datasourceId)}/records/new`, {
|
|
83
|
-
method: "POST",
|
|
84
|
-
headers: {
|
|
85
|
-
"Content-Type": "application/json",
|
|
86
|
-
"Authorization": `Bearer ${readSessionToken()}`
|
|
87
|
-
},
|
|
88
|
-
body: JSON.stringify({ fields: rawFields })
|
|
89
|
-
});
|
|
90
|
-
await assertResponseOk(res, `Softr datasource create failed for "${datasourceId}"`);
|
|
91
|
-
const record = await res.json();
|
|
92
|
-
return {
|
|
93
|
-
id: record.id,
|
|
94
|
-
fields: options.select ? aliasFields(record.fields, options.select) : record.fields
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
async function updateRecord(datasourceId, options) {
|
|
98
|
-
if (!options.id) throw new Error(`Softr datasource update: id must not be empty`);
|
|
99
|
-
const rawFields = options.select ? dealiasData(options.data, options.select) : options.data;
|
|
100
|
-
const res = await fetch(`${dsUrl(datasourceId)}/records/${options.id}`, {
|
|
101
|
-
method: "PATCH",
|
|
102
|
-
headers: {
|
|
103
|
-
"Content-Type": "application/json",
|
|
104
|
-
"Authorization": `Bearer ${readSessionToken()}`
|
|
105
|
-
},
|
|
106
|
-
body: JSON.stringify({ fields: rawFields })
|
|
107
|
-
});
|
|
108
|
-
await assertResponseOk(res, `Softr datasource update failed for "${datasourceId}"`);
|
|
109
|
-
const record = await res.json();
|
|
110
|
-
return {
|
|
111
|
-
id: record.id,
|
|
112
|
-
fields: options.select ? aliasFields(record.fields, options.select) : record.fields
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
async function deleteRecord(datasourceId, id) {
|
|
116
|
-
await assertResponseOk(await fetch(`${dsUrl(datasourceId)}/records/${id}`, {
|
|
117
|
-
method: "DELETE",
|
|
118
|
-
headers: { "Authorization": `Bearer ${readSessionToken()}` }
|
|
119
|
-
}), `Softr datasource delete failed for "${datasourceId}"`);
|
|
120
|
-
}
|
|
121
|
-
async function getMetric(datasourceId, options) {
|
|
122
|
-
const { select, where, aggregation, field } = options;
|
|
123
|
-
if (aggregation !== "COUNT" && !field) throw new Error(`field is required for aggregation "${aggregation}"`);
|
|
124
|
-
const fieldId = field ? select?.[field] ?? field : void 0;
|
|
125
|
-
const filterCriteria = where ? dealiasFilterCriteria(buildFilterCriteria(where), select ?? {}) : {};
|
|
126
|
-
const res = await fetch(`${dsUrl(datasourceId)}/chart/data`, {
|
|
127
|
-
method: "POST",
|
|
128
|
-
headers: {
|
|
129
|
-
"Content-Type": "application/json",
|
|
130
|
-
"X-User-Timezone": options.timeZone ?? readUserTimezone() ?? "UTC",
|
|
131
|
-
"Authorization": `Bearer ${readSessionToken()}`
|
|
132
|
-
},
|
|
133
|
-
body: JSON.stringify({
|
|
134
|
-
options: { userLocale: "en-US" },
|
|
135
|
-
groupBy: [],
|
|
136
|
-
metrics: [{
|
|
137
|
-
field: fieldId,
|
|
138
|
-
aggregation
|
|
139
|
-
}],
|
|
140
|
-
filterCriteria
|
|
141
|
-
})
|
|
142
|
-
});
|
|
143
|
-
await assertResponseOk(res, `Softr datasource metric failed for "${datasourceId}"`);
|
|
144
|
-
const payload = await res.json();
|
|
145
|
-
const executionTimestamp = /* @__PURE__ */ new Date(payload.executionTimestamp * 1e3);
|
|
146
|
-
let value;
|
|
147
|
-
const responseKey = aggregation === "COUNT" && !fieldId ? COUNT_CNT : fieldId;
|
|
148
|
-
value = payload.data.find((item) => responseKey in item)?.[responseKey] ?? null;
|
|
149
|
-
return {
|
|
150
|
-
value,
|
|
151
|
-
executionTimestamp
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
async function getChart(datasourceId, options) {
|
|
155
|
-
const { select, where, groupBy, metrics, orderBy } = options;
|
|
156
|
-
const filterCriteria = where ? dealiasFilterCriteria(buildFilterCriteria(where), select ?? {}) : {};
|
|
157
|
-
const sortingOptions = orderBy ? dealiasOrderBy(buildOrderBy(orderBy), select ?? {}) : void 0;
|
|
158
|
-
const groupByFields = (Array.isArray(groupBy) ? groupBy : [groupBy]).map((field) => ({ field: select?.[field] ?? field }));
|
|
159
|
-
const yAxisFields = Object.values(metrics).map(({ field, aggregation }) => ({
|
|
160
|
-
field: field ? select?.[field] ?? field : void 0,
|
|
161
|
-
aggregation
|
|
162
|
-
}));
|
|
163
|
-
if (!yAxisFields.length) throw new Error("chart requires at least one metric");
|
|
164
|
-
const res = await fetch(`${dsUrl(datasourceId)}/chart/data`, {
|
|
165
|
-
method: "POST",
|
|
166
|
-
headers: {
|
|
167
|
-
"Content-Type": "application/json",
|
|
168
|
-
"X-User-Timezone": options.timeZone ?? readUserTimezone() ?? "UTC",
|
|
169
|
-
"Authorization": `Bearer ${readSessionToken()}`
|
|
170
|
-
},
|
|
171
|
-
body: JSON.stringify({
|
|
172
|
-
options: { userLocale: "en-US" },
|
|
173
|
-
filterCriteria,
|
|
174
|
-
sortingOptions,
|
|
175
|
-
groupBy: groupByFields,
|
|
176
|
-
metrics: yAxisFields
|
|
177
|
-
})
|
|
178
|
-
});
|
|
179
|
-
await assertResponseOk(res, `Softr datasource chart failed for "${datasourceId}"`);
|
|
180
|
-
const payload = await res.json();
|
|
181
|
-
const executionTimestamp = /* @__PURE__ */ new Date(payload.executionTimestamp * 1e3);
|
|
182
|
-
const aliases = {};
|
|
183
|
-
const multipleMetrics = Object.keys(metrics).length > 1;
|
|
184
|
-
for (const [alias, { field, aggregation }] of Object.entries(metrics)) if (aggregation === "COUNT") aliases[COUNT_CNT] = alias;
|
|
185
|
-
else {
|
|
186
|
-
const fieldId = field ? select?.[field] ?? field : field;
|
|
187
|
-
if (fieldId) {
|
|
188
|
-
const suffix = multipleMetrics ? `_${aggregation.toLowerCase()}` : "";
|
|
189
|
-
aliases[fieldId + suffix] = alias;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
for (const field of Array.isArray(groupBy) ? groupBy : [groupBy]) aliases[select?.[field] ?? field] = field;
|
|
193
|
-
return {
|
|
194
|
-
data: payload.data.map((item) => {
|
|
195
|
-
const row = {};
|
|
196
|
-
for (const [rawKey, alias] of Object.entries(aliases)) if (rawKey in item) row[alias] = item[rawKey];
|
|
197
|
-
return row;
|
|
198
|
-
}),
|
|
199
|
-
executionTimestamp
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
function aliasFields(fields, aliases) {
|
|
203
|
-
const result = {};
|
|
204
|
-
for (const [alias, fieldId] of Object.entries(aliases)) result[alias] = fields[fieldId];
|
|
205
|
-
return result;
|
|
206
|
-
}
|
|
207
|
-
function dealiasData(data, select) {
|
|
208
|
-
const result = {};
|
|
209
|
-
for (const [alias, value] of Object.entries(data)) result[select[alias] ?? alias] = value;
|
|
210
|
-
return result;
|
|
211
|
-
}
|
|
212
|
-
const ds = {
|
|
213
|
-
list: listRecords,
|
|
214
|
-
get: getRecord,
|
|
215
|
-
create: createRecord,
|
|
216
|
-
update: updateRecord,
|
|
217
|
-
delete: deleteRecord,
|
|
218
|
-
metric: getMetric,
|
|
219
|
-
chart: getChart
|
|
220
|
-
};
|
|
221
|
-
function withoutTrailingSlash(url) {
|
|
222
|
-
return url.replace(/\/+$/, "");
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
//#endregion
|
|
226
|
-
export { ds, dsUrl };
|