@dashai/sdk 0.7.0
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/LICENSE +21 -0
- package/README.md +377 -0
- package/dist/auth/client.cjs +185 -0
- package/dist/auth/client.cjs.map +1 -0
- package/dist/auth/client.d.cts +137 -0
- package/dist/auth/client.d.ts +137 -0
- package/dist/auth/client.js +175 -0
- package/dist/auth/client.js.map +1 -0
- package/dist/auth/middleware.cjs +352 -0
- package/dist/auth/middleware.cjs.map +1 -0
- package/dist/auth/middleware.d.cts +90 -0
- package/dist/auth/middleware.d.ts +90 -0
- package/dist/auth/middleware.js +343 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/server.cjs +445 -0
- package/dist/auth/server.cjs.map +1 -0
- package/dist/auth/server.d.cts +170 -0
- package/dist/auth/server.d.ts +170 -0
- package/dist/auth/server.js +432 -0
- package/dist/auth/server.js.map +1 -0
- package/dist/auth/types.cjs +31 -0
- package/dist/auth/types.cjs.map +1 -0
- package/dist/auth/types.d.cts +163 -0
- package/dist/auth/types.d.ts +163 -0
- package/dist/auth/types.js +29 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/deps/index.cjs +117 -0
- package/dist/deps/index.cjs.map +1 -0
- package/dist/deps/index.d.cts +93 -0
- package/dist/deps/index.d.ts +93 -0
- package/dist/deps/index.js +112 -0
- package/dist/deps/index.js.map +1 -0
- package/dist/errors-BV75u7b9.d.cts +207 -0
- package/dist/errors-BV75u7b9.d.ts +207 -0
- package/dist/index.cjs +721 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +171 -0
- package/dist/index.d.ts +171 -0
- package/dist/index.js +703 -0
- package/dist/index.js.map +1 -0
- package/dist/query/index.cjs +621 -0
- package/dist/query/index.cjs.map +1 -0
- package/dist/query/index.d.cts +800 -0
- package/dist/query/index.d.ts +800 -0
- package/dist/query/index.js +617 -0
- package/dist/query/index.js.map +1 -0
- package/dist/react/index.cjs +199 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +117 -0
- package/dist/react/index.d.ts +117 -0
- package/dist/react/index.js +195 -0
- package/dist/react/index.js.map +1 -0
- package/dist/types-BLNQ1S1C.d.cts +396 -0
- package/dist/types-BLNQ1S1C.d.ts +396 -0
- package/package.json +109 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for the @dashai/sdk/auth/* subpaths.
|
|
3
|
+
*
|
|
4
|
+
* The DashWise NestJS backend is the identity provider (IDP); these
|
|
5
|
+
* types describe the SSO + session contract the SDK consumes. Changes
|
|
6
|
+
* here are SemVer-significant — every Next.js module + custom-runtime
|
|
7
|
+
* deployment depends on this shape.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Workspace-scoped session for a logged-in user. Returned by
|
|
11
|
+
* `getServerSession()` (server-side) and `useSession()` (client-side).
|
|
12
|
+
*
|
|
13
|
+
* `user` carries identity; `workspaceId` + `role` carry workspace
|
|
14
|
+
* scoping; `installationId` + `moduleId` + `moduleSlug` carry
|
|
15
|
+
* module-install context (present in custom-module runtimes and
|
|
16
|
+
* post-install AI-builder previews; absent in pre-install /
|
|
17
|
+
* marketplace / hand-author dev contexts).
|
|
18
|
+
*
|
|
19
|
+
* `expiresAt` is wall-clock ms (Unix epoch). The session is auto-
|
|
20
|
+
* refreshed by the SDK middleware/server helpers while the user is
|
|
21
|
+
* active; consumers shouldn't manually compare against `Date.now()`
|
|
22
|
+
* unless they're displaying a countdown to the user.
|
|
23
|
+
*/
|
|
24
|
+
interface Session {
|
|
25
|
+
user: {
|
|
26
|
+
id: number;
|
|
27
|
+
email: string;
|
|
28
|
+
/** Display name. Null if the user hasn't set one yet. */
|
|
29
|
+
name: string | null;
|
|
30
|
+
};
|
|
31
|
+
workspaceId: number;
|
|
32
|
+
role: 'admin' | 'editor' | 'viewer';
|
|
33
|
+
installationId?: number;
|
|
34
|
+
moduleId?: number;
|
|
35
|
+
moduleSlug?: string;
|
|
36
|
+
/** Wall-clock expiry, Unix ms. */
|
|
37
|
+
expiresAt: number;
|
|
38
|
+
/**
|
|
39
|
+
* Module RBAC roles for the current installation (P12.4). Populated
|
|
40
|
+
* by the SDK via a parallel `/api/installations/:id/rbac/me` fetch
|
|
41
|
+
* when `installationId` is set. Values are NAMESPACED keys —
|
|
42
|
+
* `<module_slug>:<role>` — to leave room for future cross-module
|
|
43
|
+
* grants without breaking the shape.
|
|
44
|
+
*
|
|
45
|
+
* Empty array means the user has no role grants for this install
|
|
46
|
+
* (denied to anything role-gated). `undefined` means the SDK
|
|
47
|
+
* hasn't fetched the RBAC data yet (sessions outside an install
|
|
48
|
+
* context, or transient pre-resolution state).
|
|
49
|
+
*/
|
|
50
|
+
rbacRoles?: string[];
|
|
51
|
+
/**
|
|
52
|
+
* Module RBAC permissions for the current installation (P12.4).
|
|
53
|
+
* Same shape + nullability semantics as `rbacRoles`. Values are
|
|
54
|
+
* namespaced (`<module_slug>:<permission_key>`); the SDK's
|
|
55
|
+
* `hasPermission` / `requirePermission` helpers accept either the
|
|
56
|
+
* bare or the namespaced form.
|
|
57
|
+
*
|
|
58
|
+
* This is the resolved permission set — already expanded via
|
|
59
|
+
* `includes` graph traversal on the backend — so client-side
|
|
60
|
+
* checks are O(1) set-membership.
|
|
61
|
+
*/
|
|
62
|
+
rbacPermissions?: string[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Response shape of `GET /api/installations/:id/rbac/me`. The SDK
|
|
66
|
+
* fetches this in parallel with `/api/auth/sessions/me` when the
|
|
67
|
+
* session has an `installationId`.
|
|
68
|
+
*
|
|
69
|
+
* Both arrays are NAMESPACED (`<module_slug>:<key>`) — the SDK's
|
|
70
|
+
* permission/role helpers strip the namespace at lookup time so
|
|
71
|
+
* authors write bare keys (`tasks:delete`, `editor`).
|
|
72
|
+
*/
|
|
73
|
+
interface RbacMeResponse {
|
|
74
|
+
roles: string[];
|
|
75
|
+
permissions: string[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Status returned by `useSession()` on the client. `loading` is the
|
|
79
|
+
* initial render before the session fetch resolves; SSR-friendly
|
|
80
|
+
* apps may want to gate suspense or skeletons on this.
|
|
81
|
+
*/
|
|
82
|
+
type SessionStatus = 'loading' | 'authenticated' | 'unauthenticated';
|
|
83
|
+
/**
|
|
84
|
+
* Shared configuration accepted by every auth helper. All fields are
|
|
85
|
+
* optional; defaults come from the runtime env (`DASHWISE_*`) or
|
|
86
|
+
* sensible static fallbacks.
|
|
87
|
+
*
|
|
88
|
+
* Pass an explicit object to override per-call (useful for tests +
|
|
89
|
+
* for runtimes that can't read env vars at request time).
|
|
90
|
+
*/
|
|
91
|
+
interface AuthOptions {
|
|
92
|
+
/**
|
|
93
|
+
* Base URL for the DashWise backend API (e.g.
|
|
94
|
+
* `https://api.dashwise.io`). Used for `/api/auth/sessions/me`,
|
|
95
|
+
* `/api/auth/sessions/exchange`, etc. Defaults to
|
|
96
|
+
* `process.env.DASHWISE_API_URL`.
|
|
97
|
+
*/
|
|
98
|
+
apiBaseUrl?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Base URL for the DashWise main app's SSO entry point (e.g.
|
|
101
|
+
* `https://app.dashwise.io`). Used for the initial redirect from
|
|
102
|
+
* module middleware. Defaults to `process.env.DASHWISE_AUTH_URL`,
|
|
103
|
+
* falling back to `apiBaseUrl` with no path suffix.
|
|
104
|
+
*/
|
|
105
|
+
authBaseUrl?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Name of the session cookie. Defaults to
|
|
108
|
+
* `process.env.DASHWISE_SESSION_COOKIE_NAME` or `'dashwise.session'`.
|
|
109
|
+
* Cookie scope is configured by the backend on set; the SDK only
|
|
110
|
+
* reads/clears it.
|
|
111
|
+
*/
|
|
112
|
+
cookieName?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Optional fetch override (testing / custom HTTP agent). Defaults
|
|
115
|
+
* to `globalThis.fetch`.
|
|
116
|
+
*/
|
|
117
|
+
fetch?: typeof globalThis.fetch;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Response shape from `POST /api/auth/sessions/exchange`. The
|
|
121
|
+
* `session_token` is the opaque cookie value to set; the consumer
|
|
122
|
+
* shouldn't decode or store it elsewhere.
|
|
123
|
+
*/
|
|
124
|
+
interface ExchangeResult {
|
|
125
|
+
/** Opaque session cookie value (JWT under the hood). */
|
|
126
|
+
sessionToken: string;
|
|
127
|
+
/** Cookie max-age in seconds (matches the JWT exp). */
|
|
128
|
+
maxAgeSeconds: number;
|
|
129
|
+
/** Already-decoded session — saves an immediate /me round-trip. */
|
|
130
|
+
session: Session;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Errors thrown by the auth layer. Mirrors the data-plane error model
|
|
134
|
+
* (subclass of `DashwiseError`) so consumers can catch all SDK errors
|
|
135
|
+
* uniformly.
|
|
136
|
+
*/
|
|
137
|
+
declare const AuthErrorCode: {
|
|
138
|
+
/** Cookie missing or invalid — caller should redirect to sign-in. */
|
|
139
|
+
readonly NO_SESSION: "NO_SESSION";
|
|
140
|
+
/** Backend rejected the exchange code (expired / replay / forged). */
|
|
141
|
+
readonly INVALID_EXCHANGE_CODE: "INVALID_EXCHANGE_CODE";
|
|
142
|
+
/** Backend rejected the session refresh (expired beyond refresh window). */
|
|
143
|
+
readonly REFRESH_FAILED: "REFRESH_FAILED";
|
|
144
|
+
/** Backend returned an unexpected response. */
|
|
145
|
+
readonly PROTOCOL_ERROR: "PROTOCOL_ERROR";
|
|
146
|
+
/**
|
|
147
|
+
* `requirePermission(...)` / `requireRole(...)` rejected because
|
|
148
|
+
* the current session lacks the requested permission/role (P12.4).
|
|
149
|
+
* Includes the requested key in the error's `context`.
|
|
150
|
+
*/
|
|
151
|
+
readonly PERMISSION_DENIED: "PERMISSION_DENIED";
|
|
152
|
+
/**
|
|
153
|
+
* `requirePermission(...)` / `requireRole(...)` was called from a
|
|
154
|
+
* context without RBAC info — usually because the session has no
|
|
155
|
+
* `installationId` (the user isn't inside an installed module),
|
|
156
|
+
* or the `/rbac/me` fetch failed silently. Callers in this state
|
|
157
|
+
* should fall back to anon UI or surface a missing-context error.
|
|
158
|
+
*/
|
|
159
|
+
readonly RBAC_UNAVAILABLE: "RBAC_UNAVAILABLE";
|
|
160
|
+
};
|
|
161
|
+
type AuthErrorCode = (typeof AuthErrorCode)[keyof typeof AuthErrorCode];
|
|
162
|
+
|
|
163
|
+
export { AuthErrorCode, type AuthOptions, type ExchangeResult, type RbacMeResponse, type Session, type SessionStatus };
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for the @dashai/sdk/auth/* subpaths.
|
|
3
|
+
*
|
|
4
|
+
* The DashWise NestJS backend is the identity provider (IDP); these
|
|
5
|
+
* types describe the SSO + session contract the SDK consumes. Changes
|
|
6
|
+
* here are SemVer-significant — every Next.js module + custom-runtime
|
|
7
|
+
* deployment depends on this shape.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Workspace-scoped session for a logged-in user. Returned by
|
|
11
|
+
* `getServerSession()` (server-side) and `useSession()` (client-side).
|
|
12
|
+
*
|
|
13
|
+
* `user` carries identity; `workspaceId` + `role` carry workspace
|
|
14
|
+
* scoping; `installationId` + `moduleId` + `moduleSlug` carry
|
|
15
|
+
* module-install context (present in custom-module runtimes and
|
|
16
|
+
* post-install AI-builder previews; absent in pre-install /
|
|
17
|
+
* marketplace / hand-author dev contexts).
|
|
18
|
+
*
|
|
19
|
+
* `expiresAt` is wall-clock ms (Unix epoch). The session is auto-
|
|
20
|
+
* refreshed by the SDK middleware/server helpers while the user is
|
|
21
|
+
* active; consumers shouldn't manually compare against `Date.now()`
|
|
22
|
+
* unless they're displaying a countdown to the user.
|
|
23
|
+
*/
|
|
24
|
+
interface Session {
|
|
25
|
+
user: {
|
|
26
|
+
id: number;
|
|
27
|
+
email: string;
|
|
28
|
+
/** Display name. Null if the user hasn't set one yet. */
|
|
29
|
+
name: string | null;
|
|
30
|
+
};
|
|
31
|
+
workspaceId: number;
|
|
32
|
+
role: 'admin' | 'editor' | 'viewer';
|
|
33
|
+
installationId?: number;
|
|
34
|
+
moduleId?: number;
|
|
35
|
+
moduleSlug?: string;
|
|
36
|
+
/** Wall-clock expiry, Unix ms. */
|
|
37
|
+
expiresAt: number;
|
|
38
|
+
/**
|
|
39
|
+
* Module RBAC roles for the current installation (P12.4). Populated
|
|
40
|
+
* by the SDK via a parallel `/api/installations/:id/rbac/me` fetch
|
|
41
|
+
* when `installationId` is set. Values are NAMESPACED keys —
|
|
42
|
+
* `<module_slug>:<role>` — to leave room for future cross-module
|
|
43
|
+
* grants without breaking the shape.
|
|
44
|
+
*
|
|
45
|
+
* Empty array means the user has no role grants for this install
|
|
46
|
+
* (denied to anything role-gated). `undefined` means the SDK
|
|
47
|
+
* hasn't fetched the RBAC data yet (sessions outside an install
|
|
48
|
+
* context, or transient pre-resolution state).
|
|
49
|
+
*/
|
|
50
|
+
rbacRoles?: string[];
|
|
51
|
+
/**
|
|
52
|
+
* Module RBAC permissions for the current installation (P12.4).
|
|
53
|
+
* Same shape + nullability semantics as `rbacRoles`. Values are
|
|
54
|
+
* namespaced (`<module_slug>:<permission_key>`); the SDK's
|
|
55
|
+
* `hasPermission` / `requirePermission` helpers accept either the
|
|
56
|
+
* bare or the namespaced form.
|
|
57
|
+
*
|
|
58
|
+
* This is the resolved permission set — already expanded via
|
|
59
|
+
* `includes` graph traversal on the backend — so client-side
|
|
60
|
+
* checks are O(1) set-membership.
|
|
61
|
+
*/
|
|
62
|
+
rbacPermissions?: string[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Response shape of `GET /api/installations/:id/rbac/me`. The SDK
|
|
66
|
+
* fetches this in parallel with `/api/auth/sessions/me` when the
|
|
67
|
+
* session has an `installationId`.
|
|
68
|
+
*
|
|
69
|
+
* Both arrays are NAMESPACED (`<module_slug>:<key>`) — the SDK's
|
|
70
|
+
* permission/role helpers strip the namespace at lookup time so
|
|
71
|
+
* authors write bare keys (`tasks:delete`, `editor`).
|
|
72
|
+
*/
|
|
73
|
+
interface RbacMeResponse {
|
|
74
|
+
roles: string[];
|
|
75
|
+
permissions: string[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Status returned by `useSession()` on the client. `loading` is the
|
|
79
|
+
* initial render before the session fetch resolves; SSR-friendly
|
|
80
|
+
* apps may want to gate suspense or skeletons on this.
|
|
81
|
+
*/
|
|
82
|
+
type SessionStatus = 'loading' | 'authenticated' | 'unauthenticated';
|
|
83
|
+
/**
|
|
84
|
+
* Shared configuration accepted by every auth helper. All fields are
|
|
85
|
+
* optional; defaults come from the runtime env (`DASHWISE_*`) or
|
|
86
|
+
* sensible static fallbacks.
|
|
87
|
+
*
|
|
88
|
+
* Pass an explicit object to override per-call (useful for tests +
|
|
89
|
+
* for runtimes that can't read env vars at request time).
|
|
90
|
+
*/
|
|
91
|
+
interface AuthOptions {
|
|
92
|
+
/**
|
|
93
|
+
* Base URL for the DashWise backend API (e.g.
|
|
94
|
+
* `https://api.dashwise.io`). Used for `/api/auth/sessions/me`,
|
|
95
|
+
* `/api/auth/sessions/exchange`, etc. Defaults to
|
|
96
|
+
* `process.env.DASHWISE_API_URL`.
|
|
97
|
+
*/
|
|
98
|
+
apiBaseUrl?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Base URL for the DashWise main app's SSO entry point (e.g.
|
|
101
|
+
* `https://app.dashwise.io`). Used for the initial redirect from
|
|
102
|
+
* module middleware. Defaults to `process.env.DASHWISE_AUTH_URL`,
|
|
103
|
+
* falling back to `apiBaseUrl` with no path suffix.
|
|
104
|
+
*/
|
|
105
|
+
authBaseUrl?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Name of the session cookie. Defaults to
|
|
108
|
+
* `process.env.DASHWISE_SESSION_COOKIE_NAME` or `'dashwise.session'`.
|
|
109
|
+
* Cookie scope is configured by the backend on set; the SDK only
|
|
110
|
+
* reads/clears it.
|
|
111
|
+
*/
|
|
112
|
+
cookieName?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Optional fetch override (testing / custom HTTP agent). Defaults
|
|
115
|
+
* to `globalThis.fetch`.
|
|
116
|
+
*/
|
|
117
|
+
fetch?: typeof globalThis.fetch;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Response shape from `POST /api/auth/sessions/exchange`. The
|
|
121
|
+
* `session_token` is the opaque cookie value to set; the consumer
|
|
122
|
+
* shouldn't decode or store it elsewhere.
|
|
123
|
+
*/
|
|
124
|
+
interface ExchangeResult {
|
|
125
|
+
/** Opaque session cookie value (JWT under the hood). */
|
|
126
|
+
sessionToken: string;
|
|
127
|
+
/** Cookie max-age in seconds (matches the JWT exp). */
|
|
128
|
+
maxAgeSeconds: number;
|
|
129
|
+
/** Already-decoded session — saves an immediate /me round-trip. */
|
|
130
|
+
session: Session;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Errors thrown by the auth layer. Mirrors the data-plane error model
|
|
134
|
+
* (subclass of `DashwiseError`) so consumers can catch all SDK errors
|
|
135
|
+
* uniformly.
|
|
136
|
+
*/
|
|
137
|
+
declare const AuthErrorCode: {
|
|
138
|
+
/** Cookie missing or invalid — caller should redirect to sign-in. */
|
|
139
|
+
readonly NO_SESSION: "NO_SESSION";
|
|
140
|
+
/** Backend rejected the exchange code (expired / replay / forged). */
|
|
141
|
+
readonly INVALID_EXCHANGE_CODE: "INVALID_EXCHANGE_CODE";
|
|
142
|
+
/** Backend rejected the session refresh (expired beyond refresh window). */
|
|
143
|
+
readonly REFRESH_FAILED: "REFRESH_FAILED";
|
|
144
|
+
/** Backend returned an unexpected response. */
|
|
145
|
+
readonly PROTOCOL_ERROR: "PROTOCOL_ERROR";
|
|
146
|
+
/**
|
|
147
|
+
* `requirePermission(...)` / `requireRole(...)` rejected because
|
|
148
|
+
* the current session lacks the requested permission/role (P12.4).
|
|
149
|
+
* Includes the requested key in the error's `context`.
|
|
150
|
+
*/
|
|
151
|
+
readonly PERMISSION_DENIED: "PERMISSION_DENIED";
|
|
152
|
+
/**
|
|
153
|
+
* `requirePermission(...)` / `requireRole(...)` was called from a
|
|
154
|
+
* context without RBAC info — usually because the session has no
|
|
155
|
+
* `installationId` (the user isn't inside an installed module),
|
|
156
|
+
* or the `/rbac/me` fetch failed silently. Callers in this state
|
|
157
|
+
* should fall back to anon UI or surface a missing-context error.
|
|
158
|
+
*/
|
|
159
|
+
readonly RBAC_UNAVAILABLE: "RBAC_UNAVAILABLE";
|
|
160
|
+
};
|
|
161
|
+
type AuthErrorCode = (typeof AuthErrorCode)[keyof typeof AuthErrorCode];
|
|
162
|
+
|
|
163
|
+
export { AuthErrorCode, type AuthOptions, type ExchangeResult, type RbacMeResponse, type Session, type SessionStatus };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/auth/types.ts
|
|
2
|
+
var AuthErrorCode = {
|
|
3
|
+
/** Cookie missing or invalid — caller should redirect to sign-in. */
|
|
4
|
+
NO_SESSION: "NO_SESSION",
|
|
5
|
+
/** Backend rejected the exchange code (expired / replay / forged). */
|
|
6
|
+
INVALID_EXCHANGE_CODE: "INVALID_EXCHANGE_CODE",
|
|
7
|
+
/** Backend rejected the session refresh (expired beyond refresh window). */
|
|
8
|
+
REFRESH_FAILED: "REFRESH_FAILED",
|
|
9
|
+
/** Backend returned an unexpected response. */
|
|
10
|
+
PROTOCOL_ERROR: "PROTOCOL_ERROR",
|
|
11
|
+
/**
|
|
12
|
+
* `requirePermission(...)` / `requireRole(...)` rejected because
|
|
13
|
+
* the current session lacks the requested permission/role (P12.4).
|
|
14
|
+
* Includes the requested key in the error's `context`.
|
|
15
|
+
*/
|
|
16
|
+
PERMISSION_DENIED: "PERMISSION_DENIED",
|
|
17
|
+
/**
|
|
18
|
+
* `requirePermission(...)` / `requireRole(...)` was called from a
|
|
19
|
+
* context without RBAC info — usually because the session has no
|
|
20
|
+
* `installationId` (the user isn't inside an installed module),
|
|
21
|
+
* or the `/rbac/me` fetch failed silently. Callers in this state
|
|
22
|
+
* should fall back to anon UI or surface a missing-context error.
|
|
23
|
+
*/
|
|
24
|
+
RBAC_UNAVAILABLE: "RBAC_UNAVAILABLE"
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { AuthErrorCode };
|
|
28
|
+
//# sourceMappingURL=types.js.map
|
|
29
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/auth/types.ts"],"names":[],"mappings":";AAkJO,IAAM,aAAA,GAAgB;AAAA;AAAA,EAE3B,UAAA,EAAY,YAAA;AAAA;AAAA,EAEZ,qBAAA,EAAuB,uBAAA;AAAA;AAAA,EAEvB,cAAA,EAAgB,gBAAA;AAAA;AAAA,EAEhB,cAAA,EAAgB,gBAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhB,iBAAA,EAAmB,mBAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQnB,gBAAA,EAAkB;AACpB","file":"types.js","sourcesContent":["/**\n * Public types for the @dashai/sdk/auth/* subpaths.\n *\n * The DashWise NestJS backend is the identity provider (IDP); these\n * types describe the SSO + session contract the SDK consumes. Changes\n * here are SemVer-significant — every Next.js module + custom-runtime\n * deployment depends on this shape.\n */\n\n/**\n * Workspace-scoped session for a logged-in user. Returned by\n * `getServerSession()` (server-side) and `useSession()` (client-side).\n *\n * `user` carries identity; `workspaceId` + `role` carry workspace\n * scoping; `installationId` + `moduleId` + `moduleSlug` carry\n * module-install context (present in custom-module runtimes and\n * post-install AI-builder previews; absent in pre-install /\n * marketplace / hand-author dev contexts).\n *\n * `expiresAt` is wall-clock ms (Unix epoch). The session is auto-\n * refreshed by the SDK middleware/server helpers while the user is\n * active; consumers shouldn't manually compare against `Date.now()`\n * unless they're displaying a countdown to the user.\n */\nexport interface Session {\n user: {\n id: number;\n email: string;\n /** Display name. Null if the user hasn't set one yet. */\n name: string | null;\n };\n workspaceId: number;\n role: 'admin' | 'editor' | 'viewer';\n installationId?: number;\n moduleId?: number;\n moduleSlug?: string;\n /** Wall-clock expiry, Unix ms. */\n expiresAt: number;\n /**\n * Module RBAC roles for the current installation (P12.4). Populated\n * by the SDK via a parallel `/api/installations/:id/rbac/me` fetch\n * when `installationId` is set. Values are NAMESPACED keys —\n * `<module_slug>:<role>` — to leave room for future cross-module\n * grants without breaking the shape.\n *\n * Empty array means the user has no role grants for this install\n * (denied to anything role-gated). `undefined` means the SDK\n * hasn't fetched the RBAC data yet (sessions outside an install\n * context, or transient pre-resolution state).\n */\n rbacRoles?: string[];\n\n /**\n * Module RBAC permissions for the current installation (P12.4).\n * Same shape + nullability semantics as `rbacRoles`. Values are\n * namespaced (`<module_slug>:<permission_key>`); the SDK's\n * `hasPermission` / `requirePermission` helpers accept either the\n * bare or the namespaced form.\n *\n * This is the resolved permission set — already expanded via\n * `includes` graph traversal on the backend — so client-side\n * checks are O(1) set-membership.\n */\n rbacPermissions?: string[];\n}\n\n/**\n * Response shape of `GET /api/installations/:id/rbac/me`. The SDK\n * fetches this in parallel with `/api/auth/sessions/me` when the\n * session has an `installationId`.\n *\n * Both arrays are NAMESPACED (`<module_slug>:<key>`) — the SDK's\n * permission/role helpers strip the namespace at lookup time so\n * authors write bare keys (`tasks:delete`, `editor`).\n */\nexport interface RbacMeResponse {\n roles: string[];\n permissions: string[];\n}\n\n/**\n * Status returned by `useSession()` on the client. `loading` is the\n * initial render before the session fetch resolves; SSR-friendly\n * apps may want to gate suspense or skeletons on this.\n */\nexport type SessionStatus = 'loading' | 'authenticated' | 'unauthenticated';\n\n/**\n * Shared configuration accepted by every auth helper. All fields are\n * optional; defaults come from the runtime env (`DASHWISE_*`) or\n * sensible static fallbacks.\n *\n * Pass an explicit object to override per-call (useful for tests +\n * for runtimes that can't read env vars at request time).\n */\nexport interface AuthOptions {\n /**\n * Base URL for the DashWise backend API (e.g.\n * `https://api.dashwise.io`). Used for `/api/auth/sessions/me`,\n * `/api/auth/sessions/exchange`, etc. Defaults to\n * `process.env.DASHWISE_API_URL`.\n */\n apiBaseUrl?: string;\n\n /**\n * Base URL for the DashWise main app's SSO entry point (e.g.\n * `https://app.dashwise.io`). Used for the initial redirect from\n * module middleware. Defaults to `process.env.DASHWISE_AUTH_URL`,\n * falling back to `apiBaseUrl` with no path suffix.\n */\n authBaseUrl?: string;\n\n /**\n * Name of the session cookie. Defaults to\n * `process.env.DASHWISE_SESSION_COOKIE_NAME` or `'dashwise.session'`.\n * Cookie scope is configured by the backend on set; the SDK only\n * reads/clears it.\n */\n cookieName?: string;\n\n /**\n * Optional fetch override (testing / custom HTTP agent). Defaults\n * to `globalThis.fetch`.\n */\n fetch?: typeof globalThis.fetch;\n}\n\n/**\n * Response shape from `POST /api/auth/sessions/exchange`. The\n * `session_token` is the opaque cookie value to set; the consumer\n * shouldn't decode or store it elsewhere.\n */\nexport interface ExchangeResult {\n /** Opaque session cookie value (JWT under the hood). */\n sessionToken: string;\n /** Cookie max-age in seconds (matches the JWT exp). */\n maxAgeSeconds: number;\n /** Already-decoded session — saves an immediate /me round-trip. */\n session: Session;\n}\n\n/**\n * Errors thrown by the auth layer. Mirrors the data-plane error model\n * (subclass of `DashwiseError`) so consumers can catch all SDK errors\n * uniformly.\n */\nexport const AuthErrorCode = {\n /** Cookie missing or invalid — caller should redirect to sign-in. */\n NO_SESSION: 'NO_SESSION',\n /** Backend rejected the exchange code (expired / replay / forged). */\n INVALID_EXCHANGE_CODE: 'INVALID_EXCHANGE_CODE',\n /** Backend rejected the session refresh (expired beyond refresh window). */\n REFRESH_FAILED: 'REFRESH_FAILED',\n /** Backend returned an unexpected response. */\n PROTOCOL_ERROR: 'PROTOCOL_ERROR',\n /**\n * `requirePermission(...)` / `requireRole(...)` rejected because\n * the current session lacks the requested permission/role (P12.4).\n * Includes the requested key in the error's `context`.\n */\n PERMISSION_DENIED: 'PERMISSION_DENIED',\n /**\n * `requirePermission(...)` / `requireRole(...)` was called from a\n * context without RBAC info — usually because the session has no\n * `installationId` (the user isn't inside an installed module),\n * or the `/rbac/me` fetch failed silently. Callers in this state\n * should fall back to anon UI or surface a missing-context error.\n */\n RBAC_UNAVAILABLE: 'RBAC_UNAVAILABLE',\n} as const;\n\nexport type AuthErrorCode = (typeof AuthErrorCode)[keyof typeof AuthErrorCode];\n"]}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/data/deps.ts
|
|
4
|
+
var MODULE_SLUG_REGEX = /^[a-z][a-z0-9-]*[a-z0-9]$/;
|
|
5
|
+
var TABLE_SLUG_REGEX = /^[a-z][a-z0-9_]*[a-z0-9]$/;
|
|
6
|
+
function assertValidProviderSlug(slug) {
|
|
7
|
+
if (typeof slug !== "string" || !MODULE_SLUG_REGEX.test(slug)) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
`@dashai/sdk: invalid provider slug "${slug}". Provider slugs are the dependency module's slug (kebab-case, e.g. "crm-app"); must match /^[a-z][a-z0-9-]*[a-z0-9]$/.`
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function assertValidTableSlug(slug) {
|
|
14
|
+
if (typeof slug !== "string" || !TABLE_SLUG_REGEX.test(slug)) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`@dashai/sdk: invalid table slug "${slug}". Table slugs must match /^[a-z][a-z0-9_]*[a-z0-9]$/ (snake_case).`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function makeDeps(transport, providerSlug) {
|
|
21
|
+
assertValidProviderSlug(providerSlug);
|
|
22
|
+
return {
|
|
23
|
+
db(tableSlug) {
|
|
24
|
+
assertValidTableSlug(tableSlug);
|
|
25
|
+
return makeBorrowedTableClient(transport, providerSlug, tableSlug);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function makeBorrowedTableClient(transport, providerSlug, tableSlug) {
|
|
30
|
+
const root = `/deps/${providerSlug}/db/${tableSlug}`;
|
|
31
|
+
return {
|
|
32
|
+
async list(opts) {
|
|
33
|
+
const body = opts ?? {};
|
|
34
|
+
const result = await transport.request("POST", `${root}/list`, body);
|
|
35
|
+
if (Array.isArray(result)) {
|
|
36
|
+
return {
|
|
37
|
+
rows: result,
|
|
38
|
+
total: result.length,
|
|
39
|
+
limit: body.limit ?? result.length,
|
|
40
|
+
offset: body.offset ?? 0
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
},
|
|
45
|
+
async get(id) {
|
|
46
|
+
return transport.request("GET", `${root}/${id}`);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/data/errors.ts
|
|
52
|
+
var DashwiseErrorCode = {
|
|
53
|
+
PROVIDER_TABLE_MISSING: "PROVIDER_TABLE_MISSING",
|
|
54
|
+
OPERATION_NOT_ALLOWED_BY_PROVIDER: "OPERATION_NOT_ALLOWED_BY_PROVIDER"};
|
|
55
|
+
var DashwiseError = class extends Error {
|
|
56
|
+
code;
|
|
57
|
+
status;
|
|
58
|
+
retriable;
|
|
59
|
+
context;
|
|
60
|
+
/**
|
|
61
|
+
* Backend-issued trace anchor (Phase 6 slice 6.2, 2026-05-19).
|
|
62
|
+
* Mirrors the `x-request-id` response header. Populated whenever
|
|
63
|
+
* the backend's `RuntimeQueryController` (or any future endpoint
|
|
64
|
+
* that echoes the header) is the origin of this error. `null` when
|
|
65
|
+
* the failure is client-side (NetworkError / TimeoutError — the
|
|
66
|
+
* request never reached the server, so there's no server-issued
|
|
67
|
+
* ID to surface).
|
|
68
|
+
*
|
|
69
|
+
* Use this as a debugging trace anchor when filing a bug against a
|
|
70
|
+
* specific failed query — the matching row in `module_query_log`
|
|
71
|
+
* carries the full backend context (ast_hash, error_code,
|
|
72
|
+
* duration_ms, etc.).
|
|
73
|
+
*/
|
|
74
|
+
requestId;
|
|
75
|
+
constructor(code, message, options = {}) {
|
|
76
|
+
super(message);
|
|
77
|
+
this.name = this.constructor.name;
|
|
78
|
+
this.code = code;
|
|
79
|
+
this.status = options.status ?? 0;
|
|
80
|
+
this.retriable = options.retriable ?? false;
|
|
81
|
+
this.context = options.context;
|
|
82
|
+
this.requestId = options.requestId ?? null;
|
|
83
|
+
if (options.cause !== void 0) {
|
|
84
|
+
this.cause = options.cause;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var DependencyContractError = class extends DashwiseError {
|
|
89
|
+
constructor(code, message, context, requestId = null) {
|
|
90
|
+
super(code, message, { status: 404, context, requestId });
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var ProviderTableMissingError = class extends DashwiseError {
|
|
94
|
+
constructor(message, context, requestId = null) {
|
|
95
|
+
super(DashwiseErrorCode.PROVIDER_TABLE_MISSING, message, {
|
|
96
|
+
status: 404,
|
|
97
|
+
context,
|
|
98
|
+
requestId
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var OperationNotAllowedByProviderError = class extends DashwiseError {
|
|
103
|
+
constructor(message, context, requestId = null) {
|
|
104
|
+
super(DashwiseErrorCode.OPERATION_NOT_ALLOWED_BY_PROVIDER, message, {
|
|
105
|
+
status: 403,
|
|
106
|
+
context,
|
|
107
|
+
requestId
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
exports.DependencyContractError = DependencyContractError;
|
|
113
|
+
exports.OperationNotAllowedByProviderError = OperationNotAllowedByProviderError;
|
|
114
|
+
exports.ProviderTableMissingError = ProviderTableMissingError;
|
|
115
|
+
exports.makeDeps = makeDeps;
|
|
116
|
+
//# sourceMappingURL=index.cjs.map
|
|
117
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/data/deps.ts","../../src/data/errors.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Borrowed-table accessor: `client.deps(providerSlug).db<Row>(tableSlug)`.\n *\n * Phase 1 of the SDK query plane — see\n * `dashwise-devops-docs/plans/modules-sdk-query-v1.md` §6.2.\n *\n * The factory is intentionally a thin wrapper around the existing\n * {@link HttpTransport}: cross-module reads target a parallel URL\n * shape (`/deps/<provider>/db/<table>/list` instead of\n * `/db/<table>/list`) but otherwise behave identically — same JSON\n * body, same {@link Page} envelope, same {@link DashwiseError} model.\n * No new transport, no new auth flow, no new caching.\n *\n * Cross-module **writes** are deliberately omitted: the returned\n * {@link ReadOnlyTableClient} has no `create` / `update` / `delete`.\n * See plan §4.3 — writes flow through provider-published actions in\n * a later phase, never as direct mutations across the trust boundary.\n *\n * Slug validation runs client-side as a fail-fast check so a typo\n * doesn't reach the backend as a 404 from a malformed URL. The\n * authoritative check is server-side: the backend re-validates\n * provider existence + contract coverage on every call.\n */\n\nimport type { HttpTransport } from './http.js';\nimport type {\n BaseRow,\n Deps,\n ListOpts,\n Page,\n ReadOnlyTableClient,\n} from './types.js';\n\n/**\n * Module-slug character set matches the backend manifest validator's\n * `MODULE_SLUG_REGEX` exactly: kebab-case, 2-63 chars, starts with a\n * letter, ends with letter/digit, no leading/trailing dash.\n *\n * Mirrored here (not imported from the backend) because the SDK is\n * published independently — pulling backend code in would couple\n * release cycles. If the backend's regex changes, the SDK's mirror\n * must update in lockstep; covered by an integration test.\n */\nconst MODULE_SLUG_REGEX = /^[a-z][a-z0-9-]*[a-z0-9]$/;\n\n/**\n * Table-slug character set matches the backend's `SLUG_REGEX`: snake_case,\n * 2-63 chars, starts with a letter, ends with letter/digit. Same\n * mirror-vs-import tradeoff as {@link MODULE_SLUG_REGEX}.\n */\nconst TABLE_SLUG_REGEX = /^[a-z][a-z0-9_]*[a-z0-9]$/;\n\nfunction assertValidProviderSlug(slug: string): void {\n if (typeof slug !== 'string' || !MODULE_SLUG_REGEX.test(slug)) {\n throw new Error(\n `@dashai/sdk: invalid provider slug \"${slug}\". Provider slugs are the dependency module's slug (kebab-case, e.g. \"crm-app\"); must match /^[a-z][a-z0-9-]*[a-z0-9]$/.`,\n );\n }\n}\n\nfunction assertValidTableSlug(slug: string): void {\n if (typeof slug !== 'string' || !TABLE_SLUG_REGEX.test(slug)) {\n throw new Error(\n `@dashai/sdk: invalid table slug \"${slug}\". Table slugs must match /^[a-z][a-z0-9_]*[a-z0-9]$/ (snake_case).`,\n );\n }\n}\n\n/**\n * Build a {@link Deps} accessor for `providerSlug`. Validates the\n * slug shape eagerly; defers everything else to the backend.\n *\n * Exported for use by `createClient()` in `./client.ts`. Not part\n * of the public API directly — consumers reach it via `client.deps(...)`.\n */\nexport function makeDeps(transport: HttpTransport, providerSlug: string): Deps {\n assertValidProviderSlug(providerSlug);\n\n return {\n db<Row extends BaseRow>(tableSlug: string): ReadOnlyTableClient<Row> {\n assertValidTableSlug(tableSlug);\n return makeBorrowedTableClient<Row>(transport, providerSlug, tableSlug);\n },\n };\n}\n\n/**\n * Internal: produce a {@link ReadOnlyTableClient} for one specific\n * `(providerSlug, tableSlug)` pair. Closure captures the transport +\n * the URL root so the public methods are tiny.\n */\nfunction makeBorrowedTableClient<Row extends BaseRow>(\n transport: HttpTransport,\n providerSlug: string,\n tableSlug: string,\n): ReadOnlyTableClient<Row> {\n // URL shape mirrors the owned-table CRUD pipeline but interpolates\n // the provider's slug between the installation root + the table\n // segment: `/deps/<provider>/db/<table>/...`. The full URL after\n // the transport's base prefix is therefore\n // `<baseUrl>/api/modules/<installationId>/deps/<provider>/db/<table>/<op>`.\n const root = `/deps/${providerSlug}/db/${tableSlug}`;\n\n return {\n async list(opts?: ListOpts<Row>): Promise<Page<Row>> {\n const body = opts ?? {};\n const result = await transport.request<Page<Row>>('POST', `${root}/list`, body);\n // Same defense-in-depth shape coercion as the owned-table client:\n // if the backend ever responds with a bare array (older endpoint\n // version, proxy stripping the envelope, etc.), wrap it so the\n // consumer always sees `{ rows, ... }`.\n if (Array.isArray(result)) {\n return {\n rows: result as Row[],\n total: result.length,\n limit: body.limit ?? result.length,\n offset: body.offset ?? 0,\n };\n }\n return result;\n },\n\n async get(id: number): Promise<Row> {\n return transport.request<Row>('GET', `${root}/${id}`);\n },\n };\n}\n","/**\n * Error model for @dashai/sdk.\n *\n * The backend speaks a structured error envelope:\n *\n * {\n * \"code\": \"ROW_NOT_FOUND\",\n * \"message\": \"Row 123 not found in tasks\",\n * \"retriable\": false,\n * \"context\": { ... }\n * }\n *\n * This module wraps that envelope in a typed `DashwiseError` plus a small\n * set of subclasses for the codes consumers most commonly want to branch\n * on (network errors, auth failures, row-not-found, contract violations).\n *\n * Anything not matched by a specific subclass becomes a plain\n * `DashwiseError` carrying the raw `code`. Branch on `.code` for codes\n * we don't have a class for; use `instanceof` for the common cases.\n */\n\n/**\n * Known error codes. Not exhaustive — the backend emits many codes\n * specific to individual modules / endpoints. Use these constants when\n * you want compile-time safety; otherwise treat `err.code` as a string.\n *\n * The list is curated to the codes a module author is most likely to\n * branch on. Adding a new code here is a non-breaking change.\n */\nexport const DashwiseErrorCode = {\n // Auth / authz\n UNAUTHENTICATED: 'UNAUTHENTICATED',\n FORBIDDEN: 'FORBIDDEN',\n TOKEN_EXPIRED: 'TOKEN_EXPIRED',\n INSTALLATION_NOT_FOUND: 'INSTALLATION_NOT_FOUND',\n INSTALLATION_MODE_UNSUPPORTED: 'INSTALLATION_MODE_UNSUPPORTED',\n // Contract enforcement\n CONTRACT_VIOLATION: 'CONTRACT_VIOLATION',\n FIELD_NOT_READABLE: 'FIELD_NOT_READABLE',\n FILTER_OP_UNKNOWN: 'FILTER_OP_UNKNOWN',\n OPERATION_NOT_ALLOWED: 'OPERATION_NOT_ALLOWED',\n // Cross-module dependencies (Phase 1 of the SDK query plane —\n // see dashwise-devops-docs/plans/modules-sdk-query-v1.md).\n // Surfaced when reading borrowed tables via `client.deps(slug)`.\n DEPENDENCY_NOT_DECLARED: 'DEPENDENCY_NOT_DECLARED',\n TABLE_NOT_IN_CONTRACT: 'TABLE_NOT_IN_CONTRACT',\n PROVIDER_TABLE_MISSING: 'PROVIDER_TABLE_MISSING',\n OPERATION_NOT_ALLOWED_BY_PROVIDER: 'OPERATION_NOT_ALLOWED_BY_PROVIDER',\n // Query-plane joins (Phase 3 — see\n // dashwise-devops-docs/plans/modules-sdk-query-v1-p3-execution.md).\n // Surfaced when `.join('<slug>')` / `.leftJoin('<slug>')` can't\n // resolve the target. Pre-declared in slice 3.1 so slice 3.2's\n // backend translator can emit them through fromBackendEnvelope\n // without an SDK roundtrip.\n LINK_FIELD_NOT_FOUND: 'LINK_FIELD_NOT_FOUND',\n NOT_A_LINK_FIELD: 'NOT_A_LINK_FIELD',\n JOIN_NOT_LINKED: 'JOIN_NOT_LINKED',\n // Cross-module actions (Phase 7 slice 7.3a — see\n // dashwise-devops-docs/plans/modules-sdk-query-v1-p7-cross-module-writes.md).\n // Surfaced when `qb.deps.<dep>.actions.<name>(input)` (generated\n // factory) dispatches against the new backend endpoint\n // `POST :installationId/deps/:providerSlug/actions/:actionName`.\n // Slice 7.2b ships the backend emitter; slice 7.3a (this slice)\n // reserves the codes + maps them to DepActionError in the SDK.\n ACTION_NOT_EXPOSED: 'ACTION_NOT_EXPOSED',\n ACTION_NOT_DECLARED: 'ACTION_NOT_DECLARED',\n ACTION_INPUT_INVALID: 'ACTION_INPUT_INVALID',\n ACTION_OUTPUT_INVALID: 'ACTION_OUTPUT_INVALID',\n ACTION_RATE_LIMITED: 'ACTION_RATE_LIMITED',\n ACTION_EXECUTION_FAILED: 'ACTION_EXECUTION_FAILED',\n // Data plane\n ROW_NOT_FOUND: 'ROW_NOT_FOUND',\n TABLE_NOT_FOUND: 'TABLE_NOT_FOUND',\n VALIDATION_FAILED: 'VALIDATION_FAILED',\n ROW_LIMIT_EXCEEDED: 'ROW_LIMIT_EXCEEDED',\n STORAGE_LIMIT_EXCEEDED: 'STORAGE_LIMIT_EXCEEDED',\n // Outbound / runtime\n OUTBOUND_NOT_ALLOWED: 'OUTBOUND_NOT_ALLOWED',\n OUTBOUND_CONCURRENCY_LIMITED: 'OUTBOUND_CONCURRENCY_LIMITED',\n // Transport\n NETWORK_ERROR: 'NETWORK_ERROR',\n TIMEOUT: 'TIMEOUT',\n // Server\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n} as const;\n\nexport type DashwiseErrorCode = (typeof DashwiseErrorCode)[keyof typeof DashwiseErrorCode];\n\n/**\n * Base class for every error thrown by @dashai/sdk. Carries the\n * structured envelope from the backend (or a synthetic one for\n * client-side failures like network errors / timeouts).\n *\n * Always thrown — never returned as a value. The async client methods\n * `.list()` / `.get()` etc. reject with one of these on failure.\n */\nexport class DashwiseError extends Error {\n readonly code: string;\n readonly status: number;\n readonly retriable: boolean;\n readonly context: Record<string, unknown> | undefined;\n /**\n * Backend-issued trace anchor (Phase 6 slice 6.2, 2026-05-19).\n * Mirrors the `x-request-id` response header. Populated whenever\n * the backend's `RuntimeQueryController` (or any future endpoint\n * that echoes the header) is the origin of this error. `null` when\n * the failure is client-side (NetworkError / TimeoutError — the\n * request never reached the server, so there's no server-issued\n * ID to surface).\n *\n * Use this as a debugging trace anchor when filing a bug against a\n * specific failed query — the matching row in `module_query_log`\n * carries the full backend context (ast_hash, error_code,\n * duration_ms, etc.).\n */\n readonly requestId: string | null;\n\n constructor(\n code: string,\n message: string,\n options: {\n status?: number;\n retriable?: boolean;\n context?: Record<string, unknown>;\n cause?: unknown;\n /**\n * Phase 6 slice 6.2: backend `x-request-id` response header,\n * captured by the transport before throwing. Pass `undefined`\n * (or omit) for client-side errors that never hit the server.\n */\n requestId?: string | null;\n } = {},\n ) {\n super(message);\n this.name = this.constructor.name;\n this.code = code;\n this.status = options.status ?? 0;\n this.retriable = options.retriable ?? false;\n this.context = options.context;\n this.requestId = options.requestId ?? null;\n if (options.cause !== undefined) {\n // Node 18+ and modern browsers support Error.cause natively.\n // Use a property assignment because `super({ cause })` is awkward\n // when the base class only takes a string.\n (this as { cause?: unknown }).cause = options.cause;\n }\n }\n}\n\n/** Network-level failure (DNS, connection refused, TLS, fetch threw). */\nexport class NetworkError extends DashwiseError {\n constructor(message: string, cause?: unknown) {\n super(DashwiseErrorCode.NETWORK_ERROR, message, { retriable: true, cause });\n }\n}\n\n/** Request exceeded the configured timeout (`timeoutMs`). */\nexport class TimeoutError extends DashwiseError {\n constructor(message: string, timeoutMs: number) {\n super(DashwiseErrorCode.TIMEOUT, message, {\n retriable: true,\n context: { timeout_ms: timeoutMs },\n });\n }\n}\n\n/** 401 — token missing/invalid. */\nexport class UnauthenticatedError extends DashwiseError {\n constructor(message = 'Authentication required', requestId: string | null = null) {\n super(DashwiseErrorCode.UNAUTHENTICATED, message, { status: 401, requestId });\n }\n}\n\n/** 403 — caller lacks the role/scope for this operation. */\nexport class ForbiddenError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.FORBIDDEN, message, { status: 403, context, requestId });\n }\n}\n\n/** 404 — `GET /db/<table>/<id>` for a missing or soft-deleted row. */\nexport class RowNotFoundError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.ROW_NOT_FOUND, message, { status: 404, context, requestId });\n }\n}\n\n/**\n * Schema-level rejection: dep contract violated, table not exposed, field\n * not readable, filter op not allowed. Covers the family of \"you asked for\n * something the manifest doesn't permit\" errors.\n */\nexport class ContractViolationError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.CONTRACT_VIOLATION, message, {\n status: 403,\n context,\n requestId,\n });\n }\n}\n\n/** 422-ish — request body failed validation. Inspect `context` for field-level errors. */\nexport class ValidationError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.VALIDATION_FAILED, message, {\n status: 400,\n context,\n requestId,\n });\n }\n}\n\n// ── Cross-module dependency errors ─────────────────────────────────────\n//\n// Surfaced when calling `client.deps(slug).db<Row>(table).<op>(...)`.\n// Each subclass preserves the original backend `code` (unlike the older\n// `ContractViolationError` which collapses several codes onto a single\n// `CONTRACT_VIOLATION` string — that's a known wart we don't replicate\n// in new subclasses). Branch on `instanceof` for the common cases;\n// inspect `err.code` if you need to distinguish the two contract-error\n// codes (`DEPENDENCY_NOT_DECLARED` vs `TABLE_NOT_IN_CONTRACT`).\n\n/**\n * 404 — the caller's `module.json#dependencies` doesn't declare a\n * dependency on the requested provider module, OR the declared dep\n * exists but its `reads[]` block doesn't include the requested table.\n *\n * Either way the fix is on the **caller's** side: update the manifest\n * to declare the dep / add the table to `reads[]`, then re-publish.\n *\n * Covers both `DEPENDENCY_NOT_DECLARED` and `TABLE_NOT_IN_CONTRACT` —\n * inspect `err.code` to distinguish if you need different UX per case.\n */\nexport class DependencyContractError extends DashwiseError {\n constructor(\n code: string,\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(code, message, { status: 404, context, requestId });\n }\n}\n\n/**\n * 404 — the provider's physical table no longer exists. This means the\n * provider published a version that dropped the table while the\n * consumer's installation still pinned the old contract. Recovery is\n * on the **provider's** side (republish with the table) or the\n * workspace admin's (downgrade the provider, or upgrade the consumer\n * to a version whose `dependencies[]` no longer references it).\n */\nexport class ProviderTableMissingError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.PROVIDER_TABLE_MISSING, message, {\n status: 404,\n context,\n requestId,\n });\n }\n}\n\n/**\n * 403 — the provider's `exposes.operations.<verb>.allowed` is explicitly\n * `false` for the requested verb. Distinct from `OPERATION_NOT_ALLOWED`\n * (which is the **owner's** own-table policy) — `OPERATION_NOT_ALLOWED_BY_PROVIDER`\n * is the provider opting out of letting depending modules use a verb.\n *\n * No caller-side fix; either the provider needs to flip the gate, or\n * the consumer needs to find a different way to satisfy the use case.\n */\nexport class OperationNotAllowedByProviderError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.OPERATION_NOT_ALLOWED_BY_PROVIDER, message, {\n status: 403,\n context,\n requestId,\n });\n }\n}\n\n// ── Join errors (Phase 3) ──────────────────────────────────────────────\n//\n// Surfaced when `.join('<slug>')` / `.leftJoin('<slug>')` on the\n// generated `qb` builder can't resolve the target against the\n// manifest. Pre-declared in slice 3.1; raised by the backend\n// translator in slice 3.2. Same one-subclass-with-preserved-code\n// pattern as DependencyContractError so authors can switch on\n// `err.code` for the three sub-cases:\n//\n// LINK_FIELD_NOT_FOUND `.join('xyz')` where `xyz` isn't a field\n// on the source table. Fix: pass an\n// explicit `on` argument, or correct the slug.\n//\n// NOT_A_LINK_FIELD `.join('title')` where `title` exists on\n// the source table but its type isn't\n// `link_row`. Fix: use a real link field,\n// or pass explicit `on`.\n//\n// JOIN_NOT_LINKED `.join('sibling_table')` where the target\n// is a sibling table but no link field\n// bridges them. Fix: pass explicit `on`.\n\n/**\n * 400 — `.join()` / `.leftJoin()` referenced a target the manifest\n * can't resolve. Covers the three sub-cases above; inspect\n * `err.code` to distinguish.\n *\n * Author-side fix in every case: either correct the slug, or pass\n * an explicit `on` argument to bypass link-field inference.\n */\nexport class JoinError extends DashwiseError {\n constructor(\n code: string,\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(code, message, { status: 400, context, requestId });\n }\n}\n\n// ── Cross-module action errors (Phase 7 slice 7.3a, 2026-05-20) ──────\n//\n// Surfaced by the codegen-emitted `qb.deps.<dep>.actions.<name>(input)`\n// factory when the backend's cross-module dispatch\n// (`CrossModuleActionService.dispatch()`) rejects. Six sub-cases all\n// covered by `DepActionError`; inspect `err.code` to discriminate.\n//\n// ACTION_NOT_EXPOSED 403 — provider's manifest doesn't expose\n// the action, OR visibility='private'.\n// Recovery: provider author needs to\n// publish a version that exposes it.\n// ACTION_NOT_DECLARED 403 — consumer's manifest doesn't list\n// the action in dependencies[].actions[].\n// Recovery: add it + republish.\n// ACTION_INPUT_INVALID 400 — input fails Ajv against the\n// provider's declared input schema.\n// Recovery: fix the call site (typical\n// with stale codegen + drift).\n// ACTION_OUTPUT_INVALID 500 — provider returned a value that fails\n// the declared output schema. This is a\n// PROVIDER-author bug; consumer can\n// only file an issue.\n// ACTION_RATE_LIMITED 429 — per-actor or per-caller-installation\n// rate limit exceeded. Retry with\n// backoff after Retry-After (slice 7.4).\n// ACTION_EXECUTION_FAILED 200-envelope — action body threw, timed out,\n// or OOM-ed. Provider-author bug;\n// trace surfaced in err.context.trace.\n\n/**\n * Single subclass for every cross-module action failure. The code\n * (and HTTP status) varies per sub-case; the wrapping class lets\n * consumers `catch (e) { if (e instanceof DepActionError) {...} }`\n * regardless of which sub-case fired.\n */\nexport class DepActionError extends DashwiseError {\n constructor(\n code: string,\n message: string,\n status: number,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(code, message, {\n status,\n retriable: code === DashwiseErrorCode.ACTION_RATE_LIMITED,\n context,\n requestId,\n });\n }\n}\n\n// ── Factory: backend envelope → typed error ────────────────────────────\n\ninterface BackendErrorEnvelope {\n code?: string;\n message?: string;\n retriable?: boolean;\n context?: Record<string, unknown>;\n}\n\n/**\n * Build a typed `DashwiseError` from the backend's response envelope.\n *\n * The mapping picks the most specific subclass for codes we have a class\n * for; everything else becomes a plain `DashwiseError` carrying the raw\n * code. This keeps the type set small while still letting consumers\n * branch on string codes when they want to.\n *\n * Phase 6 slice 6.2 (2026-05-19): accepts an optional `requestId` —\n * the backend's `x-request-id` response header — and threads it onto\n * every produced error so consumers can use it as a debugging trace\n * anchor. Default `null` for backward compat; the transport always\n * passes it through.\n */\nexport function fromBackendEnvelope(\n status: number,\n envelope: BackendErrorEnvelope | null,\n requestId: string | null = null,\n): DashwiseError {\n const code = envelope?.code ?? codeFromStatus(status);\n const message = envelope?.message ?? `HTTP ${status}`;\n const context = envelope?.context;\n\n switch (code) {\n case DashwiseErrorCode.UNAUTHENTICATED:\n case DashwiseErrorCode.TOKEN_EXPIRED:\n return new UnauthenticatedError(message, requestId);\n case DashwiseErrorCode.FORBIDDEN:\n return new ForbiddenError(message, context, requestId);\n case DashwiseErrorCode.ROW_NOT_FOUND:\n return new RowNotFoundError(message, context, requestId);\n case DashwiseErrorCode.CONTRACT_VIOLATION:\n case DashwiseErrorCode.FIELD_NOT_READABLE:\n case DashwiseErrorCode.FILTER_OP_UNKNOWN:\n case DashwiseErrorCode.OPERATION_NOT_ALLOWED:\n return new ContractViolationError(message, context, requestId);\n case DashwiseErrorCode.VALIDATION_FAILED:\n return new ValidationError(message, context, requestId);\n case DashwiseErrorCode.DEPENDENCY_NOT_DECLARED:\n case DashwiseErrorCode.TABLE_NOT_IN_CONTRACT:\n return new DependencyContractError(code, message, context, requestId);\n case DashwiseErrorCode.PROVIDER_TABLE_MISSING:\n return new ProviderTableMissingError(message, context, requestId);\n case DashwiseErrorCode.OPERATION_NOT_ALLOWED_BY_PROVIDER:\n return new OperationNotAllowedByProviderError(message, context, requestId);\n case DashwiseErrorCode.LINK_FIELD_NOT_FOUND:\n case DashwiseErrorCode.NOT_A_LINK_FIELD:\n case DashwiseErrorCode.JOIN_NOT_LINKED:\n return new JoinError(code, message, context, requestId);\n case DashwiseErrorCode.ACTION_NOT_EXPOSED:\n case DashwiseErrorCode.ACTION_NOT_DECLARED:\n return new DepActionError(code, message, 403, context, requestId);\n case DashwiseErrorCode.ACTION_INPUT_INVALID:\n return new DepActionError(code, message, 400, context, requestId);\n case DashwiseErrorCode.ACTION_OUTPUT_INVALID:\n return new DepActionError(code, message, 500, context, requestId);\n case DashwiseErrorCode.ACTION_RATE_LIMITED:\n return new DepActionError(code, message, 429, context, requestId);\n case DashwiseErrorCode.ACTION_EXECUTION_FAILED:\n // ACTION_EXECUTION_FAILED arrives as a 200 envelope from the\n // backend (the host succeeded; the action body crashed). The\n // _callAction helper unwraps the envelope and synthesizes this\n // via the factory call — status 200 reflects \"the transport was\n // fine; the failure is in the application layer\".\n return new DepActionError(code, message, 200, context, requestId);\n default:\n return new DashwiseError(code, message, {\n status,\n retriable: envelope?.retriable ?? false,\n context,\n requestId,\n });\n }\n}\n\nfunction codeFromStatus(status: number): string {\n if (status === 401) return DashwiseErrorCode.UNAUTHENTICATED;\n if (status === 403) return DashwiseErrorCode.FORBIDDEN;\n if (status === 404) return DashwiseErrorCode.ROW_NOT_FOUND;\n if (status >= 500) return DashwiseErrorCode.INTERNAL_ERROR;\n return 'HTTP_ERROR';\n}\n"]}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { D as Deps } from '../types-BLNQ1S1C.cjs';
|
|
2
|
+
export { R as ReadOnlyTableClient } from '../types-BLNQ1S1C.cjs';
|
|
3
|
+
export { D as DependencyContractError, O as OperationNotAllowedByProviderError, P as ProviderTableMissingError } from '../errors-BV75u7b9.cjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* HTTP transport for @dashai/sdk.
|
|
7
|
+
*
|
|
8
|
+
* Wraps `globalThis.fetch` with:
|
|
9
|
+
* - configurable base URL + installation scoping
|
|
10
|
+
* - token attachment via the caller-supplied resolver
|
|
11
|
+
* - timeout enforcement via `AbortController`
|
|
12
|
+
* - response envelope parsing → typed `DashwiseError`
|
|
13
|
+
* - 204 No Content handling
|
|
14
|
+
*
|
|
15
|
+
* No retries here — retries are policy and belong one layer up. The
|
|
16
|
+
* `retriable` flag on `DashwiseError` is hinting to the caller, not
|
|
17
|
+
* something the transport acts on automatically. (We'd rather a module
|
|
18
|
+
* author opt into retry per-call than have the SDK silently double-fire
|
|
19
|
+
* an idempotent-looking POST that wasn't actually idempotent.)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
interface HttpTransport {
|
|
23
|
+
/**
|
|
24
|
+
* Make a request to the DashWise API.
|
|
25
|
+
*
|
|
26
|
+
* @param method HTTP verb.
|
|
27
|
+
* @param path Path appended to the installation-scoped base URL,
|
|
28
|
+
* starting with `/`. Example: `/db/tasks/list`.
|
|
29
|
+
* @param body JSON-serializable body. Omit/pass `undefined` to skip
|
|
30
|
+
* the body entirely.
|
|
31
|
+
* @returns Parsed JSON response, or `undefined` for 204 No Content.
|
|
32
|
+
* @throws `DashwiseError` (or a subclass) on any non-2xx response,
|
|
33
|
+
* `NetworkError` on transport failure, `TimeoutError` on
|
|
34
|
+
* aborted requests.
|
|
35
|
+
*/
|
|
36
|
+
request<T = unknown>(method: string, path: string, body?: unknown): Promise<T>;
|
|
37
|
+
/**
|
|
38
|
+
* Phase 6 streaming (2026-05-19): low-level transport entry that
|
|
39
|
+
* returns the unparsed `Response` so the caller can stream the body
|
|
40
|
+
* chunk-by-chunk. Used by `qb.from(...).stream()` to consume the
|
|
41
|
+
* backend's SSE endpoint.
|
|
42
|
+
*
|
|
43
|
+
* Differs from `request()`:
|
|
44
|
+
* - No body parsing — caller owns the `Response.body` ReadableStream.
|
|
45
|
+
* - Caller MUST handle non-2xx (read the error envelope from the
|
|
46
|
+
* response body + throw the typed `DashwiseError`); this method
|
|
47
|
+
* returns the Response regardless of status so error JSON can
|
|
48
|
+
* be read off the stream.
|
|
49
|
+
* - Optional `signal` parameter for caller-side cancellation
|
|
50
|
+
* (combined with the transport's own timeout). Abort propagates
|
|
51
|
+
* to fetch, which cancels the underlying network read.
|
|
52
|
+
* - Timeout handling: still applies via the transport's
|
|
53
|
+
* `timeoutMs` config, but ONLY to the headers-arrival phase.
|
|
54
|
+
* Streaming bodies can take arbitrarily long; the stream
|
|
55
|
+
* reader's own backpressure handles the long tail.
|
|
56
|
+
*/
|
|
57
|
+
streamRaw(method: string, path: string, body?: unknown, signal?: AbortSignal): Promise<Response>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Borrowed-table accessor: `client.deps(providerSlug).db<Row>(tableSlug)`.
|
|
62
|
+
*
|
|
63
|
+
* Phase 1 of the SDK query plane — see
|
|
64
|
+
* `dashwise-devops-docs/plans/modules-sdk-query-v1.md` §6.2.
|
|
65
|
+
*
|
|
66
|
+
* The factory is intentionally a thin wrapper around the existing
|
|
67
|
+
* {@link HttpTransport}: cross-module reads target a parallel URL
|
|
68
|
+
* shape (`/deps/<provider>/db/<table>/list` instead of
|
|
69
|
+
* `/db/<table>/list`) but otherwise behave identically — same JSON
|
|
70
|
+
* body, same {@link Page} envelope, same {@link DashwiseError} model.
|
|
71
|
+
* No new transport, no new auth flow, no new caching.
|
|
72
|
+
*
|
|
73
|
+
* Cross-module **writes** are deliberately omitted: the returned
|
|
74
|
+
* {@link ReadOnlyTableClient} has no `create` / `update` / `delete`.
|
|
75
|
+
* See plan §4.3 — writes flow through provider-published actions in
|
|
76
|
+
* a later phase, never as direct mutations across the trust boundary.
|
|
77
|
+
*
|
|
78
|
+
* Slug validation runs client-side as a fail-fast check so a typo
|
|
79
|
+
* doesn't reach the backend as a 404 from a malformed URL. The
|
|
80
|
+
* authoritative check is server-side: the backend re-validates
|
|
81
|
+
* provider existence + contract coverage on every call.
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Build a {@link Deps} accessor for `providerSlug`. Validates the
|
|
86
|
+
* slug shape eagerly; defers everything else to the backend.
|
|
87
|
+
*
|
|
88
|
+
* Exported for use by `createClient()` in `./client.ts`. Not part
|
|
89
|
+
* of the public API directly — consumers reach it via `client.deps(...)`.
|
|
90
|
+
*/
|
|
91
|
+
declare function makeDeps(transport: HttpTransport, providerSlug: string): Deps;
|
|
92
|
+
|
|
93
|
+
export { Deps, makeDeps };
|