@softr/sdk 0.0.11 → 0.0.13

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 (47) hide show
  1. package/dist/assertions.js +68 -0
  2. package/dist/auth/config.js +27 -0
  3. package/dist/auth/context.d.ts +18 -0
  4. package/dist/auth/isomorphic.d.ts +73 -0
  5. package/dist/auth/isomorphic.js +113 -0
  6. package/dist/auth/methods/password.d.ts +22 -0
  7. package/dist/auth/methods/password.js +51 -0
  8. package/dist/auth/methods/signup.d.ts +37 -0
  9. package/dist/auth/methods/signup.js +89 -0
  10. package/dist/auth/methods/utils.d.ts +15 -0
  11. package/dist/auth/methods/utils.js +13 -0
  12. package/dist/auth/permissions.d.ts +17 -0
  13. package/dist/auth/permissions.js +13 -0
  14. package/dist/auth/platform.js +138 -0
  15. package/dist/auth/server.d.ts +17 -0
  16. package/dist/auth/server.js +17 -0
  17. package/dist/auth/session.js +167 -0
  18. package/dist/{user-D23hwTsf.d.ts → auth/user.d.ts} +1 -1
  19. package/dist/client/timezone.d.ts +4 -0
  20. package/dist/client/timezone.js +11 -0
  21. package/dist/client.d.ts +1 -4
  22. package/dist/client.js +1 -9
  23. package/dist/cookies.js +47 -0
  24. package/dist/core/datasource.d.ts +21 -0
  25. package/dist/core/datasource.js +196 -0
  26. package/dist/core/integration.d.ts +7 -0
  27. package/dist/core/integration.js +32 -0
  28. package/dist/core/query.d.ts +354 -0
  29. package/dist/core/query.js +528 -0
  30. package/dist/core/workflow.d.ts +7 -0
  31. package/dist/core/workflow.js +19 -0
  32. package/dist/index.d.ts +8 -685
  33. package/dist/index.js +4 -1019
  34. package/dist/routes/discover.d.ts +9 -0
  35. package/dist/routes/discover.js +198 -0
  36. package/dist/routes/manifest.d.ts +17 -0
  37. package/dist/routes/manifest.js +148 -0
  38. package/dist/routes/mem-fs.js +71 -0
  39. package/dist/routes/visibility.d.ts +142 -0
  40. package/dist/routes/visibility.js +125 -0
  41. package/dist/server.d.ts +5 -84
  42. package/dist/server.js +5 -194
  43. package/dist/types.d.ts +105 -0
  44. package/dist/vite.d.ts +1 -21
  45. package/dist/vite.js +2 -405
  46. package/package.json +4 -4
  47. package/dist/session-GUEI96C8.js +0 -360
