fauxbase 0.1.1 → 0.4.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 +187 -513
- package/dist/index.cjs +937 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +227 -4
- package/dist/index.d.ts +227 -4
- package/dist/index.js +924 -9
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
package/dist/index.js
CHANGED
|
@@ -36,6 +36,29 @@ var ForbiddenError = class extends FauxbaseError {
|
|
|
36
36
|
this.name = "ForbiddenError";
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
|
+
var NetworkError = class extends FauxbaseError {
|
|
40
|
+
constructor(message = "Network request failed") {
|
|
41
|
+
super(message, "NETWORK");
|
|
42
|
+
this.name = "NetworkError";
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var TimeoutError = class extends FauxbaseError {
|
|
46
|
+
constructor(message = "Request timed out") {
|
|
47
|
+
super(message, "TIMEOUT");
|
|
48
|
+
this.name = "TimeoutError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var HttpError = class extends FauxbaseError {
|
|
52
|
+
status;
|
|
53
|
+
constructor(message, status, details) {
|
|
54
|
+
super(message, "HTTP", details);
|
|
55
|
+
this.name = "HttpError";
|
|
56
|
+
this.status = status;
|
|
57
|
+
}
|
|
58
|
+
toJSON() {
|
|
59
|
+
return { ...super.toJSON(), status: this.status };
|
|
60
|
+
}
|
|
61
|
+
};
|
|
39
62
|
|
|
40
63
|
// src/registry.ts
|
|
41
64
|
var fieldRegistry = /* @__PURE__ */ new Map();
|
|
@@ -157,11 +180,16 @@ function afterUpdate() {
|
|
|
157
180
|
var Service = class {
|
|
158
181
|
driver;
|
|
159
182
|
resourceName;
|
|
183
|
+
client;
|
|
160
184
|
/** @internal — called by createClient to wire the service */
|
|
161
185
|
_init(driver, resourceName) {
|
|
162
186
|
this.driver = driver;
|
|
163
187
|
this.resourceName = resourceName;
|
|
164
188
|
}
|
|
189
|
+
/** @internal — called by createClient to give services access to the client */
|
|
190
|
+
_setClient(client) {
|
|
191
|
+
this.client = client;
|
|
192
|
+
}
|
|
165
193
|
async list(query = {}) {
|
|
166
194
|
return this.driver.list(this.resourceName, query);
|
|
167
195
|
}
|
|
@@ -202,6 +230,173 @@ var Service = class {
|
|
|
202
230
|
}
|
|
203
231
|
};
|
|
204
232
|
|
|
233
|
+
// src/auth.ts
|
|
234
|
+
var AuthService = class extends Service {
|
|
235
|
+
authState = null;
|
|
236
|
+
saveState = null;
|
|
237
|
+
httpDriver = null;
|
|
238
|
+
/** @internal — called by createClient to wire persistence */
|
|
239
|
+
_initAuth(loadState, saveState) {
|
|
240
|
+
this.saveState = saveState;
|
|
241
|
+
this.authState = loadState();
|
|
242
|
+
}
|
|
243
|
+
/** @internal — called by createClient when using HttpDriver */
|
|
244
|
+
_setHttpMode(driver) {
|
|
245
|
+
this.httpDriver = driver;
|
|
246
|
+
}
|
|
247
|
+
async login(credentials) {
|
|
248
|
+
if (this.httpDriver) {
|
|
249
|
+
return this.httpLogin(credentials);
|
|
250
|
+
}
|
|
251
|
+
return this.localLogin(credentials);
|
|
252
|
+
}
|
|
253
|
+
async register(data) {
|
|
254
|
+
if (this.httpDriver) {
|
|
255
|
+
return this.httpRegister(data);
|
|
256
|
+
}
|
|
257
|
+
return this.localRegister(data);
|
|
258
|
+
}
|
|
259
|
+
logout() {
|
|
260
|
+
this.authState = null;
|
|
261
|
+
this.persistState();
|
|
262
|
+
}
|
|
263
|
+
get currentUser() {
|
|
264
|
+
return this.authState ? { id: this.authState.userId, email: this.authState.email } : null;
|
|
265
|
+
}
|
|
266
|
+
get isLoggedIn() {
|
|
267
|
+
return this.authState !== null;
|
|
268
|
+
}
|
|
269
|
+
get token() {
|
|
270
|
+
return this.authState?.token ?? null;
|
|
271
|
+
}
|
|
272
|
+
hasRole(role) {
|
|
273
|
+
return this.authState?.role === role;
|
|
274
|
+
}
|
|
275
|
+
getAuthContext() {
|
|
276
|
+
if (!this.authState) return null;
|
|
277
|
+
return {
|
|
278
|
+
userId: this.authState.userId,
|
|
279
|
+
userName: this.authState.userName
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
// --- Local mode (original implementation) ---
|
|
283
|
+
async localLogin(credentials) {
|
|
284
|
+
const { items } = await this.list({ filter: { email: credentials.email } });
|
|
285
|
+
if (items.length === 0) {
|
|
286
|
+
throw new NotFoundError("Invalid email or password");
|
|
287
|
+
}
|
|
288
|
+
const user = items[0];
|
|
289
|
+
if (user.password !== credentials.password) {
|
|
290
|
+
throw new ForbiddenError("Invalid email or password");
|
|
291
|
+
}
|
|
292
|
+
this.authState = {
|
|
293
|
+
userId: user.id,
|
|
294
|
+
email: user.email,
|
|
295
|
+
userName: user.name || user.email,
|
|
296
|
+
role: user.role,
|
|
297
|
+
token: this.generateToken(user)
|
|
298
|
+
};
|
|
299
|
+
this.persistState();
|
|
300
|
+
return user;
|
|
301
|
+
}
|
|
302
|
+
async localRegister(data) {
|
|
303
|
+
const email = data.email;
|
|
304
|
+
if (email) {
|
|
305
|
+
const { items } = await this.list({ filter: { email } });
|
|
306
|
+
if (items.length > 0) {
|
|
307
|
+
throw new ConflictError("Email already registered");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const { data: user } = await this.create(data);
|
|
311
|
+
const u = user;
|
|
312
|
+
this.authState = {
|
|
313
|
+
userId: u.id,
|
|
314
|
+
email: u.email,
|
|
315
|
+
userName: u.name || u.email,
|
|
316
|
+
role: u.role,
|
|
317
|
+
token: this.generateToken(u)
|
|
318
|
+
};
|
|
319
|
+
this.persistState();
|
|
320
|
+
return user;
|
|
321
|
+
}
|
|
322
|
+
// --- HTTP mode ---
|
|
323
|
+
async httpLogin(credentials) {
|
|
324
|
+
const preset = this.httpDriver.preset;
|
|
325
|
+
const baseUrl = this.httpDriver.baseUrl;
|
|
326
|
+
const url = `${baseUrl}${preset.auth.loginUrl}`;
|
|
327
|
+
const response = await fetch(url, {
|
|
328
|
+
method: "POST",
|
|
329
|
+
headers: { "Content-Type": "application/json" },
|
|
330
|
+
body: JSON.stringify(credentials)
|
|
331
|
+
});
|
|
332
|
+
if (!response.ok) {
|
|
333
|
+
const body2 = await response.json().catch(() => ({}));
|
|
334
|
+
if (response.status === 401 || response.status === 403) {
|
|
335
|
+
throw new ForbiddenError(body2.message ?? "Invalid email or password");
|
|
336
|
+
}
|
|
337
|
+
if (response.status === 404) {
|
|
338
|
+
throw new NotFoundError(body2.message ?? "Invalid email or password");
|
|
339
|
+
}
|
|
340
|
+
throw new ForbiddenError(body2.message ?? "Login failed");
|
|
341
|
+
}
|
|
342
|
+
const body = await response.json();
|
|
343
|
+
const token = body[preset.auth.tokenField];
|
|
344
|
+
const user = body[preset.auth.userField] ?? body;
|
|
345
|
+
this.authState = {
|
|
346
|
+
userId: user.id,
|
|
347
|
+
email: user.email ?? credentials.email,
|
|
348
|
+
userName: user.name || user.email || credentials.email,
|
|
349
|
+
role: user.role,
|
|
350
|
+
token
|
|
351
|
+
};
|
|
352
|
+
this.persistState();
|
|
353
|
+
return user;
|
|
354
|
+
}
|
|
355
|
+
async httpRegister(data) {
|
|
356
|
+
const preset = this.httpDriver.preset;
|
|
357
|
+
const baseUrl = this.httpDriver.baseUrl;
|
|
358
|
+
const url = `${baseUrl}${preset.auth.registerUrl}`;
|
|
359
|
+
const response = await fetch(url, {
|
|
360
|
+
method: "POST",
|
|
361
|
+
headers: { "Content-Type": "application/json" },
|
|
362
|
+
body: JSON.stringify(data)
|
|
363
|
+
});
|
|
364
|
+
if (!response.ok) {
|
|
365
|
+
const body2 = await response.json().catch(() => ({}));
|
|
366
|
+
if (response.status === 409) {
|
|
367
|
+
throw new ConflictError(body2.message ?? "Email already registered");
|
|
368
|
+
}
|
|
369
|
+
throw new ForbiddenError(body2.message ?? "Registration failed");
|
|
370
|
+
}
|
|
371
|
+
const body = await response.json();
|
|
372
|
+
const token = body[preset.auth.tokenField];
|
|
373
|
+
const user = body[preset.auth.userField] ?? body;
|
|
374
|
+
this.authState = {
|
|
375
|
+
userId: user.id,
|
|
376
|
+
email: user.email ?? data.email,
|
|
377
|
+
userName: user.name || user.email || data.email,
|
|
378
|
+
role: user.role,
|
|
379
|
+
token
|
|
380
|
+
};
|
|
381
|
+
this.persistState();
|
|
382
|
+
return user;
|
|
383
|
+
}
|
|
384
|
+
generateToken(user) {
|
|
385
|
+
return btoa(JSON.stringify({
|
|
386
|
+
userId: user.id,
|
|
387
|
+
email: user.email,
|
|
388
|
+
role: user.role,
|
|
389
|
+
iat: Date.now(),
|
|
390
|
+
exp: Date.now() + 24 * 60 * 60 * 1e3
|
|
391
|
+
}));
|
|
392
|
+
}
|
|
393
|
+
persistState() {
|
|
394
|
+
if (this.saveState) {
|
|
395
|
+
this.saveState(this.authState);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
205
400
|
// src/query-engine.ts
|
|
206
401
|
var OPERATORS = [
|
|
207
402
|
"startswith",
|
|
@@ -390,11 +585,143 @@ var LocalStorageBackend = class {
|
|
|
390
585
|
localStorage.setItem(`${LS_META_PREFIX}${key}`, value);
|
|
391
586
|
}
|
|
392
587
|
};
|
|
588
|
+
function idbRequest(req) {
|
|
589
|
+
return new Promise((resolve, reject) => {
|
|
590
|
+
req.onsuccess = () => resolve(req.result);
|
|
591
|
+
req.onerror = () => reject(req.error);
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
var IDB_DATA_STORE = "data";
|
|
595
|
+
var IDB_META_STORE = "meta";
|
|
596
|
+
var IndexedDBBackend = class {
|
|
597
|
+
cache = new MemoryStorage();
|
|
598
|
+
db = null;
|
|
599
|
+
_ready;
|
|
600
|
+
constructor(dbName) {
|
|
601
|
+
this._ready = this.open(dbName);
|
|
602
|
+
}
|
|
603
|
+
get ready() {
|
|
604
|
+
return this._ready;
|
|
605
|
+
}
|
|
606
|
+
open(dbName) {
|
|
607
|
+
return new Promise((resolve, reject) => {
|
|
608
|
+
const req = indexedDB.open(dbName, 1);
|
|
609
|
+
req.onupgradeneeded = () => {
|
|
610
|
+
const db = req.result;
|
|
611
|
+
if (!db.objectStoreNames.contains(IDB_DATA_STORE)) {
|
|
612
|
+
db.createObjectStore(IDB_DATA_STORE);
|
|
613
|
+
}
|
|
614
|
+
if (!db.objectStoreNames.contains(IDB_META_STORE)) {
|
|
615
|
+
db.createObjectStore(IDB_META_STORE);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
req.onsuccess = async () => {
|
|
619
|
+
this.db = req.result;
|
|
620
|
+
await this.loadAll();
|
|
621
|
+
resolve();
|
|
622
|
+
};
|
|
623
|
+
req.onerror = () => reject(req.error);
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
async loadAll() {
|
|
627
|
+
const db = this.db;
|
|
628
|
+
const tx = db.transaction([IDB_DATA_STORE, IDB_META_STORE], "readonly");
|
|
629
|
+
const dataStore = tx.objectStore(IDB_DATA_STORE);
|
|
630
|
+
const metaStore = tx.objectStore(IDB_META_STORE);
|
|
631
|
+
const dataKeys = await idbRequest(dataStore.getAllKeys());
|
|
632
|
+
const dataValues = await idbRequest(dataStore.getAll());
|
|
633
|
+
for (let i = 0; i < dataKeys.length; i++) {
|
|
634
|
+
const compositeKey = dataKeys[i];
|
|
635
|
+
const sepIdx = compositeKey.indexOf(":");
|
|
636
|
+
const resource = compositeKey.substring(0, sepIdx);
|
|
637
|
+
const id = compositeKey.substring(sepIdx + 1);
|
|
638
|
+
this.cache.set(resource, id, dataValues[i]);
|
|
639
|
+
}
|
|
640
|
+
const metaKeys = await idbRequest(metaStore.getAllKeys());
|
|
641
|
+
const metaValues = await idbRequest(metaStore.getAll());
|
|
642
|
+
for (let i = 0; i < metaKeys.length; i++) {
|
|
643
|
+
this.cache.setMeta(metaKeys[i], metaValues[i]);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
writeData(resource, id, data) {
|
|
647
|
+
if (!this.db) return;
|
|
648
|
+
const tx = this.db.transaction(IDB_DATA_STORE, "readwrite");
|
|
649
|
+
tx.objectStore(IDB_DATA_STORE).put(data, `${resource}:${id}`);
|
|
650
|
+
}
|
|
651
|
+
deleteData(resource, id) {
|
|
652
|
+
if (!this.db) return;
|
|
653
|
+
const tx = this.db.transaction(IDB_DATA_STORE, "readwrite");
|
|
654
|
+
tx.objectStore(IDB_DATA_STORE).delete(`${resource}:${id}`);
|
|
655
|
+
}
|
|
656
|
+
writeMeta(key, value) {
|
|
657
|
+
if (!this.db) return;
|
|
658
|
+
const tx = this.db.transaction(IDB_META_STORE, "readwrite");
|
|
659
|
+
tx.objectStore(IDB_META_STORE).put(value, key);
|
|
660
|
+
}
|
|
661
|
+
getAll(resource) {
|
|
662
|
+
return this.cache.getAll(resource);
|
|
663
|
+
}
|
|
664
|
+
getById(resource, id) {
|
|
665
|
+
return this.cache.getById(resource, id);
|
|
666
|
+
}
|
|
667
|
+
set(resource, id, data) {
|
|
668
|
+
this.cache.set(resource, id, data);
|
|
669
|
+
this.writeData(resource, id, data);
|
|
670
|
+
}
|
|
671
|
+
remove(resource, id) {
|
|
672
|
+
this.cache.remove(resource, id);
|
|
673
|
+
this.deleteData(resource, id);
|
|
674
|
+
}
|
|
675
|
+
clear(resource) {
|
|
676
|
+
const items = this.cache.getAll(resource);
|
|
677
|
+
this.cache.clear(resource);
|
|
678
|
+
if (this.db) {
|
|
679
|
+
const tx = this.db.transaction(IDB_DATA_STORE, "readwrite");
|
|
680
|
+
const store = tx.objectStore(IDB_DATA_STORE);
|
|
681
|
+
for (const item of items) {
|
|
682
|
+
store.delete(`${resource}:${item.id}`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
getMeta(key) {
|
|
687
|
+
return this.cache.getMeta(key);
|
|
688
|
+
}
|
|
689
|
+
setMeta(key, value) {
|
|
690
|
+
this.cache.setMeta(key, value);
|
|
691
|
+
this.writeMeta(key, value);
|
|
692
|
+
}
|
|
693
|
+
};
|
|
393
694
|
var LocalDriver = class {
|
|
394
695
|
storage;
|
|
395
696
|
entityClasses = /* @__PURE__ */ new Map();
|
|
697
|
+
authProvider = null;
|
|
698
|
+
_ready;
|
|
699
|
+
_isReady;
|
|
396
700
|
constructor(config) {
|
|
397
|
-
|
|
701
|
+
if (config.persist === "indexeddb") {
|
|
702
|
+
const backend = new IndexedDBBackend(config.dbName ?? "fauxbase");
|
|
703
|
+
this.storage = backend;
|
|
704
|
+
this._isReady = false;
|
|
705
|
+
this._ready = backend.ready.then(() => {
|
|
706
|
+
this._isReady = true;
|
|
707
|
+
});
|
|
708
|
+
} else {
|
|
709
|
+
this.storage = config.persist === "localStorage" ? new LocalStorageBackend() : new MemoryStorage();
|
|
710
|
+
this._isReady = true;
|
|
711
|
+
this._ready = Promise.resolve();
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
get ready() {
|
|
715
|
+
return this._ready;
|
|
716
|
+
}
|
|
717
|
+
get isReady() {
|
|
718
|
+
return this._isReady;
|
|
719
|
+
}
|
|
720
|
+
setAuthProvider(provider) {
|
|
721
|
+
this.authProvider = provider;
|
|
722
|
+
}
|
|
723
|
+
getStorageBackend() {
|
|
724
|
+
return this.storage;
|
|
398
725
|
}
|
|
399
726
|
registerEntity(resource, entityClass) {
|
|
400
727
|
this.entityClasses.set(resource, entityClass);
|
|
@@ -417,13 +744,20 @@ var LocalDriver = class {
|
|
|
417
744
|
async create(resource, data) {
|
|
418
745
|
const entityClass = this.entityClasses.get(resource);
|
|
419
746
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
747
|
+
const authContext = this.authProvider?.();
|
|
420
748
|
let record = {
|
|
421
749
|
...data,
|
|
422
750
|
id: data.id || generateUUID(),
|
|
423
751
|
createdAt: now,
|
|
424
752
|
updatedAt: now,
|
|
425
753
|
deletedAt: null,
|
|
426
|
-
version: 1
|
|
754
|
+
version: 1,
|
|
755
|
+
...authContext ? {
|
|
756
|
+
createdById: authContext.userId,
|
|
757
|
+
createdByName: authContext.userName,
|
|
758
|
+
updatedById: authContext.userId,
|
|
759
|
+
updatedByName: authContext.userName
|
|
760
|
+
} : {}
|
|
427
761
|
};
|
|
428
762
|
if (entityClass) {
|
|
429
763
|
record = applyDefaults(record, entityClass);
|
|
@@ -442,13 +776,18 @@ var LocalDriver = class {
|
|
|
442
776
|
if (entityClass) {
|
|
443
777
|
validateEntity(data, entityClass, false);
|
|
444
778
|
}
|
|
779
|
+
const authContext = this.authProvider?.();
|
|
445
780
|
const record = {
|
|
446
781
|
...existing,
|
|
447
782
|
...data,
|
|
448
783
|
id,
|
|
449
784
|
createdAt: existing.createdAt,
|
|
450
785
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
451
|
-
version: (existing.version || 0) + 1
|
|
786
|
+
version: (existing.version || 0) + 1,
|
|
787
|
+
...authContext ? {
|
|
788
|
+
updatedById: authContext.userId,
|
|
789
|
+
updatedByName: authContext.userName
|
|
790
|
+
} : {}
|
|
452
791
|
};
|
|
453
792
|
this.storage.set(resource, id, record);
|
|
454
793
|
const result = entityClass ? applyComputedFields(record, entityClass) : record;
|
|
@@ -460,11 +799,18 @@ var LocalDriver = class {
|
|
|
460
799
|
throw new NotFoundError(`${resource} with id "${id}" not found`);
|
|
461
800
|
}
|
|
462
801
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
802
|
+
const authContext = this.authProvider?.();
|
|
463
803
|
const record = {
|
|
464
804
|
...existing,
|
|
465
805
|
deletedAt: now,
|
|
466
806
|
updatedAt: now,
|
|
467
|
-
version: (existing.version || 0) + 1
|
|
807
|
+
version: (existing.version || 0) + 1,
|
|
808
|
+
...authContext ? {
|
|
809
|
+
deletedById: authContext.userId,
|
|
810
|
+
deletedByName: authContext.userName,
|
|
811
|
+
updatedById: authContext.userId,
|
|
812
|
+
updatedByName: authContext.userName
|
|
813
|
+
} : {}
|
|
468
814
|
};
|
|
469
815
|
this.storage.set(resource, id, record);
|
|
470
816
|
return { data: record };
|
|
@@ -527,6 +873,491 @@ var LocalDriver = class {
|
|
|
527
873
|
}
|
|
528
874
|
};
|
|
529
875
|
|
|
876
|
+
// src/presets/types.ts
|
|
877
|
+
function definePreset(config) {
|
|
878
|
+
return config;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// src/presets/default.ts
|
|
882
|
+
var defaultPreset = definePreset({
|
|
883
|
+
name: "default",
|
|
884
|
+
response: {
|
|
885
|
+
single: (raw) => ({ data: raw.data ?? raw }),
|
|
886
|
+
list: (raw) => ({
|
|
887
|
+
items: raw.items ?? raw.data ?? [],
|
|
888
|
+
meta: raw.meta ?? {}
|
|
889
|
+
}),
|
|
890
|
+
error: (raw) => ({
|
|
891
|
+
error: raw.error ?? raw.message ?? "Unknown error",
|
|
892
|
+
code: raw.code ?? "UNKNOWN",
|
|
893
|
+
details: raw.details
|
|
894
|
+
})
|
|
895
|
+
},
|
|
896
|
+
meta: { page: "page", size: "size", totalItems: "totalItems", totalPages: "totalPages" },
|
|
897
|
+
query: {
|
|
898
|
+
filterStyle: "django",
|
|
899
|
+
pageParam: "page",
|
|
900
|
+
sizeParam: "size",
|
|
901
|
+
sortParam: "sort",
|
|
902
|
+
sortFormat: "field,direction"
|
|
903
|
+
},
|
|
904
|
+
auth: {
|
|
905
|
+
loginUrl: "/auth/login",
|
|
906
|
+
registerUrl: "/auth/register",
|
|
907
|
+
logoutUrl: "/auth/logout",
|
|
908
|
+
tokenField: "token",
|
|
909
|
+
userField: "user",
|
|
910
|
+
headerFormat: "Bearer {token}"
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
// src/presets/spring-boot.ts
|
|
915
|
+
var springBootPreset = definePreset({
|
|
916
|
+
name: "spring-boot",
|
|
917
|
+
response: {
|
|
918
|
+
single: (raw) => ({ data: raw }),
|
|
919
|
+
list: (raw) => ({
|
|
920
|
+
items: raw.content ?? [],
|
|
921
|
+
meta: {
|
|
922
|
+
page: (raw.pageable?.pageNumber ?? 0) + 1,
|
|
923
|
+
size: raw.pageable?.pageSize ?? raw.size ?? 20,
|
|
924
|
+
totalItems: raw.totalElements ?? 0,
|
|
925
|
+
totalPages: raw.totalPages ?? 0
|
|
926
|
+
}
|
|
927
|
+
}),
|
|
928
|
+
error: (raw) => ({
|
|
929
|
+
error: raw.message ?? raw.error ?? "Unknown error",
|
|
930
|
+
code: raw.status?.toString() ?? "UNKNOWN",
|
|
931
|
+
details: raw.errors?.reduce?.((acc, e) => {
|
|
932
|
+
acc[e.field ?? e.code ?? "unknown"] = e.defaultMessage ?? e.message ?? "";
|
|
933
|
+
return acc;
|
|
934
|
+
}, {}) ?? void 0
|
|
935
|
+
})
|
|
936
|
+
},
|
|
937
|
+
meta: { page: "page", size: "size", totalItems: "totalElements", totalPages: "totalPages" },
|
|
938
|
+
query: {
|
|
939
|
+
filterStyle: "dot",
|
|
940
|
+
pageParam: "page",
|
|
941
|
+
sizeParam: "size",
|
|
942
|
+
sortParam: "sort",
|
|
943
|
+
sortFormat: "field,direction",
|
|
944
|
+
pageOffset: -1
|
|
945
|
+
// 0-indexed
|
|
946
|
+
},
|
|
947
|
+
auth: {
|
|
948
|
+
loginUrl: "/api/auth/login",
|
|
949
|
+
registerUrl: "/api/auth/register",
|
|
950
|
+
tokenField: "token",
|
|
951
|
+
userField: "user",
|
|
952
|
+
headerFormat: "Bearer {token}"
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
// src/presets/laravel.ts
|
|
957
|
+
var laravelPreset = definePreset({
|
|
958
|
+
name: "laravel",
|
|
959
|
+
response: {
|
|
960
|
+
single: (raw) => ({ data: raw.data ?? raw }),
|
|
961
|
+
list: (raw) => ({
|
|
962
|
+
items: raw.data ?? [],
|
|
963
|
+
meta: {
|
|
964
|
+
page: raw.meta?.current_page ?? raw.current_page ?? 1,
|
|
965
|
+
size: raw.meta?.per_page ?? raw.per_page ?? 15,
|
|
966
|
+
totalItems: raw.meta?.total ?? raw.total ?? 0,
|
|
967
|
+
totalPages: raw.meta?.last_page ?? raw.last_page ?? 0
|
|
968
|
+
}
|
|
969
|
+
}),
|
|
970
|
+
error: (raw) => ({
|
|
971
|
+
error: raw.message ?? "Unknown error",
|
|
972
|
+
code: raw.status?.toString() ?? "UNKNOWN",
|
|
973
|
+
details: raw.errors ? Object.entries(raw.errors).reduce((acc, [key, val]) => {
|
|
974
|
+
acc[key] = Array.isArray(val) ? val[0] : val;
|
|
975
|
+
return acc;
|
|
976
|
+
}, {}) : void 0
|
|
977
|
+
})
|
|
978
|
+
},
|
|
979
|
+
meta: { page: "current_page", size: "per_page", totalItems: "total", totalPages: "last_page" },
|
|
980
|
+
query: {
|
|
981
|
+
filterStyle: "bracket",
|
|
982
|
+
pageParam: "page",
|
|
983
|
+
sizeParam: "per_page",
|
|
984
|
+
sortParam: "sort",
|
|
985
|
+
sortFormat: "field,direction"
|
|
986
|
+
},
|
|
987
|
+
auth: {
|
|
988
|
+
loginUrl: "/api/login",
|
|
989
|
+
registerUrl: "/api/register",
|
|
990
|
+
logoutUrl: "/api/logout",
|
|
991
|
+
tokenField: "token",
|
|
992
|
+
userField: "user",
|
|
993
|
+
headerFormat: "Bearer {token}"
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
// src/presets/django.ts
|
|
998
|
+
var djangoPreset = definePreset({
|
|
999
|
+
name: "django",
|
|
1000
|
+
response: {
|
|
1001
|
+
single: (raw) => ({ data: raw }),
|
|
1002
|
+
list: (raw) => ({
|
|
1003
|
+
items: raw.results ?? [],
|
|
1004
|
+
meta: {
|
|
1005
|
+
page: 1,
|
|
1006
|
+
// Django REST doesn't always include page number
|
|
1007
|
+
size: raw.results?.length ?? 0,
|
|
1008
|
+
totalItems: raw.count ?? 0,
|
|
1009
|
+
totalPages: raw.count && raw.results?.length ? Math.ceil(raw.count / raw.results.length) : 0
|
|
1010
|
+
}
|
|
1011
|
+
}),
|
|
1012
|
+
error: (raw) => ({
|
|
1013
|
+
error: raw.detail ?? raw.message ?? "Unknown error",
|
|
1014
|
+
code: raw.status_code?.toString() ?? "UNKNOWN",
|
|
1015
|
+
details: typeof raw === "object" && !raw.detail ? Object.entries(raw).reduce((acc, [key, val]) => {
|
|
1016
|
+
if (key !== "status_code") {
|
|
1017
|
+
acc[key] = Array.isArray(val) ? val[0] : String(val);
|
|
1018
|
+
}
|
|
1019
|
+
return acc;
|
|
1020
|
+
}, {}) : void 0
|
|
1021
|
+
})
|
|
1022
|
+
},
|
|
1023
|
+
meta: { page: "page", size: "page_size", totalItems: "count", totalPages: "total_pages" },
|
|
1024
|
+
query: {
|
|
1025
|
+
filterStyle: "django",
|
|
1026
|
+
pageParam: "page",
|
|
1027
|
+
sizeParam: "page_size",
|
|
1028
|
+
sortParam: "ordering",
|
|
1029
|
+
sortFormat: "field,direction"
|
|
1030
|
+
},
|
|
1031
|
+
auth: {
|
|
1032
|
+
loginUrl: "/api/auth/login/",
|
|
1033
|
+
registerUrl: "/api/auth/register/",
|
|
1034
|
+
logoutUrl: "/api/auth/logout/",
|
|
1035
|
+
tokenField: "token",
|
|
1036
|
+
userField: "user",
|
|
1037
|
+
headerFormat: "Token {token}"
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
// src/presets/nestjs.ts
|
|
1042
|
+
var nestjsPreset = definePreset({
|
|
1043
|
+
name: "nestjs",
|
|
1044
|
+
response: {
|
|
1045
|
+
single: (raw) => ({ data: raw.data ?? raw }),
|
|
1046
|
+
list: (raw) => ({
|
|
1047
|
+
items: raw.data ?? raw.items ?? [],
|
|
1048
|
+
meta: raw.meta ?? {}
|
|
1049
|
+
}),
|
|
1050
|
+
error: (raw) => ({
|
|
1051
|
+
error: raw.message ?? "Unknown error",
|
|
1052
|
+
code: raw.error ?? raw.statusCode?.toString() ?? "UNKNOWN",
|
|
1053
|
+
details: raw.message && Array.isArray(raw.message) ? raw.message.reduce((acc, msg, i) => {
|
|
1054
|
+
acc[`field_${i}`] = msg;
|
|
1055
|
+
return acc;
|
|
1056
|
+
}, {}) : void 0
|
|
1057
|
+
})
|
|
1058
|
+
},
|
|
1059
|
+
meta: { page: "page", size: "limit", totalItems: "totalItems", totalPages: "totalPages" },
|
|
1060
|
+
query: {
|
|
1061
|
+
filterStyle: "nestjs",
|
|
1062
|
+
pageParam: "page",
|
|
1063
|
+
sizeParam: "limit",
|
|
1064
|
+
sortParam: "sort",
|
|
1065
|
+
sortFormat: "field:direction"
|
|
1066
|
+
},
|
|
1067
|
+
auth: {
|
|
1068
|
+
loginUrl: "/auth/login",
|
|
1069
|
+
registerUrl: "/auth/register",
|
|
1070
|
+
tokenField: "access_token",
|
|
1071
|
+
userField: "user",
|
|
1072
|
+
headerFormat: "Bearer {token}"
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
// src/presets/express.ts
|
|
1077
|
+
var expressPreset = definePreset({
|
|
1078
|
+
name: "express",
|
|
1079
|
+
response: {
|
|
1080
|
+
single: (raw) => ({ data: raw.data ?? raw }),
|
|
1081
|
+
list: (raw) => ({
|
|
1082
|
+
items: raw.data ?? raw.items ?? [],
|
|
1083
|
+
meta: raw.meta ?? {}
|
|
1084
|
+
}),
|
|
1085
|
+
error: (raw) => ({
|
|
1086
|
+
error: raw.error ?? raw.message ?? "Unknown error",
|
|
1087
|
+
code: raw.code ?? "UNKNOWN",
|
|
1088
|
+
details: raw.details
|
|
1089
|
+
})
|
|
1090
|
+
},
|
|
1091
|
+
meta: { page: "page", size: "size", totalItems: "totalItems", totalPages: "totalPages" },
|
|
1092
|
+
query: {
|
|
1093
|
+
filterStyle: "django",
|
|
1094
|
+
pageParam: "page",
|
|
1095
|
+
sizeParam: "size",
|
|
1096
|
+
sortParam: "sort",
|
|
1097
|
+
sortFormat: "field,direction"
|
|
1098
|
+
},
|
|
1099
|
+
auth: {
|
|
1100
|
+
loginUrl: "/api/auth/login",
|
|
1101
|
+
registerUrl: "/api/auth/register",
|
|
1102
|
+
logoutUrl: "/api/auth/logout",
|
|
1103
|
+
tokenField: "token",
|
|
1104
|
+
userField: "user",
|
|
1105
|
+
headerFormat: "Bearer {token}"
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
// src/presets/index.ts
|
|
1110
|
+
var presetRegistry = /* @__PURE__ */ new Map([
|
|
1111
|
+
["default", defaultPreset],
|
|
1112
|
+
["spring-boot", springBootPreset],
|
|
1113
|
+
["laravel", laravelPreset],
|
|
1114
|
+
["django", djangoPreset],
|
|
1115
|
+
["nestjs", nestjsPreset],
|
|
1116
|
+
["express", expressPreset]
|
|
1117
|
+
]);
|
|
1118
|
+
function getPreset(name) {
|
|
1119
|
+
const preset = presetRegistry.get(name);
|
|
1120
|
+
if (!preset) {
|
|
1121
|
+
throw new Error(`Unknown preset: "${name}". Available: ${Array.from(presetRegistry.keys()).join(", ")}`);
|
|
1122
|
+
}
|
|
1123
|
+
return preset;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// src/drivers/query-serializer.ts
|
|
1127
|
+
function serializeQuery(query, config) {
|
|
1128
|
+
const params = new URLSearchParams();
|
|
1129
|
+
if (query.filter) {
|
|
1130
|
+
serializeFilters(params, query.filter, config.filterStyle);
|
|
1131
|
+
}
|
|
1132
|
+
if (query.sort && config.sortParam) {
|
|
1133
|
+
const direction = query.sort.direction;
|
|
1134
|
+
if (config.sortParam === "ordering") {
|
|
1135
|
+
params.set(config.sortParam, direction === "desc" ? `-${query.sort.field}` : query.sort.field);
|
|
1136
|
+
} else if (config.sortFormat === "field:direction") {
|
|
1137
|
+
params.set(config.sortParam, `${query.sort.field}:${direction}`);
|
|
1138
|
+
} else {
|
|
1139
|
+
params.set(config.sortParam, `${query.sort.field},${direction}`);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
if (query.page !== void 0) {
|
|
1143
|
+
const pageOffset = config.pageOffset ?? 0;
|
|
1144
|
+
params.set(config.pageParam, String(query.page + pageOffset));
|
|
1145
|
+
}
|
|
1146
|
+
if (query.size !== void 0) {
|
|
1147
|
+
params.set(config.sizeParam, String(query.size));
|
|
1148
|
+
}
|
|
1149
|
+
return params;
|
|
1150
|
+
}
|
|
1151
|
+
function serializeFilters(params, filter, style) {
|
|
1152
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
1153
|
+
if (value === void 0) continue;
|
|
1154
|
+
const serialized = typeof value === "object" && !Array.isArray(value) ? JSON.stringify(value) : String(value);
|
|
1155
|
+
switch (style) {
|
|
1156
|
+
case "django":
|
|
1157
|
+
params.set(key, serialized);
|
|
1158
|
+
break;
|
|
1159
|
+
case "dot":
|
|
1160
|
+
params.set(key.replace(/__/g, "."), serialized);
|
|
1161
|
+
break;
|
|
1162
|
+
case "bracket": {
|
|
1163
|
+
const bracketKey = key.replace(/__/g, "_");
|
|
1164
|
+
params.set(`filter[${bracketKey}]`, serialized);
|
|
1165
|
+
break;
|
|
1166
|
+
}
|
|
1167
|
+
case "nestjs": {
|
|
1168
|
+
const parts = key.split("__");
|
|
1169
|
+
if (parts.length === 2) {
|
|
1170
|
+
params.set(`filter.${parts[0]}.$${parts[1]}`, serialized);
|
|
1171
|
+
} else {
|
|
1172
|
+
params.set(`filter.${key}`, serialized);
|
|
1173
|
+
}
|
|
1174
|
+
break;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// src/drivers/http.ts
|
|
1181
|
+
var HttpDriver = class {
|
|
1182
|
+
baseUrl;
|
|
1183
|
+
preset;
|
|
1184
|
+
timeout;
|
|
1185
|
+
maxRetries;
|
|
1186
|
+
baseDelay;
|
|
1187
|
+
defaultHeaders;
|
|
1188
|
+
endpoints = /* @__PURE__ */ new Map();
|
|
1189
|
+
authProvider = null;
|
|
1190
|
+
constructor(config) {
|
|
1191
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
1192
|
+
this.preset = typeof config.preset === "string" ? getPreset(config.preset ?? "default") : config.preset ?? getPreset("default");
|
|
1193
|
+
this.timeout = config.timeout ?? 3e4;
|
|
1194
|
+
this.maxRetries = config.retry?.maxRetries ?? 3;
|
|
1195
|
+
this.baseDelay = config.retry?.baseDelay ?? 300;
|
|
1196
|
+
this.defaultHeaders = config.headers ?? {};
|
|
1197
|
+
}
|
|
1198
|
+
setAuthProvider(provider) {
|
|
1199
|
+
this.authProvider = provider;
|
|
1200
|
+
}
|
|
1201
|
+
registerEndpoint(resource, endpoint) {
|
|
1202
|
+
this.endpoints.set(resource, endpoint);
|
|
1203
|
+
}
|
|
1204
|
+
getEndpoint(resource) {
|
|
1205
|
+
return this.endpoints.get(resource) ?? `/${resource}`;
|
|
1206
|
+
}
|
|
1207
|
+
buildUrl(resource, id) {
|
|
1208
|
+
const endpoint = this.getEndpoint(resource);
|
|
1209
|
+
const base = `${this.baseUrl}${endpoint}`;
|
|
1210
|
+
return id ? `${base}/${id}` : base;
|
|
1211
|
+
}
|
|
1212
|
+
buildHeaders() {
|
|
1213
|
+
const headers = {
|
|
1214
|
+
"Content-Type": "application/json",
|
|
1215
|
+
...this.defaultHeaders
|
|
1216
|
+
};
|
|
1217
|
+
const auth = this.authProvider?.();
|
|
1218
|
+
if (auth?.token) {
|
|
1219
|
+
headers["Authorization"] = this.preset.auth.headerFormat.replace("{token}", auth.token);
|
|
1220
|
+
}
|
|
1221
|
+
return headers;
|
|
1222
|
+
}
|
|
1223
|
+
async request(url, options = {}, retryCount = 0) {
|
|
1224
|
+
const controller = new AbortController();
|
|
1225
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
1226
|
+
try {
|
|
1227
|
+
const response = await fetch(url, {
|
|
1228
|
+
...options,
|
|
1229
|
+
headers: { ...this.buildHeaders(), ...options.headers ?? {} },
|
|
1230
|
+
signal: controller.signal
|
|
1231
|
+
});
|
|
1232
|
+
clearTimeout(timer);
|
|
1233
|
+
if (!response.ok) {
|
|
1234
|
+
if (response.status >= 500 && retryCount < this.maxRetries) {
|
|
1235
|
+
const delay = this.baseDelay * Math.pow(2, retryCount);
|
|
1236
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
1237
|
+
return this.request(url, options, retryCount + 1);
|
|
1238
|
+
}
|
|
1239
|
+
const body = await response.json().catch(() => ({}));
|
|
1240
|
+
this.throwMappedError(response.status, body);
|
|
1241
|
+
}
|
|
1242
|
+
if (response.status === 204) {
|
|
1243
|
+
return {};
|
|
1244
|
+
}
|
|
1245
|
+
return response.json();
|
|
1246
|
+
} catch (err) {
|
|
1247
|
+
clearTimeout(timer);
|
|
1248
|
+
if (err instanceof FauxbaseError) throw err;
|
|
1249
|
+
if (err.name === "AbortError") {
|
|
1250
|
+
throw new TimeoutError(`Request timed out after ${this.timeout}ms`);
|
|
1251
|
+
}
|
|
1252
|
+
throw new NetworkError(err.message ?? "Network request failed");
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
throwMappedError(status, body) {
|
|
1256
|
+
const parsed = this.preset.response.error(body);
|
|
1257
|
+
switch (true) {
|
|
1258
|
+
case (status === 400 || status === 422):
|
|
1259
|
+
throw new ValidationError(parsed.error, parsed.details);
|
|
1260
|
+
case (status === 401 || status === 403):
|
|
1261
|
+
throw new ForbiddenError(parsed.error);
|
|
1262
|
+
case status === 404:
|
|
1263
|
+
throw new NotFoundError(parsed.error);
|
|
1264
|
+
case status === 409:
|
|
1265
|
+
throw new ConflictError(parsed.error);
|
|
1266
|
+
default:
|
|
1267
|
+
throw new HttpError(parsed.error, status, parsed.details);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
async list(resource, query) {
|
|
1271
|
+
const url = this.buildUrl(resource);
|
|
1272
|
+
const params = serializeQuery(query, this.preset.query);
|
|
1273
|
+
const queryString = params.toString();
|
|
1274
|
+
const fullUrl = queryString ? `${url}?${queryString}` : url;
|
|
1275
|
+
const raw = await this.request(fullUrl);
|
|
1276
|
+
const parsed = this.preset.response.list(raw);
|
|
1277
|
+
return {
|
|
1278
|
+
items: parsed.items,
|
|
1279
|
+
meta: {
|
|
1280
|
+
page: parsed.meta[this.preset.meta.page] ?? parsed.meta.page ?? query.page ?? 1,
|
|
1281
|
+
size: parsed.meta[this.preset.meta.size] ?? parsed.meta.size ?? query.size ?? 20,
|
|
1282
|
+
totalItems: parsed.meta[this.preset.meta.totalItems] ?? parsed.meta.totalItems ?? 0,
|
|
1283
|
+
totalPages: parsed.meta[this.preset.meta.totalPages] ?? parsed.meta.totalPages ?? 0
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
async get(resource, id) {
|
|
1288
|
+
const url = this.buildUrl(resource, id);
|
|
1289
|
+
const raw = await this.request(url);
|
|
1290
|
+
return this.preset.response.single(raw);
|
|
1291
|
+
}
|
|
1292
|
+
async create(resource, data) {
|
|
1293
|
+
const url = this.buildUrl(resource);
|
|
1294
|
+
const raw = await this.request(url, {
|
|
1295
|
+
method: "POST",
|
|
1296
|
+
body: JSON.stringify(data)
|
|
1297
|
+
});
|
|
1298
|
+
return this.preset.response.single(raw);
|
|
1299
|
+
}
|
|
1300
|
+
async update(resource, id, data) {
|
|
1301
|
+
const url = this.buildUrl(resource, id);
|
|
1302
|
+
const raw = await this.request(url, {
|
|
1303
|
+
method: "PATCH",
|
|
1304
|
+
body: JSON.stringify(data)
|
|
1305
|
+
});
|
|
1306
|
+
return this.preset.response.single(raw);
|
|
1307
|
+
}
|
|
1308
|
+
async delete(resource, id) {
|
|
1309
|
+
const url = this.buildUrl(resource, id);
|
|
1310
|
+
const raw = await this.request(url, {
|
|
1311
|
+
method: "DELETE"
|
|
1312
|
+
});
|
|
1313
|
+
return this.preset.response.single(raw);
|
|
1314
|
+
}
|
|
1315
|
+
async count(resource, filter) {
|
|
1316
|
+
const url = `${this.buildUrl(resource)}/count`;
|
|
1317
|
+
const params = filter ? serializeQuery({ filter }, this.preset.query) : new URLSearchParams();
|
|
1318
|
+
const queryString = params.toString();
|
|
1319
|
+
const fullUrl = queryString ? `${url}?${queryString}` : url;
|
|
1320
|
+
const raw = await this.request(fullUrl);
|
|
1321
|
+
return raw.count ?? raw.data?.count ?? 0;
|
|
1322
|
+
}
|
|
1323
|
+
async bulkCreate(resource, data) {
|
|
1324
|
+
const url = `${this.buildUrl(resource)}/bulk`;
|
|
1325
|
+
const raw = await this.request(url, {
|
|
1326
|
+
method: "POST",
|
|
1327
|
+
body: JSON.stringify(data)
|
|
1328
|
+
});
|
|
1329
|
+
const parsed = this.preset.response.single(raw);
|
|
1330
|
+
return { data: Array.isArray(parsed.data) ? parsed.data : [parsed.data] };
|
|
1331
|
+
}
|
|
1332
|
+
async bulkUpdate(resource, updates) {
|
|
1333
|
+
const url = `${this.buildUrl(resource)}/bulk`;
|
|
1334
|
+
const raw = await this.request(url, {
|
|
1335
|
+
method: "PATCH",
|
|
1336
|
+
body: JSON.stringify(updates)
|
|
1337
|
+
});
|
|
1338
|
+
const parsed = this.preset.response.single(raw);
|
|
1339
|
+
return { data: Array.isArray(parsed.data) ? parsed.data : [parsed.data] };
|
|
1340
|
+
}
|
|
1341
|
+
async bulkDelete(resource, ids) {
|
|
1342
|
+
const url = `${this.buildUrl(resource)}/bulk`;
|
|
1343
|
+
const raw = await this.request(url, {
|
|
1344
|
+
method: "DELETE",
|
|
1345
|
+
body: JSON.stringify({ ids })
|
|
1346
|
+
});
|
|
1347
|
+
return { data: { count: raw.count ?? raw.data?.count ?? ids.length } };
|
|
1348
|
+
}
|
|
1349
|
+
// Seed methods are no-ops for HTTP — backend owns data
|
|
1350
|
+
seed() {
|
|
1351
|
+
}
|
|
1352
|
+
getSeedVersion() {
|
|
1353
|
+
return null;
|
|
1354
|
+
}
|
|
1355
|
+
setSeedVersion() {
|
|
1356
|
+
}
|
|
1357
|
+
clear() {
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
|
|
530
1361
|
// src/seed.ts
|
|
531
1362
|
function seed(entityClass, data) {
|
|
532
1363
|
const entityName = entityClass.name.toLowerCase();
|
|
@@ -551,19 +1382,103 @@ function simpleHash(str) {
|
|
|
551
1382
|
// src/client.ts
|
|
552
1383
|
function createClient(config) {
|
|
553
1384
|
const driverConfig = config.driver ?? { type: "local" };
|
|
554
|
-
const
|
|
1385
|
+
const defaultDriver = createDriver(driverConfig);
|
|
555
1386
|
const client = {};
|
|
1387
|
+
const overrideDrivers = /* @__PURE__ */ new Map();
|
|
1388
|
+
if (config.overrides) {
|
|
1389
|
+
for (const [name, override] of Object.entries(config.overrides)) {
|
|
1390
|
+
overrideDrivers.set(name, createDriver(override.driver));
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
556
1393
|
for (const [name, ServiceClass] of Object.entries(config.services)) {
|
|
557
1394
|
const instance = new ServiceClass();
|
|
1395
|
+
const driver = overrideDrivers.get(name) ?? defaultDriver;
|
|
558
1396
|
instance._init(driver, name);
|
|
559
1397
|
if (driver instanceof LocalDriver) {
|
|
560
1398
|
driver.registerEntity(name, instance.entity);
|
|
561
1399
|
}
|
|
1400
|
+
if (driver instanceof HttpDriver) {
|
|
1401
|
+
driver.registerEndpoint(name, instance.endpoint);
|
|
1402
|
+
}
|
|
562
1403
|
client[name] = instance;
|
|
563
1404
|
}
|
|
564
|
-
if (config.
|
|
565
|
-
|
|
1405
|
+
if (config.auth) {
|
|
1406
|
+
const AuthClass = config.auth;
|
|
1407
|
+
const authInstance = new AuthClass();
|
|
1408
|
+
const resourceName = authInstance.entity.name.toLowerCase();
|
|
1409
|
+
if (defaultDriver instanceof LocalDriver) {
|
|
1410
|
+
authInstance._init(defaultDriver, resourceName);
|
|
1411
|
+
defaultDriver.registerEntity(resourceName, authInstance.entity);
|
|
1412
|
+
const storage = defaultDriver.getStorageBackend();
|
|
1413
|
+
authInstance._initAuth(
|
|
1414
|
+
() => {
|
|
1415
|
+
const raw = storage.getMeta("_authState");
|
|
1416
|
+
return raw ? JSON.parse(raw) : null;
|
|
1417
|
+
},
|
|
1418
|
+
(state) => {
|
|
1419
|
+
if (state) {
|
|
1420
|
+
storage.setMeta("_authState", JSON.stringify(state));
|
|
1421
|
+
} else {
|
|
1422
|
+
storage.setMeta("_authState", "");
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
);
|
|
1426
|
+
defaultDriver.setAuthProvider(() => authInstance.getAuthContext());
|
|
1427
|
+
} else if (defaultDriver instanceof HttpDriver) {
|
|
1428
|
+
authInstance._init(defaultDriver, resourceName);
|
|
1429
|
+
defaultDriver.registerEndpoint(resourceName, authInstance.endpoint);
|
|
1430
|
+
let memoryAuthState = null;
|
|
1431
|
+
authInstance._initAuth(
|
|
1432
|
+
() => memoryAuthState,
|
|
1433
|
+
(state) => {
|
|
1434
|
+
memoryAuthState = state;
|
|
1435
|
+
}
|
|
1436
|
+
);
|
|
1437
|
+
authInstance._setHttpMode(defaultDriver);
|
|
1438
|
+
defaultDriver.setAuthProvider(() => {
|
|
1439
|
+
const token = authInstance.token;
|
|
1440
|
+
return token ? { token } : null;
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
client.auth = authInstance;
|
|
1444
|
+
for (const driver of overrideDrivers.values()) {
|
|
1445
|
+
if (driver instanceof HttpDriver) {
|
|
1446
|
+
driver.setAuthProvider(() => {
|
|
1447
|
+
const token = client.auth?.token;
|
|
1448
|
+
return token ? { token } : null;
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
566
1452
|
}
|
|
1453
|
+
for (const key of Object.keys(client)) {
|
|
1454
|
+
const svc = client[key];
|
|
1455
|
+
if (svc && typeof svc._setClient === "function") {
|
|
1456
|
+
svc._setClient(client);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
let readyPromise;
|
|
1460
|
+
if (defaultDriver instanceof LocalDriver) {
|
|
1461
|
+
if (defaultDriver.isReady) {
|
|
1462
|
+
if (config.seeds) {
|
|
1463
|
+
applySeedsIfNeeded(defaultDriver, config.seeds);
|
|
1464
|
+
}
|
|
1465
|
+
readyPromise = Promise.resolve();
|
|
1466
|
+
} else {
|
|
1467
|
+
readyPromise = defaultDriver.ready.then(() => {
|
|
1468
|
+
if (config.seeds) {
|
|
1469
|
+
applySeedsIfNeeded(defaultDriver, config.seeds);
|
|
1470
|
+
}
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
} else {
|
|
1474
|
+
readyPromise = Promise.resolve();
|
|
1475
|
+
}
|
|
1476
|
+
Object.defineProperty(client, "ready", {
|
|
1477
|
+
value: readyPromise,
|
|
1478
|
+
writable: false,
|
|
1479
|
+
enumerable: false,
|
|
1480
|
+
configurable: false
|
|
1481
|
+
});
|
|
567
1482
|
return client;
|
|
568
1483
|
}
|
|
569
1484
|
function createDriver(config) {
|
|
@@ -571,7 +1486,7 @@ function createDriver(config) {
|
|
|
571
1486
|
case "local":
|
|
572
1487
|
return new LocalDriver(config);
|
|
573
1488
|
case "http":
|
|
574
|
-
|
|
1489
|
+
return new HttpDriver(config);
|
|
575
1490
|
default:
|
|
576
1491
|
throw new Error(`Unknown driver type: ${config.type}`);
|
|
577
1492
|
}
|
|
@@ -586,6 +1501,6 @@ function applySeedsIfNeeded(driver, seeds) {
|
|
|
586
1501
|
driver.setSeedVersion(newVersion);
|
|
587
1502
|
}
|
|
588
1503
|
|
|
589
|
-
export { ConflictError, Entity, FauxbaseError, ForbiddenError, NotFoundError, Service, ValidationError, afterCreate, afterUpdate, beforeCreate, beforeUpdate, computed, createClient, field, relation, seed };
|
|
1504
|
+
export { AuthService, ConflictError, Entity, FauxbaseError, ForbiddenError, HttpDriver, HttpError, LocalDriver, NetworkError, NotFoundError, Service, TimeoutError, ValidationError, afterCreate, afterUpdate, beforeCreate, beforeUpdate, computed, createClient, defaultPreset, definePreset, djangoPreset, expressPreset, field, getPreset, laravelPreset, nestjsPreset, relation, seed, springBootPreset };
|
|
590
1505
|
//# sourceMappingURL=index.js.map
|
|
591
1506
|
//# sourceMappingURL=index.js.map
|