@object-ui/data-objectstack 3.3.1 → 3.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/CHANGELOG.md +16 -0
- package/dist/index.cjs +32 -8
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +32 -8
- package/package.json +3 -3
- package/src/index.ts +48 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @object-ui/data-objectstack
|
|
2
2
|
|
|
3
|
+
## 3.4.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [f1ca238]
|
|
8
|
+
- Updated dependencies [de881ef]
|
|
9
|
+
- @object-ui/types@3.4.0
|
|
10
|
+
- @object-ui/core@3.4.0
|
|
11
|
+
|
|
12
|
+
## 3.3.2
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- @object-ui/types@3.3.2
|
|
17
|
+
- @object-ui/core@3.3.2
|
|
18
|
+
|
|
3
19
|
## 3.3.1
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -844,6 +844,13 @@ async function getSharedDiscovery(baseUrl, fetcher) {
|
|
|
844
844
|
function clearSharedDiscoveryCache() {
|
|
845
845
|
discoveryCache.clear();
|
|
846
846
|
}
|
|
847
|
+
function stableStringify(value) {
|
|
848
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
849
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
850
|
+
const obj = value;
|
|
851
|
+
const keys = Object.keys(obj).sort();
|
|
852
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
|
|
853
|
+
}
|
|
847
854
|
var ObjectStackAdapter = class {
|
|
848
855
|
constructor(config) {
|
|
849
856
|
__publicField(this, "client");
|
|
@@ -860,6 +867,11 @@ var ObjectStackAdapter = class {
|
|
|
860
867
|
__publicField(this, "baseUrl");
|
|
861
868
|
__publicField(this, "token");
|
|
862
869
|
__publicField(this, "fetchImpl");
|
|
870
|
+
// In-flight find() requests keyed by resource + serialized params.
|
|
871
|
+
// Coalesces concurrent identical reads (e.g. React StrictMode double-mount,
|
|
872
|
+
// multiple sibling components requesting the same dataset on first paint)
|
|
873
|
+
// into a single network round trip.
|
|
874
|
+
__publicField(this, "inflightFinds", /* @__PURE__ */ new Map());
|
|
863
875
|
this.client = new import_client.ObjectStackClient(config);
|
|
864
876
|
this.metadataCache = new MetadataCache(config.cache);
|
|
865
877
|
this.autoReconnect = config.autoReconnect ?? true;
|
|
@@ -997,14 +1009,26 @@ var ObjectStackAdapter = class {
|
|
|
997
1009
|
* Converts OData-style params to ObjectStack query options.
|
|
998
1010
|
*/
|
|
999
1011
|
async find(resource, params) {
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1012
|
+
const key = `${resource}::${stableStringify(params)}`;
|
|
1013
|
+
const existing = this.inflightFinds.get(key);
|
|
1014
|
+
if (existing) return existing;
|
|
1015
|
+
const promise = (async () => {
|
|
1016
|
+
await this.connect();
|
|
1017
|
+
if (params?.$expand && params.$expand.length > 0) {
|
|
1018
|
+
const result2 = await this.rawFindWithPopulate(resource, params);
|
|
1019
|
+
return this.normalizeQueryResult(result2, params);
|
|
1020
|
+
}
|
|
1021
|
+
const queryOptions = this.convertQueryParams(params);
|
|
1022
|
+
const result = await this.client.data.find(resource, queryOptions);
|
|
1023
|
+
return this.normalizeQueryResult(result, params);
|
|
1024
|
+
})();
|
|
1025
|
+
this.inflightFinds.set(key, promise);
|
|
1026
|
+
promise.finally(() => {
|
|
1027
|
+
if (this.inflightFinds.get(key) === promise) {
|
|
1028
|
+
this.inflightFinds.delete(key);
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
return promise;
|
|
1008
1032
|
}
|
|
1009
1033
|
/**
|
|
1010
1034
|
* Find a single record by ID.
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -802,6 +802,13 @@ async function getSharedDiscovery(baseUrl, fetcher) {
|
|
|
802
802
|
function clearSharedDiscoveryCache() {
|
|
803
803
|
discoveryCache.clear();
|
|
804
804
|
}
|
|
805
|
+
function stableStringify(value) {
|
|
806
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
807
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
808
|
+
const obj = value;
|
|
809
|
+
const keys = Object.keys(obj).sort();
|
|
810
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
|
|
811
|
+
}
|
|
805
812
|
var ObjectStackAdapter = class {
|
|
806
813
|
constructor(config) {
|
|
807
814
|
__publicField(this, "client");
|
|
@@ -818,6 +825,11 @@ var ObjectStackAdapter = class {
|
|
|
818
825
|
__publicField(this, "baseUrl");
|
|
819
826
|
__publicField(this, "token");
|
|
820
827
|
__publicField(this, "fetchImpl");
|
|
828
|
+
// In-flight find() requests keyed by resource + serialized params.
|
|
829
|
+
// Coalesces concurrent identical reads (e.g. React StrictMode double-mount,
|
|
830
|
+
// multiple sibling components requesting the same dataset on first paint)
|
|
831
|
+
// into a single network round trip.
|
|
832
|
+
__publicField(this, "inflightFinds", /* @__PURE__ */ new Map());
|
|
821
833
|
this.client = new ObjectStackClient(config);
|
|
822
834
|
this.metadataCache = new MetadataCache(config.cache);
|
|
823
835
|
this.autoReconnect = config.autoReconnect ?? true;
|
|
@@ -955,14 +967,26 @@ var ObjectStackAdapter = class {
|
|
|
955
967
|
* Converts OData-style params to ObjectStack query options.
|
|
956
968
|
*/
|
|
957
969
|
async find(resource, params) {
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
970
|
+
const key = `${resource}::${stableStringify(params)}`;
|
|
971
|
+
const existing = this.inflightFinds.get(key);
|
|
972
|
+
if (existing) return existing;
|
|
973
|
+
const promise = (async () => {
|
|
974
|
+
await this.connect();
|
|
975
|
+
if (params?.$expand && params.$expand.length > 0) {
|
|
976
|
+
const result2 = await this.rawFindWithPopulate(resource, params);
|
|
977
|
+
return this.normalizeQueryResult(result2, params);
|
|
978
|
+
}
|
|
979
|
+
const queryOptions = this.convertQueryParams(params);
|
|
980
|
+
const result = await this.client.data.find(resource, queryOptions);
|
|
981
|
+
return this.normalizeQueryResult(result, params);
|
|
982
|
+
})();
|
|
983
|
+
this.inflightFinds.set(key, promise);
|
|
984
|
+
promise.finally(() => {
|
|
985
|
+
if (this.inflightFinds.get(key) === promise) {
|
|
986
|
+
this.inflightFinds.delete(key);
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
return promise;
|
|
966
990
|
}
|
|
967
991
|
/**
|
|
968
992
|
* Find a single record by ID.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/data-objectstack",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "ObjectStack Data Adapter for Object UI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@objectstack/client": "^4.0.4",
|
|
26
|
-
"@object-ui/core": "3.
|
|
27
|
-
"@object-ui/types": "3.
|
|
26
|
+
"@object-ui/core": "3.4.0",
|
|
27
|
+
"@object-ui/types": "3.4.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"tsup": "^8.5.1",
|
package/src/index.ts
CHANGED
|
@@ -88,6 +88,20 @@ export type BatchProgressListener = (event: BatchProgressEvent) => void;
|
|
|
88
88
|
// Re-export FileUploadResult from types for consumers
|
|
89
89
|
export type { FileUploadResult } from '@object-ui/types';
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Deterministic JSON.stringify with sorted object keys, used to build cache
|
|
93
|
+
* keys for in-flight request coalescing. Produces identical output for
|
|
94
|
+
* `{ a: 1, b: 2 }` and `{ b: 2, a: 1 }` so callers that build params in
|
|
95
|
+
* different orders still hit the same key.
|
|
96
|
+
*/
|
|
97
|
+
function stableStringify(value: unknown): string {
|
|
98
|
+
if (value === null || typeof value !== 'object') return JSON.stringify(value);
|
|
99
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
|
|
100
|
+
const obj = value as Record<string, unknown>;
|
|
101
|
+
const keys = Object.keys(obj).sort();
|
|
102
|
+
return `{${keys.map(k => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(',')}}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
91
105
|
/**
|
|
92
106
|
* ObjectStack Data Source Adapter
|
|
93
107
|
*
|
|
@@ -132,6 +146,11 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
132
146
|
private baseUrl: string;
|
|
133
147
|
private token?: string;
|
|
134
148
|
private fetchImpl: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
149
|
+
// In-flight find() requests keyed by resource + serialized params.
|
|
150
|
+
// Coalesces concurrent identical reads (e.g. React StrictMode double-mount,
|
|
151
|
+
// multiple sibling components requesting the same dataset on first paint)
|
|
152
|
+
// into a single network round trip.
|
|
153
|
+
private inflightFinds = new Map<string, Promise<QueryResult<T>>>();
|
|
135
154
|
|
|
136
155
|
constructor(config: {
|
|
137
156
|
baseUrl: string;
|
|
@@ -323,22 +342,38 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
|
323
342
|
* Converts OData-style params to ObjectStack query options.
|
|
324
343
|
*/
|
|
325
344
|
async find(resource: string, params?: QueryParams): Promise<QueryResult<T>> {
|
|
326
|
-
|
|
345
|
+
const key = `${resource}::${stableStringify(params)}`;
|
|
346
|
+
const existing = this.inflightFinds.get(key);
|
|
347
|
+
if (existing) return existing;
|
|
327
348
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
349
|
+
const promise = (async () => {
|
|
350
|
+
await this.connect();
|
|
351
|
+
|
|
352
|
+
// When $expand is requested, use a raw GET request to the REST API with
|
|
353
|
+
// `populate` as a URL query param. The server's REST plugin routes
|
|
354
|
+
// GET /data/:object to protocol.findData({ object, query: req.query }),
|
|
355
|
+
// which parses `populate` (comma-separated) into an array for lookup expansion.
|
|
356
|
+
// We use a raw request because the client SDK's data.find() QueryOptions
|
|
357
|
+
// interface does not include populate/expand fields.
|
|
358
|
+
if (params?.$expand && params.$expand.length > 0) {
|
|
359
|
+
const result = await this.rawFindWithPopulate(resource, params);
|
|
360
|
+
return this.normalizeQueryResult(result, params);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const queryOptions = this.convertQueryParams(params);
|
|
364
|
+
const result: unknown = await this.client.data.find<T>(resource, queryOptions);
|
|
336
365
|
return this.normalizeQueryResult(result, params);
|
|
337
|
-
}
|
|
366
|
+
})();
|
|
338
367
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
368
|
+
this.inflightFinds.set(key, promise);
|
|
369
|
+
promise.finally(() => {
|
|
370
|
+
// Only clear if the entry still points at this promise; a later call
|
|
371
|
+
// that started after settle may have already replaced it.
|
|
372
|
+
if (this.inflightFinds.get(key) === promise) {
|
|
373
|
+
this.inflightFinds.delete(key);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
return promise;
|
|
342
377
|
}
|
|
343
378
|
|
|
344
379
|
/**
|