@softr/sdk 0.0.3 → 0.0.5

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/index.d.ts CHANGED
@@ -1,84 +1,37 @@
1
- import { a as ListResult, c as SoftrRecord, d as WorkflowAPI, i as ListOptions, l as SoftrSDK, n as DatasourceAPI, o as SoftrMe, r as DatasourcePermissions, s as SoftrPermissions, t as AuthAPI, u as SoftrUser } from "./types-BLLUI6MI.js";
1
+ import { A as defineVisibility, C as AccessReason, D as SoftrUserGroups, E as SoftrUserGroupId, F as q, M as isAlreadySignedInError, N as isForbiddenError, O as User, P as isUnauthenticatedError, S as AccessError, T as SoftrContext, _ as SoftrUser, a as CreateOptions, b as AccessBehaviour, c as GetOptions, d as MetricOptions, f as MetricResult, g as SoftrSDK, h as SoftrRecord, i as ChartDataResult, j as isAccessError, k as VisibilityHandle, l as ListOptions, m as SoftrPermissions, n as AuthAPI, o as DatasourceAPI, p as SoftrMe, r as ChartDataOptions, s as DatasourcePermissions, t as AggregationFunction, u as ListResult, v as UpdateOptions, w as RouteVisibility, x as AccessCheck, y as WorkflowAPI } from "./types-BYfW8LV3.js";
2
+ import * as _tanstack_react_start0 from "@tanstack/react-start";
2
3
 
3
- //#region src/routes/visibility.d.ts
4
- type RouteVisibility = {
5
- userGroup: "ALL_USERS";
6
- } | {
7
- userGroup: "NON_LOGGED_IN_USERS";
8
- } | {
9
- userGroup: "LOGGED_IN_USERS";
10
- userGroupIds?: Array<SoftrUserGroupId>;
11
- };
12
- /** Shape of the TanStack route context after the root route's beforeLoad. */
13
- type SoftrContext = {
14
- user: SoftrUser | null;
15
- };
16
- type RefusalReason = "unauthenticated" | "forbidden" | "alreadySignedIn";
17
- type RefusalBehaviour = string | {
18
- strategy: "redirect";
19
- to: string;
20
- } | {
21
- strategy: "throw";
22
- };
23
- type GuardOverrides = Partial<Record<RefusalReason, RefusalBehaviour>>;
24
- type CheckResult = {
25
- ok: true;
26
- } | {
27
- ok: false;
28
- reason: "unauthenticated" | "alreadySignedIn";
29
- } | {
30
- ok: false;
31
- reason: "forbidden";
32
- requiredGroupIds: Array<SoftrUserGroupId>;
33
- };
34
- declare class VisibilityError extends Error {
35
- readonly reason: RefusalReason;
36
- readonly requiredGroupIds: Array<SoftrUserGroupId> | undefined;
37
- constructor(reason: RefusalReason, requiredGroupIds?: Array<SoftrUserGroupId>);
38
- }
39
- interface VisibilityHandle {
40
- /** Structured check — returns { ok: true } or { ok: false, reason }. */
41
- check(context: SoftrContext): CheckResult;
42
- /** Returns true if the visitor passes the rule. */
43
- isAuthorized(context: SoftrContext): boolean;
44
- /**
45
- * Throws a VisibilityError if not authorized. Acts as a TypeScript assertion,
46
- * narrowing context.user to non-null for code that follows.
47
- *
48
- * @example
49
- * beforeLoad: ({ context }) => {
50
- * visibility.assert(context)
51
- * if (context.user.trialExpired) throw redirect({ to: '/renew' })
52
- * }
53
- */
54
- assert(context: SoftrContext): asserts context is SoftrContext & {
55
- user: SoftrUser;
56
- };
57
- /**
58
- * Returns a TanStack beforeLoad handler. On refusal, throws a VisibilityError
59
- * caught by the route's errorComponent.
60
- *
61
- * For redirect-on-refusal, use isAuthorized or check directly and throw
62
- * TanStack's redirect() yourself.
63
- *
64
- * @example
65
- * beforeLoad: visibility.guard()
66
- * errorComponent: ({ error }) => isVisibilityError(error) ? <NoAccess /> : <AppError />
67
- */
68
- guard(overrides?: GuardOverrides): (params: {
69
- context: SoftrContext;
70
- }) => void;
71
- }
72
- declare function defineVisibility(rule: RouteVisibility): VisibilityHandle;
73
- declare function isVisibilityError(error: unknown): error is VisibilityError;
74
- declare function isUnauthenticatedError(error: unknown): error is VisibilityError & {
75
- reason: "unauthenticated";
76
- };
77
- declare function isForbiddenError(error: unknown): error is VisibilityError & {
78
- reason: "forbidden";
79
- };
80
- declare function isAlreadySignedInError(error: unknown): error is VisibilityError & {
81
- reason: "alreadySignedIn";
82
- };
4
+ //#region src/auth/session.d.ts
5
+
6
+ /**
7
+ * Returns the signed-in visitor, or `null` when nobody is signed in.
8
+ *
9
+ * Await it in the root route's `beforeLoad` to publish the visitor as route
10
+ * context, which every `defineVisibility` guard then reads. It requires
11
+ * {@link softrAuthMiddleware} to be registered.
12
+ *
13
+ * @example
14
+ * export const Route = createRootRouteWithContext<SoftrContext>()({
15
+ * beforeLoad: async () => ({ user: await getSoftrUser() }),
16
+ * })
17
+ */
18
+ declare const getSoftrUser: _tanstack_react_start0.OptionalFetcher<undefined, undefined, Promise<User | null>>;
19
+ //#endregion
20
+ //#region src/auth/middleware.d.ts
21
+ /**
22
+ * Resolves the Softr visitor for every request, so that `getSoftrUser` and the
23
+ * route guards have someone to check.
24
+ *
25
+ * Register it once, as a global request middleware in the app's `src/start.ts`.
26
+ * It touches nothing but the visitor.
27
+ *
28
+ * @example
29
+ * export const startInstance = createStart(() => ({
30
+ * requestMiddleware: [softrAuthMiddleware()],
31
+ * }))
32
+ */
33
+ declare function softrAuthMiddleware(): _tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, {
34
+ user: User | null;
35
+ }>;
83
36
  //#endregion