@@ -0,0 +1,125 @@
1
+ import { redirect } from "@tanstack/react-router";
2
+
3
+ //#region src/routes/visibility.ts
4
+ /**
5
+ * Why a visitor was denied.
6
+ *
7
+ * - `unauthenticated` — the route needs a signed-in visitor and there is none.
8
+ * - `forbidden` — signed in, but in none of the groups the route names.
9
+ * - `alreadySignedIn` — the route is for signed-out visitors only.
10
+ */
11
+ const ACCESS_REASONS = [
12
+ "unauthenticated",
13
+ "forbidden",
14
+ "alreadySignedIn"
15
+ ];
16
+ /** Decides whether a visitor passes a visibility rule, without throwing. */
17
+ function checkAccess(rule, context) {
18
+ const { user } = context;
19
+ switch (rule.userGroup) {
20
+ case "ALL_USERS": return { ok: true };
21
+ case "NON_LOGGED_IN_USERS": return user === null ? { ok: true } : {
22
+ ok: false,
23
+ reason: "alreadySignedIn"
24
+ };
25
+ case "LOGGED_IN_USERS": {
26
+ if (user === null) return {
27
+ ok: false,
28
+ reason: "unauthenticated"
29
+ };
30
+ const required = rule.userGroupIds ?? [];
31
+ if (required.length === 0) return { ok: true };
32
+ return required.some((id) => user.userGroupIds.includes(id)) ? { ok: true } : {
33
+ ok: false,
34
+ reason: "forbidden"
35
+ };
36
+ }
37
+ }
38
+ }
39
+ function isAccessReason(value) {
40
+ return ACCESS_REASONS.some((reason) => reason === value);
41
+ }
42
+ /**
43
+ * True for any access error thrown by a guard, whatever its reason.
44
+ *
45
+ * An access error arrives as `unknown` — hydrated from the page, or thrown by
46
+ * anything else at all — so every field is checked before it is narrowed.
47
+ */
48
+ function isAccessError(error) {
49
+ if (typeof error !== "object" || error === null) return false;
50
+ const candidate = error;
51
+ if (candidate.type !== "access_error") return false;
52
+ return isAccessReason(candidate.reason);
53
+ }
54
+ function isReason(error, reason) {
55
+ return isAccessError(error) && error.reason === reason;
56
+ }
57
+ /** True for an access error thrown because nobody was signed in. */
58
+ function isUnauthenticatedError(error) {
59
+ return isReason(error, "unauthenticated");
60
+ }
61
+ /** True for an access error thrown because the visitor was in none of the groups. */
62
+ function isForbiddenError(error) {
63
+ return isReason(error, "forbidden");
64
+ }
65
+ /** True for an access error thrown because a signed-out-only route saw a user. */
66
+ function isAlreadySignedInError(error) {
67
+ return isReason(error, "alreadySignedIn");
68
+ }
69
+ const DEFAULT_ACCESS_BEHAVIOUR = {
70
+ unauthenticated: {
71
+ strategy: "redirect",
72
+ to: "/login"
73
+ },
74
+ forbidden: { strategy: "throw" },
75
+ alreadySignedIn: {
76
+ strategy: "redirect",
77
+ to: "/"
78
+ }
79
+ };
80
+ /** What a guard throws for a reason: a redirect, or an {@link AccessError}. */
81
+ function thrownFor(reason, overrides) {
82
+ const behaviour = overrides?.[reason] ?? DEFAULT_ACCESS_BEHAVIOUR[reason];
83
+ const resolved = typeof behaviour === "string" ? {
84
+ strategy: "redirect",
85
+ to: behaviour
86
+ } : behaviour;
87
+ if (resolved.strategy === "redirect") return redirect({ to: resolved.to });
88
+ return {
89
+ type: "access_error",
90
+ reason
91
+ };
92
+ }
93
+ /**
94
+ * Declares who may see a route, and returns the handle that enforces it.
95
+ *
96
+ * One call per route file, at module scope: the build reads these declarations
97
+ * to publish the app's route visibility, so the rule must be a literal.
98
+ *
99
+ * @example
100
+ * const visibility = defineVisibility({ userGroup: "LOGGED_IN_USERS" })
101
+ *
102
+ * export const Route = createFileRoute("/about")({
103
+ * beforeLoad: visibility.guard(),
104
+ * component: About,
105
+ * })
106
+ */
107
+ function defineVisibility(rule) {
108
+ return {
109
+ guard: (overrides) => ({ context }) => {
110
+ const result = checkAccess(rule, context);
111
+ if (result.ok) return;
112
+ throw thrownFor(result.reason, overrides);
113
+ },
114
+ assert: (context) => {
115
+ const result = checkAccess(rule, context);
116
+ if (result.ok) return;
117
+ throw thrownFor(result.reason, void 0);
118
+ },
119
+ isAuthorized: (context) => checkAccess(rule, context).ok,
120
+ check: (context) => checkAccess(rule, context)
121
+ };
122
+ }
123
+
124
+ //#endregion
125
+ export { defineVisibility, isAccessError, isAlreadySignedInError, isForbiddenError, isUnauthenticatedError };
package/dist/server.d.ts CHANGED
@@ -1,85 +1,6 @@
1
- import { r as User } from "./user-D23hwTsf.js";
1
+ import { auth } from "./auth/server.js";
2
+ import { ds } from "./core/datasource.js";
3
+ import { workflow } from "./core/workflow.js";
4
+ import { integration } from "./core/integration.js";
2
5
  import "@tanstack/react-start/server-only";
