@larc-iu/plaid-client 0.2.0-alpha.29 → 0.2.0-alpha.30
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/package.json +3 -2
- package/src/apiAnnotations.d.ts +508 -0
- package/src/apiAnnotations.js +543 -0
- package/src/apiDocuments.d.ts +569 -0
- package/src/apiDocuments.js +551 -0
- package/src/apiLayers.d.ts +369 -0
- package/src/apiLayers.js +222 -0
- package/src/apiMessages.d.ts +293 -0
- package/src/apiMessages.js +265 -0
- package/src/apiPeople.d.ts +626 -0
- package/src/apiPeople.js +698 -0
- package/src/apiProject.d.ts +761 -0
- package/src/apiProject.js +774 -0
- package/src/apiServer.d.ts +455 -0
- package/src/apiServer.js +422 -0
- package/src/apiVocab.d.ts +456 -0
- package/src/apiVocab.js +425 -0
- package/src/batch.d.ts +54 -0
- package/src/batch.js +323 -0
- package/src/client.d.ts +1321 -0
- package/src/client.js +743 -0
- package/src/constraints.d.ts +8 -0
- package/src/constraints.js +14 -0
- package/src/documentLock.d.ts +18 -4
- package/src/documentLock.js +43 -9
- package/src/entryForms.d.ts +63 -0
- package/src/entryForms.js +152 -0
- package/src/events.js +2 -1
- package/src/families.d.ts +349 -0
- package/src/families.js +396 -0
- package/src/http.d.ts +39 -18
- package/src/http.js +127 -87
- package/src/index.d.ts +90 -4557
- package/src/index.js +113 -5172
- package/src/operations.d.ts +36 -0
- package/src/operations.js +164 -0
- package/src/provenance.d.ts +47 -4
- package/src/provenance.js +168 -12
- package/src/roles.d.ts +39 -0
- package/src/roles.js +44 -0
- package/src/serviceSchema.d.ts +19 -13
- package/src/serviceSchema.js +30 -15
- package/src/services.d.ts +20 -3
- package/src/services.js +45 -8
- package/src/sse.js +10 -1
- package/src/testing.d.ts +128 -0
- package/src/testing.js +379 -0
- package/src/types.d.ts +867 -0
- package/src/types.js +663 -0
package/src/http.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { markReplayed } from "./replayed.js";
|
|
2
2
|
import { transformRequest, transformResponse } from "./transforms.js";
|
|
3
|
-
import {
|
|
4
|
-
import { uuidv7 } from "./ids.js";
|
|
3
|
+
import { nextIdempotencyKey } from "./operations.js";
|
|
5
4
|
|
|
6
5
|
// ---------------------------------------------------------------------------
|
|
7
6
|
// Batches: which calls a batch carries, and which go over the wire anyway.
|
|
@@ -268,7 +267,7 @@ export async function retryWhileBusy(
|
|
|
268
267
|
// keyed write for a day, and the same key sent again for the same request is
|
|
269
268
|
// answered from it with `Idempotent-Replayed: true`, writing nothing. So a
|
|
270
269
|
// write whose answer was lost (no response, 502, 504) can be sent again
|
|
271
|
-
// safely: it lands once. `makeRequest` and `
|
|
270
|
+
// safely: it lands once. `makeRequest` and `postBatch` do that three times
|
|
272
271
|
// themselves (retryUnknown), and the error that escapes carries the key as
|
|
273
272
|
// `idempotencyKey`.
|
|
274
273
|
//
|
|
@@ -338,29 +337,6 @@ export async function retryUnknown(
|
|
|
338
337
|
}
|
|
339
338
|
}
|
|
340
339
|
|
|
341
|
-
/**
|
|
342
|
-
* The key for the next keyed request, and the strict-mode stamp it must
|
|
343
|
-
* claim when an earlier run of its operation already sent it. `joins` says
|
|
344
|
-
* whether the request joins the open logical operation.
|
|
345
|
-
* Returns `{ key, pin, record }`: `pin` is undefined when nothing is pinned,
|
|
346
|
-
* and `record(version)` pins what this request claimed, the first time.
|
|
347
|
-
*/
|
|
348
|
-
export function nextIdempotencyKey(client, joins) {
|
|
349
|
-
const frame = joins ? client.operationGroup?.frames.at(-1) : null;
|
|
350
|
-
const keys = frame?.keys;
|
|
351
|
-
if (!keys) return { key: uuidv7(), pin: undefined, record: () => {} };
|
|
352
|
-
const n = frame.count;
|
|
353
|
-
frame.count += 1;
|
|
354
|
-
const pinned = keys.stamps.has(n);
|
|
355
|
-
return {
|
|
356
|
-
key: `${keys.seed}.${n}`,
|
|
357
|
-
pin: pinned ? keys.stamps.get(n) : undefined,
|
|
358
|
-
record: (version) => {
|
|
359
|
-
if (!pinned) keys.stamps.set(n, version ?? null);
|
|
360
|
-
},
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
|
|
364
340
|
/**
|
|
365
341
|
* Build a fetch AbortSignal that fires after `timeout` ms, or undefined when
|
|
366
342
|
* timeouts are disabled / unsupported.
|
|
@@ -699,43 +675,49 @@ async function learnOmittedVersionQuietly(client) {
|
|
|
699
675
|
}
|
|
700
676
|
|
|
701
677
|
/**
|
|
702
|
-
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
* signal marked `outOfBand`, is the client's to make and goes over the wire
|
|
706
|
-
* now, exactly as if it had been made on the client.
|
|
678
|
+
* One send of a request by fetch, under a fresh timeout signal: an
|
|
679
|
+
* AbortSignal.timeout fires once and stays aborted, so a retry needs its own.
|
|
680
|
+
* `timeoutMs` 0 or null sends with none.
|
|
707
681
|
*/
|
|
708
|
-
export
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
682
|
+
export function fetchWithTimeout(url, fetchOptions, timeoutMs) {
|
|
683
|
+
const signal = timeoutSignal(timeoutMs);
|
|
684
|
+
return fetch(url, signal ? { ...fetchOptions, signal } : fetchOptions);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Run `attempt` with the client's retries: a 503 is retried (unless
|
|
689
|
+
* `noBusyRetry`), and a keyed request whose answer was lost is sent again
|
|
690
|
+
* under its key (retryUnknown). Each retry is told to `onRetry` listeners.
|
|
691
|
+
*/
|
|
692
|
+
export function withRetries(client, attempt, { keyed = false, noBusyRetry = false } = {}) {
|
|
693
|
+
const onRetry = (info) => client._noteRetry?.(info);
|
|
694
|
+
const busy = noBusyRetry ? { onRetry, retries: 0 } : { onRetry };
|
|
695
|
+
return keyed
|
|
696
|
+
? retryUnknown(() => retryWhileBusy(attempt, busy), {
|
|
697
|
+
delaysMs: client.retryDelaysMs,
|
|
698
|
+
onRetry,
|
|
699
|
+
})
|
|
700
|
+
: retryWhileBusy(attempt, busy);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* A 401 means the token is missing, expired or invalid. Fire the app's
|
|
705
|
+
* auth-error handler once per client (it discards the token and routes back
|
|
706
|
+
* to login). A 403 (authenticated but not permitted) deliberately does not.
|
|
707
|
+
*/
|
|
708
|
+
export function noteAuthError(client, error) {
|
|
709
|
+
if (
|
|
710
|
+
error?.status === 401 &&
|
|
711
|
+
typeof client.onAuthError === "function" &&
|
|
712
|
+
!client._authErrorFired
|
|
713
|
+
) {
|
|
714
|
+
client._authErrorFired = true;
|
|
715
|
+
try {
|
|
716
|
+
client.onAuthError(error);
|
|
717
|
+
} catch (_) {
|
|
718
|
+
/* handler must not mask the original error */
|
|
719
|
+
}
|
|
734
720
|
}
|
|
735
|
-
batch.operations.push(operation);
|
|
736
|
-
batch.stampedDocuments.push(stampedDocument);
|
|
737
|
-
batch.stampedGroups.push(stampedGroup);
|
|
738
|
-
return { batched: true };
|
|
739
721
|
}
|
|
740
722
|
|
|
741
723
|
/**
|
|
@@ -762,6 +744,10 @@ export async function queueRequest(batch, method, path, options = {}) {
|
|
|
762
744
|
* skipResponseTransform - Return raw parsed JSON (no transformResponse)
|
|
763
745
|
* noAuth - Skip Authorization header
|
|
764
746
|
* binaryResponse - Return arrayBuffer instead of JSON/text
|
|
747
|
+
* blobResponse - Return a Blob of the body, its `type` the response's
|
|
748
|
+
* Content-Type (a recording, whose reader needs both)
|
|
749
|
+
* onDownloadProgress - With blobResponse, called with `{ loaded, total }`
|
|
750
|
+
* (bytes; total null when unknown) as the body arrives
|
|
765
751
|
* noBusyRetry - If true, a 503 is not retried. For a query: core
|
|
766
752
|
* answers 503 when its queue of large queries stays full
|
|
767
753
|
* until the query's deadline, and asking again at once
|
|
@@ -775,12 +761,72 @@ export async function queueRequest(batch, method, path, options = {}) {
|
|
|
775
761
|
* where the timeout only fires when the upload stalls;
|
|
776
762
|
* elsewhere the callback is ignored and fetch is used.
|
|
777
763
|
*/
|
|
764
|
+
/**
|
|
765
|
+
* What a write about to go out needs to tell the lock keepers it renewed:
|
|
766
|
+
* each held document's keeper, its version now and the send time. Empty when
|
|
767
|
+
* no `documents.locked()` block on `client` keeps a lock. The server renews a
|
|
768
|
+
* live lock on every write its holder makes to the document, so a write that
|
|
769
|
+
* moved the held document's version renewed it (`writesLanded`).
|
|
770
|
+
* @private
|
|
771
|
+
*/
|
|
772
|
+
export function writesStarted(client) {
|
|
773
|
+
const keepers = client.lockKeepers;
|
|
774
|
+
if (!keepers || keepers.size === 0) return [];
|
|
775
|
+
return [...keepers.entries()].map(([docId, keeper]) => [
|
|
776
|
+
keeper,
|
|
777
|
+
client.documentVersions?.[docId],
|
|
778
|
+
keeper.now(),
|
|
779
|
+
]);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* The write `started` was read before has been taken, and its answer's
|
|
784
|
+
* versions learned. A held document whose version it moved was written by
|
|
785
|
+
* this run. A replayed answer was not taken again, and renewed nothing.
|
|
786
|
+
* @private
|
|
787
|
+
*/
|
|
788
|
+
export function writesLanded(client, started, replayed = false) {
|
|
789
|
+
if (replayed || !started?.length) return;
|
|
790
|
+
for (const [keeper, before, sent] of started) {
|
|
791
|
+
const after = client.documentVersions?.[keeper.documentId];
|
|
792
|
+
if (before != null && after != null && after !== before) {
|
|
793
|
+
keeper.noteWrite(sent);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* A response's body as a Blob typed by its Content-Type, `onProgress` told
|
|
800
|
+
* `{ loaded, total }` after each piece when given (total null when the
|
|
801
|
+
* response has no Content-Length).
|
|
802
|
+
* @private
|
|
803
|
+
*/
|
|
804
|
+
async function readBlob(response, onProgress) {
|
|
805
|
+
const type = response.headers.get("content-type") ?? "";
|
|
806
|
+
const reader = onProgress ? response.body?.getReader?.() : null;
|
|
807
|
+
if (!reader) return new Blob([await response.arrayBuffer()], { type });
|
|
808
|
+
const length = Number(response.headers.get("content-length"));
|
|
809
|
+
const total = length > 0 ? length : null;
|
|
810
|
+
const parts = [];
|
|
811
|
+
let loaded = 0;
|
|
812
|
+
for (;;) {
|
|
813
|
+
const { done, value } = await reader.read();
|
|
814
|
+
if (done) break;
|
|
815
|
+
parts.push(value);
|
|
816
|
+
loaded += value.byteLength;
|
|
817
|
+
onProgress({ loaded, total });
|
|
818
|
+
}
|
|
819
|
+
return new Blob(parts, { type });
|
|
820
|
+
}
|
|
821
|
+
|
|
778
822
|
export async function makeRequest(client, method, path, options = {}) {
|
|
779
823
|
const {
|
|
780
824
|
formData,
|
|
781
825
|
skipResponseTransform,
|
|
782
826
|
noAuth,
|
|
783
827
|
binaryResponse,
|
|
828
|
+
blobResponse,
|
|
829
|
+
onDownloadProgress,
|
|
784
830
|
timeout,
|
|
785
831
|
onUploadProgress,
|
|
786
832
|
learningOmittedVersion,
|
|
@@ -829,13 +875,19 @@ export async function makeRequest(client, method, path, options = {}) {
|
|
|
829
875
|
timeout: timeoutMs,
|
|
830
876
|
});
|
|
831
877
|
}
|
|
832
|
-
|
|
833
|
-
return fetch(url, signal ? { ...fetchOptions, signal } : fetchOptions);
|
|
878
|
+
return fetchWithTimeout(url, fetchOptions, timeoutMs);
|
|
834
879
|
};
|
|
835
880
|
|
|
836
881
|
// One attempt: a 503, and for a keyed write a 502 or 504, surfaces as a
|
|
837
882
|
// throw so the retries below can see it. Other failures are thrown from
|
|
838
883
|
// the checks after it.
|
|
884
|
+
// A write that lands on a held document renews its lock on the server, so
|
|
885
|
+
// its keeper hears of it (writesLanded).
|
|
886
|
+
const started =
|
|
887
|
+
method !== "GET" && !options.outOfBand ? writesStarted(client) : [];
|
|
888
|
+
const landed = (headers) =>
|
|
889
|
+
writesLanded(client, started, headers.get(REPLAYED_HEADER) === "true");
|
|
890
|
+
|
|
839
891
|
const attempt = async () => {
|
|
840
892
|
let res;
|
|
841
893
|
try {
|
|
@@ -853,14 +905,7 @@ export async function makeRequest(client, method, path, options = {}) {
|
|
|
853
905
|
};
|
|
854
906
|
|
|
855
907
|
try {
|
|
856
|
-
const
|
|
857
|
-
const busy = noBusyRetry ? { onRetry, retries: 0 } : { onRetry };
|
|
858
|
-
const response = keyed
|
|
859
|
-
? await retryUnknown(() => retryWhileBusy(attempt, busy), {
|
|
860
|
-
delaysMs: client.retryDelaysMs,
|
|
861
|
-
onRetry,
|
|
862
|
-
})
|
|
863
|
-
: await retryWhileBusy(attempt, busy);
|
|
908
|
+
const response = await withRetries(client, attempt, { keyed, noBusyRetry });
|
|
864
909
|
noteServerClock(client, response.headers);
|
|
865
910
|
|
|
866
911
|
if (!response.ok) {
|
|
@@ -876,32 +921,25 @@ export async function makeRequest(client, method, path, options = {}) {
|
|
|
876
921
|
return transformResponse({ id: errorData.id });
|
|
877
922
|
}
|
|
878
923
|
const error = makeHttpError(response, errorData, url, method);
|
|
879
|
-
|
|
880
|
-
// handler once (it discards the token and routes back to login). 403
|
|
881
|
-
// (forbidden — authenticated but not permitted) deliberately does NOT.
|
|
882
|
-
if (
|
|
883
|
-
response.status === 401 &&
|
|
884
|
-
typeof client.onAuthError === "function" &&
|
|
885
|
-
!client._authErrorFired
|
|
886
|
-
) {
|
|
887
|
-
client._authErrorFired = true;
|
|
888
|
-
try {
|
|
889
|
-
client.onAuthError(error);
|
|
890
|
-
} catch (_) {
|
|
891
|
-
/* handler must not mask the original error */
|
|
892
|
-
}
|
|
893
|
-
}
|
|
924
|
+
noteAuthError(client, error);
|
|
894
925
|
throw error;
|
|
895
926
|
}
|
|
896
927
|
// The server took it, so the operation's group exists.
|
|
897
928
|
if (stampedGroup) stampedGroup.written = true;
|
|
898
929
|
|
|
899
|
-
// Binary response (
|
|
930
|
+
// Binary response (users.getAvatar)
|
|
900
931
|
if (binaryResponse) {
|
|
901
932
|
extractDocumentVersions(client, response.headers);
|
|
933
|
+
landed(response.headers);
|
|
902
934
|
if (!learningOmittedVersion) await learnOmittedVersionQuietly(client);
|
|
903
935
|
return await response.arrayBuffer();
|
|
904
936
|
}
|
|
937
|
+
if (blobResponse) {
|
|
938
|
+
extractDocumentVersions(client, response.headers);
|
|
939
|
+
landed(response.headers);
|
|
940
|
+
if (!learningOmittedVersion) await learnOmittedVersionQuietly(client);
|
|
941
|
+
return await readBlob(response, onDownloadProgress);
|
|
942
|
+
}
|
|
905
943
|
|
|
906
944
|
// JSON or text response
|
|
907
945
|
const contentType = response.headers.get("content-type");
|
|
@@ -910,6 +948,7 @@ export async function makeRequest(client, method, path, options = {}) {
|
|
|
910
948
|
extractDocumentVersions(client, response.headers, data, {
|
|
911
949
|
historical: /[?&]as-of=/.test(url),
|
|
912
950
|
});
|
|
951
|
+
landed(response.headers);
|
|
913
952
|
if (!learningOmittedVersion) await learnOmittedVersionQuietly(client);
|
|
914
953
|
const answer = skipResponseTransform ? data : transformResponse(data);
|
|
915
954
|
return response.headers.get(REPLAYED_HEADER) === "true"
|
|
@@ -917,6 +956,7 @@ export async function makeRequest(client, method, path, options = {}) {
|
|
|
917
956
|
: answer;
|
|
918
957
|
} else {
|
|
919
958
|
extractDocumentVersions(client, response.headers);
|
|
959
|
+
landed(response.headers);
|
|
920
960
|
if (!learningOmittedVersion) await learnOmittedVersionQuietly(client);
|
|
921
961
|
return await response.text();
|
|
922
962
|
}
|