alsabase 1.0.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/README.md +272 -0
- package/dist/Client.d.ts +60 -0
- package/dist/Client.js +220 -0
- package/dist/ClientResponseError.d.ts +25 -0
- package/dist/ClientResponseError.js +46 -0
- package/dist/index.cjs +1428 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1405 -0
- package/dist/services/BaseService.d.ts +7 -0
- package/dist/services/BaseService.js +9 -0
- package/dist/services/CollectionService.d.ts +42 -0
- package/dist/services/CollectionService.js +94 -0
- package/dist/services/FileService.d.ts +71 -0
- package/dist/services/FileService.js +119 -0
- package/dist/services/HooksService.d.ts +76 -0
- package/dist/services/HooksService.js +135 -0
- package/dist/services/LogService.d.ts +32 -0
- package/dist/services/LogService.js +63 -0
- package/dist/services/RealtimeService.d.ts +28 -0
- package/dist/services/RealtimeService.js +186 -0
- package/dist/services/RecordService.d.ts +105 -0
- package/dist/services/RecordService.js +287 -0
- package/dist/services/SuperuserService.d.ts +35 -0
- package/dist/services/SuperuserService.js +80 -0
- package/dist/stores/AsyncAuthStore.d.ts +13 -0
- package/dist/stores/AsyncAuthStore.js +32 -0
- package/dist/stores/BaseAuthStore.d.ts +18 -0
- package/dist/stores/BaseAuthStore.js +81 -0
- package/dist/stores/LocalAuthStore.d.ts +8 -0
- package/dist/stores/LocalAuthStore.js +43 -0
- package/dist/types.d.ts +241 -0
- package/dist/types.js +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { io } from "socket.io-client";
|
|
2
|
+
import { BaseService } from "./BaseService";
|
|
3
|
+
export class RealtimeService extends BaseService {
|
|
4
|
+
subscriptions = new Map();
|
|
5
|
+
socket = null;
|
|
6
|
+
isConnecting = false;
|
|
7
|
+
/**
|
|
8
|
+
* Checks if realtime is currently connected
|
|
9
|
+
*/
|
|
10
|
+
get isConnected() {
|
|
11
|
+
return !!(this.socket && this.socket.connected);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Subscribes a listener callback to a specific topic or collection
|
|
15
|
+
*/
|
|
16
|
+
async subscribe(topic, listener) {
|
|
17
|
+
const trimmedTopic = (topic || "*").trim();
|
|
18
|
+
if (!this.subscriptions.has(trimmedTopic)) {
|
|
19
|
+
this.subscriptions.set(trimmedTopic, new Set());
|
|
20
|
+
}
|
|
21
|
+
this.subscriptions.get(trimmedTopic).add(listener);
|
|
22
|
+
this.ensureConnection();
|
|
23
|
+
// If socket is already connected, emit subscribe immediately
|
|
24
|
+
if (this.socket && this.socket.connected) {
|
|
25
|
+
this.socket.emit("subscribe", trimmedTopic);
|
|
26
|
+
}
|
|
27
|
+
return () => {
|
|
28
|
+
this.unsubscribeFromTopic(trimmedTopic, listener);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Unsubscribes all listeners or a specific listener from a topic
|
|
33
|
+
*/
|
|
34
|
+
async unsubscribe(topic) {
|
|
35
|
+
if (!topic) {
|
|
36
|
+
if (this.socket && this.socket.connected && this.subscriptions.size > 0) {
|
|
37
|
+
this.socket.emit("unsubscribe", Array.from(this.subscriptions.keys()));
|
|
38
|
+
}
|
|
39
|
+
this.subscriptions.clear();
|
|
40
|
+
this.disconnect();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const trimmedTopic = topic.trim();
|
|
44
|
+
this.subscriptions.delete(trimmedTopic);
|
|
45
|
+
if (this.socket && this.socket.connected) {
|
|
46
|
+
this.socket.emit("unsubscribe", trimmedTopic);
|
|
47
|
+
}
|
|
48
|
+
if (this.subscriptions.size === 0) {
|
|
49
|
+
this.disconnect();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Publishes a custom event to a realtime topic
|
|
54
|
+
*/
|
|
55
|
+
async publish(topic, data, event) {
|
|
56
|
+
if (this.socket && this.socket.connected) {
|
|
57
|
+
this.socket.emit("publish", { topic, data, event });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
await this.send("/api/realtime/publish", {
|
|
61
|
+
method: "POST",
|
|
62
|
+
body: {
|
|
63
|
+
topic,
|
|
64
|
+
data,
|
|
65
|
+
event,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
unsubscribeFromTopic(topic, listener) {
|
|
70
|
+
const set = this.subscriptions.get(topic);
|
|
71
|
+
if (set) {
|
|
72
|
+
set.delete(listener);
|
|
73
|
+
if (set.size === 0) {
|
|
74
|
+
this.subscriptions.delete(topic);
|
|
75
|
+
if (this.socket && this.socket.connected) {
|
|
76
|
+
this.socket.emit("unsubscribe", topic);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (this.subscriptions.size === 0) {
|
|
81
|
+
this.disconnect();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
ensureConnection() {
|
|
85
|
+
if (this.isConnecting || this.isConnected)
|
|
86
|
+
return;
|
|
87
|
+
this.connect();
|
|
88
|
+
}
|
|
89
|
+
connect() {
|
|
90
|
+
if (this.socket)
|
|
91
|
+
return;
|
|
92
|
+
this.isConnecting = true;
|
|
93
|
+
const socketUrl = this.client.baseUrl;
|
|
94
|
+
this.socket = io(socketUrl, {
|
|
95
|
+
path: "/api/socket.io",
|
|
96
|
+
auth: {
|
|
97
|
+
token: this.client.authStore.token,
|
|
98
|
+
},
|
|
99
|
+
transports: ["websocket", "polling"],
|
|
100
|
+
reconnection: true,
|
|
101
|
+
reconnectionAttempts: Infinity,
|
|
102
|
+
reconnectionDelay: 1000,
|
|
103
|
+
reconnectionDelayMax: 5000,
|
|
104
|
+
});
|
|
105
|
+
this.socket.on("connect", () => {
|
|
106
|
+
this.isConnecting = false;
|
|
107
|
+
const topics = Array.from(this.subscriptions.keys());
|
|
108
|
+
if (topics.length > 0) {
|
|
109
|
+
this.socket.emit("subscribe", topics);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
this.socket.on("disconnect", () => {
|
|
113
|
+
this.isConnecting = false;
|
|
114
|
+
});
|
|
115
|
+
this.socket.on("connect_error", () => {
|
|
116
|
+
this.isConnecting = false;
|
|
117
|
+
});
|
|
118
|
+
// Handle incoming logs
|
|
119
|
+
this.socket.on("logs", (data) => {
|
|
120
|
+
const logData = data?.log || data;
|
|
121
|
+
this.dispatchMessage(logData, "logs");
|
|
122
|
+
});
|
|
123
|
+
this.socket.on("log", (data) => {
|
|
124
|
+
const logData = data?.log || data;
|
|
125
|
+
this.dispatchMessage(logData, "logs");
|
|
126
|
+
});
|
|
127
|
+
// Handle generic record events
|
|
128
|
+
this.socket.on("record", (data) => {
|
|
129
|
+
const collection = data?.collection;
|
|
130
|
+
this.dispatchMessage(data, collection);
|
|
131
|
+
});
|
|
132
|
+
// Catch-all listener for collection or custom topics
|
|
133
|
+
this.socket.onAny((eventName, ...args) => {
|
|
134
|
+
if ([
|
|
135
|
+
"connect",
|
|
136
|
+
"disconnect",
|
|
137
|
+
"connect_error",
|
|
138
|
+
"connected",
|
|
139
|
+
"subscriptions",
|
|
140
|
+
"pong",
|
|
141
|
+
].includes(eventName)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const data = args[0];
|
|
145
|
+
this.dispatchMessage(data, eventName);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
dispatchMessage(data, explicitTopic) {
|
|
149
|
+
if (!data)
|
|
150
|
+
return;
|
|
151
|
+
const collection = explicitTopic || data.collection || data.topic;
|
|
152
|
+
const recordId = data.record?.id;
|
|
153
|
+
for (const [topic, listeners] of this.subscriptions.entries()) {
|
|
154
|
+
let isMatch = false;
|
|
155
|
+
if (topic === "*" || topic === collection) {
|
|
156
|
+
isMatch = true;
|
|
157
|
+
}
|
|
158
|
+
else if (recordId && topic === `${collection}/${recordId}`) {
|
|
159
|
+
isMatch = true;
|
|
160
|
+
}
|
|
161
|
+
else if (topic === `${collection}/*`) {
|
|
162
|
+
isMatch = true;
|
|
163
|
+
}
|
|
164
|
+
if (isMatch) {
|
|
165
|
+
for (const listener of listeners) {
|
|
166
|
+
try {
|
|
167
|
+
listener(data);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
console.error("[AlsaBase Realtime] Listener error:", err);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
disconnect() {
|
|
177
|
+
this.isConnecting = false;
|
|
178
|
+
if (this.socket) {
|
|
179
|
+
try {
|
|
180
|
+
this.socket.disconnect();
|
|
181
|
+
}
|
|
182
|
+
catch { }
|
|
183
|
+
this.socket = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { BaseService } from "./BaseService";
|
|
2
|
+
import type { AlsaBase } from "../Client";
|
|
3
|
+
import type { ListResult, RecordListOptions, FullListOptions, RecordOptions, CommonOptions, AuthResponse, RealtimeListener, UnsubscribeFunc, CollectionModel } from "../types";
|
|
4
|
+
export declare class RecordService<T = any> extends BaseService {
|
|
5
|
+
readonly collectionIdOrName: string;
|
|
6
|
+
constructor(client: AlsaBase, collectionIdOrName: string);
|
|
7
|
+
/**
|
|
8
|
+
* Base API endpoint path for the collection records
|
|
9
|
+
*/
|
|
10
|
+
get baseCrudPath(): string;
|
|
11
|
+
/**
|
|
12
|
+
* Returns the current schema and column definitions of this table/collection (Requires Superuser authentication)
|
|
13
|
+
*/
|
|
14
|
+
getSchema(options?: CommonOptions): Promise<CollectionModel>;
|
|
15
|
+
/**
|
|
16
|
+
* Returns the schema of this table/collection (Requires Superuser authentication)
|
|
17
|
+
* Alias for getSchema()
|
|
18
|
+
*/
|
|
19
|
+
schema(options?: CommonOptions): Promise<CollectionModel>;
|
|
20
|
+
/**
|
|
21
|
+
* Returns the schema of this table/collection (Requires Superuser authentication)
|
|
22
|
+
* Alias for getSchema()
|
|
23
|
+
*/
|
|
24
|
+
getTableSchema(options?: CommonOptions): Promise<CollectionModel>;
|
|
25
|
+
/**
|
|
26
|
+
* Returns a paginated list of records
|
|
27
|
+
*/
|
|
28
|
+
getList(page?: number, perPage?: number, options?: RecordListOptions): Promise<ListResult<T>>;
|
|
29
|
+
/**
|
|
30
|
+
* Returns a list of all records in batches
|
|
31
|
+
*/
|
|
32
|
+
getFullList(options?: FullListOptions): Promise<T[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Returns the first record matching the specified filter expression
|
|
35
|
+
*/
|
|
36
|
+
getFirstListItem(filter: string, options?: RecordOptions): Promise<T>;
|
|
37
|
+
/**
|
|
38
|
+
* Returns a single record by its ID
|
|
39
|
+
*/
|
|
40
|
+
getOne(id: string, options?: RecordOptions): Promise<T>;
|
|
41
|
+
/**
|
|
42
|
+
* Creates a new record in the collection
|
|
43
|
+
*/
|
|
44
|
+
create(body: any, options?: RecordOptions): Promise<T>;
|
|
45
|
+
/**
|
|
46
|
+
* Updates an existing record by its ID
|
|
47
|
+
*/
|
|
48
|
+
update(id: string, body: any, options?: RecordOptions): Promise<T>;
|
|
49
|
+
/**
|
|
50
|
+
* Deletes a record by its ID
|
|
51
|
+
*/
|
|
52
|
+
delete(id: string, options?: CommonOptions): Promise<boolean>;
|
|
53
|
+
/**
|
|
54
|
+
* Truncates (deletes all records) in the collection (Requires superuser access)
|
|
55
|
+
*/
|
|
56
|
+
truncate(options?: CommonOptions): Promise<boolean>;
|
|
57
|
+
/**
|
|
58
|
+
* Authenticates a user with username/email and password
|
|
59
|
+
*/
|
|
60
|
+
authWithPassword(identity: string, password: string, options?: CommonOptions): Promise<AuthResponse<T>>;
|
|
61
|
+
/**
|
|
62
|
+
* Authenticates a user with a one-time password (OTP)
|
|
63
|
+
*/
|
|
64
|
+
authWithOTP(otp: string, email: string, options?: CommonOptions): Promise<AuthResponse<T>>;
|
|
65
|
+
/**
|
|
66
|
+
* Sends an OTP verification email to the user
|
|
67
|
+
*/
|
|
68
|
+
requestOTP(email: string, options?: CommonOptions): Promise<any>;
|
|
69
|
+
/**
|
|
70
|
+
* Refreshes the currently authenticated record token and profile
|
|
71
|
+
*/
|
|
72
|
+
authRefresh(options?: CommonOptions): Promise<AuthResponse<T>>;
|
|
73
|
+
/**
|
|
74
|
+
* Sends a password reset email
|
|
75
|
+
*/
|
|
76
|
+
requestPasswordReset(email: string, options?: CommonOptions): Promise<boolean>;
|
|
77
|
+
/**
|
|
78
|
+
* Confirms a password reset request with a token and new password
|
|
79
|
+
*/
|
|
80
|
+
confirmPasswordReset(token: string, password: string, _passwordConfirm?: string, options?: CommonOptions): Promise<boolean>;
|
|
81
|
+
/**
|
|
82
|
+
* Sends an email verification request
|
|
83
|
+
*/
|
|
84
|
+
requestVerification(email: string, options?: CommonOptions): Promise<boolean>;
|
|
85
|
+
/**
|
|
86
|
+
* Confirms an email verification request
|
|
87
|
+
*/
|
|
88
|
+
confirmVerification(token: string, options?: CommonOptions): Promise<boolean>;
|
|
89
|
+
/**
|
|
90
|
+
* Sends an email change verification request
|
|
91
|
+
*/
|
|
92
|
+
requestEmailChange(newEmail: string, options?: CommonOptions): Promise<boolean>;
|
|
93
|
+
/**
|
|
94
|
+
* Confirms an email change request
|
|
95
|
+
*/
|
|
96
|
+
confirmEmailChange(token: string, password?: string, options?: CommonOptions): Promise<boolean>;
|
|
97
|
+
/**
|
|
98
|
+
* Subscribes to realtime events for this collection or a specific record ID
|
|
99
|
+
*/
|
|
100
|
+
subscribe(topicOrListener: string | RealtimeListener<T>, listener?: RealtimeListener<T>): Promise<UnsubscribeFunc>;
|
|
101
|
+
/**
|
|
102
|
+
* Unsubscribes from realtime events for this collection or a specific record ID
|
|
103
|
+
*/
|
|
104
|
+
unsubscribe(topic?: string): Promise<void>;
|
|
105
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { BaseService } from "./BaseService";
|
|
2
|
+
export class RecordService extends BaseService {
|
|
3
|
+
collectionIdOrName;
|
|
4
|
+
constructor(client, collectionIdOrName) {
|
|
5
|
+
super(client);
|
|
6
|
+
this.collectionIdOrName = collectionIdOrName;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Base API endpoint path for the collection records
|
|
10
|
+
*/
|
|
11
|
+
get baseCrudPath() {
|
|
12
|
+
return `/api/collections/${encodeURIComponent(this.collectionIdOrName)}/records`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Returns the current schema and column definitions of this table/collection (Requires Superuser authentication)
|
|
16
|
+
*/
|
|
17
|
+
async getSchema(options) {
|
|
18
|
+
return this.client.collections.getOne(this.collectionIdOrName, options);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Returns the schema of this table/collection (Requires Superuser authentication)
|
|
22
|
+
* Alias for getSchema()
|
|
23
|
+
*/
|
|
24
|
+
async schema(options) {
|
|
25
|
+
return this.getSchema(options);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Returns the schema of this table/collection (Requires Superuser authentication)
|
|
29
|
+
* Alias for getSchema()
|
|
30
|
+
*/
|
|
31
|
+
async getTableSchema(options) {
|
|
32
|
+
return this.getSchema(options);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Returns a paginated list of records
|
|
36
|
+
*/
|
|
37
|
+
async getList(page = 1, perPage = 30, options) {
|
|
38
|
+
const query = {
|
|
39
|
+
page,
|
|
40
|
+
limit: perPage,
|
|
41
|
+
...options,
|
|
42
|
+
};
|
|
43
|
+
return this.send(this.baseCrudPath, {
|
|
44
|
+
method: "GET",
|
|
45
|
+
query,
|
|
46
|
+
...options,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Returns a list of all records in batches
|
|
51
|
+
*/
|
|
52
|
+
async getFullList(options) {
|
|
53
|
+
const batchSize = options?.batch || 200;
|
|
54
|
+
let page = 1;
|
|
55
|
+
let result = [];
|
|
56
|
+
let hasMore = true;
|
|
57
|
+
while (hasMore) {
|
|
58
|
+
const list = await this.getList(page, batchSize, {
|
|
59
|
+
...options,
|
|
60
|
+
skipTotal: true,
|
|
61
|
+
});
|
|
62
|
+
result = result.concat(list.items);
|
|
63
|
+
if (list.items.length < batchSize || (list.totalPages && page >= list.totalPages)) {
|
|
64
|
+
hasMore = false;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
page++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Returns the first record matching the specified filter expression
|
|
74
|
+
*/
|
|
75
|
+
async getFirstListItem(filter, options) {
|
|
76
|
+
const list = await this.getList(1, 1, {
|
|
77
|
+
...options,
|
|
78
|
+
filter,
|
|
79
|
+
});
|
|
80
|
+
if (!list.items || list.items.length === 0) {
|
|
81
|
+
throw new Error(`Record not found for filter: ${filter}`);
|
|
82
|
+
}
|
|
83
|
+
return list.items[0];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Returns a single record by its ID
|
|
87
|
+
*/
|
|
88
|
+
async getOne(id, options) {
|
|
89
|
+
return this.send(`${this.baseCrudPath}/${encodeURIComponent(id)}`, {
|
|
90
|
+
method: "GET",
|
|
91
|
+
...options,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Creates a new record in the collection
|
|
96
|
+
*/
|
|
97
|
+
async create(body, options) {
|
|
98
|
+
return this.send(this.baseCrudPath, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
body,
|
|
101
|
+
...options,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Updates an existing record by its ID
|
|
106
|
+
*/
|
|
107
|
+
async update(id, body, options) {
|
|
108
|
+
return this.send(`${this.baseCrudPath}/${encodeURIComponent(id)}`, {
|
|
109
|
+
method: "PATCH",
|
|
110
|
+
body,
|
|
111
|
+
...options,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Deletes a record by its ID
|
|
116
|
+
*/
|
|
117
|
+
async delete(id, options) {
|
|
118
|
+
await this.send(`${this.baseCrudPath}/${encodeURIComponent(id)}`, {
|
|
119
|
+
method: "DELETE",
|
|
120
|
+
...options,
|
|
121
|
+
});
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Truncates (deletes all records) in the collection (Requires superuser access)
|
|
126
|
+
*/
|
|
127
|
+
async truncate(options) {
|
|
128
|
+
await this.send(`/api/collections/${encodeURIComponent(this.collectionIdOrName)}/truncate`, {
|
|
129
|
+
method: "DELETE",
|
|
130
|
+
...options,
|
|
131
|
+
});
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
// --- Auth Methods ---
|
|
135
|
+
/**
|
|
136
|
+
* Authenticates a user with username/email and password
|
|
137
|
+
*/
|
|
138
|
+
async authWithPassword(identity, password, options) {
|
|
139
|
+
const res = await this.send("/api/auth/users/login", {
|
|
140
|
+
method: "POST",
|
|
141
|
+
body: { identity, password },
|
|
142
|
+
...options,
|
|
143
|
+
});
|
|
144
|
+
const authResponse = {
|
|
145
|
+
token: res.token,
|
|
146
|
+
record: (res.user || res.record),
|
|
147
|
+
meta: res.meta,
|
|
148
|
+
};
|
|
149
|
+
this.client.authStore.save(authResponse.token, authResponse.record);
|
|
150
|
+
return authResponse;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Authenticates a user with a one-time password (OTP)
|
|
154
|
+
*/
|
|
155
|
+
async authWithOTP(otp, email, options) {
|
|
156
|
+
const res = await this.send("/api/auth/verify-otp", {
|
|
157
|
+
method: "POST",
|
|
158
|
+
body: { otp, email },
|
|
159
|
+
...options,
|
|
160
|
+
});
|
|
161
|
+
const authResponse = {
|
|
162
|
+
token: res.token,
|
|
163
|
+
record: (res.user || res.record),
|
|
164
|
+
meta: res.meta,
|
|
165
|
+
};
|
|
166
|
+
this.client.authStore.save(authResponse.token, authResponse.record);
|
|
167
|
+
return authResponse;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Sends an OTP verification email to the user
|
|
171
|
+
*/
|
|
172
|
+
async requestOTP(email, options) {
|
|
173
|
+
return this.send("/api/auth/request-otp", {
|
|
174
|
+
method: "POST",
|
|
175
|
+
body: { email },
|
|
176
|
+
...options,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Refreshes the currently authenticated record token and profile
|
|
181
|
+
*/
|
|
182
|
+
async authRefresh(options) {
|
|
183
|
+
const user = await this.send("/api/auth/users/me", {
|
|
184
|
+
method: "GET",
|
|
185
|
+
...options,
|
|
186
|
+
});
|
|
187
|
+
const token = this.client.authStore.token;
|
|
188
|
+
const authResponse = {
|
|
189
|
+
token,
|
|
190
|
+
record: user,
|
|
191
|
+
};
|
|
192
|
+
this.client.authStore.save(token, user);
|
|
193
|
+
return authResponse;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Sends a password reset email
|
|
197
|
+
*/
|
|
198
|
+
async requestPasswordReset(email, options) {
|
|
199
|
+
await this.send("/api/auth/request-password-reset", {
|
|
200
|
+
method: "POST",
|
|
201
|
+
body: { email },
|
|
202
|
+
...options,
|
|
203
|
+
});
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Confirms a password reset request with a token and new password
|
|
208
|
+
*/
|
|
209
|
+
async confirmPasswordReset(token, password, _passwordConfirm, options) {
|
|
210
|
+
await this.send("/api/auth/confirm-password-reset", {
|
|
211
|
+
method: "POST",
|
|
212
|
+
body: { token, password },
|
|
213
|
+
...options,
|
|
214
|
+
});
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Sends an email verification request
|
|
219
|
+
*/
|
|
220
|
+
async requestVerification(email, options) {
|
|
221
|
+
await this.send("/api/auth/request-verification", {
|
|
222
|
+
method: "POST",
|
|
223
|
+
body: { email },
|
|
224
|
+
...options,
|
|
225
|
+
});
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Confirms an email verification request
|
|
230
|
+
*/
|
|
231
|
+
async confirmVerification(token, options) {
|
|
232
|
+
await this.send("/api/auth/confirm-verification", {
|
|
233
|
+
method: "POST",
|
|
234
|
+
body: { token },
|
|
235
|
+
...options,
|
|
236
|
+
});
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Sends an email change verification request
|
|
241
|
+
*/
|
|
242
|
+
async requestEmailChange(newEmail, options) {
|
|
243
|
+
await this.send("/api/auth/request-email-change", {
|
|
244
|
+
method: "POST",
|
|
245
|
+
body: { newEmail },
|
|
246
|
+
...options,
|
|
247
|
+
});
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Confirms an email change request
|
|
252
|
+
*/
|
|
253
|
+
async confirmEmailChange(token, password, options) {
|
|
254
|
+
await this.send("/api/auth/confirm-email-change", {
|
|
255
|
+
method: "POST",
|
|
256
|
+
body: { token, password },
|
|
257
|
+
...options,
|
|
258
|
+
});
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
// --- Realtime Subscriptions ---
|
|
262
|
+
/**
|
|
263
|
+
* Subscribes to realtime events for this collection or a specific record ID
|
|
264
|
+
*/
|
|
265
|
+
async subscribe(topicOrListener, listener) {
|
|
266
|
+
let topic;
|
|
267
|
+
let cb;
|
|
268
|
+
if (typeof topicOrListener === "function") {
|
|
269
|
+
topic = this.collectionIdOrName;
|
|
270
|
+
cb = topicOrListener;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
topic = `${this.collectionIdOrName}/${topicOrListener}`;
|
|
274
|
+
cb = listener;
|
|
275
|
+
}
|
|
276
|
+
return this.client.realtime.subscribe(topic, cb);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Unsubscribes from realtime events for this collection or a specific record ID
|
|
280
|
+
*/
|
|
281
|
+
async unsubscribe(topic) {
|
|
282
|
+
const fullTopic = topic
|
|
283
|
+
? `${this.collectionIdOrName}/${topic}`
|
|
284
|
+
: this.collectionIdOrName;
|
|
285
|
+
return this.client.realtime.unsubscribe(fullTopic);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { BaseService } from "./BaseService";
|
|
2
|
+
import type { SuperuserAuthResponse, SuperuserModel, CommonOptions } from "../types";
|
|
3
|
+
export declare class SuperuserService extends BaseService {
|
|
4
|
+
/**
|
|
5
|
+
* Authenticates a superuser with email and password
|
|
6
|
+
*/
|
|
7
|
+
authWithPassword(email: string, password: string, options?: CommonOptions): Promise<SuperuserAuthResponse>;
|
|
8
|
+
/**
|
|
9
|
+
* Checks if an initial superuser exists in the system
|
|
10
|
+
*/
|
|
11
|
+
hasInitialSuperuser(options?: CommonOptions): Promise<{
|
|
12
|
+
hasSuperuser: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
/**
|
|
15
|
+
* Sets up the first superuser account (Only available when no superusers exist)
|
|
16
|
+
*/
|
|
17
|
+
setupInitialSuperuser(email: string, password: string, options?: CommonOptions): Promise<SuperuserAuthResponse>;
|
|
18
|
+
/**
|
|
19
|
+
* Returns current authenticated superuser profile
|
|
20
|
+
*/
|
|
21
|
+
getMe(options?: CommonOptions): Promise<SuperuserModel>;
|
|
22
|
+
/**
|
|
23
|
+
* Refreshes superuser auth state
|
|
24
|
+
*/
|
|
25
|
+
authRefresh(options?: CommonOptions): Promise<SuperuserAuthResponse>;
|
|
26
|
+
/**
|
|
27
|
+
* Requests a password reset email for superuser
|
|
28
|
+
*/
|
|
29
|
+
requestPasswordReset(email: string, options?: CommonOptions): Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Confirms a password reset with token
|
|
32
|
+
*/
|
|
33
|
+
confirmPasswordReset(token: string, password: string, _passwordConfirm?: string, options?: CommonOptions): Promise<boolean>;
|
|
34
|
+
}
|
|
35
|
+
export type AdminService = SuperuserService;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { BaseService } from "./BaseService";
|
|
2
|
+
export class SuperuserService extends BaseService {
|
|
3
|
+
/**
|
|
4
|
+
* Authenticates a superuser with email and password
|
|
5
|
+
*/
|
|
6
|
+
async authWithPassword(email, password, options) {
|
|
7
|
+
const res = await this.send("/api/auth/superusers/login", {
|
|
8
|
+
method: "POST",
|
|
9
|
+
body: { email, password },
|
|
10
|
+
...options,
|
|
11
|
+
});
|
|
12
|
+
this.client.authStore.save(res.token, res.user);
|
|
13
|
+
return res;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Checks if an initial superuser exists in the system
|
|
17
|
+
*/
|
|
18
|
+
async hasInitialSuperuser(options) {
|
|
19
|
+
return this.send("/api/auth/superusers/has-initial", {
|
|
20
|
+
method: "GET",
|
|
21
|
+
...options,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Sets up the first superuser account (Only available when no superusers exist)
|
|
26
|
+
*/
|
|
27
|
+
async setupInitialSuperuser(email, password, options) {
|
|
28
|
+
const res = await this.send("/api/auth/superusers/setup", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
body: { email, password },
|
|
31
|
+
...options,
|
|
32
|
+
});
|
|
33
|
+
this.client.authStore.save(res.token, res.user);
|
|
34
|
+
return res;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns current authenticated superuser profile
|
|
38
|
+
*/
|
|
39
|
+
async getMe(options) {
|
|
40
|
+
return this.send("/api/auth/superusers/me", {
|
|
41
|
+
method: "GET",
|
|
42
|
+
...options,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Refreshes superuser auth state
|
|
47
|
+
*/
|
|
48
|
+
async authRefresh(options) {
|
|
49
|
+
const user = await this.getMe(options);
|
|
50
|
+
const token = this.client.authStore.token;
|
|
51
|
+
const res = {
|
|
52
|
+
token,
|
|
53
|
+
user,
|
|
54
|
+
};
|
|
55
|
+
this.client.authStore.save(token, user);
|
|
56
|
+
return res;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Requests a password reset email for superuser
|
|
60
|
+
*/
|
|
61
|
+
async requestPasswordReset(email, options) {
|
|
62
|
+
await this.send("/api/auth/request-password-reset", {
|
|
63
|
+
method: "POST",
|
|
64
|
+
body: { email },
|
|
65
|
+
...options,
|
|
66
|
+
});
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Confirms a password reset with token
|
|
71
|
+
*/
|
|
72
|
+
async confirmPasswordReset(token, password, _passwordConfirm, options) {
|
|
73
|
+
await this.send("/api/auth/confirm-password-reset", {
|
|
74
|
+
method: "POST",
|
|
75
|
+
body: { token, password },
|
|
76
|
+
...options,
|
|
77
|
+
});
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|