3
-
4
- //#region src/auth/methods/utils.d.ts
5
- type Result<T, E = Error> = {
6
- success: false;
7
- error: E;
8
- } | {
9
- success: true;
10
- data: T;
11
- };
12
- type AuthError = {
13
- code: AuthErrorCode;
14
- message: string;
15
- };
16
- type AuthErrorCode = "invalidCredentials" | "tooManyAttempts" | "emailTaken" | "invalidDetails" | "invalidCode";
17
- //#endregion
18
- //#region src/auth/methods/password.d.ts
19
- /**
20
- * Signs a visitor in with an email and password.
21
- *
22
- * The cache is primed from the same `/me` the resolver would have called anyway,
23
- * so the next request costs no round-trip.
24
- */
25
- declare function signIn(credentials: {
26
- email: string;
27
- password: string;
28
- }): Promise<Result<User, AuthError>>;
29
- /**
30
- * Signs the visitor out and clears the session cookies.
31
- *
32
- * This revokes **every** outstanding session for that user in this app, on every
33
- * device: the platform stamps `tokensInvalidatedAt` and rejects anything issued
34
- * before it. There is no per-device sign-out.
35
- *
36
- * A platform that could not revoke leaves the caller signed out here and
37
- * possibly still signed in elsewhere — loud outside production, logged in it,
38
- * because the local half already succeeded.
39
- */
40
- declare function signOut(): Promise<void>;
41
- //#endregion
42
- //#region src/auth/methods/signup.d.ts
43
- /**
44
- * Starts a sign-up flow by emailing a verification code.
45
- *
46
- * User only exists once `confirmSignUp` returns the code.
47
- */
48
- declare function signUp(details: {
49
- email: string;
50
- name?: string;
51
- }): Promise<Result<null, AuthError>>;
52
- /**
53
- * Finishes sign-up flow with the emailed code and signs the visitor in.
54
- *
55
- * The platform splits on how the app is configured: a code-only app creates the
56
- * user on the second `sign-up` request and answers with a token, while a
57
- * password app answers it with an empty 200 and expects the password on
58
- * `users/new`.
59
- */
60
- declare function confirmSignUp(details: {
61
- email: string;
62
- /**
63
- * What `users/new` creates the account with, and unused on a code-only app,
64
- * which never reaches that request.
65
- *
66
- * TODO: temporary in one respect — it is spent a second time on the
67
- * 400-after-create workaround below, which goes away with the platform fix.
68
- */
69
- password: string;
70
- code: string;
71
- name?: string;
72
- }): Promise<Result<User, AuthError>>;
73
- //#endregion
74
- //#region src/auth/server.d.ts
75
- /**
76
- * Server-only auth methods, should be called from the app's own `createServerFn` handlers.
77
- */
78
- declare const auth: {
79
- signIn: typeof signIn;
80
- signOut: typeof signOut;
81
- signUp: typeof signUp;
82
- confirmSignUp: typeof confirmSignUp;
83
- };
84
- //#endregion
85
- export { auth };
6
+ export { auth, ds, integration, workflow };
package/dist/server.js CHANGED
@@ -1,196 +1,7 @@
1
- import { a as readSessionToken, c as writeCookies, d as softAssert, f as todo, n as makeSessionDataCookie, o as fetchVisitor, r as makeSessionTokenCookie, s as platformFetch, t as clearSession, u as panic } from "./session-GUEI96C8.js";
2
- import { z } from "zod";
1
+ import { auth } from "./auth/server.js";
2
+ import { ds } from "./core/datasource.js";
3
+ import { workflow } from "./core/workflow.js";
4
+ import { integration } from "./core/integration.js";
3
5
  import "@tanstack/react-start/server-only";
4
6
 
