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.
@@ -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
+ }