84
- export { type AuthAPI, type CheckResult, type DatasourceAPI, type DatasourcePermissions, type GuardOverrides, type ListOptions, type ListResult, type RefusalBehaviour, type RefusalReason, type RouteVisibility, type SoftrContext, type SoftrMe, type SoftrPermissions, type SoftrRecord, type SoftrSDK, type SoftrUser, VisibilityError, type VisibilityHandle, type WorkflowAPI, defineVisibility, isAlreadySignedInError, isForbiddenError, isUnauthenticatedError, isVisibilityError };
37
+ export { type AccessBehaviour, type AccessCheck, type AccessError, type AccessReason, type AggregationFunction, type AuthAPI, type ChartDataOptions, type ChartDataResult, type CreateOptions, type DatasourceAPI, type DatasourcePermissions, type GetOptions, type ListOptions, type ListResult, type MetricOptions, type MetricResult, type RouteVisibility, type SoftrContext, type SoftrMe, type SoftrPermissions, type SoftrRecord, type SoftrSDK, type SoftrUser, type SoftrUserGroupId, type SoftrUserGroups, type UpdateOptions, type User, type VisibilityHandle, type WorkflowAPI, defineVisibility, getSoftrUser, isAccessError, isAlreadySignedInError, isForbiddenError, isUnauthenticatedError, q, softrAuthMiddleware };
package/dist/index.js CHANGED
@@ -1,73 +1,170 @@
1
+ import { a as q } from "./query-C6022JhD.js";
2
+ import { redirect } from "@tanstack/react-router";
3
+ import { createMiddleware, createServerFn, getGlobalStartContext } from "@tanstack/react-start";
4
+
1
5
  //#region src/routes/visibility.ts