5
- //#region src/auth/methods/utils.ts
6
- function authError(code, message) {
7
- return {
8
- success: false,
9
- error: {
10
- code,
11
- message
12
- }
13
- };
14
- }
15
- const errorSchema = z.object({ message: z.string() });
16
- async function describe(response, action) {
17
- const body = errorSchema.safeParse(await response.json().catch(() => null));
18
- const said = body.success && body.data.message ? body.data.message : response.statusText;
19
- return `Softr platform refused the ${action}: ${response.status} ${said}`.trim();
20
- }
21
-
22
- //#endregion
23
- //#region src/auth/methods/password.ts
24
- const signInBody = z.object({
25
- jwt: z.string().nullish(),
26
- authenticatorAppAction: z.string().nullish()
27
- });
28
- /**
29
- * Signs a visitor in with an email and password.
30
- *
31
- * The cache is primed from the same `/me` the resolver would have called anyway,
32
- * so the next request costs no round-trip.
33
- */
34
- async function signIn(credentials) {
35
- const response = await platformFetch("sign-in", {
36
- method: "POST",
37
- headers: {
38
- "content-type": "application/json",
39
- accept: "application/json"
40
- },
41
- body: JSON.stringify(credentials)
42
- });
43
- if (response.status === 401 || response.status === 429) return authError(response.status === 401 ? "invalidCredentials" : "tooManyAttempts", await describe(response, "sign-in"));
44
- if (response.status >= 500) panic(await describe(response, "sign-in"));
45
- if (response.status === 400) todo(await describe(response, "sign-in"));
46
- if (!response.ok) throw new Error(`${await describe(response, "sign-in")}. Check SOFTR_APPLICATION_ID and that the app allows sign-in.`);
47
- const raw = await response.text();
48
- const body = signInBody.parse(raw.length === 0 ? {} : JSON.parse(raw));
49
- if (body.authenticatorAppAction) todo(`Softr platform asked for an authenticator step (${body.authenticatorAppAction}) to finish the sign-in. This app requires an authenticator app, which the SDK cannot finish yet.`);
50
- const token = body.jwt;
51
- if (!token) todo("Softr platform answered the sign-in by emailing a one-time code. This app is configured for passwordless sign-in, which the SDK cannot finish yet.");
52
- const user = await fetchVisitor(token);
53
- if (!user) panic("Session token issued, but no user could be resolved for it.");
54
- writeCookies([makeSessionTokenCookie(token), makeSessionDataCookie(user, token)]);
55
- return {
56
- success: true,
57
- data: user
58
- };
59
- }
60
- /**
61
- * Signs the visitor out and clears the session cookies.
62
- *
63
- * This revokes **every** outstanding session for that user in this app, on every
64
- * device: the platform stamps `tokensInvalidatedAt` and rejects anything issued
65
- * before it. There is no per-device sign-out.
66
- *
67
- * A platform that could not revoke leaves the caller signed out here and
68
- * possibly still signed in elsewhere — loud outside production, logged in it,
69
- * because the local half already succeeded.
70
- */
71
- async function signOut() {
72
- const token = readSessionToken();
73
- clearSession();
74
- if (!token) return;
75
- const response = await platformFetch("sign-out", {
76
- method: "POST",
77
- headers: {
78
- authorization: `Bearer ${token}`,
79
- accept: "application/json"
80
- }
81
- });
82
- softAssert(response.ok || response.status < 500, () => `Softr platform could not revoke the session: ${response.status}. This browser is signed out, but other devices may still be.`);
83
- }
84
-
85
- //#endregion
86
- //#region src/auth/methods/signup.ts
87
- /**
88
- * Starts a sign-up flow by emailing a verification code.
89
- *
90
- * User only exists once `confirmSignUp` returns the code.
91
- */
92
- async function signUp(details) {
93
- const response = await platformFetch("sign-up", {
94
- method: "POST",
95
- headers: {
96
- "content-type": "application/json",
97
- accept: "application/json"
98
- },
99
- body: JSON.stringify(signUpBody(details))
100
- });
101
- if (response.status >= 500) panic(await describe(response, "sign-up"));
102
- if (response.status === 409) return authError("emailTaken", await describe(response, "sign-up"));
103
- if (!response.ok) return authError("invalidDetails", await describe(response, "sign-up"));
104
- return {
105
- success: true,
106
- data: null
107
- };
108
- }
109
- /**
110
- * Finishes sign-up flow with the emailed code and signs the visitor in.
111
- *
112
- * The platform splits on how the app is configured: a code-only app creates the
113
- * user on the second `sign-up` request and answers with a token, while a
114
- * password app answers it with an empty 200 and expects the password on
115
- * `users/new`.
116
- */
117
- async function confirmSignUp(details) {
118
- const verified = await platformFetch("sign-up", {
119
- method: "POST",
120
- headers: {
121
- "content-type": "application/json",
122
- accept: "application/json"
123
- },
124
- body: JSON.stringify({
125
- ...signUpBody(details),
126
- token: details.code
127
- })
128
- });
129
- if (verified.status >= 500) panic(await describe(verified, "sign-up"));
130
- if (verified.status === 409) return authError("emailTaken", await describe(verified, "sign-up"));
131
- if (!verified.ok) return authError("invalidCode", await describe(verified, "sign-up"));
132
- const verifiedBody = await verified.text();
133
- let token = tokenBody.parse(verifiedBody.length === 0 ? {} : JSON.parse(verifiedBody)).jwt;
134
- if (!token) {
135
- const created = await platformFetch("new", {
136
- method: "POST",
137
- headers: {
138
- "content-type": "application/json",
139
- accept: "application/json"
140
- },
141
- body: JSON.stringify({
142
- ...signUpBody(details),
143
- password: details.password,
144
- token: details.code
145
- })
146
- });
147
- if (created.status >= 500) panic(await describe(created, "sign-up"));
148
- if (created.status === 409) return authError("emailTaken", await describe(created, "sign-up"));
149
- if (created.ok) {
150
- const createdBody = await created.text();
151
- token = tokenBody.parse(createdBody.length === 0 ? {} : JSON.parse(createdBody)).jwt;
152
- } else {
153
- const signedIn = await signIn({
154
- email: details.email,
155
- password: details.password
156
- });
157
- if (!signedIn.success) return authError("invalidCode", await describe(created, "sign-up"));
158
- return signedIn;
159
- }
160
- }
161
- if (!token) todo("Softr platform accepted the sign-up and returned no session token. This app is configured for a sign-up flow the SDK cannot finish yet.");
162
- const user = await fetchVisitor(token);
163
- if (!user) panic("Session token issued, but no user could be resolved for it.");
164
- writeCookies([makeSessionTokenCookie(token), makeSessionDataCookie(user, token)]);
165
- return {
166
- success: true,
167
- data: user
168
- };
169
- }
170
- /**
171
- * `name` is dropped when absent rather than sent as `undefined`: the platform's
172
- * validator runs on whatever key is present, and an explicit null is not the
173
- * same request as no name at all.
174
- */
175
- function signUpBody(details) {
176
- return details.name === void 0 ? { email: details.email } : {
177
- email: details.email,
178
- name: details.name
179
- };
180
- }
181
- const tokenBody = z.object({ jwt: z.string().nullish() });
182
-
183
- //#endregion
184
- //#region src/auth/server.ts
185
- /**
186
- * Server-only auth methods, should be called from the app's own `createServerFn` handlers.
187
- */
188
- const auth = {
189
- signIn,
190
- signOut,
191
- signUp,
192
- confirmSignUp
193
- };
194
-
195
- //#endregion
196
- export { auth };
7
+ export { auth, ds, integration, workflow };
@@ -0,0 +1,105 @@
1
+ import { AnyConstructable, ConstructableOrderBy } from "./core/query.js";
2
+ import { SoftrUserGroupId } from "./auth/user.js";
3
+
4
+ //#region src/types.d.ts
5
+ interface SoftrUser {
6
+ id: string;
7
+ email: string;
8
+ name: string;
9
+ userGroupIds: Array<SoftrUserGroupId>;
10
+ }
11
+ interface DatasourcePermissions {
12
+ read: boolean;
13
+ create: boolean;
14
+ update: boolean;
15
+ delete: boolean;
16
+ }
17
+ interface SoftrPermissions {
18
+ datasources: Record<string, DatasourcePermissions>;
19
+ workflows: Record<string, boolean>;
20
+ }
21
+ interface SoftrMe {
22
+ user: SoftrUser | null;
23
+ permissions: SoftrPermissions;
24
+ }
25
+ type GetByIdOptions = {
26
+ id: string;
27
+ where?: never;
28
+ timeZone?: string;
29
+ };
30
+ type GetByWhereOptions = {
31
+ id?: never;
32
+ where: AnyConstructable;
33
+ timeZone?: string;
34
+ };
35
+ type GetOptions = GetByIdOptions | GetByWhereOptions;
36
+ interface ListOptions {
37
+ /**
38
+ * Maps human-readable aliases to Softr field IDs.
39
+ * When provided, returned fields are keyed by alias instead of raw field ID.
40
+ */
41
+ select?: Record<string, string>;
42
+ where?: AnyConstructable;
43
+ orderBy?: ConstructableOrderBy | ConstructableOrderBy[];
44
+ /**
45
+ * Number of records to return per page. Defaults to 20, max 100.
46
+ */
47
+ limit?: number;
48
+ /**
49
+ * Offset to start from. Pass the `nextOffset` from a previous result to paginate.
50
+ * Omit or pass `null` to start from the beginning.
51
+ */
52
+ offset?: number | null;
53
+ /** Override the timezone for this request. */
54
+ timeZone?: string;
55
+ }
56
+ interface ListResult<T = SoftrRecord> {
57
+ records: T[];
58
+ total: number;
59
+ /** Offset to pass as `offset` in the next call, or `null` when there are no more pages. */
60
+ nextOffset: number | null;
61
+ }
62
+ interface SoftrRecord {
63
+ id: string;
64
+ fields: Record<string, unknown>;
65
+ }
66
+ type AggregationFunction = 'SUM' | 'AVG' | 'MAX' | 'MIN' | 'COUNT' | 'DISTINCT';
67
+ interface MetricOptions {
68
+ select?: Record<string, string>;
69
+ where?: AnyConstructable;
70
+ aggregation: AggregationFunction;
71
+ /** Field alias (resolved via select) or raw field ID. Not required for COUNT. */
72
+ field?: string;
73
+ /** Override the timezone for this request. */
74
+ timeZone?: string;
75
+ }
76
+ interface MetricResult {
77
+ value: number | null;
78
+ executionTimestamp: Date;
79
+ }
80
+ interface ChartDataOptions {
81
+ select?: Record<string, string>;
82
+ where?: AnyConstructable;
83
+ /** Field alias (resolved via select) or raw field ID. */
84
+ groupBy: string | string[];
85
+ metrics: Record<string, {
86
+ field?: string;
87
+ aggregation: AggregationFunction;
88
+ }>;
89
+ orderBy?: ConstructableOrderBy;
90
+ /** Override the timezone for this request. */
91
+ timeZone?: string;
92
+ }
93
+ interface ChartDataResult {
94
+ data: Record<string, unknown>[];
95
+ executionTimestamp: Date;
96
+ }
97
+ interface CreateOptions {
98
+ fields: Record<string, unknown>;
99
+ }
100
+ interface UpdateOptions {
101
+ id: string;
102
+ fields: Record<string, unknown>;
103
+ }
104
+ //#endregion
105
+ export { AggregationFunction, ChartDataOptions, ChartDataResult, CreateOptions, DatasourcePermissions, GetOptions, ListOptions, ListResult, MetricOptions, MetricResult, SoftrMe, SoftrPermissions, SoftrRecord, SoftrUser, UpdateOptions };
package/dist/vite.d.ts CHANGED
@@ -1,26 +1,6 @@
1
- import "@tanstack/router-generator";
1
+ import { Manifest, ManifestNode } from "./routes/manifest.js";
2
2
  import { PluginOption } from "vite";
3
3
 
4
- //#region src/routes/discover.d.ts
5
-
6
- type VisibilityValue = string | Array<VisibilityValue> | {
7
- [key: string]: VisibilityValue;
8
- };
9
- //#endregion
10
- //#region src/routes/manifest.d.ts
11
- type ManifestNode = {
12
- routePath: string;
13
- filePath: string;
14
- _fsRouteType?: string;
15
- inferFullPath: string | null;
16
- createFileRouteProps: Array<string>;
17
- visibility: VisibilityValue | null;
18
- children: Array<ManifestNode>;
19
- };
20
- type Manifest = {
21
- root: ManifestNode;
22
- };
23
- //#endregion
24
4
  //#region src/vite.d.ts
25
5
  declare const MANIFEST_RELATIVE_PATH: string;
26
6
  /** Writes `<root>/dist/routes.json` and returns its path. Throws on a rule it cannot read. */