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
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1428 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AlsaBase: () => AlsaBase,
|
|
24
|
+
AsyncAuthStore: () => AsyncAuthStore,
|
|
25
|
+
BaseAuthStore: () => BaseAuthStore,
|
|
26
|
+
BaseService: () => BaseService,
|
|
27
|
+
Client: () => AlsaBase,
|
|
28
|
+
ClientResponseError: () => ClientResponseError,
|
|
29
|
+
CollectionService: () => CollectionService,
|
|
30
|
+
FileService: () => FileService,
|
|
31
|
+
HooksService: () => HooksService,
|
|
32
|
+
LocalAuthStore: () => LocalAuthStore,
|
|
33
|
+
LogService: () => LogService,
|
|
34
|
+
RealtimeService: () => RealtimeService,
|
|
35
|
+
RecordService: () => RecordService,
|
|
36
|
+
SuperuserService: () => SuperuserService,
|
|
37
|
+
default: () => index_default
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
|
|
41
|
+
// src/stores/BaseAuthStore.ts
|
|
42
|
+
var BaseAuthStore = class {
|
|
43
|
+
_token = "";
|
|
44
|
+
_model = null;
|
|
45
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
46
|
+
get token() {
|
|
47
|
+
return this._token;
|
|
48
|
+
}
|
|
49
|
+
get model() {
|
|
50
|
+
return this._model;
|
|
51
|
+
}
|
|
52
|
+
get isValid() {
|
|
53
|
+
if (!this.token) return false;
|
|
54
|
+
const jwt = this.parseJwt(this.token);
|
|
55
|
+
if (!jwt || !jwt.exp) return true;
|
|
56
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
57
|
+
return jwt.exp > now;
|
|
58
|
+
}
|
|
59
|
+
get isSuperuser() {
|
|
60
|
+
if (!this.model) return false;
|
|
61
|
+
return !!this.model.email && this.model.collectionName === void 0;
|
|
62
|
+
}
|
|
63
|
+
get isAdmin() {
|
|
64
|
+
return this.isSuperuser;
|
|
65
|
+
}
|
|
66
|
+
save(token, model) {
|
|
67
|
+
this._token = token || "";
|
|
68
|
+
this._model = model || null;
|
|
69
|
+
this.triggerChange();
|
|
70
|
+
}
|
|
71
|
+
clear() {
|
|
72
|
+
this._token = "";
|
|
73
|
+
this._model = null;
|
|
74
|
+
this.triggerChange();
|
|
75
|
+
}
|
|
76
|
+
onChange(callback) {
|
|
77
|
+
this._listeners.add(callback);
|
|
78
|
+
return () => {
|
|
79
|
+
this._listeners.delete(callback);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
triggerChange() {
|
|
83
|
+
for (const listener of this._listeners) {
|
|
84
|
+
try {
|
|
85
|
+
listener(this._token, this._model);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error("AuthStore change listener error:", err);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
parseJwt(token) {
|
|
92
|
+
if (!token) return null;
|
|
93
|
+
try {
|
|
94
|
+
const base64Url = token.split(".")[1];
|
|
95
|
+
if (!base64Url) return null;
|
|
96
|
+
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
|
97
|
+
let jsonPayload;
|
|
98
|
+
if (typeof atob === "function") {
|
|
99
|
+
jsonPayload = decodeURIComponent(
|
|
100
|
+
atob(base64).split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
|
|
101
|
+
);
|
|
102
|
+
} else if (typeof globalThis.Buffer !== "undefined") {
|
|
103
|
+
jsonPayload = globalThis.Buffer.from(base64, "base64").toString("utf8");
|
|
104
|
+
} else {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
return JSON.parse(jsonPayload);
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// src/stores/LocalAuthStore.ts
|
|
115
|
+
var DEFAULT_STORAGE_KEY = "alsabase_auth";
|
|
116
|
+
var LocalAuthStore = class extends BaseAuthStore {
|
|
117
|
+
storageKey;
|
|
118
|
+
constructor(storageKey = DEFAULT_STORAGE_KEY) {
|
|
119
|
+
super();
|
|
120
|
+
this.storageKey = storageKey;
|
|
121
|
+
this.loadInitial();
|
|
122
|
+
}
|
|
123
|
+
loadInitial() {
|
|
124
|
+
if (typeof window === "undefined" || !window.localStorage) return;
|
|
125
|
+
try {
|
|
126
|
+
const raw = window.localStorage.getItem(this.storageKey);
|
|
127
|
+
if (!raw) return;
|
|
128
|
+
const parsed = JSON.parse(raw);
|
|
129
|
+
if (parsed && typeof parsed === "object") {
|
|
130
|
+
this._token = parsed.token || "";
|
|
131
|
+
this._model = parsed.model || null;
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
save(token, model) {
|
|
137
|
+
super.save(token, model);
|
|
138
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
139
|
+
try {
|
|
140
|
+
window.localStorage.setItem(
|
|
141
|
+
this.storageKey,
|
|
142
|
+
JSON.stringify({ token: this._token, model: this._model })
|
|
143
|
+
);
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
clear() {
|
|
149
|
+
super.clear();
|
|
150
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
151
|
+
try {
|
|
152
|
+
window.localStorage.removeItem(this.storageKey);
|
|
153
|
+
} catch {
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// src/ClientResponseError.ts
|
|
160
|
+
var ClientResponseError = class _ClientResponseError extends Error {
|
|
161
|
+
url = "";
|
|
162
|
+
status = 0;
|
|
163
|
+
response = {};
|
|
164
|
+
data = {};
|
|
165
|
+
isAbort = false;
|
|
166
|
+
originalError = null;
|
|
167
|
+
constructor(errData) {
|
|
168
|
+
super("ClientResponseError");
|
|
169
|
+
if (errData !== null && typeof errData === "object") {
|
|
170
|
+
this.url = errData.url || "";
|
|
171
|
+
this.status = errData.status || 0;
|
|
172
|
+
this.data = errData.data || {};
|
|
173
|
+
this.response = errData.response || this.data;
|
|
174
|
+
this.isAbort = !!errData.isAbort;
|
|
175
|
+
this.originalError = errData.originalError || null;
|
|
176
|
+
if (errData.message) {
|
|
177
|
+
this.message = errData.message;
|
|
178
|
+
} else if (this.data?.message) {
|
|
179
|
+
this.message = this.data.message;
|
|
180
|
+
} else if (this.data?.error) {
|
|
181
|
+
this.message = this.data.error;
|
|
182
|
+
} else if (this.status) {
|
|
183
|
+
this.message = `Response error. Status code: ${this.status}`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (typeof DOMException !== "undefined" && errData instanceof DOMException && errData.name === "AbortError") {
|
|
187
|
+
this.isAbort = true;
|
|
188
|
+
this.message = "The request was autocancelled or aborted.";
|
|
189
|
+
}
|
|
190
|
+
Object.setPrototypeOf(this, _ClientResponseError.prototype);
|
|
191
|
+
}
|
|
192
|
+
toJSON() {
|
|
193
|
+
return {
|
|
194
|
+
url: this.url,
|
|
195
|
+
status: this.status,
|
|
196
|
+
data: this.data,
|
|
197
|
+
response: this.response,
|
|
198
|
+
isAbort: this.isAbort,
|
|
199
|
+
message: this.message
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// src/services/BaseService.ts
|
|
205
|
+
var BaseService = class {
|
|
206
|
+
client;
|
|
207
|
+
constructor(client) {
|
|
208
|
+
this.client = client;
|
|
209
|
+
}
|
|
210
|
+
async send(path, options) {
|
|
211
|
+
return this.client.send(path, options);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// src/services/RecordService.ts
|
|
216
|
+
var RecordService = class extends BaseService {
|
|
217
|
+
collectionIdOrName;
|
|
218
|
+
constructor(client, collectionIdOrName) {
|
|
219
|
+
super(client);
|
|
220
|
+
this.collectionIdOrName = collectionIdOrName;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Base API endpoint path for the collection records
|
|
224
|
+
*/
|
|
225
|
+
get baseCrudPath() {
|
|
226
|
+
return `/api/collections/${encodeURIComponent(this.collectionIdOrName)}/records`;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Returns the current schema and column definitions of this table/collection (Requires Superuser authentication)
|
|
230
|
+
*/
|
|
231
|
+
async getSchema(options) {
|
|
232
|
+
return this.client.collections.getOne(this.collectionIdOrName, options);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Returns the schema of this table/collection (Requires Superuser authentication)
|
|
236
|
+
* Alias for getSchema()
|
|
237
|
+
*/
|
|
238
|
+
async schema(options) {
|
|
239
|
+
return this.getSchema(options);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Returns the schema of this table/collection (Requires Superuser authentication)
|
|
243
|
+
* Alias for getSchema()
|
|
244
|
+
*/
|
|
245
|
+
async getTableSchema(options) {
|
|
246
|
+
return this.getSchema(options);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Returns a paginated list of records
|
|
250
|
+
*/
|
|
251
|
+
async getList(page = 1, perPage = 30, options) {
|
|
252
|
+
const query = {
|
|
253
|
+
page,
|
|
254
|
+
limit: perPage,
|
|
255
|
+
...options
|
|
256
|
+
};
|
|
257
|
+
return this.send(this.baseCrudPath, {
|
|
258
|
+
method: "GET",
|
|
259
|
+
query,
|
|
260
|
+
...options
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Returns a list of all records in batches
|
|
265
|
+
*/
|
|
266
|
+
async getFullList(options) {
|
|
267
|
+
const batchSize = options?.batch || 200;
|
|
268
|
+
let page = 1;
|
|
269
|
+
let result = [];
|
|
270
|
+
let hasMore = true;
|
|
271
|
+
while (hasMore) {
|
|
272
|
+
const list = await this.getList(page, batchSize, {
|
|
273
|
+
...options,
|
|
274
|
+
skipTotal: true
|
|
275
|
+
});
|
|
276
|
+
result = result.concat(list.items);
|
|
277
|
+
if (list.items.length < batchSize || list.totalPages && page >= list.totalPages) {
|
|
278
|
+
hasMore = false;
|
|
279
|
+
} else {
|
|
280
|
+
page++;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Returns the first record matching the specified filter expression
|
|
287
|
+
*/
|
|
288
|
+
async getFirstListItem(filter, options) {
|
|
289
|
+
const list = await this.getList(1, 1, {
|
|
290
|
+
...options,
|
|
291
|
+
filter
|
|
292
|
+
});
|
|
293
|
+
if (!list.items || list.items.length === 0) {
|
|
294
|
+
throw new Error(`Record not found for filter: ${filter}`);
|
|
295
|
+
}
|
|
296
|
+
return list.items[0];
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Returns a single record by its ID
|
|
300
|
+
*/
|
|
301
|
+
async getOne(id, options) {
|
|
302
|
+
return this.send(`${this.baseCrudPath}/${encodeURIComponent(id)}`, {
|
|
303
|
+
method: "GET",
|
|
304
|
+
...options
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Creates a new record in the collection
|
|
309
|
+
*/
|
|
310
|
+
async create(body, options) {
|
|
311
|
+
return this.send(this.baseCrudPath, {
|
|
312
|
+
method: "POST",
|
|
313
|
+
body,
|
|
314
|
+
...options
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Updates an existing record by its ID
|
|
319
|
+
*/
|
|
320
|
+
async update(id, body, options) {
|
|
321
|
+
return this.send(`${this.baseCrudPath}/${encodeURIComponent(id)}`, {
|
|
322
|
+
method: "PATCH",
|
|
323
|
+
body,
|
|
324
|
+
...options
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Deletes a record by its ID
|
|
329
|
+
*/
|
|
330
|
+
async delete(id, options) {
|
|
331
|
+
await this.send(`${this.baseCrudPath}/${encodeURIComponent(id)}`, {
|
|
332
|
+
method: "DELETE",
|
|
333
|
+
...options
|
|
334
|
+
});
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Truncates (deletes all records) in the collection (Requires superuser access)
|
|
339
|
+
*/
|
|
340
|
+
async truncate(options) {
|
|
341
|
+
await this.send(
|
|
342
|
+
`/api/collections/${encodeURIComponent(this.collectionIdOrName)}/truncate`,
|
|
343
|
+
{
|
|
344
|
+
method: "DELETE",
|
|
345
|
+
...options
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
// --- Auth Methods ---
|
|
351
|
+
/**
|
|
352
|
+
* Authenticates a user with username/email and password
|
|
353
|
+
*/
|
|
354
|
+
async authWithPassword(identity, password, options) {
|
|
355
|
+
const res = await this.send("/api/auth/users/login", {
|
|
356
|
+
method: "POST",
|
|
357
|
+
body: { identity, password },
|
|
358
|
+
...options
|
|
359
|
+
});
|
|
360
|
+
const authResponse = {
|
|
361
|
+
token: res.token,
|
|
362
|
+
record: res.user || res.record,
|
|
363
|
+
meta: res.meta
|
|
364
|
+
};
|
|
365
|
+
this.client.authStore.save(authResponse.token, authResponse.record);
|
|
366
|
+
return authResponse;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Authenticates a user with a one-time password (OTP)
|
|
370
|
+
*/
|
|
371
|
+
async authWithOTP(otp, email, options) {
|
|
372
|
+
const res = await this.send("/api/auth/verify-otp", {
|
|
373
|
+
method: "POST",
|
|
374
|
+
body: { otp, email },
|
|
375
|
+
...options
|
|
376
|
+
});
|
|
377
|
+
const authResponse = {
|
|
378
|
+
token: res.token,
|
|
379
|
+
record: res.user || res.record,
|
|
380
|
+
meta: res.meta
|
|
381
|
+
};
|
|
382
|
+
this.client.authStore.save(authResponse.token, authResponse.record);
|
|
383
|
+
return authResponse;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Sends an OTP verification email to the user
|
|
387
|
+
*/
|
|
388
|
+
async requestOTP(email, options) {
|
|
389
|
+
return this.send("/api/auth/request-otp", {
|
|
390
|
+
method: "POST",
|
|
391
|
+
body: { email },
|
|
392
|
+
...options
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Refreshes the currently authenticated record token and profile
|
|
397
|
+
*/
|
|
398
|
+
async authRefresh(options) {
|
|
399
|
+
const user = await this.send("/api/auth/users/me", {
|
|
400
|
+
method: "GET",
|
|
401
|
+
...options
|
|
402
|
+
});
|
|
403
|
+
const token = this.client.authStore.token;
|
|
404
|
+
const authResponse = {
|
|
405
|
+
token,
|
|
406
|
+
record: user
|
|
407
|
+
};
|
|
408
|
+
this.client.authStore.save(token, user);
|
|
409
|
+
return authResponse;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Sends a password reset email
|
|
413
|
+
*/
|
|
414
|
+
async requestPasswordReset(email, options) {
|
|
415
|
+
await this.send("/api/auth/request-password-reset", {
|
|
416
|
+
method: "POST",
|
|
417
|
+
body: { email },
|
|
418
|
+
...options
|
|
419
|
+
});
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Confirms a password reset request with a token and new password
|
|
424
|
+
*/
|
|
425
|
+
async confirmPasswordReset(token, password, _passwordConfirm, options) {
|
|
426
|
+
await this.send("/api/auth/confirm-password-reset", {
|
|
427
|
+
method: "POST",
|
|
428
|
+
body: { token, password },
|
|
429
|
+
...options
|
|
430
|
+
});
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Sends an email verification request
|
|
435
|
+
*/
|
|
436
|
+
async requestVerification(email, options) {
|
|
437
|
+
await this.send("/api/auth/request-verification", {
|
|
438
|
+
method: "POST",
|
|
439
|
+
body: { email },
|
|
440
|
+
...options
|
|
441
|
+
});
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Confirms an email verification request
|
|
446
|
+
*/
|
|
447
|
+
async confirmVerification(token, options) {
|
|
448
|
+
await this.send("/api/auth/confirm-verification", {
|
|
449
|
+
method: "POST",
|
|
450
|
+
body: { token },
|
|
451
|
+
...options
|
|
452
|
+
});
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Sends an email change verification request
|
|
457
|
+
*/
|
|
458
|
+
async requestEmailChange(newEmail, options) {
|
|
459
|
+
await this.send("/api/auth/request-email-change", {
|
|
460
|
+
method: "POST",
|
|
461
|
+
body: { newEmail },
|
|
462
|
+
...options
|
|
463
|
+
});
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Confirms an email change request
|
|
468
|
+
*/
|
|
469
|
+
async confirmEmailChange(token, password, options) {
|
|
470
|
+
await this.send("/api/auth/confirm-email-change", {
|
|
471
|
+
method: "POST",
|
|
472
|
+
body: { token, password },
|
|
473
|
+
...options
|
|
474
|
+
});
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
// --- Realtime Subscriptions ---
|
|
478
|
+
/**
|
|
479
|
+
* Subscribes to realtime events for this collection or a specific record ID
|
|
480
|
+
*/
|
|
481
|
+
async subscribe(topicOrListener, listener) {
|
|
482
|
+
let topic;
|
|
483
|
+
let cb;
|
|
484
|
+
if (typeof topicOrListener === "function") {
|
|
485
|
+
topic = this.collectionIdOrName;
|
|
486
|
+
cb = topicOrListener;
|
|
487
|
+
} else {
|
|
488
|
+
topic = `${this.collectionIdOrName}/${topicOrListener}`;
|
|
489
|
+
cb = listener;
|
|
490
|
+
}
|
|
491
|
+
return this.client.realtime.subscribe(topic, cb);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Unsubscribes from realtime events for this collection or a specific record ID
|
|
495
|
+
*/
|
|
496
|
+
async unsubscribe(topic) {
|
|
497
|
+
const fullTopic = topic ? `${this.collectionIdOrName}/${topic}` : this.collectionIdOrName;
|
|
498
|
+
return this.client.realtime.unsubscribe(fullTopic);
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
// src/services/CollectionService.ts
|
|
503
|
+
var CollectionService = class extends BaseService {
|
|
504
|
+
/**
|
|
505
|
+
* Returns a list of all collections (Superuser required)
|
|
506
|
+
*/
|
|
507
|
+
async getFullList(options) {
|
|
508
|
+
const res = await this.send(
|
|
509
|
+
"/api/collections",
|
|
510
|
+
{
|
|
511
|
+
method: "GET",
|
|
512
|
+
...options
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
return res.items || [];
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Returns a paginated list of collections
|
|
519
|
+
*/
|
|
520
|
+
async getList(page = 1, perPage = 30, options) {
|
|
521
|
+
const all = await this.getFullList(options);
|
|
522
|
+
const start = (page - 1) * perPage;
|
|
523
|
+
const end = start + perPage;
|
|
524
|
+
const items = all.slice(start, end);
|
|
525
|
+
const total = all.length;
|
|
526
|
+
const totalPages = Math.ceil(total / perPage) || 1;
|
|
527
|
+
return {
|
|
528
|
+
page,
|
|
529
|
+
perPage,
|
|
530
|
+
totalItems: total,
|
|
531
|
+
totalPages,
|
|
532
|
+
items
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Returns a single collection schema by its name or ID (Superuser required)
|
|
537
|
+
*/
|
|
538
|
+
async getOne(idOrName, options) {
|
|
539
|
+
return this.send(
|
|
540
|
+
`/api/collections/${encodeURIComponent(idOrName)}/schema`,
|
|
541
|
+
{
|
|
542
|
+
method: "GET",
|
|
543
|
+
...options
|
|
544
|
+
}
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Returns the schema and column definitions of a table/collection (Superuser required)
|
|
549
|
+
* Alias for getOne()
|
|
550
|
+
*/
|
|
551
|
+
async getSchema(idOrName, options) {
|
|
552
|
+
return this.getOne(idOrName, options);
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Returns the schema of a table/collection (Superuser required)
|
|
556
|
+
* Alias for getOne()
|
|
557
|
+
*/
|
|
558
|
+
async getTableSchema(idOrName, options) {
|
|
559
|
+
return this.getOne(idOrName, options);
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Creates a new collection schema
|
|
563
|
+
*/
|
|
564
|
+
async create(data, options) {
|
|
565
|
+
return this.send("/api/collections", {
|
|
566
|
+
method: "POST",
|
|
567
|
+
body: data,
|
|
568
|
+
...options
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Updates an existing collection schema
|
|
573
|
+
*/
|
|
574
|
+
async update(idOrName, data, options) {
|
|
575
|
+
return this.send(
|
|
576
|
+
`/api/collections/${encodeURIComponent(idOrName)}`,
|
|
577
|
+
{
|
|
578
|
+
method: "PATCH",
|
|
579
|
+
body: data,
|
|
580
|
+
...options
|
|
581
|
+
}
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Deletes a collection schema
|
|
586
|
+
*/
|
|
587
|
+
async delete(idOrName, options) {
|
|
588
|
+
await this.send(`/api/collections/${encodeURIComponent(idOrName)}`, {
|
|
589
|
+
method: "DELETE",
|
|
590
|
+
...options
|
|
591
|
+
});
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Truncates all records in a collection
|
|
596
|
+
*/
|
|
597
|
+
async truncate(idOrName, options) {
|
|
598
|
+
await this.send(
|
|
599
|
+
`/api/collections/${encodeURIComponent(idOrName)}/truncate`,
|
|
600
|
+
{
|
|
601
|
+
method: "DELETE",
|
|
602
|
+
...options
|
|
603
|
+
}
|
|
604
|
+
);
|
|
605
|
+
return true;
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// src/services/SuperuserService.ts
|
|
610
|
+
var SuperuserService = class extends BaseService {
|
|
611
|
+
/**
|
|
612
|
+
* Authenticates a superuser with email and password
|
|
613
|
+
*/
|
|
614
|
+
async authWithPassword(email, password, options) {
|
|
615
|
+
const res = await this.send(
|
|
616
|
+
"/api/auth/superusers/login",
|
|
617
|
+
{
|
|
618
|
+
method: "POST",
|
|
619
|
+
body: { email, password },
|
|
620
|
+
...options
|
|
621
|
+
}
|
|
622
|
+
);
|
|
623
|
+
this.client.authStore.save(res.token, res.user);
|
|
624
|
+
return res;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Checks if an initial superuser exists in the system
|
|
628
|
+
*/
|
|
629
|
+
async hasInitialSuperuser(options) {
|
|
630
|
+
return this.send(
|
|
631
|
+
"/api/auth/superusers/has-initial",
|
|
632
|
+
{
|
|
633
|
+
method: "GET",
|
|
634
|
+
...options
|
|
635
|
+
}
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Sets up the first superuser account (Only available when no superusers exist)
|
|
640
|
+
*/
|
|
641
|
+
async setupInitialSuperuser(email, password, options) {
|
|
642
|
+
const res = await this.send(
|
|
643
|
+
"/api/auth/superusers/setup",
|
|
644
|
+
{
|
|
645
|
+
method: "POST",
|
|
646
|
+
body: { email, password },
|
|
647
|
+
...options
|
|
648
|
+
}
|
|
649
|
+
);
|
|
650
|
+
this.client.authStore.save(res.token, res.user);
|
|
651
|
+
return res;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Returns current authenticated superuser profile
|
|
655
|
+
*/
|
|
656
|
+
async getMe(options) {
|
|
657
|
+
return this.send("/api/auth/superusers/me", {
|
|
658
|
+
method: "GET",
|
|
659
|
+
...options
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Refreshes superuser auth state
|
|
664
|
+
*/
|
|
665
|
+
async authRefresh(options) {
|
|
666
|
+
const user = await this.getMe(options);
|
|
667
|
+
const token = this.client.authStore.token;
|
|
668
|
+
const res = {
|
|
669
|
+
token,
|
|
670
|
+
user
|
|
671
|
+
};
|
|
672
|
+
this.client.authStore.save(token, user);
|
|
673
|
+
return res;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Requests a password reset email for superuser
|
|
677
|
+
*/
|
|
678
|
+
async requestPasswordReset(email, options) {
|
|
679
|
+
await this.send("/api/auth/request-password-reset", {
|
|
680
|
+
method: "POST",
|
|
681
|
+
body: { email },
|
|
682
|
+
...options
|
|
683
|
+
});
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Confirms a password reset with token
|
|
688
|
+
*/
|
|
689
|
+
async confirmPasswordReset(token, password, _passwordConfirm, options) {
|
|
690
|
+
await this.send("/api/auth/confirm-password-reset", {
|
|
691
|
+
method: "POST",
|
|
692
|
+
body: { token, password },
|
|
693
|
+
...options
|
|
694
|
+
});
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
// src/services/LogService.ts
|
|
700
|
+
var LogService = class extends BaseService {
|
|
701
|
+
/**
|
|
702
|
+
* Returns a paginated list of system access and error logs (Superuser required)
|
|
703
|
+
*/
|
|
704
|
+
async getList(page = 1, perPage = 50, options) {
|
|
705
|
+
const query = {
|
|
706
|
+
page,
|
|
707
|
+
limit: perPage,
|
|
708
|
+
...options
|
|
709
|
+
};
|
|
710
|
+
const res = await this.send("/api/logs", {
|
|
711
|
+
method: "GET",
|
|
712
|
+
query,
|
|
713
|
+
...options
|
|
714
|
+
});
|
|
715
|
+
return {
|
|
716
|
+
page: res.page,
|
|
717
|
+
perPage: res.limit,
|
|
718
|
+
totalItems: res.total,
|
|
719
|
+
totalPages: res.totalPages,
|
|
720
|
+
items: res.items
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Returns timeline distribution of system requests and errors
|
|
725
|
+
*/
|
|
726
|
+
async getTimeline(options) {
|
|
727
|
+
return this.send("/api/logs/timeline", {
|
|
728
|
+
method: "GET",
|
|
729
|
+
...options
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Returns summary stats for server logs
|
|
734
|
+
*/
|
|
735
|
+
async getStats(options) {
|
|
736
|
+
return this.send("/api/logs/stats", {
|
|
737
|
+
method: "GET",
|
|
738
|
+
...options
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Deletes a batch of logs by IDs or all matching filter criteria
|
|
743
|
+
*/
|
|
744
|
+
async deleteBatch(params, options) {
|
|
745
|
+
return this.send("/api/logs/delete-batch", {
|
|
746
|
+
method: "POST",
|
|
747
|
+
body: params,
|
|
748
|
+
...options
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Clears all system logs
|
|
753
|
+
*/
|
|
754
|
+
async clear(options) {
|
|
755
|
+
await this.send("/api/logs", {
|
|
756
|
+
method: "DELETE",
|
|
757
|
+
...options
|
|
758
|
+
});
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
// src/services/RealtimeService.ts
|
|
764
|
+
var import_socket = require("socket.io-client");
|
|
765
|
+
var RealtimeService = class extends BaseService {
|
|
766
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
767
|
+
socket = null;
|
|
768
|
+
isConnecting = false;
|
|
769
|
+
/**
|
|
770
|
+
* Checks if realtime is currently connected
|
|
771
|
+
*/
|
|
772
|
+
get isConnected() {
|
|
773
|
+
return !!(this.socket && this.socket.connected);
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Subscribes a listener callback to a specific topic or collection
|
|
777
|
+
*/
|
|
778
|
+
async subscribe(topic, listener) {
|
|
779
|
+
const trimmedTopic = (topic || "*").trim();
|
|
780
|
+
if (!this.subscriptions.has(trimmedTopic)) {
|
|
781
|
+
this.subscriptions.set(trimmedTopic, /* @__PURE__ */ new Set());
|
|
782
|
+
}
|
|
783
|
+
this.subscriptions.get(trimmedTopic).add(listener);
|
|
784
|
+
this.ensureConnection();
|
|
785
|
+
if (this.socket && this.socket.connected) {
|
|
786
|
+
this.socket.emit("subscribe", trimmedTopic);
|
|
787
|
+
}
|
|
788
|
+
return () => {
|
|
789
|
+
this.unsubscribeFromTopic(trimmedTopic, listener);
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Unsubscribes all listeners or a specific listener from a topic
|
|
794
|
+
*/
|
|
795
|
+
async unsubscribe(topic) {
|
|
796
|
+
if (!topic) {
|
|
797
|
+
if (this.socket && this.socket.connected && this.subscriptions.size > 0) {
|
|
798
|
+
this.socket.emit("unsubscribe", Array.from(this.subscriptions.keys()));
|
|
799
|
+
}
|
|
800
|
+
this.subscriptions.clear();
|
|
801
|
+
this.disconnect();
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
const trimmedTopic = topic.trim();
|
|
805
|
+
this.subscriptions.delete(trimmedTopic);
|
|
806
|
+
if (this.socket && this.socket.connected) {
|
|
807
|
+
this.socket.emit("unsubscribe", trimmedTopic);
|
|
808
|
+
}
|
|
809
|
+
if (this.subscriptions.size === 0) {
|
|
810
|
+
this.disconnect();
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Publishes a custom event to a realtime topic
|
|
815
|
+
*/
|
|
816
|
+
async publish(topic, data, event) {
|
|
817
|
+
if (this.socket && this.socket.connected) {
|
|
818
|
+
this.socket.emit("publish", { topic, data, event });
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
await this.send("/api/realtime/publish", {
|
|
822
|
+
method: "POST",
|
|
823
|
+
body: {
|
|
824
|
+
topic,
|
|
825
|
+
data,
|
|
826
|
+
event
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
unsubscribeFromTopic(topic, listener) {
|
|
831
|
+
const set = this.subscriptions.get(topic);
|
|
832
|
+
if (set) {
|
|
833
|
+
set.delete(listener);
|
|
834
|
+
if (set.size === 0) {
|
|
835
|
+
this.subscriptions.delete(topic);
|
|
836
|
+
if (this.socket && this.socket.connected) {
|
|
837
|
+
this.socket.emit("unsubscribe", topic);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (this.subscriptions.size === 0) {
|
|
842
|
+
this.disconnect();
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
ensureConnection() {
|
|
846
|
+
if (this.isConnecting || this.isConnected) return;
|
|
847
|
+
this.connect();
|
|
848
|
+
}
|
|
849
|
+
connect() {
|
|
850
|
+
if (this.socket) return;
|
|
851
|
+
this.isConnecting = true;
|
|
852
|
+
const socketUrl = this.client.baseUrl;
|
|
853
|
+
this.socket = (0, import_socket.io)(socketUrl, {
|
|
854
|
+
path: "/api/socket.io",
|
|
855
|
+
auth: {
|
|
856
|
+
token: this.client.authStore.token
|
|
857
|
+
},
|
|
858
|
+
transports: ["websocket", "polling"],
|
|
859
|
+
reconnection: true,
|
|
860
|
+
reconnectionAttempts: Infinity,
|
|
861
|
+
reconnectionDelay: 1e3,
|
|
862
|
+
reconnectionDelayMax: 5e3
|
|
863
|
+
});
|
|
864
|
+
this.socket.on("connect", () => {
|
|
865
|
+
this.isConnecting = false;
|
|
866
|
+
const topics = Array.from(this.subscriptions.keys());
|
|
867
|
+
if (topics.length > 0) {
|
|
868
|
+
this.socket.emit("subscribe", topics);
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
this.socket.on("disconnect", () => {
|
|
872
|
+
this.isConnecting = false;
|
|
873
|
+
});
|
|
874
|
+
this.socket.on("connect_error", () => {
|
|
875
|
+
this.isConnecting = false;
|
|
876
|
+
});
|
|
877
|
+
this.socket.on("logs", (data) => {
|
|
878
|
+
const logData = data?.log || data;
|
|
879
|
+
this.dispatchMessage(logData, "logs");
|
|
880
|
+
});
|
|
881
|
+
this.socket.on("log", (data) => {
|
|
882
|
+
const logData = data?.log || data;
|
|
883
|
+
this.dispatchMessage(logData, "logs");
|
|
884
|
+
});
|
|
885
|
+
this.socket.on("record", (data) => {
|
|
886
|
+
const collection = data?.collection;
|
|
887
|
+
this.dispatchMessage(data, collection);
|
|
888
|
+
});
|
|
889
|
+
this.socket.onAny((eventName, ...args) => {
|
|
890
|
+
if ([
|
|
891
|
+
"connect",
|
|
892
|
+
"disconnect",
|
|
893
|
+
"connect_error",
|
|
894
|
+
"connected",
|
|
895
|
+
"subscriptions",
|
|
896
|
+
"pong"
|
|
897
|
+
].includes(eventName)) {
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
const data = args[0];
|
|
901
|
+
this.dispatchMessage(data, eventName);
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
dispatchMessage(data, explicitTopic) {
|
|
905
|
+
if (!data) return;
|
|
906
|
+
const collection = explicitTopic || data.collection || data.topic;
|
|
907
|
+
const recordId = data.record?.id;
|
|
908
|
+
for (const [topic, listeners] of this.subscriptions.entries()) {
|
|
909
|
+
let isMatch = false;
|
|
910
|
+
if (topic === "*" || topic === collection) {
|
|
911
|
+
isMatch = true;
|
|
912
|
+
} else if (recordId && topic === `${collection}/${recordId}`) {
|
|
913
|
+
isMatch = true;
|
|
914
|
+
} else if (topic === `${collection}/*`) {
|
|
915
|
+
isMatch = true;
|
|
916
|
+
}
|
|
917
|
+
if (isMatch) {
|
|
918
|
+
for (const listener of listeners) {
|
|
919
|
+
try {
|
|
920
|
+
listener(data);
|
|
921
|
+
} catch (err) {
|
|
922
|
+
console.error("[AlsaBase Realtime] Listener error:", err);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
disconnect() {
|
|
929
|
+
this.isConnecting = false;
|
|
930
|
+
if (this.socket) {
|
|
931
|
+
try {
|
|
932
|
+
this.socket.disconnect();
|
|
933
|
+
} catch {
|
|
934
|
+
}
|
|
935
|
+
this.socket = null;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
// src/services/FileService.ts
|
|
941
|
+
var FileService = class extends BaseService {
|
|
942
|
+
/**
|
|
943
|
+
* Generates a URL for accessing an uploaded record file asset
|
|
944
|
+
*
|
|
945
|
+
* @param record Record object or record ID string
|
|
946
|
+
* @param filename Filename of the uploaded asset
|
|
947
|
+
* @param queryParams Optional query parameters (e.g. thumb, download)
|
|
948
|
+
*/
|
|
949
|
+
getUrl(record, filename, queryParams) {
|
|
950
|
+
if (!filename) return "";
|
|
951
|
+
let fileUrlPath;
|
|
952
|
+
if (typeof record === "object" && record !== null) {
|
|
953
|
+
const col = record.collectionName || record.collectionId || "";
|
|
954
|
+
const id = record.id || "";
|
|
955
|
+
if (col && id) {
|
|
956
|
+
fileUrlPath = `/api/static-files/file/${encodeURIComponent(col)}/${encodeURIComponent(id)}/${encodeURIComponent(filename)}`;
|
|
957
|
+
} else if (id) {
|
|
958
|
+
fileUrlPath = `/api/static-files/file/${encodeURIComponent(id)}/${encodeURIComponent(filename)}`;
|
|
959
|
+
} else {
|
|
960
|
+
fileUrlPath = `/api/static-files/file/${encodeURIComponent(filename)}`;
|
|
961
|
+
}
|
|
962
|
+
} else if (typeof record === "string" && record) {
|
|
963
|
+
fileUrlPath = `/api/static-files/file/${encodeURIComponent(record)}/${encodeURIComponent(filename)}`;
|
|
964
|
+
} else {
|
|
965
|
+
fileUrlPath = `/api/static-files/file/${encodeURIComponent(filename)}`;
|
|
966
|
+
}
|
|
967
|
+
return this.client.buildUrl(fileUrlPath, queryParams);
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Generates a URL for a public website static asset hosted in _public
|
|
971
|
+
*
|
|
972
|
+
* @param relPath Relative path inside _public (e.g. 'images/banner.png' or 'maps/mafia/lost-heaven/')
|
|
973
|
+
*/
|
|
974
|
+
getPublicUrl(relPath) {
|
|
975
|
+
const clean = relPath.replace(/^[\/\\]+/, "");
|
|
976
|
+
return `${this.client.baseUrl.replace(/\/+$/, "")}/${clean}`;
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Lists all files in the _public directory
|
|
980
|
+
*/
|
|
981
|
+
async listPublicFiles(options) {
|
|
982
|
+
return this.send("/api/static-files", {
|
|
983
|
+
method: "GET",
|
|
984
|
+
...options
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Gets a folder tree for the _public directory
|
|
989
|
+
*/
|
|
990
|
+
async getPublicTree(dir = "", options) {
|
|
991
|
+
return this.send(`/api/static-files/tree?dir=${encodeURIComponent(dir)}`, {
|
|
992
|
+
method: "GET",
|
|
993
|
+
...options
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Reads a file's content from the _public directory
|
|
998
|
+
*/
|
|
999
|
+
async readPublicFile(path, options) {
|
|
1000
|
+
return this.send(`/api/static-files/file?path=${encodeURIComponent(path)}`, {
|
|
1001
|
+
method: "GET",
|
|
1002
|
+
...options
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Saves or updates a file in the _public directory (Superuser required)
|
|
1007
|
+
*/
|
|
1008
|
+
async savePublicFile(name, content, isBase64 = false, options) {
|
|
1009
|
+
return this.send("/api/static-files/file", {
|
|
1010
|
+
method: "POST",
|
|
1011
|
+
body: { name, content, isBase64 },
|
|
1012
|
+
...options
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Uploads a batch of files to the _public directory (Superuser required)
|
|
1017
|
+
*/
|
|
1018
|
+
async uploadPublicBatch(files, options) {
|
|
1019
|
+
return this.send("/api/static-files/upload-batch", {
|
|
1020
|
+
method: "POST",
|
|
1021
|
+
body: { files },
|
|
1022
|
+
...options
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Deletes a file in the _public directory (Superuser required)
|
|
1027
|
+
*/
|
|
1028
|
+
async deletePublicFile(path, options) {
|
|
1029
|
+
return this.send(`/api/static-files/file?path=${encodeURIComponent(path)}`, {
|
|
1030
|
+
method: "DELETE",
|
|
1031
|
+
...options
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Creates a folder inside the _public directory (Superuser required)
|
|
1036
|
+
*/
|
|
1037
|
+
async createPublicFolder(path, options) {
|
|
1038
|
+
return this.send("/api/static-files/folder", {
|
|
1039
|
+
method: "POST",
|
|
1040
|
+
body: { path },
|
|
1041
|
+
...options
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Deletes a folder and all its contents inside the _public directory (Superuser required)
|
|
1046
|
+
*/
|
|
1047
|
+
async deletePublicFolder(path, options) {
|
|
1048
|
+
return this.send(`/api/static-files/folder?path=${encodeURIComponent(path)}`, {
|
|
1049
|
+
method: "DELETE",
|
|
1050
|
+
...options
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
|
|
1055
|
+
// src/services/HooksService.ts
|
|
1056
|
+
var HooksService = class extends BaseService {
|
|
1057
|
+
/**
|
|
1058
|
+
* Returns an overview of all active hooks, routes, crons, and commands (Superuser required)
|
|
1059
|
+
*/
|
|
1060
|
+
async getOverview(options) {
|
|
1061
|
+
return this.send("/api/hooks", {
|
|
1062
|
+
method: "GET",
|
|
1063
|
+
...options
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Lists all files in the _hooks directory (Superuser required)
|
|
1068
|
+
*/
|
|
1069
|
+
async listFiles(options) {
|
|
1070
|
+
return this.send("/api/hooks/files", {
|
|
1071
|
+
method: "GET",
|
|
1072
|
+
...options
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Gets a folder tree for the _hooks directory (Superuser required)
|
|
1077
|
+
*/
|
|
1078
|
+
async getTree(dir = "", options) {
|
|
1079
|
+
return this.send(`/api/hooks/tree?dir=${encodeURIComponent(dir)}`, {
|
|
1080
|
+
method: "GET",
|
|
1081
|
+
...options
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Reads a file's content from the _hooks directory (Superuser required)
|
|
1086
|
+
*/
|
|
1087
|
+
async readFile(path, options) {
|
|
1088
|
+
return this.send(`/api/hooks/files/file?path=${encodeURIComponent(path)}`, {
|
|
1089
|
+
method: "GET",
|
|
1090
|
+
...options
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Saves or updates a file in the _hooks directory (Superuser required)
|
|
1095
|
+
*/
|
|
1096
|
+
async saveFile(name, content, isBase64 = false, options) {
|
|
1097
|
+
return this.send("/api/hooks/files", {
|
|
1098
|
+
method: "POST",
|
|
1099
|
+
body: { name, content, isBase64 },
|
|
1100
|
+
...options
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Uploads a batch of files to the _hooks directory (Superuser required)
|
|
1105
|
+
*/
|
|
1106
|
+
async uploadBatch(files, options) {
|
|
1107
|
+
return this.send("/api/hooks/files/upload-batch", {
|
|
1108
|
+
method: "POST",
|
|
1109
|
+
body: { files },
|
|
1110
|
+
...options
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Deletes a file in the _hooks directory (Superuser required)
|
|
1115
|
+
*/
|
|
1116
|
+
async deleteFile(path, options) {
|
|
1117
|
+
return this.send(`/api/hooks/files/file?path=${encodeURIComponent(path)}`, {
|
|
1118
|
+
method: "DELETE",
|
|
1119
|
+
...options
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Creates a folder inside the _hooks directory (Superuser required)
|
|
1124
|
+
*/
|
|
1125
|
+
async createFolder(path, options) {
|
|
1126
|
+
return this.send("/api/hooks/files/folder", {
|
|
1127
|
+
method: "POST",
|
|
1128
|
+
body: { path },
|
|
1129
|
+
...options
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Deletes a folder and all its contents inside the _hooks directory (Superuser required)
|
|
1134
|
+
*/
|
|
1135
|
+
async deleteFolder(path, options) {
|
|
1136
|
+
return this.send(`/api/hooks/files/folder?path=${encodeURIComponent(path)}`, {
|
|
1137
|
+
method: "DELETE",
|
|
1138
|
+
...options
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Manually triggers execution of a scheduled cron job (Superuser required)
|
|
1143
|
+
*/
|
|
1144
|
+
async triggerCron(name, options) {
|
|
1145
|
+
return this.send(`/api/hooks/cron/${encodeURIComponent(name)}/trigger`, {
|
|
1146
|
+
method: "POST",
|
|
1147
|
+
...options
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Cancels a running cron job (Superuser required)
|
|
1152
|
+
*/
|
|
1153
|
+
async cancelCron(executionId, name, options) {
|
|
1154
|
+
return this.send("/api/hooks/cron/cancel", {
|
|
1155
|
+
method: "POST",
|
|
1156
|
+
body: { executionId, name },
|
|
1157
|
+
...options
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Runs a custom hook CLI command (Superuser required)
|
|
1162
|
+
*/
|
|
1163
|
+
async runCommand(name, args, options) {
|
|
1164
|
+
return this.send(`/api/hooks/commands/${encodeURIComponent(name)}/run`, {
|
|
1165
|
+
method: "POST",
|
|
1166
|
+
body: { args },
|
|
1167
|
+
...options
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Cancels a running CLI command (Superuser required)
|
|
1172
|
+
*/
|
|
1173
|
+
async cancelCommand(executionId, name, options) {
|
|
1174
|
+
return this.send("/api/hooks/commands/cancel", {
|
|
1175
|
+
method: "POST",
|
|
1176
|
+
body: { executionId, name },
|
|
1177
|
+
...options
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Reloads all hook files and re-registers custom routes (Superuser required)
|
|
1182
|
+
*/
|
|
1183
|
+
async reload(options) {
|
|
1184
|
+
return this.send("/api/hooks/reload", {
|
|
1185
|
+
method: "POST",
|
|
1186
|
+
...options
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
// src/Client.ts
|
|
1192
|
+
var AlsaBase = class {
|
|
1193
|
+
baseUrl;
|
|
1194
|
+
authStore;
|
|
1195
|
+
superusers;
|
|
1196
|
+
collections;
|
|
1197
|
+
logs;
|
|
1198
|
+
realtime;
|
|
1199
|
+
files;
|
|
1200
|
+
hooks;
|
|
1201
|
+
recordServices = /* @__PURE__ */ new Map();
|
|
1202
|
+
cancelControllers = /* @__PURE__ */ new Map();
|
|
1203
|
+
constructor(baseUrl = "/", authStore) {
|
|
1204
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
1205
|
+
this.authStore = authStore || (typeof window !== "undefined" ? new LocalAuthStore() : new BaseAuthStore());
|
|
1206
|
+
this.superusers = new SuperuserService(this);
|
|
1207
|
+
this.collections = new CollectionService(this);
|
|
1208
|
+
this.logs = new LogService(this);
|
|
1209
|
+
this.realtime = new RealtimeService(this);
|
|
1210
|
+
this.files = new FileService(this);
|
|
1211
|
+
this.hooks = new HooksService(this);
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Alias for superusers service (admins)
|
|
1215
|
+
*/
|
|
1216
|
+
get admins() {
|
|
1217
|
+
return this.superusers;
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Returns a RecordService instance for the specified collection
|
|
1221
|
+
*/
|
|
1222
|
+
collection(idOrName) {
|
|
1223
|
+
if (!this.recordServices.has(idOrName)) {
|
|
1224
|
+
this.recordServices.set(
|
|
1225
|
+
idOrName,
|
|
1226
|
+
new RecordService(this, idOrName)
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
return this.recordServices.get(idOrName);
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Returns the schema and column definitions for a table/collection (Requires Superuser authentication)
|
|
1233
|
+
*/
|
|
1234
|
+
async getSchema(idOrName, options) {
|
|
1235
|
+
return this.collections.getOne(idOrName, options);
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Returns the schema and column definitions for a table/collection (Requires Superuser authentication)
|
|
1239
|
+
* Alias for getSchema()
|
|
1240
|
+
*/
|
|
1241
|
+
async getTableSchema(idOrName, options) {
|
|
1242
|
+
return this.collections.getOne(idOrName, options);
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Helper to format filter expression string with parameterized values
|
|
1246
|
+
*/
|
|
1247
|
+
filter(expr, params = {}) {
|
|
1248
|
+
if (!params || Object.keys(params).length === 0) {
|
|
1249
|
+
return expr;
|
|
1250
|
+
}
|
|
1251
|
+
let result = expr;
|
|
1252
|
+
for (const [key, val] of Object.entries(params)) {
|
|
1253
|
+
let formattedVal;
|
|
1254
|
+
if (val === null || val === void 0) {
|
|
1255
|
+
formattedVal = "null";
|
|
1256
|
+
} else if (typeof val === "number" || typeof val === "boolean") {
|
|
1257
|
+
formattedVal = String(val);
|
|
1258
|
+
} else if (val instanceof Date) {
|
|
1259
|
+
formattedVal = `"${val.toISOString()}"`;
|
|
1260
|
+
} else {
|
|
1261
|
+
formattedVal = `"${String(val).replace(/"/g, '\\"')}"`;
|
|
1262
|
+
}
|
|
1263
|
+
const pattern = new RegExp(`{:?\\b${key}\\b}`, "g");
|
|
1264
|
+
result = result.replace(pattern, formattedVal);
|
|
1265
|
+
}
|
|
1266
|
+
return result;
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Cancels a pending request with matching requestKey
|
|
1270
|
+
*/
|
|
1271
|
+
cancelRequest(requestKey) {
|
|
1272
|
+
const controller = this.cancelControllers.get(requestKey);
|
|
1273
|
+
if (controller) {
|
|
1274
|
+
controller.abort();
|
|
1275
|
+
this.cancelControllers.delete(requestKey);
|
|
1276
|
+
}
|
|
1277
|
+
return this;
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Cancels all pending requests
|
|
1281
|
+
*/
|
|
1282
|
+
cancelAllRequests() {
|
|
1283
|
+
for (const controller of this.cancelControllers.values()) {
|
|
1284
|
+
controller.abort();
|
|
1285
|
+
}
|
|
1286
|
+
this.cancelControllers.clear();
|
|
1287
|
+
return this;
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Builds an absolute URL with query parameters
|
|
1291
|
+
*/
|
|
1292
|
+
buildUrl(path, query) {
|
|
1293
|
+
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
|
1294
|
+
let url = `${this.baseUrl}${cleanPath}`;
|
|
1295
|
+
if (query && Object.keys(query).length > 0) {
|
|
1296
|
+
const searchParams = new URLSearchParams();
|
|
1297
|
+
for (const [k, v] of Object.entries(query)) {
|
|
1298
|
+
if (v !== void 0 && v !== null && v !== "") {
|
|
1299
|
+
searchParams.append(k, typeof v === "object" ? JSON.stringify(v) : String(v));
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
const qs = searchParams.toString();
|
|
1303
|
+
if (qs) {
|
|
1304
|
+
url += (url.includes("?") ? "&" : "?") + qs;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
return url;
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Dispatches an HTTP request to the AlsaBase server
|
|
1311
|
+
*/
|
|
1312
|
+
async send(path, options = {}) {
|
|
1313
|
+
const url = this.buildUrl(path, options.query || options.params);
|
|
1314
|
+
let requestKey = options.requestKey;
|
|
1315
|
+
if (requestKey === void 0 && options.autoCancel !== false && (options.method === "GET" || !options.method)) {
|
|
1316
|
+
requestKey = `${options.method || "GET"} ${url}`;
|
|
1317
|
+
}
|
|
1318
|
+
let controller;
|
|
1319
|
+
if (requestKey) {
|
|
1320
|
+
this.cancelRequest(requestKey);
|
|
1321
|
+
controller = new AbortController();
|
|
1322
|
+
this.cancelControllers.set(requestKey, controller);
|
|
1323
|
+
}
|
|
1324
|
+
const headers = {
|
|
1325
|
+
...options.headers || {}
|
|
1326
|
+
};
|
|
1327
|
+
if (!headers["Authorization"] && !headers["authorization"] && this.authStore.token) {
|
|
1328
|
+
headers["Authorization"] = `Bearer ${this.authStore.token}`;
|
|
1329
|
+
}
|
|
1330
|
+
let body = options.body;
|
|
1331
|
+
if (body !== void 0 && body !== null && typeof body === "object" && typeof body.append !== "function" && !(typeof Blob !== "undefined" && body instanceof Blob) && !(typeof ArrayBuffer !== "undefined" && body instanceof ArrayBuffer)) {
|
|
1332
|
+
if (!headers["Content-Type"] && !headers["content-type"]) {
|
|
1333
|
+
headers["Content-Type"] = "application/json";
|
|
1334
|
+
}
|
|
1335
|
+
body = JSON.stringify(body);
|
|
1336
|
+
}
|
|
1337
|
+
const fetchOptions = {
|
|
1338
|
+
...options,
|
|
1339
|
+
headers,
|
|
1340
|
+
body,
|
|
1341
|
+
signal: controller ? controller.signal : options.signal
|
|
1342
|
+
};
|
|
1343
|
+
try {
|
|
1344
|
+
const response = await fetch(url, fetchOptions);
|
|
1345
|
+
if (requestKey) {
|
|
1346
|
+
this.cancelControllers.delete(requestKey);
|
|
1347
|
+
}
|
|
1348
|
+
let data = null;
|
|
1349
|
+
const contentType = response.headers.get("content-type") || "";
|
|
1350
|
+
if (contentType.includes("application/json")) {
|
|
1351
|
+
data = await response.json().catch(() => null);
|
|
1352
|
+
} else {
|
|
1353
|
+
data = await response.text().catch(() => null);
|
|
1354
|
+
}
|
|
1355
|
+
if (!response.ok) {
|
|
1356
|
+
throw new ClientResponseError({
|
|
1357
|
+
url,
|
|
1358
|
+
status: response.status,
|
|
1359
|
+
data,
|
|
1360
|
+
response
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
return data;
|
|
1364
|
+
} catch (err) {
|
|
1365
|
+
if (requestKey) {
|
|
1366
|
+
this.cancelControllers.delete(requestKey);
|
|
1367
|
+
}
|
|
1368
|
+
if (err.name === "AbortError") {
|
|
1369
|
+
throw new ClientResponseError({
|
|
1370
|
+
url,
|
|
1371
|
+
status: 0,
|
|
1372
|
+
data: { message: "The request was autocancelled or aborted." },
|
|
1373
|
+
isAbort: true,
|
|
1374
|
+
originalError: err
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
if (err instanceof ClientResponseError) {
|
|
1378
|
+
throw err;
|
|
1379
|
+
}
|
|
1380
|
+
throw new ClientResponseError({
|
|
1381
|
+
url,
|
|
1382
|
+
status: 0,
|
|
1383
|
+
data: { message: err.message },
|
|
1384
|
+
originalError: err
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
|
|
1390
|
+
// src/stores/AsyncAuthStore.ts
|
|
1391
|
+
var AsyncAuthStore = class extends BaseAuthStore {
|
|
1392
|
+
_saveHandler;
|
|
1393
|
+
_clearHandler;
|
|
1394
|
+
constructor(options = {}) {
|
|
1395
|
+
super();
|
|
1396
|
+
this._saveHandler = options.save;
|
|
1397
|
+
this._clearHandler = options.clear;
|
|
1398
|
+
if (options.initial) {
|
|
1399
|
+
try {
|
|
1400
|
+
const parsed = JSON.parse(options.initial);
|
|
1401
|
+
if (parsed && typeof parsed === "object") {
|
|
1402
|
+
this._token = parsed.token || "";
|
|
1403
|
+
this._model = parsed.model || null;
|
|
1404
|
+
}
|
|
1405
|
+
} catch {
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
save(token, model) {
|
|
1410
|
+
super.save(token, model);
|
|
1411
|
+
if (this._saveHandler) {
|
|
1412
|
+
this._saveHandler(JSON.stringify({ token: this._token, model: this._model })).catch(
|
|
1413
|
+
(err) => console.error("AsyncAuthStore save failed:", err)
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
clear() {
|
|
1418
|
+
super.clear();
|
|
1419
|
+
if (this._clearHandler) {
|
|
1420
|
+
this._clearHandler().catch(
|
|
1421
|
+
(err) => console.error("AsyncAuthStore clear failed:", err)
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
// src/index.ts
|
|
1428
|
+
var index_default = AlsaBase;
|