2
- var VisibilityError = class extends Error {
3
- reason;
4
- requiredGroupIds;
5
- constructor(reason, requiredGroupIds) {
6
- super(`Access denied: ${reason}`);
7
- this.name = "VisibilityError";
8
- this.reason = reason;
9
- this.requiredGroupIds = requiredGroupIds;
10
- }
11
- };
12
- function runCheck(rule, context) {
6
+ /**
7
+ * Why a visitor was denied.
8
+ *
9
+ * - `unauthenticated` — the route needs a signed-in visitor and there is none.
10
+ * - `forbidden` — signed in, but in none of the groups the route names.
11
+ * - `alreadySignedIn` — the route is for signed-out visitors only.
12
+ */
13
+ const ACCESS_REASONS = [
14
+ "unauthenticated",
15
+ "forbidden",
16
+ "alreadySignedIn"
17
+ ];
18
+ /** Decides whether a visitor passes a visibility rule, without throwing. */
19
+ function checkAccess(rule, context) {
13
20
  const { user } = context;
14
- if (rule.userGroup === "ALL_USERS") return { ok: true };
15
- if (rule.userGroup === "NON_LOGGED_IN_USERS") return user ? {
16
- ok: false,
17
- reason: "alreadySignedIn"
18
- } : { ok: true };
19
- if (!user) return {
20
- ok: false,
21
- reason: "unauthenticated"
22
- };
23
- const { userGroupIds } = rule;
24
- if (userGroupIds?.length) {
25
- if (!userGroupIds.some((id) => user.userGroupIds.includes(id))) return {
21
+ switch (rule.userGroup) {
22
+ case "ALL_USERS": return { ok: true };
23
+ case "NON_LOGGED_IN_USERS": return user === null ? { ok: true } : {
26
24
  ok: false,
27
- reason: "forbidden",
28
- requiredGroupIds: userGroupIds
25
+ reason: "alreadySignedIn"
29
26
  };
27
+ case "LOGGED_IN_USERS": {
28
+ if (user === null) return {
29
+ ok: false,
30
+ reason: "unauthenticated"
31
+ };
32
+ const required = rule.userGroupIds ?? [];
33
+ if (required.length === 0) return { ok: true };
34
+ return required.some((id) => user.userGroupIds.includes(id)) ? { ok: true } : {
35
+ ok: false,
36
+ reason: "forbidden"
37
+ };
38
+ }
30
39
  }
31
- return { ok: true };
32
40
  }
33
- function throwIfDenied(result) {
34
- if (result.ok) return;
35
- throw new VisibilityError(result.reason, result.reason === "forbidden" ? result.requiredGroupIds : void 0);
41
+ function isAccessReason(value) {
42
+ return ACCESS_REASONS.some((reason) => reason === value);
36
43
  }
37
- function defineVisibility(rule) {
38
- function check(context) {
39
- return runCheck(rule, context);
40
- }
41
- function isAuthorized(context) {
42
- return runCheck(rule, context).ok;
43
- }
44
- function assert(context) {
45
- throwIfDenied(runCheck(rule, context));
46
- }
47
- function guard(_overrides) {
48
- return ({ context }) => {
49
- throwIfDenied(runCheck(rule, context));
50
- };
51
- }
52
- return {
53
- check,
54
- isAuthorized,
55
- assert,
56
- guard
57
- };
44
+ /**
45
+ * True for any access error thrown by a guard, whatever its reason.
46
+ *
47
+ * An access error arrives as `unknown` — hydrated from the page, or thrown by
48
+ * anything else at all — so every field is checked before it is narrowed.
49
+ */
50
+ function isAccessError(error) {
51
+ if (typeof error !== "object" || error === null) return false;
52
+ const candidate = error;
53
+ if (candidate.type !== "access_error") return false;
54
+ return isAccessReason(candidate.reason);
58
55
  }
59
- function isVisibilityError(error) {
60
- return error instanceof VisibilityError;
56
+ function isReason(error, reason) {
57
+ return isAccessError(error) && error.reason === reason;
61
58
  }
59
+ /** True for an access error thrown because nobody was signed in. */
62
60
  function isUnauthenticatedError(error) {
63
- return error instanceof VisibilityError && error.reason === "unauthenticated";
61
+ return isReason(error, "unauthenticated");
64
62
  }
63
+ /** True for an access error thrown because the visitor was in none of the groups. */
65
64
  function isForbiddenError(error) {
66
- return error instanceof VisibilityError && error.reason === "forbidden";
65
+ return isReason(error, "forbidden");
67
66
  }
67
+ /** True for an access error thrown because a signed-out-only route saw a user. */
68
68
  function isAlreadySignedInError(error) {
69
- return error instanceof VisibilityError && error.reason === "alreadySignedIn";
69
+ return isReason(error, "alreadySignedIn");
70
+ }
71
+ const DEFAULT_ACCESS_BEHAVIOUR = {
72
+ unauthenticated: {
73
+ strategy: "redirect",
74
+ to: "/login"
75
+ },
76
+ forbidden: { strategy: "throw" },
77
+ alreadySignedIn: {
78
+ strategy: "redirect",
79
+ to: "/"
80
+ }
81
+ };
82
+ /** What a guard throws for a reason: a redirect, or an {@link AccessError}. */
83
+ function thrownFor(reason, overrides) {
84
+ const behaviour = overrides?.[reason] ?? DEFAULT_ACCESS_BEHAVIOUR[reason];
85
+ const resolved = typeof behaviour === "string" ? {
86
+ strategy: "redirect",
87
+ to: behaviour
88
+ } : behaviour;
89
+ if (resolved.strategy === "redirect") return redirect({ to: resolved.to });
90
+ return {
91
+ type: "access_error",
92
+ reason
93
+ };
94
+ }
95
+ /**
96
+ * Declares who may see a route, and returns the handle that enforces it.
97
+ *
98
+ * One call per route file, at module scope: the build reads these declarations
99
+ * to publish the app's route visibility, so the rule must be a literal.
100
+ *
101
+ * @example
102
+ * const visibility = defineVisibility({ userGroup: "LOGGED_IN_USERS" })
103
+ *
104
+ * export const Route = createFileRoute("/about")({
105
+ * beforeLoad: visibility.guard(),
106
+ * component: About,
107
+ * })
108
+ */
109
+ function defineVisibility(rule) {
110
+ return {
111
+ guard: (overrides) => ({ context }) => {
112
+ const result = checkAccess(rule, context);
113
+ if (result.ok) return;
114
+ throw thrownFor(result.reason, overrides);
115
+ },
116
+ assert: (context) => {
117
+ const result = checkAccess(rule, context);
118
+ if (result.ok) return;
119
+ throw thrownFor(result.reason, void 0);
120
+ },
121
+ isAuthorized: (context) => checkAccess(rule, context).ok,
122
+ check: (context) => checkAccess(rule, context)
123
+ };
124
+ }
125
+
126
+ //#endregion
127
+ //#region src/auth/session.ts
128
+ async function resolveVisitor(_request) {
129
+ return null;
130
+ }
131
+ function visitorFromContext(context) {
132
+ if (typeof context !== "object" || context === null || !("user" in context)) throw new Error("softrAuthMiddleware has not run: the Start global context carries no `user`. Register it as a global request middleware, e.g. createStart(() => ({ requestMiddleware: [softrAuthMiddleware()] })).");
133
+ return context.user ?? null;
134
+ }
135
+ /**
136
+ * Returns the signed-in visitor, or `null` when nobody is signed in.
137
+ *
138
+ * Await it in the root route's `beforeLoad` to publish the visitor as route
139
+ * context, which every `defineVisibility` guard then reads. It requires
140
+ * {@link softrAuthMiddleware} to be registered.
141
+ *
142
+ * @example
143
+ * export const Route = createRootRouteWithContext<SoftrContext>()({
144
+ * beforeLoad: async () => ({ user: await getSoftrUser() }),
145
+ * })
146
+ */
147
+ const getSoftrUser = createServerFn({ method: "GET" }).handler(async () => visitorFromContext(getGlobalStartContext()));
148
+
149
+ //#endregion
150
+ //#region src/auth/middleware.ts
151
+ /**
152
+ * Resolves the Softr visitor for every request, so that `getSoftrUser` and the
153
+ * route guards have someone to check.
154
+ *
155
+ * Register it once, as a global request middleware in the app's `src/start.ts`.
156
+ * It touches nothing but the visitor.
157
+ *
158
+ * @example
159
+ * export const startInstance = createStart(() => ({
160
+ * requestMiddleware: [softrAuthMiddleware()],
161
+ * }))
162
+ */
163
+ function softrAuthMiddleware() {
164
+ return createMiddleware({ type: "request" }).server(async ({ request, next }) => {
165
+ return next({ context: { user: await resolveVisitor(request) } });
166
+ });
70
167
  }
71
168
 
72
169
  //#endregion
73
- export { VisibilityError, defineVisibility, isAlreadySignedInError, isForbiddenError, isUnauthenticatedError, isVisibilityError };
170
+ export { defineVisibility, getSoftrUser, isAccessError, isAlreadySignedInError, isForbiddenError, isUnauthenticatedError, q, softrAuthMiddleware };