@larc-iu/plaid-client 0.2.0-alpha.3 → 0.2.0-alpha.32
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 +39 -0
- package/package.json +13 -6
- 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 +457 -0
- package/src/apiServer.js +424 -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/batchRef.d.ts +30 -0
- package/src/batchRef.js +128 -0
- package/src/client.d.ts +1321 -0
- package/src/client.js +743 -0
- package/src/codepoint.d.ts +94 -0
- package/src/codepoint.js +131 -1
- package/src/constraints.d.ts +85 -0
- package/src/constraints.js +112 -0
- package/src/created.d.ts +2 -0
- package/src/created.js +29 -0
- package/src/documentLock.d.ts +172 -0
- package/src/documentLock.js +384 -0
- package/src/entryForms.d.ts +68 -0
- package/src/entryForms.js +164 -0
- package/src/events.d.ts +60 -0
- package/src/events.js +271 -0
- package/src/families.d.ts +349 -0
- package/src/families.js +396 -0
- package/src/http.d.ts +233 -0
- package/src/http.js +809 -93
- package/src/ids.d.ts +26 -0
- package/src/ids.js +96 -0
- package/src/index.d.ts +101 -0
- package/src/index.js +183 -2105
- package/src/metadataOps.d.ts +15 -0
- package/src/metadataOps.js +124 -0
- package/src/operations.d.ts +36 -0
- package/src/operations.js +164 -0
- package/src/pagination.d.ts +49 -0
- package/src/pagination.js +20 -53
- package/src/provenance.d.ts +243 -0
- package/src/provenance.js +458 -36
- package/src/replayed.d.ts +3 -0
- package/src/replayed.js +31 -0
- package/src/requester.d.ts +60 -0
- package/src/requester.js +74 -0
- package/src/roles.d.ts +104 -0
- package/src/roles.js +64 -2
- package/src/serviceSchema.d.ts +337 -0
- package/src/serviceSchema.js +177 -60
- package/src/services.d.ts +319 -0
- package/src/services.js +742 -58
- package/src/sse.d.ts +30 -0
- package/src/sse.js +80 -3
- package/src/testing.d.ts +128 -0
- package/src/testing.js +379 -0
- package/src/textEdits.d.ts +59 -0
- package/src/textEdits.js +234 -0
- package/src/transforms.d.ts +25 -0
- package/src/transforms.js +11 -1
- package/src/types.d.ts +867 -0
- package/src/types.js +663 -0
- package/index.d.ts +0 -532
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The code points of `body`, as a frozen array shared with other callers.
|
|
3
|
+
* @param {string} body
|
|
4
|
+
* @returns {readonly string[]}
|
|
5
|
+
*/
|
|
6
|
+
export function bodyCodePoints(body: string): readonly string[];
|
|
7
|
+
/**
|
|
8
|
+
* The net change of `ops` applied in turn to `body`, as gaps in the old
|
|
9
|
+
* body's code points: sorted, and never touching (at least one old code point
|
|
10
|
+
* is kept between two gaps). Old text deleted and typed back is typed text,
|
|
11
|
+
* and a gap whose value is the old text it removes is dropped. Throws on a
|
|
12
|
+
* malformed or out-of-bounds op.
|
|
13
|
+
* @param {string} body
|
|
14
|
+
* @param {TextEditOp[]} ops
|
|
15
|
+
* @returns {TextGap[]}
|
|
16
|
+
*/
|
|
17
|
+
export function composeTextEdits(body: string, ops: TextEditOp[]): TextGap[];
|
|
18
|
+
/**
|
|
19
|
+
* Gaps (old-body coordinates, sorted, not overlapping) as running ops: an
|
|
20
|
+
* insert where a gap removes nothing, a delete where it types nothing, and a
|
|
21
|
+
* replace otherwise.
|
|
22
|
+
* @param {TextGap[]} gaps
|
|
23
|
+
* @returns {TextEditOp[]}
|
|
24
|
+
*/
|
|
25
|
+
export function gapsToOps(gaps: TextGap[]): TextEditOp[];
|
|
26
|
+
/**
|
|
27
|
+
* `body` with `ops` applied in turn (running coordinates, code points).
|
|
28
|
+
* Throws on a malformed or out-of-bounds op.
|
|
29
|
+
* @param {string} body
|
|
30
|
+
* @param {TextEditOp[]} ops
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
export function applyTextOps(body: string, ops: TextEditOp[]): string;
|
|
34
|
+
/**
|
|
35
|
+
* One op as `PATCH /texts/:id` takes it. Each index is in the body the ops
|
|
36
|
+
* before it left.
|
|
37
|
+
*/
|
|
38
|
+
export type TextEditOp = {
|
|
39
|
+
type: "insert";
|
|
40
|
+
index: number;
|
|
41
|
+
value: string;
|
|
42
|
+
} | {
|
|
43
|
+
type: "delete";
|
|
44
|
+
index: number;
|
|
45
|
+
value: number;
|
|
46
|
+
} | {
|
|
47
|
+
type: "replace";
|
|
48
|
+
index: number;
|
|
49
|
+
length: number;
|
|
50
|
+
value: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Old-body code points [start, end) give way to `value`.
|
|
54
|
+
*/
|
|
55
|
+
export type TextGap = {
|
|
56
|
+
start: number;
|
|
57
|
+
end: number;
|
|
58
|
+
value: string;
|
|
59
|
+
};
|
package/src/textEdits.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text edit operations: composing a stream of ops into the net change.
|
|
3
|
+
*
|
|
4
|
+
* An op is one of the shapes `PATCH /texts/:id` takes:
|
|
5
|
+
*
|
|
6
|
+
* { type: 'insert', index, value: string }
|
|
7
|
+
* { type: 'delete', index, value: count }
|
|
8
|
+
* { type: 'replace', index, length, value: string }
|
|
9
|
+
*
|
|
10
|
+
* Every index and count is in Unicode CODE POINTS (see ./codepoint.js), and
|
|
11
|
+
* the ops are in running coordinates: each op's `index` is in the body as the
|
|
12
|
+
* ops before it left it.
|
|
13
|
+
*
|
|
14
|
+
* A GAP is `{ start, end, value }` in the OLD body's coordinates: old
|
|
15
|
+
* [start, end) gives way to `value`. `composeTextEdits` turns any op stream
|
|
16
|
+
* into gaps that depend only on the net change, never on how the keystrokes
|
|
17
|
+
* came, which is also how the server reads an edit. The Python client's
|
|
18
|
+
* `plaid_client.text_edits` is the twin of this module.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* One op as `PATCH /texts/:id` takes it. Each index is in the body the ops
|
|
23
|
+
* before it left.
|
|
24
|
+
* @typedef {{type: "insert", index: number, value: string}
|
|
25
|
+
* | {type: "delete", index: number, value: number}
|
|
26
|
+
* | {type: "replace", index: number, length: number, value: string}} TextEditOp
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Old-body code points [start, end) give way to `value`.
|
|
31
|
+
* @typedef {{start: number, end: number, value: string}} TextGap
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const codePoints = (s) => [...s];
|
|
35
|
+
|
|
36
|
+
// The last body split into code points, kept: an edit log composes the same
|
|
37
|
+
// long body once for each of its kept states, and splitting it each time made
|
|
38
|
+
// a save on a long text block the page (H31-TEXT-1). Frozen, as every caller
|
|
39
|
+
// shares it.
|
|
40
|
+
let lastBody = null;
|
|
41
|
+
let lastPoints = Object.freeze([]);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The code points of `body`, as a frozen array shared with other callers.
|
|
45
|
+
* @param {string} body
|
|
46
|
+
* @returns {readonly string[]}
|
|
47
|
+
*/
|
|
48
|
+
export function bodyCodePoints(body) {
|
|
49
|
+
if (body !== lastBody) {
|
|
50
|
+
lastPoints = Object.freeze(codePoints(body));
|
|
51
|
+
lastBody = body;
|
|
52
|
+
}
|
|
53
|
+
return lastPoints;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// The op as a refusal names it.
|
|
57
|
+
const opJson = (op) => String(JSON.stringify(op) ?? 'null');
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The op's type after checking its shape and bounds against a body of `len`
|
|
61
|
+
* code points, as the server checks it. Throws an Error otherwise.
|
|
62
|
+
*/
|
|
63
|
+
function checkOp(op, len) {
|
|
64
|
+
const type = op && op.type;
|
|
65
|
+
const { index, value, length } = op || {};
|
|
66
|
+
const int = Number.isInteger;
|
|
67
|
+
const wellFormed =
|
|
68
|
+
(type === 'insert' && int(index) && typeof value === 'string') ||
|
|
69
|
+
(type === 'delete' && int(index) && int(value)) ||
|
|
70
|
+
(type === 'replace' && int(index) && int(length) && typeof value === 'string');
|
|
71
|
+
if (!wellFormed) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Malformed text edit operation: ${opJson(op)}. Expected {type: "insert", index: int, ` +
|
|
74
|
+
`value: string}, {type: "delete", index: int, value: int} or {type: "replace", ` +
|
|
75
|
+
`index: int, length: int, value: string}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (!(op.side == null || op.side === 'before' || op.side === 'after')) {
|
|
79
|
+
throw new Error(`A text edit's side is "before" or "after": ${opJson(op)}`);
|
|
80
|
+
}
|
|
81
|
+
const inBounds =
|
|
82
|
+
type === 'insert'
|
|
83
|
+
? index >= 0 && index <= len
|
|
84
|
+
: type === 'delete'
|
|
85
|
+
? index >= 0 && value >= 0 && index + value <= len
|
|
86
|
+
: index >= 0 && length >= 0 && index + length <= len;
|
|
87
|
+
if (!inBounds) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Text edit operation out of bounds: ${opJson(op)} (text length is ${len} code points)`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return type;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// How many code points an op removes and what it types.
|
|
96
|
+
function opParts(type, op) {
|
|
97
|
+
if (type === 'insert') return [0, op.value];
|
|
98
|
+
if (type === 'delete') return [op.value, ''];
|
|
99
|
+
return [op.length, op.value];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const segLength = (seg) => (seg.old ? seg.end - seg.start : seg.chars.length);
|
|
103
|
+
|
|
104
|
+
// The segment list of the new text, cut so that a segment boundary falls at
|
|
105
|
+
// code point `at`. Returns the index of the first segment at or after `at`.
|
|
106
|
+
function cutAt(segs, at) {
|
|
107
|
+
let pos = 0;
|
|
108
|
+
for (let i = 0; i < segs.length; i += 1) {
|
|
109
|
+
const seg = segs[i];
|
|
110
|
+
const n = segLength(seg);
|
|
111
|
+
if (at === pos) return i;
|
|
112
|
+
if (at < pos + n) {
|
|
113
|
+
const k = at - pos;
|
|
114
|
+
const left = seg.old
|
|
115
|
+
? { old: true, start: seg.start, end: seg.start + k }
|
|
116
|
+
: { old: false, chars: seg.chars.slice(0, k) };
|
|
117
|
+
const right = seg.old
|
|
118
|
+
? { old: true, start: seg.start + k, end: seg.end }
|
|
119
|
+
: { old: false, chars: seg.chars.slice(k) };
|
|
120
|
+
segs.splice(i, 1, left, right);
|
|
121
|
+
return i + 1;
|
|
122
|
+
}
|
|
123
|
+
pos += n;
|
|
124
|
+
}
|
|
125
|
+
return segs.length;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The net change of `ops` applied in turn to `body`, as gaps in the old
|
|
130
|
+
* body's code points: sorted, and never touching (at least one old code point
|
|
131
|
+
* is kept between two gaps). Old text deleted and typed back is typed text,
|
|
132
|
+
* and a gap whose value is the old text it removes is dropped. Throws on a
|
|
133
|
+
* malformed or out-of-bounds op.
|
|
134
|
+
* @param {string} body
|
|
135
|
+
* @param {TextEditOp[]} ops
|
|
136
|
+
* @returns {TextGap[]}
|
|
137
|
+
*/
|
|
138
|
+
export function composeTextEdits(body, ops) {
|
|
139
|
+
const old = bodyCodePoints(body);
|
|
140
|
+
let segs = old.length ? [{ old: true, start: 0, end: old.length }] : [];
|
|
141
|
+
let len = old.length;
|
|
142
|
+
for (const op of ops) {
|
|
143
|
+
const type = checkOp(op, len);
|
|
144
|
+
const [del, typed] = opParts(type, op);
|
|
145
|
+
const chars = codePoints(typed);
|
|
146
|
+
const from = cutAt(segs, op.index);
|
|
147
|
+
const to = cutAt(segs, op.index + del);
|
|
148
|
+
const inserted = chars.length ? [{ old: false, chars }] : [];
|
|
149
|
+
segs.splice(from, to - from, ...inserted);
|
|
150
|
+
len += chars.length - del;
|
|
151
|
+
segs = mergeTyped(segs);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const gaps = [];
|
|
155
|
+
let kept = 0; // the old position the last kept run ended at
|
|
156
|
+
let typed = [];
|
|
157
|
+
const close = (next) => {
|
|
158
|
+
if (next > kept || typed.length) {
|
|
159
|
+
const value = typed.join('');
|
|
160
|
+
if (value !== old.slice(kept, next).join('')) gaps.push({ start: kept, end: next, value });
|
|
161
|
+
}
|
|
162
|
+
typed = [];
|
|
163
|
+
};
|
|
164
|
+
for (const seg of segs) {
|
|
165
|
+
if (seg.old) {
|
|
166
|
+
close(seg.start);
|
|
167
|
+
kept = seg.end;
|
|
168
|
+
} else {
|
|
169
|
+
for (const c of seg.chars) typed.push(c);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
close(old.length);
|
|
173
|
+
return gaps;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Neighbouring typed segments as one, and neighbouring old ranges as one.
|
|
177
|
+
function mergeTyped(segs) {
|
|
178
|
+
const out = [];
|
|
179
|
+
for (const seg of segs) {
|
|
180
|
+
const last = out[out.length - 1];
|
|
181
|
+
if (last && !last.old && !seg.old) {
|
|
182
|
+
out[out.length - 1] = { old: false, chars: last.chars.concat(seg.chars) };
|
|
183
|
+
} else if (last && last.old && seg.old && last.end === seg.start) {
|
|
184
|
+
out[out.length - 1] = { old: true, start: last.start, end: seg.end };
|
|
185
|
+
} else if (segLength(seg) > 0) {
|
|
186
|
+
out.push(seg);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Gaps (old-body coordinates, sorted, not overlapping) as running ops: an
|
|
194
|
+
* insert where a gap removes nothing, a delete where it types nothing, and a
|
|
195
|
+
* replace otherwise.
|
|
196
|
+
* @param {TextGap[]} gaps
|
|
197
|
+
* @returns {TextEditOp[]}
|
|
198
|
+
*/
|
|
199
|
+
export function gapsToOps(gaps) {
|
|
200
|
+
const ops = [];
|
|
201
|
+
let shift = 0;
|
|
202
|
+
for (const { start, end, value } of gaps) {
|
|
203
|
+
const index = start + shift;
|
|
204
|
+
const typed = codePoints(value).length;
|
|
205
|
+
if (start === end) {
|
|
206
|
+
if (typed) ops.push({ type: 'insert', index, value });
|
|
207
|
+
} else if (!typed) {
|
|
208
|
+
ops.push({ type: 'delete', index, value: end - start });
|
|
209
|
+
} else {
|
|
210
|
+
ops.push({ type: 'replace', index, length: end - start, value });
|
|
211
|
+
}
|
|
212
|
+
shift += typed - (end - start);
|
|
213
|
+
}
|
|
214
|
+
return ops;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* `body` with `ops` applied in turn (running coordinates, code points).
|
|
219
|
+
* Throws on a malformed or out-of-bounds op.
|
|
220
|
+
* @param {string} body
|
|
221
|
+
* @param {TextEditOp[]} ops
|
|
222
|
+
* @returns {string}
|
|
223
|
+
*/
|
|
224
|
+
export function applyTextOps(body, ops) {
|
|
225
|
+
let chars = codePoints(body);
|
|
226
|
+
for (const op of ops) {
|
|
227
|
+
const type = checkOp(op, chars.length);
|
|
228
|
+
const [del, typed] = opParts(type, op);
|
|
229
|
+
chars = chars
|
|
230
|
+
.slice(0, op.index)
|
|
231
|
+
.concat(codePoints(typed), chars.slice(op.index + del));
|
|
232
|
+
}
|
|
233
|
+
return chars.join('');
|
|
234
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert kebab-case/namespaced key to camelCase.
|
|
3
|
+
* 'layer-id' -> 'layerId'
|
|
4
|
+
* 'relation/layer' -> 'layer' (namespace stripped)
|
|
5
|
+
* Hyphens before a digit are also consumed ('layer-2' -> 'layer2') so no stray
|
|
6
|
+
* hyphen is ever left in the key. (The Python client uses snake_case, where the
|
|
7
|
+
* analogous key is 'layer_2' — the local spelling differs by convention, but
|
|
8
|
+
* neither leaves a separator that doesn't belong to the convention.)
|
|
9
|
+
*/
|
|
10
|
+
export function transformKeyToCamel(key: any): any;
|
|
11
|
+
/**
|
|
12
|
+
* Convert camelCase key to kebab-case.
|
|
13
|
+
* 'layerId' -> 'layer-id'
|
|
14
|
+
*/
|
|
15
|
+
export function transformKeyFromCamel(key: any): any;
|
|
16
|
+
/**
|
|
17
|
+
* Recursively transform request object keys from camelCase to kebab-case.
|
|
18
|
+
* Preserves `metadata` and `config` contents without transformation.
|
|
19
|
+
*/
|
|
20
|
+
export function transformRequest(obj: any): any;
|
|
21
|
+
/**
|
|
22
|
+
* Recursively transform response object keys from kebab-case/namespaced to camelCase.
|
|
23
|
+
* Preserves `metadata` and `config` contents without transformation.
|
|
24
|
+
*/
|
|
25
|
+
export function transformResponse(obj: any): any;
|
package/src/transforms.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { BatchRef } from "./batchRef.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Convert kebab-case/namespaced key to camelCase.
|
|
3
5
|
* 'layer-id' -> 'layerId'
|
|
@@ -27,19 +29,27 @@ export function transformKeyFromCamel(key) {
|
|
|
27
29
|
// usual case conversion.
|
|
28
30
|
const OPAQUE_KEYS = new Set(['metadata', 'config', 'bindings']);
|
|
29
31
|
|
|
32
|
+
// A request's `metadata` may also be a list of metadata ops (a bulk update
|
|
33
|
+
// entry), whose values are user data too, so it passes through verbatim.
|
|
34
|
+
const isOpaque = (key, value) =>
|
|
35
|
+
typeof value === 'object' && value !== null &&
|
|
36
|
+
(Array.isArray(value) ? key === 'metadata' : OPAQUE_KEYS.has(key));
|
|
37
|
+
|
|
30
38
|
/**
|
|
31
39
|
* Recursively transform request object keys from camelCase to kebab-case.
|
|
32
40
|
* Preserves `metadata` and `config` contents without transformation.
|
|
33
41
|
*/
|
|
34
42
|
export function transformRequest(obj) {
|
|
35
43
|
if (obj === null || obj === undefined) return obj;
|
|
44
|
+
// A batch ref is taken out whole when its write is queued.
|
|
45
|
+
if (obj instanceof BatchRef) return obj;
|
|
36
46
|
if (Array.isArray(obj)) return obj.map(item => transformRequest(item));
|
|
37
47
|
if (typeof obj !== 'object') return obj;
|
|
38
48
|
|
|
39
49
|
const transformed = {};
|
|
40
50
|
for (const [key, value] of Object.entries(obj)) {
|
|
41
51
|
const newKey = transformKeyFromCamel(key);
|
|
42
|
-
if (
|
|
52
|
+
if (isOpaque(key, value)) {
|
|
43
53
|
transformed[newKey] = value;
|
|
44
54
|
} else {
|
|
45
55
|
transformed[newKey] = transformRequest(value);
|