@marianmeres/ownsuite 2.2.1 → 2.2.3
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/AGENTS.md +9 -2
- package/API.md +13 -4
- package/README.md +6 -9
- package/dist/adapters/mock-auth.d.ts +24 -2
- package/dist/adapters/mock-auth.js +8 -0
- package/dist/adapters/mock.d.ts +2 -0
- package/dist/adapters/stack-account.d.ts +7 -0
- package/dist/adapters/stack-account.js +6 -0
- package/dist/domains/auth.d.ts +42 -0
- package/dist/domains/auth.js +36 -0
- package/dist/domains/base.d.ts +20 -0
- package/dist/domains/base.js +18 -0
- package/dist/domains/owned-collection.d.ts +5 -0
- package/dist/domains/owned-collection.js +2 -0
- package/dist/domains/profile.d.ts +18 -0
- package/dist/domains/profile.js +11 -0
- package/dist/domains/session.d.ts +9 -0
- package/dist/domains/session.js +8 -0
- package/dist/mod.d.ts +3 -0
- package/dist/oauth/popup.d.ts +25 -0
- package/dist/ownsuite.d.ts +13 -2
- package/dist/ownsuite.js +10 -2
- package/dist/types/adapter.d.ts +4 -0
- package/dist/types/auth.d.ts +68 -0
- package/dist/types/events.d.ts +59 -8
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -6,7 +6,7 @@ Machine-readable documentation for AI coding assistants.
|
|
|
6
6
|
|
|
7
7
|
```yaml
|
|
8
8
|
name: "@marianmeres/ownsuite"
|
|
9
|
-
version: "2.
|
|
9
|
+
version: "2.1.0"
|
|
10
10
|
type: "library"
|
|
11
11
|
language: "typescript"
|
|
12
12
|
runtime: "deno"
|
|
@@ -153,7 +153,14 @@ export {
|
|
|
153
153
|
createMockAuthAdapter, createMockProfileAdapter,
|
|
154
154
|
createMockAuthStore, verifyMockAccount,
|
|
155
155
|
} from "./adapters/mod.ts";
|
|
156
|
-
export type { MockAuthStore } from "./adapters/mod.ts";
|
|
156
|
+
export type { MockAccount, MockAuthStore } from "./adapters/mod.ts";
|
|
157
|
+
|
|
158
|
+
// Upstream types re-exported so they appear in the public doc graph
|
|
159
|
+
// (they already surface as return types of subscribe / on). Not intended
|
|
160
|
+
// as primary consumer API — use them only when typing wrappers.
|
|
161
|
+
export type { PubSub, Subscriber, Unsubscriber } from "@marianmeres/pubsub";
|
|
162
|
+
export type { StoreLike } from "@marianmeres/store";
|
|
163
|
+
export type { Clog } from "@marianmeres/clog";
|
|
157
164
|
```
|
|
158
165
|
|
|
159
166
|
## Account lifecycle (optional)
|
package/API.md
CHANGED
|
@@ -360,8 +360,6 @@ interface OwnedRowResult<TRow> {
|
|
|
360
360
|
}
|
|
361
361
|
```
|
|
362
362
|
|
|
363
|
-
Matches `@marianmeres/collection`'s REST envelope.
|
|
364
|
-
|
|
365
363
|
### `OwnedCollectionState<TRow>`
|
|
366
364
|
|
|
367
365
|
```typescript
|
|
@@ -515,7 +513,7 @@ Attached automatically when `adapters.auth` is passed to `createOwnsuite`. See a
|
|
|
515
513
|
|
|
516
514
|
### `createStackAccountAuthAdapter(options?)`
|
|
517
515
|
|
|
518
|
-
Default `AuthAdapter` pointing at
|
|
516
|
+
Default `AuthAdapter` pointing at a conventional account REST surface (register / login / logout / OAuth / verify).
|
|
519
517
|
|
|
520
518
|
**Parameters:**
|
|
521
519
|
- `options` (`StackAccountAdapterOptions`, optional)
|
|
@@ -797,9 +795,18 @@ interface StackAccountAdapterOptions {
|
|
|
797
795
|
|
|
798
796
|
See [src/oauth/popup.ts](src/oauth/popup.ts) for full definitions. `OAuthPopupMessage` is a union of `OAuthPopupLoginMessage` (`{ type: "oauth_login_success", jwt, email, roles?, ... }`) and `OAuthPopupLinkMessage` (`{ type: "oauth_link_success", provider }`). Errors posted by the server (`{ type: "oauth_error", error }`) cause the promise to reject.
|
|
799
797
|
|
|
800
|
-
### `MockAuthStore`
|
|
798
|
+
### `MockAccount` / `MockAuthStore`
|
|
801
799
|
|
|
802
800
|
```typescript
|
|
801
|
+
interface MockAccount {
|
|
802
|
+
email: string;
|
|
803
|
+
password: string; // plaintext — mock does no hashing
|
|
804
|
+
roles: string[];
|
|
805
|
+
isVerified: boolean;
|
|
806
|
+
hasPassword: boolean;
|
|
807
|
+
oauthConnections: OAuthConnection[];
|
|
808
|
+
}
|
|
809
|
+
|
|
803
810
|
interface MockAuthStore {
|
|
804
811
|
accounts: Map<string, MockAccount>;
|
|
805
812
|
requireVerifiedEmail: boolean;
|
|
@@ -807,6 +814,8 @@ interface MockAuthStore {
|
|
|
807
814
|
}
|
|
808
815
|
```
|
|
809
816
|
|
|
817
|
+
Fields are public by design so test code can peek at / mutate them directly.
|
|
818
|
+
|
|
810
819
|
---
|
|
811
820
|
|
|
812
821
|
## Account-lifecycle events
|
package/README.md
CHANGED
|
@@ -8,10 +8,7 @@ Client-side helper library for owner-scoped UIs. Generic domain managers with op
|
|
|
8
8
|
|
|
9
9
|
## What it does
|
|
10
10
|
|
|
11
|
-
Ownsuite gives front-end applications a uniform way to read, create, update and delete records from owner-scoped REST endpoints (typically `/me/*`). Each row is implicitly scoped to the authenticated subject by the server — the client never sets `owner_id`.
|
|
12
|
-
|
|
13
|
-
- **@marianmeres/collection** — the `ownerIdScope` route hook enforces owner-based filtering on the server.
|
|
14
|
-
- **@marianmeres/stack-common** — the `ownsuiteOptions()` helper wires the server mount.
|
|
11
|
+
Ownsuite gives front-end applications a uniform way to read, create, update and delete records from owner-scoped REST endpoints (typically `/me/*`). Each row is implicitly scoped to the authenticated subject by the server — the client never sets `owner_id`.
|
|
15
12
|
|
|
16
13
|
## Features
|
|
17
14
|
|
|
@@ -24,11 +21,11 @@ Ownsuite gives front-end applications a uniform way to read, create, update and
|
|
|
24
21
|
- **Event system** — subscribe to list fetches, row CRUD, and lifecycle transitions
|
|
25
22
|
- **Mock adapter** — in-memory fixture for tests, with configurable failure injection and latency
|
|
26
23
|
- **Explicit lifecycle** — `suite.destroy()` aborts in-flight work and releases listeners cleanly
|
|
27
|
-
- **Account lifecycle (opt-in)** — `suite.auth` / `suite.session` / `suite.profile` for register / login / OAuth / verify / logout / profile edit / delete account,
|
|
24
|
+
- **Account lifecycle (opt-in)** — `suite.auth` / `suite.session` / `suite.profile` for register / login / OAuth / verify / logout / profile edit / delete account, with bundled default adapters for a standard account REST surface
|
|
28
25
|
|
|
29
26
|
## Authentication (optional)
|
|
30
27
|
|
|
31
|
-
Pass an `AuthAdapter` to `createOwnsuite` to attach the account-lifecycle managers. The default adapters target
|
|
28
|
+
Pass an `AuthAdapter` to `createOwnsuite` to attach the account-lifecycle managers. The bundled default adapters target a conventional account REST surface (register / login / logout / OAuth / verify / profile CRUD); apps with custom routes can write their own against the `AuthAdapter` / `ProfileAdapter` interfaces exported from this package.
|
|
32
29
|
|
|
33
30
|
```typescript
|
|
34
31
|
import {
|
|
@@ -58,7 +55,7 @@ suite.session!.subscribe(({ status, subject }) => {
|
|
|
58
55
|
if (status === "anonymous") console.log("signed out");
|
|
59
56
|
});
|
|
60
57
|
|
|
61
|
-
// Register → server requires email verification
|
|
58
|
+
// Register → server requires email verification by default
|
|
62
59
|
await suite.auth!.register({
|
|
63
60
|
email: "alice@example.com",
|
|
64
61
|
password: "mysecretpassword",
|
|
@@ -84,8 +81,8 @@ await suite.profile!.update({
|
|
|
84
81
|
current_password: "mysecretpassword",
|
|
85
82
|
});
|
|
86
83
|
|
|
87
|
-
// Logout — revokes JWT server-side
|
|
88
|
-
//
|
|
84
|
+
// Logout — revokes JWT server-side and clears local session storage.
|
|
85
|
+
// Owner-scoped domains reset to initializing.
|
|
89
86
|
await suite.auth!.logout();
|
|
90
87
|
```
|
|
91
88
|
|
|
@@ -10,15 +10,27 @@
|
|
|
10
10
|
* adapters close over, so tests can peek at or mutate state directly.
|
|
11
11
|
*/
|
|
12
12
|
import type { AuthAdapter, OAuthConnection, ProfileAdapter } from "../types/mod.js";
|
|
13
|
-
|
|
13
|
+
/** In-memory account record held by {@link MockAuthStore}. Test code may
|
|
14
|
+
* construct these via the `seed` option of {@link createMockAuthStore}. */
|
|
15
|
+
export interface MockAccount {
|
|
16
|
+
/** Email (and implicit primary key in the mock store). */
|
|
14
17
|
email: string;
|
|
18
|
+
/** Plaintext password — the mock runs no hashing. */
|
|
15
19
|
password: string;
|
|
20
|
+
/** Authorization roles returned on the next login / `/me` read. */
|
|
16
21
|
roles: string[];
|
|
22
|
+
/** Verified flag — flipped by {@link verifyMockAccount}. */
|
|
17
23
|
isVerified: boolean;
|
|
24
|
+
/** Whether the account has a local password (OAuth-only accounts: `false`). */
|
|
18
25
|
hasPassword: boolean;
|
|
26
|
+
/** Linked OAuth provider connections. */
|
|
19
27
|
oauthConnections: OAuthConnection[];
|
|
20
28
|
}
|
|
29
|
+
/** In-memory store backing the mock auth + profile adapters. Pass the same
|
|
30
|
+
* store to `createMockAuthAdapter` and `createMockProfileAdapter` so they
|
|
31
|
+
* share state. Fields are public so test code can peek or mutate them. */
|
|
21
32
|
export interface MockAuthStore {
|
|
33
|
+
/** Account records, keyed by email. */
|
|
22
34
|
accounts: Map<string, MockAccount>;
|
|
23
35
|
/** If true, register/login return requiresVerification=true until the
|
|
24
36
|
* email is explicitly verified via `verifyMockAccount()`. */
|
|
@@ -26,13 +38,23 @@ export interface MockAuthStore {
|
|
|
26
38
|
/** Last-issued JWT per email (just a synthetic string). */
|
|
27
39
|
jwtsByEmail: Map<string, string>;
|
|
28
40
|
}
|
|
41
|
+
/** Create a fresh in-memory {@link MockAuthStore}. Pass `seed` to preload
|
|
42
|
+
* accounts, `requireVerifiedEmail: true` to make login gate on verification. */
|
|
29
43
|
export declare function createMockAuthStore(init?: {
|
|
44
|
+
/** When true, register/login return `requiresVerification: true` until
|
|
45
|
+
* the account is verified via {@link verifyMockAccount}. Default: `false`. */
|
|
30
46
|
requireVerifiedEmail?: boolean;
|
|
47
|
+
/** Accounts to preload into the store. */
|
|
31
48
|
seed?: MockAccount[];
|
|
32
49
|
}): MockAuthStore;
|
|
50
|
+
/** Build an {@link AuthAdapter} backed by the given in-memory
|
|
51
|
+
* {@link MockAuthStore}. Simulates register / login / password change /
|
|
52
|
+
* delete without a server. JWTs are synthetic strings — do not ship. */
|
|
33
53
|
export declare function createMockAuthAdapter(store: MockAuthStore): AuthAdapter;
|
|
54
|
+
/** Build a {@link ProfileAdapter} backed by the given {@link MockAuthStore}.
|
|
55
|
+
* Shares state with an auth adapter created from the same store so login-
|
|
56
|
+
* then-`/me` round-trips reflect each other. */
|
|
34
57
|
export declare function createMockProfileAdapter(store: MockAuthStore): ProfileAdapter;
|
|
35
58
|
/** Test helper — mark a mock account as verified as if the user had clicked
|
|
36
59
|
* the link in the verification email. */
|
|
37
60
|
export declare function verifyMockAccount(store: MockAuthStore, email: string): void;
|
|
38
|
-
export {};
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* Deliberately small: everything is held in a shared `Store` object the
|
|
10
10
|
* adapters close over, so tests can peek at or mutate state directly.
|
|
11
11
|
*/
|
|
12
|
+
/** Create a fresh in-memory {@link MockAuthStore}. Pass `seed` to preload
|
|
13
|
+
* accounts, `requireVerifiedEmail: true` to make login gate on verification. */
|
|
12
14
|
export function createMockAuthStore(init = {}) {
|
|
13
15
|
const store = {
|
|
14
16
|
accounts: new Map(),
|
|
@@ -23,6 +25,9 @@ export function createMockAuthStore(init = {}) {
|
|
|
23
25
|
function mintJwt(email) {
|
|
24
26
|
return `mock.${btoa(email)}.${Date.now().toString(36)}`;
|
|
25
27
|
}
|
|
28
|
+
/** Build an {@link AuthAdapter} backed by the given in-memory
|
|
29
|
+
* {@link MockAuthStore}. Simulates register / login / password change /
|
|
30
|
+
* delete without a server. JWTs are synthetic strings — do not ship. */
|
|
26
31
|
export function createMockAuthAdapter(store) {
|
|
27
32
|
return {
|
|
28
33
|
register(input, _ctx) {
|
|
@@ -148,6 +153,9 @@ export function createMockAuthAdapter(store) {
|
|
|
148
153
|
},
|
|
149
154
|
};
|
|
150
155
|
}
|
|
156
|
+
/** Build a {@link ProfileAdapter} backed by the given {@link MockAuthStore}.
|
|
157
|
+
* Shares state with an auth adapter created from the same store so login-
|
|
158
|
+
* then-`/me` round-trips reflect each other. */
|
|
151
159
|
export function createMockProfileAdapter(store) {
|
|
152
160
|
function emailFromCtx(ctx) {
|
|
153
161
|
const jwt = ctx.jwt;
|
package/dist/adapters/mock.d.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* path deterministically.
|
|
9
9
|
*/
|
|
10
10
|
import type { OwnedCollectionAdapter } from "../types/adapter.js";
|
|
11
|
+
/** Options for {@link createMockOwnedCollectionAdapter}. Only present in
|
|
12
|
+
* tests / storybooks — production code never calls this. */
|
|
11
13
|
export interface MockAdapterOptions<TRow> {
|
|
12
14
|
/** Initial seed rows. */
|
|
13
15
|
seed?: TRow[];
|
|
@@ -28,11 +28,18 @@
|
|
|
28
28
|
* AuthAdapter / ProfileAdapter interfaces.
|
|
29
29
|
*/
|
|
30
30
|
import type { AuthAdapter, ProfileAdapter } from "../types/mod.js";
|
|
31
|
+
/** Options shared by the two stack-account adapter factories. */
|
|
31
32
|
export interface StackAccountAdapterOptions {
|
|
32
33
|
/** Base URL of the mounted stack-account app. Default: "/api/account". */
|
|
33
34
|
baseUrl?: string;
|
|
34
35
|
/** Override the `fetch` implementation (useful for tests / SSR). */
|
|
35
36
|
fetch?: typeof fetch;
|
|
36
37
|
}
|
|
38
|
+
/** Build the default {@link AuthAdapter} for `@marianmeres/stack-account`.
|
|
39
|
+
* Points at `{baseUrl}/auth/*` (register/login/logout/verify/password/
|
|
40
|
+
* delete) and `{baseUrl}/oauth/*` (init + callback). */
|
|
37
41
|
export declare function createStackAccountAuthAdapter(opts?: StackAccountAdapterOptions): AuthAdapter;
|
|
42
|
+
/** Build the default {@link ProfileAdapter} for `@marianmeres/stack-account`.
|
|
43
|
+
* Points at `{baseUrl}/me` (GET + PUT) and `{baseUrl}/me/oauth/*` for
|
|
44
|
+
* connection listing / unlinking. */
|
|
38
45
|
export declare function createStackAccountProfileAdapter(opts?: StackAccountAdapterOptions): ProfileAdapter;
|
|
@@ -82,6 +82,9 @@ async function requestJson(doFetch, url, init, ctx) {
|
|
|
82
82
|
return undefined;
|
|
83
83
|
return (await res.json());
|
|
84
84
|
}
|
|
85
|
+
/** Build the default {@link AuthAdapter} for `@marianmeres/stack-account`.
|
|
86
|
+
* Points at `{baseUrl}/auth/*` (register/login/logout/verify/password/
|
|
87
|
+
* delete) and `{baseUrl}/oauth/*` (init + callback). */
|
|
85
88
|
export function createStackAccountAuthAdapter(opts = {}) {
|
|
86
89
|
const base = opts.baseUrl ?? "/api/account";
|
|
87
90
|
const doFetch = resolveFetch(opts);
|
|
@@ -123,6 +126,9 @@ export function createStackAccountAuthAdapter(opts = {}) {
|
|
|
123
126
|
},
|
|
124
127
|
};
|
|
125
128
|
}
|
|
129
|
+
/** Build the default {@link ProfileAdapter} for `@marianmeres/stack-account`.
|
|
130
|
+
* Points at `{baseUrl}/me` (GET + PUT) and `{baseUrl}/me/oauth/*` for
|
|
131
|
+
* connection listing / unlinking. */
|
|
126
132
|
export function createStackAccountProfileAdapter(opts = {}) {
|
|
127
133
|
const base = opts.baseUrl ?? "/api/account";
|
|
128
134
|
const doFetch = resolveFetch(opts);
|
package/dist/domains/auth.d.ts
CHANGED
|
@@ -15,13 +15,19 @@ import { type PubSub } from "@marianmeres/pubsub";
|
|
|
15
15
|
import type { AuthActionOptions, AuthAdapter, AuthTokenResult, OAuthInitOptions, OAuthProvider, OwnsuiteContext, ProfileAdapter } from "../types/mod.js";
|
|
16
16
|
import type { SessionManager } from "./session.js";
|
|
17
17
|
import type { ProfileManager } from "./profile.js";
|
|
18
|
+
/** Construction-time options for {@link AuthManager}. Normally assembled
|
|
19
|
+
* by `createOwnsuite` — call sites rarely instantiate this directly. */
|
|
18
20
|
export interface AuthManagerOptions {
|
|
21
|
+
/** Server adapter. The manager is stateless beyond what it pushes into
|
|
22
|
+
* {@link SessionManager}. */
|
|
19
23
|
adapter: AuthAdapter;
|
|
24
|
+
/** Session manager that receives the JWT / subject / status writes. */
|
|
20
25
|
session: SessionManager;
|
|
21
26
|
/** Profile manager — auth hydrates the session subject from `/me`
|
|
22
27
|
* immediately after login so subscribers see roles/isVerified/etc.
|
|
23
28
|
* without a second await. */
|
|
24
29
|
profile: ProfileManager;
|
|
30
|
+
/** Shared pubsub for event emission. Created privately when omitted. */
|
|
25
31
|
pubsub?: PubSub;
|
|
26
32
|
/** Called after a successful identity change (register/login/OAuth
|
|
27
33
|
* login/logout). The orchestrator wires this to reset + re-init every
|
|
@@ -35,11 +41,30 @@ export interface AuthManagerOptions {
|
|
|
35
41
|
* tests that don't need a separate profile fetch. */
|
|
36
42
|
profileAdapter?: ProfileAdapter;
|
|
37
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Verbs for the account lifecycle: register / login / logout / OAuth /
|
|
46
|
+
* password-reset / delete account. Holds no state of its own — every
|
|
47
|
+
* outcome is piped into the linked {@link SessionManager}, which is the
|
|
48
|
+
* single source of truth for JWT + subject + status. Usually accessed via
|
|
49
|
+
* `suite.auth`, not constructed directly.
|
|
50
|
+
*/
|
|
38
51
|
export declare class AuthManager {
|
|
39
52
|
#private;
|
|
53
|
+
/** Construct a new `AuthManager`. Normally called by `createOwnsuite`,
|
|
54
|
+
* not by consumers. */
|
|
40
55
|
constructor(options: AuthManagerOptions);
|
|
56
|
+
/** Merge `ctx` into the current adapter context. Keys not in `ctx`
|
|
57
|
+
* are preserved. */
|
|
41
58
|
setContext(ctx: OwnsuiteContext): void;
|
|
59
|
+
/** Replace the adapter context wholesale. Callers use this when
|
|
60
|
+
* switching subjects or clearing host-app context. */
|
|
42
61
|
replaceContext(ctx: OwnsuiteContext): void;
|
|
62
|
+
/** Create a new account. Returns the server's {@link AuthTokenResult}.
|
|
63
|
+
* When the verification gate is on, the result carries
|
|
64
|
+
* `requiresVerification: true` and the session flips to `"unverified"`
|
|
65
|
+
* (no JWT). Otherwise the session flips to `"authenticated"` and the
|
|
66
|
+
* profile is hydrated from `/me`. `options.remember` pins the session's
|
|
67
|
+
* storage backend — see {@link AuthActionOptions.remember}. */
|
|
43
68
|
register(input: {
|
|
44
69
|
email: string;
|
|
45
70
|
password: string;
|
|
@@ -47,25 +72,42 @@ export declare class AuthManager {
|
|
|
47
72
|
roles?: string[];
|
|
48
73
|
extras?: Record<string, unknown>;
|
|
49
74
|
}, options?: AuthActionOptions): Promise<AuthTokenResult>;
|
|
75
|
+
/** Exchange credentials for a JWT. On success the session flips to
|
|
76
|
+
* `"authenticated"` and the profile is hydrated from `/me`. Rejects
|
|
77
|
+
* on bad credentials (session untouched) or — when the server's
|
|
78
|
+
* verification gate is on for an unverified account — throws through
|
|
79
|
+
* the adapter. `options.remember` controls storage persistence. */
|
|
50
80
|
login(input: {
|
|
51
81
|
email: string;
|
|
52
82
|
password: string;
|
|
53
83
|
}, options?: AuthActionOptions): Promise<AuthTokenResult>;
|
|
84
|
+
/** Log out. Best-effort server revoke + unconditional local clear;
|
|
85
|
+
* the session is always wiped even if the server call fails. Fires
|
|
86
|
+
* `auth:logout` and the `onIdentityChanged` hook. Safe to call when
|
|
87
|
+
* already anonymous. */
|
|
54
88
|
logout(): Promise<void>;
|
|
89
|
+
/** Trigger a fresh verification email. Anti-enumeration: always resolves
|
|
90
|
+
* regardless of whether the address corresponds to a real account. */
|
|
55
91
|
resendVerification(input: {
|
|
56
92
|
email: string;
|
|
57
93
|
lang?: string;
|
|
58
94
|
}): Promise<void>;
|
|
95
|
+
/** Trigger a password-reset email. Anti-enumeration: always resolves. */
|
|
59
96
|
requestPasswordReset(input: {
|
|
60
97
|
email: string;
|
|
61
98
|
lang?: string;
|
|
62
99
|
}): Promise<void>;
|
|
100
|
+
/** Change the password. Pass `current_password` for an authenticated
|
|
101
|
+
* self-change, or `token` for a reset-link flow. `new_password` and
|
|
102
|
+
* `confirm_password` are always required. */
|
|
63
103
|
changePassword(input: {
|
|
64
104
|
current_password?: string;
|
|
65
105
|
new_password: string;
|
|
66
106
|
confirm_password: string;
|
|
67
107
|
token?: string;
|
|
68
108
|
}): Promise<void>;
|
|
109
|
+
/** Irreversibly delete the authenticated account. On success clears the
|
|
110
|
+
* local session and fires `auth:logout` + `onIdentityChanged`. */
|
|
69
111
|
deleteAccount(input: {
|
|
70
112
|
password?: string;
|
|
71
113
|
confirm?: boolean;
|
package/dist/domains/auth.js
CHANGED
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { createPubSub } from "@marianmeres/pubsub";
|
|
15
15
|
import { openOAuthPopup } from "../oauth/popup.js";
|
|
16
|
+
/**
|
|
17
|
+
* Verbs for the account lifecycle: register / login / logout / OAuth /
|
|
18
|
+
* password-reset / delete account. Holds no state of its own — every
|
|
19
|
+
* outcome is piped into the linked {@link SessionManager}, which is the
|
|
20
|
+
* single source of truth for JWT + subject + status. Usually accessed via
|
|
21
|
+
* `suite.auth`, not constructed directly.
|
|
22
|
+
*/
|
|
16
23
|
export class AuthManager {
|
|
17
24
|
#pubsub;
|
|
18
25
|
#adapter;
|
|
@@ -20,6 +27,8 @@ export class AuthManager {
|
|
|
20
27
|
#profile;
|
|
21
28
|
#onIdentityChanged;
|
|
22
29
|
#context;
|
|
30
|
+
/** Construct a new `AuthManager`. Normally called by `createOwnsuite`,
|
|
31
|
+
* not by consumers. */
|
|
23
32
|
constructor(options) {
|
|
24
33
|
this.#adapter = options.adapter;
|
|
25
34
|
this.#session = options.session;
|
|
@@ -28,9 +37,13 @@ export class AuthManager {
|
|
|
28
37
|
this.#onIdentityChanged = options.onIdentityChanged;
|
|
29
38
|
this.#context = options.context ?? {};
|
|
30
39
|
}
|
|
40
|
+
/** Merge `ctx` into the current adapter context. Keys not in `ctx`
|
|
41
|
+
* are preserved. */
|
|
31
42
|
setContext(ctx) {
|
|
32
43
|
this.#context = { ...this.#context, ...ctx };
|
|
33
44
|
}
|
|
45
|
+
/** Replace the adapter context wholesale. Callers use this when
|
|
46
|
+
* switching subjects or clearing host-app context. */
|
|
34
47
|
replaceContext(ctx) {
|
|
35
48
|
this.#context = { ...ctx };
|
|
36
49
|
}
|
|
@@ -101,6 +114,12 @@ export class AuthManager {
|
|
|
101
114
|
return result;
|
|
102
115
|
}
|
|
103
116
|
// ─────────────────────── verbs ──────────────────────────────────────────
|
|
117
|
+
/** Create a new account. Returns the server's {@link AuthTokenResult}.
|
|
118
|
+
* When the verification gate is on, the result carries
|
|
119
|
+
* `requiresVerification: true` and the session flips to `"unverified"`
|
|
120
|
+
* (no JWT). Otherwise the session flips to `"authenticated"` and the
|
|
121
|
+
* profile is hydrated from `/me`. `options.remember` pins the session's
|
|
122
|
+
* storage backend — see {@link AuthActionOptions.remember}. */
|
|
104
123
|
async register(input, options) {
|
|
105
124
|
const result = await this.#adapter.register(input, this.#ctx());
|
|
106
125
|
this.#pubsub.publish("auth:register", {
|
|
@@ -111,6 +130,11 @@ export class AuthManager {
|
|
|
111
130
|
});
|
|
112
131
|
return await this.#applyAuthResult(result, options?.remember);
|
|
113
132
|
}
|
|
133
|
+
/** Exchange credentials for a JWT. On success the session flips to
|
|
134
|
+
* `"authenticated"` and the profile is hydrated from `/me`. Rejects
|
|
135
|
+
* on bad credentials (session untouched) or — when the server's
|
|
136
|
+
* verification gate is on for an unverified account — throws through
|
|
137
|
+
* the adapter. `options.remember` controls storage persistence. */
|
|
114
138
|
async login(input, options) {
|
|
115
139
|
const result = await this.#adapter.login(input, this.#ctx());
|
|
116
140
|
this.#pubsub.publish("auth:login", {
|
|
@@ -120,6 +144,10 @@ export class AuthManager {
|
|
|
120
144
|
});
|
|
121
145
|
return await this.#applyAuthResult(result, options?.remember);
|
|
122
146
|
}
|
|
147
|
+
/** Log out. Best-effort server revoke + unconditional local clear;
|
|
148
|
+
* the session is always wiped even if the server call fails. Fires
|
|
149
|
+
* `auth:logout` and the `onIdentityChanged` hook. Safe to call when
|
|
150
|
+
* already anonymous. */
|
|
123
151
|
async logout() {
|
|
124
152
|
const subjectId = this.#session.get().subject?.id;
|
|
125
153
|
try {
|
|
@@ -143,15 +171,23 @@ export class AuthManager {
|
|
|
143
171
|
}
|
|
144
172
|
}
|
|
145
173
|
}
|
|
174
|
+
/** Trigger a fresh verification email. Anti-enumeration: always resolves
|
|
175
|
+
* regardless of whether the address corresponds to a real account. */
|
|
146
176
|
async resendVerification(input) {
|
|
147
177
|
await this.#adapter.resendVerification(input, this.#ctx());
|
|
148
178
|
}
|
|
179
|
+
/** Trigger a password-reset email. Anti-enumeration: always resolves. */
|
|
149
180
|
async requestPasswordReset(input) {
|
|
150
181
|
await this.#adapter.requestPasswordReset(input, this.#ctx());
|
|
151
182
|
}
|
|
183
|
+
/** Change the password. Pass `current_password` for an authenticated
|
|
184
|
+
* self-change, or `token` for a reset-link flow. `new_password` and
|
|
185
|
+
* `confirm_password` are always required. */
|
|
152
186
|
async changePassword(input) {
|
|
153
187
|
await this.#adapter.changePassword(input, this.#ctx());
|
|
154
188
|
}
|
|
189
|
+
/** Irreversibly delete the authenticated account. On success clears the
|
|
190
|
+
* local session and fires `auth:logout` + `onIdentityChanged`. */
|
|
155
191
|
async deleteAccount(input) {
|
|
156
192
|
await this.#adapter.deleteAccount(input, this.#ctx());
|
|
157
193
|
// Clear session locally and fire logout/identity-change.
|
package/dist/domains/base.d.ts
CHANGED
|
@@ -27,12 +27,24 @@ export interface BaseDomainOptions {
|
|
|
27
27
|
*/
|
|
28
28
|
export declare abstract class BaseDomainManager<TData, TAdapter> {
|
|
29
29
|
#private;
|
|
30
|
+
/** Reactive store holding the full {@link DomainStateWrapper} for this
|
|
31
|
+
* domain. Subclasses read + publish through it. */
|
|
30
32
|
protected readonly store: StoreLike<DomainStateWrapper<TData>>;
|
|
33
|
+
/** Shared pubsub used to emit domain lifecycle / CRUD events. */
|
|
31
34
|
protected readonly pubsub: PubSub;
|
|
35
|
+
/** Stable name of this domain (used as event `domain` and log prefix). */
|
|
32
36
|
protected readonly domainName: DomainName;
|
|
37
|
+
/** Scoped logger, prefixed with `ownsuite:<domainName>`. */
|
|
33
38
|
protected readonly clog: Clog;
|
|
39
|
+
/** Server adapter instance; `null` until `setAdapter()` / constructor
|
|
40
|
+
* installs one. */
|
|
34
41
|
protected adapter: TAdapter | null;
|
|
42
|
+
/** Current context forwarded to every adapter call (jwt, subjectId,
|
|
43
|
+
* signal, plus any consumer-supplied extras). */
|
|
35
44
|
protected context: OwnsuiteContext;
|
|
45
|
+
/** Construct a new domain manager. Not called directly — subclasses like
|
|
46
|
+
* {@link OwnedCollectionManager} extend this and are instantiated by
|
|
47
|
+
* the suite. */
|
|
36
48
|
constructor(domainName: DomainName, options?: BaseDomainOptions);
|
|
37
49
|
/** Svelte-compatible subscribe method. */
|
|
38
50
|
get subscribe(): StoreLike<DomainStateWrapper<TData>>["subscribe"];
|
|
@@ -40,7 +52,11 @@ export declare abstract class BaseDomainManager<TData, TAdapter> {
|
|
|
40
52
|
get(): DomainStateWrapper<TData>;
|
|
41
53
|
/** True after `destroy()` has been called. */
|
|
42
54
|
get isDestroyed(): boolean;
|
|
55
|
+
/** Install or replace the server adapter for this domain. Call
|
|
56
|
+
* {@link refresh} afterwards to pick up data from the new source. */
|
|
43
57
|
setAdapter(adapter: TAdapter): void;
|
|
58
|
+
/** Current adapter, or `null` when none installed (typically only in
|
|
59
|
+
* tests or between `destroy()` and re-attachment). */
|
|
44
60
|
getAdapter(): TAdapter | null;
|
|
45
61
|
/**
|
|
46
62
|
* Merge `ctx` into the current context. Keys not present in `ctx` are
|
|
@@ -49,6 +65,8 @@ export declare abstract class BaseDomainManager<TData, TAdapter> {
|
|
|
49
65
|
setContext(context: OwnsuiteContext): void;
|
|
50
66
|
/** Replace the context object entirely (no merge with existing). */
|
|
51
67
|
replaceContext(context: OwnsuiteContext): void;
|
|
68
|
+
/** Snapshot of the current context. Mutating the returned object does
|
|
69
|
+
* not affect the manager. */
|
|
52
70
|
getContext(): OwnsuiteContext;
|
|
53
71
|
/** Transition to a new state. */
|
|
54
72
|
protected setState(state: DomainState): void;
|
|
@@ -101,6 +119,8 @@ export declare abstract class BaseDomainManager<TData, TAdapter> {
|
|
|
101
119
|
* callers that prefer a whole-data restore over per-change inversion.
|
|
102
120
|
*/
|
|
103
121
|
protected withOptimisticUpdate<T>(operation: string, optimisticUpdate: () => void, serverSync: () => Promise<T>, onSuccess?: (result: T) => void, onError?: (error: DomainError, snapshot: TData | null) => void): Promise<void>;
|
|
122
|
+
/** Boot the domain — typically fetch its initial list. Subclasses
|
|
123
|
+
* implement this; see {@link OwnedCollectionManager.initialize}. */
|
|
104
124
|
abstract initialize(): Promise<void>;
|
|
105
125
|
/**
|
|
106
126
|
* Reset to `initializing` state. Aborts any in-flight reads/mutations
|
package/dist/domains/base.js
CHANGED
|
@@ -38,11 +38,20 @@ function safeClone(value) {
|
|
|
38
38
|
* @typeParam TAdapter - The adapter interface type for server communication.
|
|
39
39
|
*/
|
|
40
40
|
export class BaseDomainManager {
|
|
41
|
+
/** Reactive store holding the full {@link DomainStateWrapper} for this
|
|
42
|
+
* domain. Subclasses read + publish through it. */
|
|
41
43
|
store;
|
|
44
|
+
/** Shared pubsub used to emit domain lifecycle / CRUD events. */
|
|
42
45
|
pubsub;
|
|
46
|
+
/** Stable name of this domain (used as event `domain` and log prefix). */
|
|
43
47
|
domainName;
|
|
48
|
+
/** Scoped logger, prefixed with `ownsuite:<domainName>`. */
|
|
44
49
|
clog;
|
|
50
|
+
/** Server adapter instance; `null` until `setAdapter()` / constructor
|
|
51
|
+
* installs one. */
|
|
45
52
|
adapter = null;
|
|
53
|
+
/** Current context forwarded to every adapter call (jwt, subjectId,
|
|
54
|
+
* signal, plus any consumer-supplied extras). */
|
|
46
55
|
context = {};
|
|
47
56
|
/** Mutation chain head. Each create/update/delete appends itself here. */
|
|
48
57
|
#mutationChain = Promise.resolve();
|
|
@@ -51,6 +60,9 @@ export class BaseDomainManager {
|
|
|
51
60
|
/** All active controllers created via `newController()`, for bulk abort. */
|
|
52
61
|
#activeControllers = new Set();
|
|
53
62
|
#destroyed = false;
|
|
63
|
+
/** Construct a new domain manager. Not called directly — subclasses like
|
|
64
|
+
* {@link OwnedCollectionManager} extend this and are instantiated by
|
|
65
|
+
* the suite. */
|
|
54
66
|
constructor(domainName, options = {}) {
|
|
55
67
|
this.domainName = domainName;
|
|
56
68
|
this.clog = createClog(`ownsuite:${domainName}`, { color: "auto" });
|
|
@@ -76,9 +88,13 @@ export class BaseDomainManager {
|
|
|
76
88
|
get isDestroyed() {
|
|
77
89
|
return this.#destroyed;
|
|
78
90
|
}
|
|
91
|
+
/** Install or replace the server adapter for this domain. Call
|
|
92
|
+
* {@link refresh} afterwards to pick up data from the new source. */
|
|
79
93
|
setAdapter(adapter) {
|
|
80
94
|
this.adapter = adapter;
|
|
81
95
|
}
|
|
96
|
+
/** Current adapter, or `null` when none installed (typically only in
|
|
97
|
+
* tests or between `destroy()` and re-attachment). */
|
|
82
98
|
getAdapter() {
|
|
83
99
|
return this.adapter;
|
|
84
100
|
}
|
|
@@ -93,6 +109,8 @@ export class BaseDomainManager {
|
|
|
93
109
|
replaceContext(context) {
|
|
94
110
|
this.context = { ...context };
|
|
95
111
|
}
|
|
112
|
+
/** Snapshot of the current context. Mutating the returned object does
|
|
113
|
+
* not affect the manager. */
|
|
96
114
|
getContext() {
|
|
97
115
|
return { ...this.context };
|
|
98
116
|
}
|
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
import type { OwnedCollectionAdapter } from "../types/adapter.js";
|
|
17
17
|
import type { OwnedCollectionState } from "../types/state.js";
|
|
18
18
|
import { BaseDomainManager, type BaseDomainOptions } from "./base.js";
|
|
19
|
+
/** Construction-time options for {@link OwnedCollectionManager}. */
|
|
19
20
|
export interface OwnedCollectionManagerOptions<TRow, TCreate, TUpdate> extends BaseDomainOptions {
|
|
21
|
+
/** Server adapter for this domain. Optional at construction — can be
|
|
22
|
+
* installed later with `setAdapter()`. */
|
|
20
23
|
adapter?: OwnedCollectionAdapter<TRow, TCreate, TUpdate>;
|
|
21
24
|
/** Function that extracts the row id from a row. Defaults to `row.model_id`. */
|
|
22
25
|
getRowId?: (row: TRow) => string;
|
|
@@ -30,6 +33,8 @@ export interface OwnedCollectionManagerOptions<TRow, TCreate, TUpdate> extends B
|
|
|
30
33
|
*/
|
|
31
34
|
export declare class OwnedCollectionManager<TRow = Record<string, unknown>, TCreate = Partial<TRow>, TUpdate = Partial<TRow>> extends BaseDomainManager<OwnedCollectionState<TRow>, OwnedCollectionAdapter<TRow, TCreate, TUpdate>> {
|
|
32
35
|
#private;
|
|
36
|
+
/** Build a new manager. Normally called by `Ownsuite.registerDomain`,
|
|
37
|
+
* not consumers. */
|
|
33
38
|
constructor(domainName: string, options?: OwnedCollectionManagerOptions<TRow, TCreate, TUpdate>);
|
|
34
39
|
/** Initialize by fetching the list from the server. */
|
|
35
40
|
initialize(): Promise<void>;
|
|
@@ -32,6 +32,8 @@ const defaultGetRowId = (row) => {
|
|
|
32
32
|
*/
|
|
33
33
|
export class OwnedCollectionManager extends BaseDomainManager {
|
|
34
34
|
#getRowId;
|
|
35
|
+
/** Build a new manager. Normally called by `Ownsuite.registerDomain`,
|
|
36
|
+
* not consumers. */
|
|
35
37
|
constructor(domainName, options = {}) {
|
|
36
38
|
super(domainName, options);
|
|
37
39
|
this.#getRowId = options.getRowId ?? defaultGetRowId;
|
|
@@ -20,8 +20,14 @@ import { type StoreLike } from "@marianmeres/store";
|
|
|
20
20
|
import { type PubSub } from "@marianmeres/pubsub";
|
|
21
21
|
import type { OAuthConnection, OAuthProvider, OwnsuiteContext, ProfileAdapter, ProfileResult } from "../types/mod.js";
|
|
22
22
|
import type { SessionManager } from "./session.js";
|
|
23
|
+
/** Construction-time options for {@link ProfileManager}. Assembled by
|
|
24
|
+
* `createOwnsuite` when a profile adapter is supplied. */
|
|
23
25
|
export interface ProfileManagerOptions {
|
|
26
|
+
/** Profile adapter — talks to `/me`. */
|
|
24
27
|
adapter: ProfileAdapter;
|
|
28
|
+
/** Session manager — used to patch the subject in place on fetch /
|
|
29
|
+
* update so consumers reading the session see updates without a second
|
|
30
|
+
* subscription. */
|
|
25
31
|
session: SessionManager;
|
|
26
32
|
/** Shared pubsub for event emission. Private if omitted. */
|
|
27
33
|
pubsub?: PubSub;
|
|
@@ -29,6 +35,7 @@ export interface ProfileManagerOptions {
|
|
|
29
35
|
* `jwt` and `signal` by the manager. */
|
|
30
36
|
context?: OwnsuiteContext;
|
|
31
37
|
}
|
|
38
|
+
/** Reactive state exposed by {@link ProfileManager.get} / `subscribe`. */
|
|
32
39
|
export interface ProfileState {
|
|
33
40
|
/** Null until the first successful fetch. */
|
|
34
41
|
profile: ProfileResult | null;
|
|
@@ -42,10 +49,15 @@ export interface ProfileState {
|
|
|
42
49
|
*/
|
|
43
50
|
export declare class ProfileManager {
|
|
44
51
|
#private;
|
|
52
|
+
/** Build a new profile manager. Normally called by `createOwnsuite`. */
|
|
45
53
|
constructor(options: ProfileManagerOptions);
|
|
54
|
+
/** Svelte-compatible subscribe method over {@link ProfileState}. */
|
|
46
55
|
get subscribe(): StoreLike<ProfileState>["subscribe"];
|
|
56
|
+
/** Current profile state snapshot. */
|
|
47
57
|
get(): ProfileState;
|
|
58
|
+
/** Merge `ctx` into the adapter context (keys not present are kept). */
|
|
48
59
|
setContext(ctx: OwnsuiteContext): void;
|
|
60
|
+
/** Replace the adapter context wholesale. */
|
|
49
61
|
replaceContext(ctx: OwnsuiteContext): void;
|
|
50
62
|
/** Fetch `/me`. Supersedes any in-flight fetch. */
|
|
51
63
|
fetch(): Promise<ProfileResult>;
|
|
@@ -55,8 +67,14 @@ export declare class ProfileManager {
|
|
|
55
67
|
email?: string;
|
|
56
68
|
current_password?: string;
|
|
57
69
|
}): Promise<ProfileResult>;
|
|
70
|
+
/** List OAuth provider connections linked to the authenticated account. */
|
|
58
71
|
listOAuth(): Promise<OAuthConnection[]>;
|
|
72
|
+
/** Unlink an OAuth provider. Emits `oauth:unlinked` and best-effort
|
|
73
|
+
* re-fetches the profile so the connection list reflects the change. */
|
|
59
74
|
unlinkOAuth(provider: OAuthProvider): Promise<void>;
|
|
75
|
+
/** Abort any in-flight fetch and clear cached profile state. */
|
|
60
76
|
reset(): void;
|
|
77
|
+
/** Tear down the manager — aborts in-flight fetches and clears state.
|
|
78
|
+
* Called by `Ownsuite.destroy()`. */
|
|
61
79
|
destroy(): void;
|
|
62
80
|
}
|
package/dist/domains/profile.js
CHANGED
|
@@ -34,6 +34,7 @@ export class ProfileManager {
|
|
|
34
34
|
#context;
|
|
35
35
|
/** Currently-active read controller, for abort-supersede semantics. */
|
|
36
36
|
#readController = null;
|
|
37
|
+
/** Build a new profile manager. Normally called by `createOwnsuite`. */
|
|
37
38
|
constructor(options) {
|
|
38
39
|
this.#adapter = options.adapter;
|
|
39
40
|
this.#session = options.session;
|
|
@@ -41,15 +42,19 @@ export class ProfileManager {
|
|
|
41
42
|
this.#context = options.context ?? {};
|
|
42
43
|
this.#store = createStore({ ...EMPTY });
|
|
43
44
|
}
|
|
45
|
+
/** Svelte-compatible subscribe method over {@link ProfileState}. */
|
|
44
46
|
get subscribe() {
|
|
45
47
|
return this.#store.subscribe;
|
|
46
48
|
}
|
|
49
|
+
/** Current profile state snapshot. */
|
|
47
50
|
get() {
|
|
48
51
|
return this.#store.get();
|
|
49
52
|
}
|
|
53
|
+
/** Merge `ctx` into the adapter context (keys not present are kept). */
|
|
50
54
|
setContext(ctx) {
|
|
51
55
|
this.#context = { ...this.#context, ...ctx };
|
|
52
56
|
}
|
|
57
|
+
/** Replace the adapter context wholesale. */
|
|
53
58
|
replaceContext(ctx) {
|
|
54
59
|
this.#context = { ...ctx };
|
|
55
60
|
}
|
|
@@ -139,10 +144,13 @@ export class ProfileManager {
|
|
|
139
144
|
throw e;
|
|
140
145
|
}
|
|
141
146
|
}
|
|
147
|
+
/** List OAuth provider connections linked to the authenticated account. */
|
|
142
148
|
async listOAuth() {
|
|
143
149
|
const ctrl = new AbortController();
|
|
144
150
|
return await this.#adapter.listOAuth(this.#ctxFor(ctrl.signal));
|
|
145
151
|
}
|
|
152
|
+
/** Unlink an OAuth provider. Emits `oauth:unlinked` and best-effort
|
|
153
|
+
* re-fetches the profile so the connection list reflects the change. */
|
|
146
154
|
async unlinkOAuth(provider) {
|
|
147
155
|
const ctrl = new AbortController();
|
|
148
156
|
await this.#adapter.unlinkOAuth(provider, this.#ctxFor(ctrl.signal));
|
|
@@ -159,10 +167,13 @@ export class ProfileManager {
|
|
|
159
167
|
// swallow — caller already got a successful unlink
|
|
160
168
|
}
|
|
161
169
|
}
|
|
170
|
+
/** Abort any in-flight fetch and clear cached profile state. */
|
|
162
171
|
reset() {
|
|
163
172
|
this.#abortActiveRead("reset");
|
|
164
173
|
this.#store.set({ ...EMPTY });
|
|
165
174
|
}
|
|
175
|
+
/** Tear down the manager — aborts in-flight fetches and clears state.
|
|
176
|
+
* Called by `Ownsuite.destroy()`. */
|
|
166
177
|
destroy() {
|
|
167
178
|
this.#abortActiveRead("destroyed");
|
|
168
179
|
this.#store.set({ ...EMPTY });
|
|
@@ -22,6 +22,7 @@ export declare function createMemorySessionStorage(): SessionStorage;
|
|
|
22
22
|
* Gracefully falls back to memory storage when running in an environment
|
|
23
23
|
* without Web Storage (SSR, worker without storage, etc). */
|
|
24
24
|
export declare function resolveSessionStorage(type?: SessionStorageType): SessionStorage;
|
|
25
|
+
/** Construction-time options for {@link SessionManager}. */
|
|
25
26
|
export interface SessionManagerOptions {
|
|
26
27
|
/** Storage backend for session persistence. Default: "local". */
|
|
27
28
|
storage?: SessionStorageType;
|
|
@@ -45,13 +46,21 @@ export interface SessionManagerOptions {
|
|
|
45
46
|
*/
|
|
46
47
|
export declare class SessionManager {
|
|
47
48
|
#private;
|
|
49
|
+
/** Build a new session manager. Hydrates synchronously from storage
|
|
50
|
+
* (probing `local → session → memory` in the built-in case) before the
|
|
51
|
+
* constructor returns, so consumers can read `get()` immediately. */
|
|
48
52
|
constructor(options?: SessionManagerOptions);
|
|
49
53
|
/** Svelte-compatible subscribe. */
|
|
50
54
|
get subscribe(): StoreLike<SessionState>["subscribe"];
|
|
51
55
|
/** Current session state snapshot. */
|
|
52
56
|
get(): SessionState;
|
|
57
|
+
/** True when `status === "authenticated"`. Shorthand for `get().status`
|
|
58
|
+
* checks in consumer code. */
|
|
53
59
|
get isAuthenticated(): boolean;
|
|
60
|
+
/** True when `status === "unverified"` — account exists but the server
|
|
61
|
+
* gates login behind email verification. */
|
|
54
62
|
get isUnverified(): boolean;
|
|
63
|
+
/** True when `status === "anonymous"` — no session. */
|
|
55
64
|
get isAnonymous(): boolean;
|
|
56
65
|
/** JWT for adapter `Authorization` headers, or null when anonymous. */
|
|
57
66
|
getJwt(): string | null;
|
package/dist/domains/session.js
CHANGED
|
@@ -109,6 +109,9 @@ export class SessionManager {
|
|
|
109
109
|
#defaultStorageType;
|
|
110
110
|
#activeStorage;
|
|
111
111
|
#activeStorageType;
|
|
112
|
+
/** Build a new session manager. Hydrates synchronously from storage
|
|
113
|
+
* (probing `local → session → memory` in the built-in case) before the
|
|
114
|
+
* constructor returns, so consumers can read `get()` immediately. */
|
|
112
115
|
constructor(options = {}) {
|
|
113
116
|
this.#pubsub = options.pubsub ?? createPubSub();
|
|
114
117
|
this.#storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
@@ -212,12 +215,17 @@ export class SessionManager {
|
|
|
212
215
|
get() {
|
|
213
216
|
return this.#store.get();
|
|
214
217
|
}
|
|
218
|
+
/** True when `status === "authenticated"`. Shorthand for `get().status`
|
|
219
|
+
* checks in consumer code. */
|
|
215
220
|
get isAuthenticated() {
|
|
216
221
|
return this.#store.get().status === "authenticated";
|
|
217
222
|
}
|
|
223
|
+
/** True when `status === "unverified"` — account exists but the server
|
|
224
|
+
* gates login behind email verification. */
|
|
218
225
|
get isUnverified() {
|
|
219
226
|
return this.#store.get().status === "unverified";
|
|
220
227
|
}
|
|
228
|
+
/** True when `status === "anonymous"` — no session. */
|
|
221
229
|
get isAnonymous() {
|
|
222
230
|
return this.#store.get().status === "anonymous";
|
|
223
231
|
}
|
package/dist/mod.d.ts
CHANGED
|
@@ -30,3 +30,6 @@ export * from "./domains/mod.js";
|
|
|
30
30
|
export * from "./types/mod.js";
|
|
31
31
|
export * from "./adapters/mod.js";
|
|
32
32
|
export * from "./oauth/popup.js";
|
|
33
|
+
export type { PubSub, Subscriber, Unsubscriber } from "@marianmeres/pubsub";
|
|
34
|
+
export type { StoreLike } from "@marianmeres/store";
|
|
35
|
+
export type { Clog } from "@marianmeres/clog";
|
package/dist/oauth/popup.d.ts
CHANGED
|
@@ -11,37 +11,62 @@
|
|
|
11
11
|
* For tests, an injectable window-like interface lets us shim `postMessage`
|
|
12
12
|
* via a MessageChannel without a real browser.
|
|
13
13
|
*/
|
|
14
|
+
/** `postMessage` shape the server's OAuth callback posts on successful
|
|
15
|
+
* login (`action: "login"`). */
|
|
14
16
|
export interface OAuthPopupLoginMessage {
|
|
17
|
+
/** Discriminator. */
|
|
15
18
|
type: "oauth_login_success";
|
|
19
|
+
/** Freshly minted JWT. */
|
|
16
20
|
jwt: string;
|
|
21
|
+
/** Email on the account (after provider-email resolution). */
|
|
17
22
|
email: string;
|
|
23
|
+
/** Roles assigned to the account. */
|
|
18
24
|
roles?: string[];
|
|
25
|
+
/** True when the account was created as part of this flow. */
|
|
19
26
|
isNewAccount?: boolean;
|
|
27
|
+
/** Optional post-login redirect hint the server proposes. */
|
|
20
28
|
redirectUrl?: string;
|
|
21
29
|
}
|
|
30
|
+
/** `postMessage` shape the server posts on successful link
|
|
31
|
+
* (`action: "link"`). */
|
|
22
32
|
export interface OAuthPopupLinkMessage {
|
|
33
|
+
/** Discriminator. */
|
|
23
34
|
type: "oauth_link_success";
|
|
35
|
+
/** Which provider was just linked. */
|
|
24
36
|
provider: string;
|
|
25
37
|
}
|
|
38
|
+
/** `postMessage` shape the server posts on error. Rejects the promise. */
|
|
26
39
|
export interface OAuthPopupErrorMessage {
|
|
40
|
+
/** Discriminator. */
|
|
27
41
|
type: "oauth_error";
|
|
42
|
+
/** Error string suitable for surfacing to the user. */
|
|
28
43
|
error: string;
|
|
29
44
|
}
|
|
45
|
+
/** Union returned by {@link openOAuthPopup}. */
|
|
30
46
|
export type OAuthPopupMessage = OAuthPopupLoginMessage | OAuthPopupLinkMessage;
|
|
31
47
|
/**
|
|
32
48
|
* Minimal window-like surface we need. Real `Window` satisfies this; tests
|
|
33
49
|
* supply a shim.
|
|
34
50
|
*/
|
|
35
51
|
export interface PopupWindowHost {
|
|
52
|
+
/** Open a popup and return a handle, or `null` when blocked. */
|
|
36
53
|
open(url: string, target: string, features?: string): PopupWindowHandle | null;
|
|
54
|
+
/** Listen for `"message"` events emitted by the popup. */
|
|
37
55
|
addEventListener(type: "message", listener: (event: MessageEvent) => void): void;
|
|
56
|
+
/** Stop listening for `"message"` events. */
|
|
38
57
|
removeEventListener(type: "message", listener: (event: MessageEvent) => void): void;
|
|
39
58
|
}
|
|
59
|
+
/** Minimal surface of a popup window handle that {@link openOAuthPopup}
|
|
60
|
+
* interacts with after `host.open(...)`. */
|
|
40
61
|
export interface PopupWindowHandle {
|
|
62
|
+
/** Reflects whether the popup has been closed (by the user or server). */
|
|
41
63
|
closed: boolean;
|
|
64
|
+
/** Bring the popup to the front, when the host supports it. */
|
|
42
65
|
focus?(): void;
|
|
66
|
+
/** Close the popup programmatically. */
|
|
43
67
|
close?(): void;
|
|
44
68
|
}
|
|
69
|
+
/** Options for {@link openOAuthPopup}. */
|
|
45
70
|
export interface OpenOAuthPopupOptions {
|
|
46
71
|
/** Host window — defaults to `globalThis` when running in the browser. */
|
|
47
72
|
host?: PopupWindowHost;
|
package/dist/ownsuite.d.ts
CHANGED
|
@@ -25,7 +25,10 @@ import { SessionManager } from "./domains/session.js";
|
|
|
25
25
|
* (defaults to reading `row.model_id` or `row.id`).
|
|
26
26
|
*/
|
|
27
27
|
export interface OwnsuiteDomainConfig<TRow = any, TCreate = any, TUpdate = any> {
|
|
28
|
+
/** Adapter that talks to the server for this domain. */
|
|
28
29
|
adapter: OwnedCollectionAdapter<TRow, TCreate, TUpdate>;
|
|
30
|
+
/** Row-id extractor — used for optimistic update / delete matching.
|
|
31
|
+
* Defaults to reading `row.model_id` or `row.id`. */
|
|
29
32
|
getRowId?: (row: TRow) => string;
|
|
30
33
|
}
|
|
31
34
|
/** Top-level ownsuite configuration. */
|
|
@@ -76,11 +79,18 @@ export interface SetContextOptions {
|
|
|
76
79
|
*/
|
|
77
80
|
export declare class Ownsuite {
|
|
78
81
|
#private;
|
|
79
|
-
/**
|
|
80
|
-
*
|
|
82
|
+
/** Session manager. Present iff `adapters.auth` was supplied at
|
|
83
|
+
* construction; otherwise `null`. */
|
|
81
84
|
readonly session: SessionManager | null;
|
|
85
|
+
/** Auth manager (register/login/OAuth verbs). Present iff
|
|
86
|
+
* `adapters.auth` was supplied; otherwise `null`. */
|
|
82
87
|
readonly auth: AuthManager | null;
|
|
88
|
+
/** Profile manager for `/me`. Present iff BOTH `adapters.auth` and
|
|
89
|
+
* `adapters.profile` were supplied; otherwise `null`. */
|
|
83
90
|
readonly profile: ProfileManager | null;
|
|
91
|
+
/** Build a new suite. See {@link OwnsuiteConfig} for every knob.
|
|
92
|
+
* Account-lifecycle managers are attached only when `adapters.auth`
|
|
93
|
+
* is supplied. */
|
|
84
94
|
constructor(config?: OwnsuiteConfig);
|
|
85
95
|
/** True after `destroy()` has been called. */
|
|
86
96
|
get isDestroyed(): boolean;
|
|
@@ -109,6 +119,7 @@ export declare class Ownsuite {
|
|
|
109
119
|
* linger when, e.g., `subjectId` changes).
|
|
110
120
|
*/
|
|
111
121
|
setContext(ctx: OwnsuiteContext, options?: SetContextOptions): void;
|
|
122
|
+
/** Snapshot of the shared context propagated to every domain adapter. */
|
|
112
123
|
getContext(): OwnsuiteContext;
|
|
113
124
|
/** Subscribe to a specific event type. */
|
|
114
125
|
on(type: OwnsuiteEventType, subscriber: Subscriber): Unsubscriber;
|
package/dist/ownsuite.js
CHANGED
|
@@ -41,11 +41,18 @@ export class Ownsuite {
|
|
|
41
41
|
// deno-lint-ignore no-explicit-any
|
|
42
42
|
#domains = new Map();
|
|
43
43
|
#destroyed = false;
|
|
44
|
-
/**
|
|
45
|
-
*
|
|
44
|
+
/** Session manager. Present iff `adapters.auth` was supplied at
|
|
45
|
+
* construction; otherwise `null`. */
|
|
46
46
|
session = null;
|
|
47
|
+
/** Auth manager (register/login/OAuth verbs). Present iff
|
|
48
|
+
* `adapters.auth` was supplied; otherwise `null`. */
|
|
47
49
|
auth = null;
|
|
50
|
+
/** Profile manager for `/me`. Present iff BOTH `adapters.auth` and
|
|
51
|
+
* `adapters.profile` were supplied; otherwise `null`. */
|
|
48
52
|
profile = null;
|
|
53
|
+
/** Build a new suite. See {@link OwnsuiteConfig} for every knob.
|
|
54
|
+
* Account-lifecycle managers are attached only when `adapters.auth`
|
|
55
|
+
* is supplied. */
|
|
49
56
|
constructor(config = {}) {
|
|
50
57
|
this.#pubsub = createPubSub();
|
|
51
58
|
this.#context = { ...(config.context ?? {}) };
|
|
@@ -221,6 +228,7 @@ export class Ownsuite {
|
|
|
221
228
|
}
|
|
222
229
|
}
|
|
223
230
|
}
|
|
231
|
+
/** Snapshot of the shared context propagated to every domain adapter. */
|
|
224
232
|
getContext() {
|
|
225
233
|
return { ...this.#context };
|
|
226
234
|
}
|
package/dist/types/adapter.d.ts
CHANGED
|
@@ -15,7 +15,9 @@ import type { OwnsuiteContext } from "./state.js";
|
|
|
15
15
|
* whatever shape their server uses and map it here.
|
|
16
16
|
*/
|
|
17
17
|
export interface OwnedListResult<TRow> {
|
|
18
|
+
/** Rows returned by the list call. */
|
|
18
19
|
data: TRow[];
|
|
20
|
+
/** Pagination / total-count / any arbitrary server-supplied metadata. */
|
|
19
21
|
meta: Record<string, unknown>;
|
|
20
22
|
}
|
|
21
23
|
/**
|
|
@@ -23,7 +25,9 @@ export interface OwnedListResult<TRow> {
|
|
|
23
25
|
* collection package's REST envelope.
|
|
24
26
|
*/
|
|
25
27
|
export interface OwnedRowResult<TRow> {
|
|
28
|
+
/** The row. */
|
|
26
29
|
data: TRow;
|
|
30
|
+
/** Optional per-row metadata from the server. */
|
|
27
31
|
meta?: Record<string, unknown>;
|
|
28
32
|
}
|
|
29
33
|
/**
|
package/dist/types/auth.d.ts
CHANGED
|
@@ -33,17 +33,24 @@ export type SessionStatus =
|
|
|
33
33
|
* `/me` returns plus the fields needed for role-gating.
|
|
34
34
|
*/
|
|
35
35
|
export interface SessionSubject {
|
|
36
|
+
/** Server-assigned subject identifier. Empty string before `/me` lands. */
|
|
36
37
|
id: string;
|
|
38
|
+
/** Verified or unverified email address of the subject. */
|
|
37
39
|
email: string;
|
|
40
|
+
/** Authorization roles — drive UI gating, not security. */
|
|
38
41
|
roles: string[];
|
|
42
|
+
/** True when the server has confirmed the email address. */
|
|
39
43
|
isVerified: boolean;
|
|
40
44
|
/** Whether the account has a password (OAuth-only accounts do not). */
|
|
41
45
|
hasPassword: boolean;
|
|
42
46
|
}
|
|
43
47
|
/** Observable session state — stored by the SessionManager. */
|
|
44
48
|
export interface SessionState {
|
|
49
|
+
/** Current lifecycle bucket; see {@link SessionStatus}. */
|
|
45
50
|
status: SessionStatus;
|
|
51
|
+
/** Loaded subject, or `null` when anonymous. */
|
|
46
52
|
subject: SessionSubject | null;
|
|
53
|
+
/** JWT for outgoing `Authorization` headers, `null` when anonymous. */
|
|
47
54
|
jwt: string | null;
|
|
48
55
|
/** Unix-seconds expiry (optional — not every server shape returns one). */
|
|
49
56
|
expiresAt: number | null;
|
|
@@ -54,22 +61,39 @@ export interface SessionState {
|
|
|
54
61
|
* matching this interface (useful for Tauri, WebExtensions, SSR guards).
|
|
55
62
|
*/
|
|
56
63
|
export interface SessionStorage {
|
|
64
|
+
/** Read a value by key; return `null` when missing or unreadable. */
|
|
57
65
|
get(key: string): string | null;
|
|
66
|
+
/** Write a value; silently no-op on quota errors. */
|
|
58
67
|
set(key: string, value: string): void;
|
|
68
|
+
/** Delete a value; silently no-op when absent. */
|
|
59
69
|
del(key: string): void;
|
|
60
70
|
}
|
|
71
|
+
/** Accepted `storage` option for `SessionManager`. String values resolve to
|
|
72
|
+
* the three built-in backends; a {@link SessionStorage} object overrides
|
|
73
|
+
* the defaults entirely (single custom backend — per-login `remember` is
|
|
74
|
+
* silently ignored in that mode). */
|
|
61
75
|
export type SessionStorageType = "local" | "session" | "memory" | SessionStorage;
|
|
76
|
+
/** Supported OAuth provider identifiers — the server side (`@marianmeres/
|
|
77
|
+
* stack-account`) decides which are actually enabled. */
|
|
62
78
|
export type OAuthProvider = "google" | "facebook" | "apple" | "twitter";
|
|
79
|
+
/** A single linked OAuth provider connection on the subject's account. */
|
|
63
80
|
export interface OAuthConnection {
|
|
81
|
+
/** Which provider this connection represents. */
|
|
64
82
|
provider: OAuthProvider;
|
|
83
|
+
/** Human-readable display name as reported by the provider. */
|
|
65
84
|
display_name?: string;
|
|
85
|
+
/** Avatar URL reported by the provider (not proxied — may be public). */
|
|
66
86
|
avatar_url?: string;
|
|
87
|
+
/** Email reported by the provider (may differ from the account email). */
|
|
67
88
|
email?: string;
|
|
68
89
|
}
|
|
69
90
|
/** Action verb for the OAuth init URL — `login` creates/looks-up an account,
|
|
70
91
|
* `link` attaches the provider to the currently authenticated subject. */
|
|
71
92
|
export type OAuthAction = "login" | "link";
|
|
93
|
+
/** Options for starting an OAuth flow via `AuthManager.initiateOAuth`. */
|
|
72
94
|
export interface OAuthInitOptions {
|
|
95
|
+
/** Whether this flow creates/looks-up an account (`login`) or attaches
|
|
96
|
+
* the provider to the currently authenticated subject (`link`). */
|
|
73
97
|
action: OAuthAction;
|
|
74
98
|
/** Where to redirect after the provider callback (server honours this). */
|
|
75
99
|
redirect?: string;
|
|
@@ -107,33 +131,60 @@ export interface AuthActionOptions {
|
|
|
107
131
|
* manager flips session.status to "unverified".
|
|
108
132
|
*/
|
|
109
133
|
export interface AuthTokenResult {
|
|
134
|
+
/** JWT to present on subsequent authenticated requests. Absent when
|
|
135
|
+
* `requiresVerification` is `true`. */
|
|
110
136
|
jwt?: string;
|
|
137
|
+
/** Email as recognised by the server (may be normalised). */
|
|
111
138
|
email: string;
|
|
139
|
+
/** Roles granted by the server. */
|
|
112
140
|
roles: string[];
|
|
141
|
+
/** True when the server confirms the email is already verified. */
|
|
113
142
|
isVerified?: boolean;
|
|
143
|
+
/** JWT `not-before` claim in unix seconds (when server returns one). */
|
|
114
144
|
validFrom?: number;
|
|
145
|
+
/** JWT `expires-at` claim in unix seconds (when server returns one). */
|
|
115
146
|
validUntil?: number;
|
|
116
147
|
/** Set by the server when login auto-login is declined for a not-yet-
|
|
117
148
|
* verified account. Mutually exclusive with jwt in practice. */
|
|
118
149
|
requiresVerification?: boolean;
|
|
119
150
|
}
|
|
151
|
+
/** `/me` profile payload returned by `ProfileAdapter.get` / `update`. */
|
|
120
152
|
export interface ProfileResult {
|
|
153
|
+
/** Current email on the account. */
|
|
121
154
|
email: string;
|
|
155
|
+
/** Current authorization roles. */
|
|
122
156
|
roles: string[];
|
|
157
|
+
/** Whether the email is verified. */
|
|
123
158
|
isVerified: boolean;
|
|
159
|
+
/** Whether the account has a password. */
|
|
124
160
|
hasPassword: boolean;
|
|
161
|
+
/** Linked OAuth provider connections. */
|
|
125
162
|
oauthConnections: OAuthConnection[];
|
|
126
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Contract the `AuthManager` uses to talk to the server. Implement this
|
|
166
|
+
* against whatever transport the backend exposes — `createStackAccountAuth
|
|
167
|
+
* Adapter` provides the default for `@marianmeres/stack-account`. Adapters
|
|
168
|
+
* hold no state; they forward `ctx.jwt` / `ctx.signal` per call.
|
|
169
|
+
*/
|
|
127
170
|
export interface AuthAdapter {
|
|
171
|
+
/** Create an account. When the verification gate is on, the returned
|
|
172
|
+
* result sets `requiresVerification: true` and omits the JWT. */
|
|
128
173
|
register(input: {
|
|
174
|
+
/** Email as entered; server normalises + stores. */
|
|
129
175
|
email: string;
|
|
176
|
+
/** Plaintext password — server hashes. */
|
|
130
177
|
password: string;
|
|
178
|
+
/** Plaintext confirmation, server cross-checks. */
|
|
131
179
|
password_confirm: string;
|
|
180
|
+
/** Roles to request on the account (server may reject). */
|
|
132
181
|
roles?: string[];
|
|
133
182
|
/** Optional extras — consumer forwards any configured
|
|
134
183
|
* registrationFields the server expects. */
|
|
135
184
|
extras?: Record<string, unknown>;
|
|
136
185
|
}, ctx: OwnsuiteContext): Promise<AuthTokenResult>;
|
|
186
|
+
/** Exchange credentials for a JWT. Throws on bad credentials; sets
|
|
187
|
+
* `requiresVerification: true` when the verification gate is on. */
|
|
137
188
|
login(input: {
|
|
138
189
|
email: string;
|
|
139
190
|
password: string;
|
|
@@ -148,22 +199,32 @@ export interface AuthAdapter {
|
|
|
148
199
|
* URL (or a server-rendered payload) and return it. Popup-mode uses the
|
|
149
200
|
* `openOAuthPopup` helper directly and does not call this. */
|
|
150
201
|
handleOAuthCallback?(ctx: OwnsuiteContext): Promise<AuthTokenResult>;
|
|
202
|
+
/** Request a fresh verification email. Anti-enumeration: must resolve
|
|
203
|
+
* regardless of whether the address exists. */
|
|
151
204
|
resendVerification(input: {
|
|
152
205
|
email: string;
|
|
153
206
|
lang?: string;
|
|
154
207
|
}, ctx: OwnsuiteContext): Promise<void>;
|
|
208
|
+
/** Request a password-reset email. Anti-enumeration: must resolve
|
|
209
|
+
* regardless of whether the address exists. */
|
|
155
210
|
requestPasswordReset(input: {
|
|
156
211
|
email: string;
|
|
157
212
|
lang?: string;
|
|
158
213
|
}, ctx: OwnsuiteContext): Promise<void>;
|
|
214
|
+
/** Change or reset the password. Provide `current_password` for an
|
|
215
|
+
* authenticated self-change, or `token` for a reset-link flow. */
|
|
159
216
|
changePassword(input: {
|
|
160
217
|
/** Required for authenticated self-change. */
|
|
161
218
|
current_password?: string;
|
|
219
|
+
/** New password (plaintext — hashed server-side). */
|
|
162
220
|
new_password: string;
|
|
221
|
+
/** Confirmation — must match new_password. */
|
|
163
222
|
confirm_password: string;
|
|
164
223
|
/** Required for token-based reset. */
|
|
165
224
|
token?: string;
|
|
166
225
|
}, ctx: OwnsuiteContext): Promise<void>;
|
|
226
|
+
/** Irreversibly delete the authenticated account. Resolves with
|
|
227
|
+
* `{ deleted: true }` on success; any failure rejects. */
|
|
167
228
|
deleteAccount(input: {
|
|
168
229
|
password?: string;
|
|
169
230
|
confirm?: boolean;
|
|
@@ -171,12 +232,19 @@ export interface AuthAdapter {
|
|
|
171
232
|
deleted: true;
|
|
172
233
|
}>;
|
|
173
234
|
}
|
|
235
|
+
/** Contract the `ProfileManager` uses to read + mutate `/me`. Like
|
|
236
|
+
* `AuthAdapter`, stateless — reads `ctx.jwt` per call. */
|
|
174
237
|
export interface ProfileAdapter {
|
|
238
|
+
/** GET `/me` → current profile snapshot. */
|
|
175
239
|
get(ctx: OwnsuiteContext): Promise<ProfileResult>;
|
|
240
|
+
/** PUT `/me` — update email and/or confirm current password. Returns
|
|
241
|
+
* the refreshed profile. */
|
|
176
242
|
update(input: {
|
|
177
243
|
email?: string;
|
|
178
244
|
current_password?: string;
|
|
179
245
|
}, ctx: OwnsuiteContext): Promise<ProfileResult>;
|
|
246
|
+
/** GET linked OAuth connections for the authenticated subject. */
|
|
180
247
|
listOAuth(ctx: OwnsuiteContext): Promise<OAuthConnection[]>;
|
|
248
|
+
/** DELETE a linked OAuth connection. */
|
|
181
249
|
unlinkOAuth(provider: OAuthProvider, ctx: OwnsuiteContext): Promise<void>;
|
|
182
250
|
}
|
package/dist/types/events.d.ts
CHANGED
|
@@ -20,82 +20,133 @@ export interface OwnsuiteEventBase {
|
|
|
20
20
|
/** Domain that emitted the event */
|
|
21
21
|
domain: DomainName;
|
|
22
22
|
}
|
|
23
|
-
/**
|
|
23
|
+
/** Emitted when a domain transitions between lifecycle states
|
|
24
|
+
* (`initializing` → `ready` ↔ `syncing` → `error`). */
|
|
24
25
|
export interface StateChangedEvent extends OwnsuiteEventBase {
|
|
26
|
+
/** Discriminator. */
|
|
25
27
|
type: "domain:state:changed";
|
|
28
|
+
/** State before the transition. */
|
|
26
29
|
previousState: DomainState;
|
|
30
|
+
/** State after the transition. */
|
|
27
31
|
newState: DomainState;
|
|
28
32
|
}
|
|
29
|
-
/**
|
|
33
|
+
/** Emitted when a domain's mutation or read fails. */
|
|
30
34
|
export interface ErrorEvent extends OwnsuiteEventBase {
|
|
35
|
+
/** Discriminator. */
|
|
31
36
|
type: "domain:error";
|
|
37
|
+
/** The failure — normalized across list / get / mutation paths. */
|
|
32
38
|
error: DomainError;
|
|
33
39
|
}
|
|
34
|
-
/**
|
|
40
|
+
/** Emitted when a sync (initialize / refresh / mutation) completes. */
|
|
35
41
|
export interface SyncedEvent extends OwnsuiteEventBase {
|
|
42
|
+
/** Discriminator. */
|
|
36
43
|
type: "domain:synced";
|
|
37
44
|
}
|
|
38
|
-
/**
|
|
45
|
+
/** Emitted after a successful list read. */
|
|
39
46
|
export interface ListFetchedEvent extends OwnsuiteEventBase {
|
|
47
|
+
/** Discriminator. */
|
|
40
48
|
type: "own:list:fetched";
|
|
49
|
+
/** Number of rows returned. */
|
|
41
50
|
count: number;
|
|
42
51
|
}
|
|
43
|
-
/**
|
|
52
|
+
/** Emitted after a successful single-row read (`getOne`). */
|
|
44
53
|
export interface RowFetchedEvent extends OwnsuiteEventBase {
|
|
54
|
+
/** Discriminator. */
|
|
45
55
|
type: "own:row:fetched";
|
|
56
|
+
/** Id of the row that was fetched. */
|
|
46
57
|
rowId: string;
|
|
47
58
|
}
|
|
48
|
-
/**
|
|
59
|
+
/** Emitted after a row is created on the server. */
|
|
49
60
|
export interface RowCreatedEvent extends OwnsuiteEventBase {
|
|
61
|
+
/** Discriminator. */
|
|
50
62
|
type: "own:row:created";
|
|
63
|
+
/** Server-assigned id of the new row. */
|
|
51
64
|
rowId: string;
|
|
52
65
|
}
|
|
53
|
-
/**
|
|
66
|
+
/** Emitted after a row is updated on the server. */
|
|
54
67
|
export interface RowUpdatedEvent extends OwnsuiteEventBase {
|
|
68
|
+
/** Discriminator. */
|
|
55
69
|
type: "own:row:updated";
|
|
70
|
+
/** Id of the updated row. */
|
|
56
71
|
rowId: string;
|
|
57
72
|
}
|
|
58
|
-
/**
|
|
73
|
+
/** Emitted after a row is deleted on the server. */
|
|
59
74
|
export interface RowDeletedEvent extends OwnsuiteEventBase {
|
|
75
|
+
/** Discriminator. */
|
|
60
76
|
type: "own:row:deleted";
|
|
77
|
+
/** Id of the deleted row. */
|
|
61
78
|
rowId: string;
|
|
62
79
|
}
|
|
80
|
+
/** Base fields shared by every auth / profile / session event. Unlike
|
|
81
|
+
* {@link OwnsuiteEventBase} there is no `domain` — these events live at the
|
|
82
|
+
* suite level, not a specific domain. */
|
|
63
83
|
export interface AuthEventBase {
|
|
84
|
+
/** Unix millis at emission time. */
|
|
64
85
|
timestamp: number;
|
|
65
86
|
}
|
|
87
|
+
/** Emitted after `AuthManager.register` returns a server response. Fires
|
|
88
|
+
* whether or not the verification gate was hit. */
|
|
66
89
|
export interface AuthRegisterEvent extends AuthEventBase {
|
|
90
|
+
/** Discriminator. */
|
|
67
91
|
type: "auth:register";
|
|
92
|
+
/** Email the account was created with. */
|
|
68
93
|
email: string;
|
|
69
94
|
/** True when the server requires email verification (no auto-login). */
|
|
70
95
|
requiresVerification: boolean;
|
|
71
96
|
}
|
|
97
|
+
/** Emitted after `AuthManager.login` completes a credential exchange. Does
|
|
98
|
+
* not imply `status === "authenticated"` — the verification gate may still
|
|
99
|
+
* flip it to `"unverified"` afterwards. */
|
|
72
100
|
export interface AuthLoginEvent extends AuthEventBase {
|
|
101
|
+
/** Discriminator. */
|
|
73
102
|
type: "auth:login";
|
|
103
|
+
/** Email used to log in. */
|
|
74
104
|
email: string;
|
|
75
105
|
}
|
|
106
|
+
/** Emitted on `AuthManager.logout` and `deleteAccount` once the session has
|
|
107
|
+
* been cleared locally. */
|
|
76
108
|
export interface AuthLogoutEvent extends AuthEventBase {
|
|
109
|
+
/** Discriminator. */
|
|
77
110
|
type: "auth:logout";
|
|
78
111
|
/** Id of the subject that just logged out, if known. */
|
|
79
112
|
subjectId?: string;
|
|
80
113
|
}
|
|
114
|
+
/** Emitted whenever `SessionManager` persists a new state — including
|
|
115
|
+
* hydration-driven changes and `patchSubject` writes. */
|
|
81
116
|
export interface AuthSessionChangedEvent extends AuthEventBase {
|
|
117
|
+
/** Discriminator. */
|
|
82
118
|
type: "auth:session:changed";
|
|
119
|
+
/** Snapshot of the session *after* the change. */
|
|
83
120
|
session: SessionState;
|
|
84
121
|
}
|
|
122
|
+
/** Emitted when the server rejects login/register because the account is
|
|
123
|
+
* not yet verified. UI should surface a "check your inbox" prompt. */
|
|
85
124
|
export interface AuthVerificationRequiredEvent extends AuthEventBase {
|
|
125
|
+
/** Discriminator. */
|
|
86
126
|
type: "auth:verification:required";
|
|
127
|
+
/** Email that needs to be verified. */
|
|
87
128
|
email: string;
|
|
88
129
|
}
|
|
130
|
+
/** Emitted after `ProfileManager.update` succeeds. The session subject has
|
|
131
|
+
* already been patched in place by the time this fires. */
|
|
89
132
|
export interface ProfileUpdatedEvent extends AuthEventBase {
|
|
133
|
+
/** Discriminator. */
|
|
90
134
|
type: "profile:updated";
|
|
135
|
+
/** Email on the profile *after* the update. */
|
|
91
136
|
email: string;
|
|
92
137
|
}
|
|
138
|
+
/** Emitted after a successful OAuth *link* flow (popup / redirect). */
|
|
93
139
|
export interface OAuthLinkedEvent extends AuthEventBase {
|
|
140
|
+
/** Discriminator. */
|
|
94
141
|
type: "oauth:linked";
|
|
142
|
+
/** Connection that was just added to the subject's account. */
|
|
95
143
|
connection: OAuthConnection;
|
|
96
144
|
}
|
|
145
|
+
/** Emitted after `ProfileManager.unlinkOAuth` succeeds. */
|
|
97
146
|
export interface OAuthUnlinkedEvent extends AuthEventBase {
|
|
147
|
+
/** Discriminator. */
|
|
98
148
|
type: "oauth:unlinked";
|
|
149
|
+
/** Provider that was just unlinked. */
|
|
99
150
|
provider: OAuthProvider;
|
|
100
151
|
}
|
|
101
152
|
/** All event types union. */
|