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