@interop/was-client 0.66.0 → 0.67.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 +30 -28
- package/dist/Collection.d.ts +30 -41
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Collection.js +35 -78
- package/dist/Collection.js.map +1 -1
- package/dist/Resource.d.ts +28 -41
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +29 -65
- package/dist/Resource.js.map +1 -1
- package/dist/Space.d.ts +14 -14
- package/dist/Space.d.ts.map +1 -1
- package/dist/Space.js +16 -16
- package/dist/Space.js.map +1 -1
- package/dist/WasClient.d.ts +3 -2
- package/dist/WasClient.d.ts.map +1 -1
- package/dist/WasClient.js +4 -7
- package/dist/WasClient.js.map +1 -1
- package/dist/codec.d.ts +2 -9
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +1 -1
- package/dist/codec.js.map +1 -1
- package/dist/edv/EdvCodec.d.ts +3 -3
- package/dist/edv/EdvCodec.d.ts.map +1 -1
- package/dist/edv/EdvCodec.js +22 -72
- package/dist/edv/EdvCodec.js.map +1 -1
- package/dist/edv/WasTransport.d.ts +30 -44
- package/dist/edv/WasTransport.d.ts.map +1 -1
- package/dist/edv/WasTransport.js +38 -119
- package/dist/edv/WasTransport.js.map +1 -1
- package/dist/edv/descriptorStore.d.ts +5 -7
- package/dist/edv/descriptorStore.d.ts.map +1 -1
- package/dist/edv/descriptorStore.js +3 -4
- package/dist/edv/descriptorStore.js.map +1 -1
- package/dist/edv/docCipher.d.ts +1 -2
- package/dist/edv/docCipher.d.ts.map +1 -1
- package/dist/edv/docCipher.js +1 -2
- package/dist/edv/docCipher.js.map +1 -1
- package/dist/edv/logGovernedDescriptorStore.d.ts.map +1 -1
- package/dist/edv/logGovernedDescriptorStore.js +1 -2
- package/dist/edv/logGovernedDescriptorStore.js.map +1 -1
- package/dist/errors.d.ts +7 -6
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -6
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/cas.d.ts +19 -19
- package/dist/internal/cas.d.ts.map +1 -1
- package/dist/internal/cas.js +30 -23
- package/dist/internal/cas.js.map +1 -1
- package/dist/internal/conditional.d.ts +17 -63
- package/dist/internal/conditional.d.ts.map +1 -1
- package/dist/internal/conditional.js +20 -76
- package/dist/internal/conditional.js.map +1 -1
- package/dist/internal/memo.d.ts +2 -2
- package/dist/internal/memo.js +2 -2
- package/dist/internal/meta.d.ts +13 -29
- package/dist/internal/meta.d.ts.map +1 -1
- package/dist/internal/meta.js +22 -41
- package/dist/internal/meta.js.map +1 -1
- package/dist/internal/paths.d.ts +1 -1
- package/dist/internal/paths.js +1 -1
- package/dist/internal/request.d.ts +0 -10
- package/dist/internal/request.d.ts.map +1 -1
- package/dist/internal/request.js.map +1 -1
- package/dist/internal/write.d.ts +12 -36
- package/dist/internal/write.d.ts.map +1 -1
- package/dist/internal/write.js +16 -87
- package/dist/internal/write.js.map +1 -1
- package/dist/log/logStore.d.ts +6 -8
- package/dist/log/logStore.d.ts.map +1 -1
- package/dist/log/logStore.js +7 -10
- package/dist/log/logStore.js.map +1 -1
- package/dist/sync/index.d.ts +9 -5
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +9 -5
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/port.d.ts +3 -7
- package/dist/sync/port.d.ts.map +1 -1
- package/dist/sync/port.js +4 -20
- package/dist/sync/port.js.map +1 -1
- package/dist/sync/predicates.d.ts +21 -2
- package/dist/sync/predicates.d.ts.map +1 -1
- package/dist/sync/predicates.js +23 -2
- package/dist/sync/predicates.js.map +1 -1
- package/dist/sync/types.d.ts +8 -9
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/types.d.ts +4 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/internal/features.d.ts +0 -111
- package/dist/internal/features.d.ts.map +0 -1
- package/dist/internal/features.js +0 -197
- package/dist/internal/features.js.map +0 -1
package/dist/internal/write.d.ts
CHANGED
|
@@ -22,7 +22,6 @@ import type { HttpResponse } from '@interop/http-client';
|
|
|
22
22
|
import type { CodecRequestContext, EncodedWrite, ResourceCodec } from '../codec.js';
|
|
23
23
|
import type { IZcap, ResourceData } from '../types.js';
|
|
24
24
|
import type { ClientContext } from './request.js';
|
|
25
|
-
import type { FeatureProbe } from './features.js';
|
|
26
25
|
import type { WritePrecondition } from './conditional.js';
|
|
27
26
|
/**
|
|
28
27
|
* Sends an encoded write (`PUT`/`POST`) to a resource path, applying the
|
|
@@ -48,10 +47,10 @@ export declare function sendEncodedWrite(context: ClientContext, { path, method,
|
|
|
48
47
|
}): Promise<HttpResponse>;
|
|
49
48
|
/**
|
|
50
49
|
* Builds the {@link CodecRequestContext} core hands a codec that drives its own
|
|
51
|
-
* I/O: the signed-request primitive bound to this handle's capability
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
50
|
+
* I/O: the signed-request primitive bound to this handle's capability. The
|
|
51
|
+
* codec never sees the zcap machinery, and the raw `HttpResponse` it gets back
|
|
52
|
+
* matches the `was.request()` escape hatch, which is what `WasTransport`
|
|
53
|
+
* consumes.
|
|
55
54
|
*
|
|
56
55
|
* Requests go through the same mapped `send` path the core write paths use, so
|
|
57
56
|
* a codec-driven write fails with the typed `WasError` subclasses the calling
|
|
@@ -61,13 +60,10 @@ export declare function sendEncodedWrite(context: ClientContext, { path, method,
|
|
|
61
60
|
*
|
|
62
61
|
* @param context {ClientContext}
|
|
63
62
|
* @param options {object}
|
|
64
|
-
* @param options.features {FeatureProbe} the handle's memoized backend-feature
|
|
65
|
-
* probe
|
|
66
63
|
* @param [options.capability] {IZcap} capability attached to each request
|
|
67
64
|
* @returns {CodecRequestContext}
|
|
68
65
|
*/
|
|
69
|
-
export declare function codecRequestContext(context: ClientContext, {
|
|
70
|
-
features: FeatureProbe;
|
|
66
|
+
export declare function codecRequestContext(context: ClientContext, { capability }?: {
|
|
71
67
|
capability?: IZcap;
|
|
72
68
|
}): CodecRequestContext;
|
|
73
69
|
/**
|
|
@@ -96,7 +92,7 @@ export type InsertOutcome = {
|
|
|
96
92
|
* A codec may also answer the encode with a multi-request plan (the EDV codec's
|
|
97
93
|
* chunked blob write). The plan is then executed over the handle's signed
|
|
98
94
|
* request context instead of being sent as one request; it owns its own
|
|
99
|
-
* preconditions
|
|
95
|
+
* preconditions.
|
|
100
96
|
*
|
|
101
97
|
* A codec that mints its own id (e.g. the encrypting codec's EDV id) writes it
|
|
102
98
|
* by `PUT` to that id's path; a codec that mints none (the identity codec)
|
|
@@ -117,19 +113,15 @@ export type InsertOutcome = {
|
|
|
117
113
|
* codec-minted id
|
|
118
114
|
* @param options.codec {ResourceCodec} the collection's resolved codec
|
|
119
115
|
* @param options.data {ResourceData} the plaintext value
|
|
120
|
-
* @param options.features {FeatureProbe} the handle's shared backend-feature
|
|
121
|
-
* probe, handed to a codec's multi-request plan so its affordance gate costs
|
|
122
|
-
* no extra round trip
|
|
123
116
|
* @param [options.contentType] {string} caller-supplied content type
|
|
124
117
|
* @param [options.capability] {IZcap}
|
|
125
118
|
* @returns {Promise<InsertOutcome>}
|
|
126
119
|
*/
|
|
127
|
-
export declare function insertResource(context: ClientContext, { itemsPath, pathForId, codec, data,
|
|
120
|
+
export declare function insertResource(context: ClientContext, { itemsPath, pathForId, codec, data, contentType, capability }: {
|
|
128
121
|
itemsPath: string;
|
|
129
122
|
pathForId: (id: string) => string;
|
|
130
123
|
codec: ResourceCodec;
|
|
131
124
|
data: ResourceData;
|
|
132
|
-
features: FeatureProbe;
|
|
133
125
|
contentType?: string;
|
|
134
126
|
capability?: IZcap;
|
|
135
127
|
}): Promise<InsertOutcome>;
|
|
@@ -145,22 +137,10 @@ export declare function insertResource(context: ClientContext, { itemsPath, path
|
|
|
145
137
|
* - The pre-read cannot distinguish "absent" from "unreadable with this
|
|
146
138
|
* capability" (WAS masks unauthorized reads as 404), so a conditional codec
|
|
147
139
|
* encodes a fresh insert (`If-None-Match: *`) in both cases. When the target
|
|
148
|
-
* in fact exists,
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* - A backend that does NOT advertise `conditional-writes` ignores the
|
|
153
|
-
* `If-None-Match: *` guard, so the 412 safety net above never fires there: a
|
|
154
|
-
* masked-404 insert would silently overwrite the existing document and reset
|
|
155
|
-
* its sequence. The insert-after-null-pre-read is therefore refused (fail
|
|
156
|
-
* closed) unless the backend advertises the feature.
|
|
157
|
-
* - A caller's named precondition is refused with `NotSupportedError` when the
|
|
158
|
-
* backend descriptor was read and advertises no `conditional-writes`,
|
|
159
|
-
* whatever the collection's codec. A backend that ignores `If-Match` would
|
|
160
|
-
* otherwise turn the caller's compare-and-swap into a silent overwrite. This
|
|
161
|
-
* is the one gate every guarded write through a handle passes, so the stores
|
|
162
|
-
* layered over `Resource.put` (the resource log, the descriptor stores) do
|
|
163
|
-
* not repeat it.
|
|
140
|
+
* in fact exists, the server rejects that insert with 412; that 412 is
|
|
141
|
+
* re-thrown here with a message naming the real cause, instead of surfacing
|
|
142
|
+
* as an inexplicable failed create. Conditional codecs therefore need read
|
|
143
|
+
* access to update an existing document.
|
|
164
144
|
*
|
|
165
145
|
* @param context {ClientContext}
|
|
166
146
|
* @param options {object}
|
|
@@ -168,21 +148,17 @@ export declare function insertResource(context: ClientContext, { itemsPath, path
|
|
|
168
148
|
* @param options.codec {ResourceCodec} the collection's resolved codec
|
|
169
149
|
* @param options.id {string} the resource id
|
|
170
150
|
* @param options.data {ResourceData} the plaintext value
|
|
171
|
-
* @param options.features {FeatureProbe} the handle's shared
|
|
172
|
-
* `BackendFeatures` probe; consulted for a named precondition and for a
|
|
173
|
-
* conditional codec's insert-after-null-pre-read
|
|
174
151
|
* @param [options.contentType] {string} caller-supplied content type
|
|
175
152
|
* @param [options.capability] {IZcap}
|
|
176
153
|
* @param [options.precondition] {WritePrecondition} the caller's explicit
|
|
177
154
|
* precondition (used only for a non-conditional codec)
|
|
178
155
|
* @returns {Promise<HttpResponse>}
|
|
179
156
|
*/
|
|
180
|
-
export declare function upsertResource(context: ClientContext, { path, codec, id, data,
|
|
157
|
+
export declare function upsertResource(context: ClientContext, { path, codec, id, data, contentType, capability, precondition: callerPrecondition }: {
|
|
181
158
|
path: string;
|
|
182
159
|
codec: ResourceCodec;
|
|
183
160
|
id: string;
|
|
184
161
|
data: ResourceData;
|
|
185
|
-
features: FeatureProbe;
|
|
186
162
|
contentType?: string;
|
|
187
163
|
capability?: IZcap;
|
|
188
164
|
precondition?: WritePrecondition;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/internal/write.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,KAAK,EACV,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACd,MAAM,aAAa,CAAA;AAGpB,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/internal/write.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,KAAK,EACV,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACd,MAAM,aAAa,CAAA;AAGpB,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AASjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAEzD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,aAAa,EACtB,EACE,IAAI,EACJ,MAAM,EACN,OAAO,EACP,UAAU,EACV,YAAY,EACb,EAAE;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,YAAY,CAAA;IACrB,UAAU,CAAC,EAAE,KAAK,CAAA;IAClB,YAAY,CAAC,EAAE,iBAAiB,CAAA;CACjC,GACA,OAAO,CAAC,YAAY,CAAC,CAcvB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,aAAa,EACtB,EAAE,UAAU,EAAE,GAAE;IAAE,UAAU,CAAC,EAAE,KAAK,CAAA;CAAO,GAC1C,mBAAmB,CASrB;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GACrB;IACE,OAAO,CAAC,EAAE,KAAK,CAAA;IACf,OAAO,EAAE,YAAY,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,YAAY,CAAA;CACvB,GACD;IACE,OAAO,EAAE,IAAI,CAAA;IACb,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAEL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,aAAa,EACtB,EACE,SAAS,EACT,SAAS,EACT,KAAK,EACL,IAAI,EACJ,WAAW,EACX,UAAU,EACX,EAAE;IACD,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAA;IACjC,KAAK,EAAE,aAAa,CAAA;IACpB,IAAI,EAAE,YAAY,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,KAAK,CAAA;CACnB,GACA,OAAO,CAAC,aAAa,CAAC,CA2BxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,aAAa,EACtB,EACE,IAAI,EACJ,KAAK,EACL,EAAE,EACF,IAAI,EACJ,WAAW,EACX,UAAU,EACV,YAAY,EAAE,kBAAkB,EACjC,EAAE;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,aAAa,CAAA;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,YAAY,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,KAAK,CAAA;IAClB,YAAY,CAAC,EAAE,iBAAiB,CAAA;CACjC,GACA,OAAO,CAAC,YAAY,CAAC,CA8EvB"}
|
package/dist/internal/write.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isChunkedWrite } from '../codec.js';
|
|
2
2
|
import { PreconditionFailedError, ValidationError } from '../errors.js';
|
|
3
3
|
import { send } from './request.js';
|
|
4
|
-
import { assertPreconditionAgainstPreRead,
|
|
4
|
+
import { assertPreconditionAgainstPreRead, assertSinglePrecondition, encodedPrecondition, writeHeaders, namedPrecondition } from './conditional.js';
|
|
5
5
|
/**
|
|
6
6
|
* Sends an encoded write (`PUT`/`POST`) to a resource path, applying the
|
|
7
7
|
* encoded body (`json` or `body`), its content-type, and the conditional-write
|
|
@@ -34,10 +34,10 @@ export async function sendEncodedWrite(context, { path, method, encoded, capabil
|
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
36
|
* Builds the {@link CodecRequestContext} core hands a codec that drives its own
|
|
37
|
-
* I/O: the signed-request primitive bound to this handle's capability
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
37
|
+
* I/O: the signed-request primitive bound to this handle's capability. The
|
|
38
|
+
* codec never sees the zcap machinery, and the raw `HttpResponse` it gets back
|
|
39
|
+
* matches the `was.request()` escape hatch, which is what `WasTransport`
|
|
40
|
+
* consumes.
|
|
41
41
|
*
|
|
42
42
|
* Requests go through the same mapped `send` path the core write paths use, so
|
|
43
43
|
* a codec-driven write fails with the typed `WasError` subclasses the calling
|
|
@@ -47,20 +47,17 @@ export async function sendEncodedWrite(context, { path, method, encoded, capabil
|
|
|
47
47
|
*
|
|
48
48
|
* @param context {ClientContext}
|
|
49
49
|
* @param options {object}
|
|
50
|
-
* @param options.features {FeatureProbe} the handle's memoized backend-feature
|
|
51
|
-
* probe
|
|
52
50
|
* @param [options.capability] {IZcap} capability attached to each request
|
|
53
51
|
* @returns {CodecRequestContext}
|
|
54
52
|
*/
|
|
55
|
-
export function codecRequestContext(context, {
|
|
53
|
+
export function codecRequestContext(context, { capability } = {}) {
|
|
56
54
|
return {
|
|
57
55
|
async request(input) {
|
|
58
56
|
// `send` only resolves `null` for the `read`/`idempotent` flags, which
|
|
59
57
|
// this surface never sets, so the response is always present.
|
|
60
58
|
const response = await send(context, { capability, ...input });
|
|
61
59
|
return response;
|
|
62
|
-
}
|
|
63
|
-
features
|
|
60
|
+
}
|
|
64
61
|
};
|
|
65
62
|
}
|
|
66
63
|
/**
|
|
@@ -71,7 +68,7 @@ export function codecRequestContext(context, { features, capability }) {
|
|
|
71
68
|
* A codec may also answer the encode with a multi-request plan (the EDV codec's
|
|
72
69
|
* chunked blob write). The plan is then executed over the handle's signed
|
|
73
70
|
* request context instead of being sent as one request; it owns its own
|
|
74
|
-
* preconditions
|
|
71
|
+
* preconditions.
|
|
75
72
|
*
|
|
76
73
|
* A codec that mints its own id (e.g. the encrypting codec's EDV id) writes it
|
|
77
74
|
* by `PUT` to that id's path; a codec that mints none (the identity codec)
|
|
@@ -92,17 +89,14 @@ export function codecRequestContext(context, { features, capability }) {
|
|
|
92
89
|
* codec-minted id
|
|
93
90
|
* @param options.codec {ResourceCodec} the collection's resolved codec
|
|
94
91
|
* @param options.data {ResourceData} the plaintext value
|
|
95
|
-
* @param options.features {FeatureProbe} the handle's shared backend-feature
|
|
96
|
-
* probe, handed to a codec's multi-request plan so its affordance gate costs
|
|
97
|
-
* no extra round trip
|
|
98
92
|
* @param [options.contentType] {string} caller-supplied content type
|
|
99
93
|
* @param [options.capability] {IZcap}
|
|
100
94
|
* @returns {Promise<InsertOutcome>}
|
|
101
95
|
*/
|
|
102
|
-
export async function insertResource(context, { itemsPath, pathForId, codec, data,
|
|
96
|
+
export async function insertResource(context, { itemsPath, pathForId, codec, data, contentType, capability }) {
|
|
103
97
|
const write = await codec.encode({ data, contentType });
|
|
104
98
|
if (isChunkedWrite(write)) {
|
|
105
|
-
const { id, etag } = await write.execute(codecRequestContext(context, {
|
|
99
|
+
const { id, etag } = await write.execute(codecRequestContext(context, { capability }));
|
|
106
100
|
return {
|
|
107
101
|
chunked: true,
|
|
108
102
|
id,
|
|
@@ -137,22 +131,10 @@ export async function insertResource(context, { itemsPath, pathForId, codec, dat
|
|
|
137
131
|
* - The pre-read cannot distinguish "absent" from "unreadable with this
|
|
138
132
|
* capability" (WAS masks unauthorized reads as 404), so a conditional codec
|
|
139
133
|
* encodes a fresh insert (`If-None-Match: *`) in both cases. When the target
|
|
140
|
-
* in fact exists,
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* - A backend that does NOT advertise `conditional-writes` ignores the
|
|
145
|
-
* `If-None-Match: *` guard, so the 412 safety net above never fires there: a
|
|
146
|
-
* masked-404 insert would silently overwrite the existing document and reset
|
|
147
|
-
* its sequence. The insert-after-null-pre-read is therefore refused (fail
|
|
148
|
-
* closed) unless the backend advertises the feature.
|
|
149
|
-
* - A caller's named precondition is refused with `NotSupportedError` when the
|
|
150
|
-
* backend descriptor was read and advertises no `conditional-writes`,
|
|
151
|
-
* whatever the collection's codec. A backend that ignores `If-Match` would
|
|
152
|
-
* otherwise turn the caller's compare-and-swap into a silent overwrite. This
|
|
153
|
-
* is the one gate every guarded write through a handle passes, so the stores
|
|
154
|
-
* layered over `Resource.put` (the resource log, the descriptor stores) do
|
|
155
|
-
* not repeat it.
|
|
134
|
+
* in fact exists, the server rejects that insert with 412; that 412 is
|
|
135
|
+
* re-thrown here with a message naming the real cause, instead of surfacing
|
|
136
|
+
* as an inexplicable failed create. Conditional codecs therefore need read
|
|
137
|
+
* access to update an existing document.
|
|
156
138
|
*
|
|
157
139
|
* @param context {ClientContext}
|
|
158
140
|
* @param options {object}
|
|
@@ -160,26 +142,18 @@ export async function insertResource(context, { itemsPath, pathForId, codec, dat
|
|
|
160
142
|
* @param options.codec {ResourceCodec} the collection's resolved codec
|
|
161
143
|
* @param options.id {string} the resource id
|
|
162
144
|
* @param options.data {ResourceData} the plaintext value
|
|
163
|
-
* @param options.features {FeatureProbe} the handle's shared
|
|
164
|
-
* `BackendFeatures` probe; consulted for a named precondition and for a
|
|
165
|
-
* conditional codec's insert-after-null-pre-read
|
|
166
145
|
* @param [options.contentType] {string} caller-supplied content type
|
|
167
146
|
* @param [options.capability] {IZcap}
|
|
168
147
|
* @param [options.precondition] {WritePrecondition} the caller's explicit
|
|
169
148
|
* precondition (used only for a non-conditional codec)
|
|
170
149
|
* @returns {Promise<HttpResponse>}
|
|
171
150
|
*/
|
|
172
|
-
export async function upsertResource(context, { path, codec, id, data,
|
|
151
|
+
export async function upsertResource(context, { path, codec, id, data, contentType, capability, precondition: callerPrecondition }) {
|
|
173
152
|
// An empty precondition object (the handle's default) names no baseline.
|
|
174
153
|
const precondition = namedPrecondition(callerPrecondition);
|
|
175
154
|
// Checked before the pre-read, whose comparison would otherwise answer the
|
|
176
155
|
// pair with a 412 either way.
|
|
177
156
|
assertSinglePrecondition(precondition);
|
|
178
|
-
await assertPreconditionEnforced({
|
|
179
|
-
features,
|
|
180
|
-
precondition,
|
|
181
|
-
operation: `Cannot write the resource at "${path}"`
|
|
182
|
-
});
|
|
183
157
|
let current;
|
|
184
158
|
if (codec.conditionalWrites) {
|
|
185
159
|
current = await send(context, {
|
|
@@ -192,22 +166,6 @@ export async function upsertResource(context, { path, codec, id, data, features,
|
|
|
192
166
|
// pre-read just observed, so a lost race fails here rather than being
|
|
193
167
|
// encoded into a sequence advance the server would then reject.
|
|
194
168
|
assertPreconditionAgainstPreRead({ path, current, precondition });
|
|
195
|
-
if (current === null && !(await conditionalWrites({ path, features }))) {
|
|
196
|
-
// The write would be encoded as a fresh insert guarded only by
|
|
197
|
-
// `If-None-Match: *`, which this backend ignores -- so if the document in
|
|
198
|
-
// fact exists but is unreadable with this capability (the masked-404
|
|
199
|
-
// ambiguity), the PUT would silently destroy it. Refuse rather than risk
|
|
200
|
-
// the clobber.
|
|
201
|
-
throw new ValidationError(`Cannot create the document at "${path}": no current document is ` +
|
|
202
|
-
'readable there, which cannot distinguish "absent" from "exists ' +
|
|
203
|
-
'but unreadable with this capability" (WAS masks unauthorized ' +
|
|
204
|
-
"reads as 404), and the collection's backend does not advertise " +
|
|
205
|
-
"the 'conditional-writes' feature -- so the server could not " +
|
|
206
|
-
'reject the write either, and an existing document would be ' +
|
|
207
|
-
'silently overwritten. Use a backend with conditional writes, a ' +
|
|
208
|
-
'capability that can read the current document and the backend ' +
|
|
209
|
-
'descriptor, or add() to mint a fresh document id.');
|
|
210
|
-
}
|
|
211
169
|
}
|
|
212
170
|
const write = await codec.encode({
|
|
213
171
|
id,
|
|
@@ -256,40 +214,11 @@ export async function upsertResource(context, { path, codec, id, data, features,
|
|
|
256
214
|
throw new PreconditionFailedError(`Cannot update the document at "${path}": it exists, but its current ` +
|
|
257
215
|
'version is not readable with this capability (WAS masks ' +
|
|
258
216
|
'unauthorized reads as 404), so the write was encoded as a fresh ' +
|
|
259
|
-
'insert and the server rejected it. A conditional
|
|
217
|
+
'insert and the server rejected it. A conditional codec ' +
|
|
260
218
|
'(e.g. the EDV codec) needs read access to update an existing ' +
|
|
261
219
|
'document.', { status, type, title, details, requestUrl, cause: err });
|
|
262
220
|
}
|
|
263
221
|
throw err;
|
|
264
222
|
}
|
|
265
223
|
}
|
|
266
|
-
/**
|
|
267
|
-
* Whether the backend advertises `conditional-writes`, with a probe failure
|
|
268
|
-
* reported as the fail-closed refusal it leads to rather than raw.
|
|
269
|
-
*
|
|
270
|
-
* The probe rethrows transient failures on purpose (a network error, a `401`,
|
|
271
|
-
* a `429`, a non-`501` `5xx`), so a blip on `GET .../backend` would otherwise
|
|
272
|
-
* surface on a first write as an unrelated `WasServerError` or
|
|
273
|
-
* `AuthRequiredError`. The write is refused either way -- an unprobed backend
|
|
274
|
-
* is one whose guard cannot be relied on -- but the refusal says which
|
|
275
|
-
* question went unanswered, and keeps the probe's own error as its `cause`.
|
|
276
|
-
*
|
|
277
|
-
* @param options {object}
|
|
278
|
-
* @param options.path {string} the resource path being written
|
|
279
|
-
* @param options.features {FeatureProbe}
|
|
280
|
-
* @returns {Promise<boolean>}
|
|
281
|
-
*/
|
|
282
|
-
async function conditionalWrites({ path, features }) {
|
|
283
|
-
try {
|
|
284
|
-
return await features.has('conditional-writes');
|
|
285
|
-
}
|
|
286
|
-
catch (err) {
|
|
287
|
-
throw new ValidationError(`Cannot create the document at "${path}": no current document is ` +
|
|
288
|
-
"readable there, and the collection's backend descriptor could not " +
|
|
289
|
-
'be read, so whether the backend enforces the `If-None-Match: *` ' +
|
|
290
|
-
'guard is unknown. Retry once the backend descriptor is reachable, ' +
|
|
291
|
-
'use a capability that can read it, or add() to mint a fresh ' +
|
|
292
|
-
'document id.', { cause: err });
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
224
|
//# sourceMappingURL=write.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write.js","sourceRoot":"","sources":["../../src/internal/write.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAGvE,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"write.js","sourceRoot":"","sources":["../../src/internal/write.ts"],"names":[],"mappings":"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"}
|
package/dist/log/logStore.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* the two places a WAS server keeps a resource log. A log may be a WAS
|
|
7
7
|
* Resource's entire body (the user key roster's `key-map/user-key.jsonl`), or
|
|
8
8
|
* a Collection's governing history log at the `/meta/log` sub-resource (the
|
|
9
|
-
*
|
|
9
|
+
* encrypted-collections profile's `governed-history-logs` feature, from which the server derives
|
|
10
10
|
* the Collection's served `encryption` member). Either way the log is stored
|
|
11
11
|
* as `text/jsonl`, with the port's read-with-etag, compare-and-swap append,
|
|
12
12
|
* and guarded genesis create mapped onto the target's conditional writes.
|
|
@@ -21,13 +21,11 @@
|
|
|
21
21
|
*
|
|
22
22
|
* Both the append and the create depend on their precondition being enforced --
|
|
23
23
|
* the profile requires it (without it, concurrent appends silently overwrite
|
|
24
|
-
* one another instead of failing into the caller's rebase-and-retry loop).
|
|
25
|
-
* a
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* not the backend advertises the feature, so nothing gates that host. Either
|
|
30
|
-
* way an append is refused when the read it extends returned no validator.
|
|
24
|
+
* one another instead of failing into the caller's rebase-and-retry loop).
|
|
25
|
+
* Conditional writes are a baseline server requirement on a Resource host, and
|
|
26
|
+
* a Collection's history log carries its own validator, which a server
|
|
27
|
+
* implementing the sub-resource honors. Either way an append is refused when
|
|
28
|
+
* the read it extends returned no validator.
|
|
31
29
|
* A lost race -- a stale `ifMatch`, or a guarded create against a log that
|
|
32
30
|
* already exists -- is rethrown as the library's
|
|
33
31
|
* `ResourceLogConflictError` with the transport's `PreconditionFailedError`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logStore.d.ts","sourceRoot":"","sources":["../../src/log/logStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"logStore.d.ts","sourceRoot":"","sources":["../../src/log/logStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAKL,KAAK,gBAAgB,EACtB,MAAM,0BAA0B,CAAA;AACjC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAG9C,OAAO,EAAW,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAElE,OAAO,EAAE,gBAAgB,EAAE,CAAA;AAwD3B;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EACH;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,UAAU,CAAC,EAAE,SAAS,CAAA;CAAE,GAC9C;IAAE,UAAU,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,GACnD,gBAAgB,CA+FlB"}
|
package/dist/log/logStore.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* the two places a WAS server keeps a resource log. A log may be a WAS
|
|
7
7
|
* Resource's entire body (the user key roster's `key-map/user-key.jsonl`), or
|
|
8
8
|
* a Collection's governing history log at the `/meta/log` sub-resource (the
|
|
9
|
-
*
|
|
9
|
+
* encrypted-collections profile's `governed-history-logs` feature, from which the server derives
|
|
10
10
|
* the Collection's served `encryption` member). Either way the log is stored
|
|
11
11
|
* as `text/jsonl`, with the port's read-with-etag, compare-and-swap append,
|
|
12
12
|
* and guarded genesis create mapped onto the target's conditional writes.
|
|
@@ -21,13 +21,11 @@
|
|
|
21
21
|
*
|
|
22
22
|
* Both the append and the create depend on their precondition being enforced --
|
|
23
23
|
* the profile requires it (without it, concurrent appends silently overwrite
|
|
24
|
-
* one another instead of failing into the caller's rebase-and-retry loop).
|
|
25
|
-
* a
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* not the backend advertises the feature, so nothing gates that host. Either
|
|
30
|
-
* way an append is refused when the read it extends returned no validator.
|
|
24
|
+
* one another instead of failing into the caller's rebase-and-retry loop).
|
|
25
|
+
* Conditional writes are a baseline server requirement on a Resource host, and
|
|
26
|
+
* a Collection's history log carries its own validator, which a server
|
|
27
|
+
* implementing the sub-resource honors. Either way an append is refused when
|
|
28
|
+
* the read it extends returned no validator.
|
|
31
29
|
* A lost race -- a stale `ifMatch`, or a guarded create against a log that
|
|
32
30
|
* already exists -- is rethrown as the library's
|
|
33
31
|
* `ResourceLogConflictError` with the transport's `PreconditionFailedError`
|
|
@@ -131,8 +129,7 @@ export function resourceLogStore(options) {
|
|
|
131
129
|
}
|
|
132
130
|
if (ifMatch === undefined) {
|
|
133
131
|
throw unenforcedPreconditionError({
|
|
134
|
-
operation: 'Cannot append to the resource log'
|
|
135
|
-
reason: 'no-validator'
|
|
132
|
+
operation: 'Cannot append to the resource log'
|
|
136
133
|
});
|
|
137
134
|
}
|
|
138
135
|
const separator = lastReadBody.endsWith('\n') ? '' : '\n';
|
package/dist/log/logStore.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logStore.js","sourceRoot":"","sources":["../../src/log/logStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"logStore.js","sourceRoot":"","sources":["../../src/log/logStore.ts"],"names":[],"mappings":"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"}
|
package/dist/sync/index.d.ts
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* Collection provisioning, and its Space half alone (ensure the Space once,
|
|
18
18
|
* then thread the returned description through every collection).
|
|
19
19
|
* - `isSyncConflictError` / `isSyncNotFoundError` / `isSyncAuthError` /
|
|
20
|
-
* `isUnknownEpochError` / `isKeyUnwrapError` / `isIntegrityError`
|
|
21
|
-
* classification contract for the signals
|
|
20
|
+
* `isUnknownEpochError` / `isKeyUnwrapError` / `isIntegrityError` /
|
|
21
|
+
* `isNotSupportedError` -- the classification contract for the signals
|
|
22
22
|
* below. A consumer matches by `err.name` and never with `instanceof`,
|
|
23
23
|
* because every one of these errors is raised inside a seam the app injects
|
|
24
24
|
* (the port, the caller's `DocCipher`) and that seam can resolve to a second
|
|
@@ -37,7 +37,11 @@
|
|
|
37
37
|
* key for it, so a refresh cannot help). So is the tamper signal
|
|
38
38
|
* (`IntegrityError`: the stored body does not verify against the resource id
|
|
39
39
|
* it was read under). `EncryptionError`, the fail-closed umbrella both
|
|
40
|
-
* `KeyUnwrapError` and `IntegrityError` fall under, rides along.
|
|
40
|
+
* `KeyUnwrapError` and `IntegrityError` fall under, rides along. So does the
|
|
41
|
+
* affordance gate (`NotSupportedError`), which a guarded write raises before
|
|
42
|
+
* any request when the read it is pinned to returned no `ETag` validator -- a
|
|
43
|
+
* permanent refusal, so a replication driver stops rather than retries.
|
|
44
|
+
* The classes are exported
|
|
41
45
|
* for construction and for a caller inside one resolved copy; across a package
|
|
42
46
|
* boundary the predicates are the contract
|
|
43
47
|
* (`decisions/0001-cross-package-errors-match-by-name.md`).
|
|
@@ -48,7 +52,7 @@ export { contentCid, cidFrom, deriveSpaceId } from './cid.js';
|
|
|
48
52
|
export { isEncryptedEnvelope } from './envelope.js';
|
|
49
53
|
export { createPlaintextDocCipher } from './plaintextCipher.js';
|
|
50
54
|
export { ensureSpace, ensureSpaceAndCollection } from './provisioning.js';
|
|
51
|
-
export { isSyncAuthError, isSyncConflictError, isSyncNotFoundError, isIntegrityError, isKeyUnwrapError, isUnknownEpochError } from './predicates.js';
|
|
52
|
-
export { EncryptionError, IntegrityError, KeyUnwrapError, UnknownEpochError, WasSyncAuthError, WasSyncConflictError, WasSyncNotFoundError } from '../errors.js';
|
|
55
|
+
export { isNotSupportedError, isSyncAuthError, isSyncConflictError, isSyncNotFoundError, isIntegrityError, isKeyUnwrapError, isUnknownEpochError } from './predicates.js';
|
|
56
|
+
export { EncryptionError, IntegrityError, KeyUnwrapError, NotSupportedError, UnknownEpochError, WasSyncAuthError, WasSyncConflictError, WasSyncNotFoundError } from '../errors.js';
|
|
53
57
|
export type { Json, SyncCheckpoint, WireDoc, SyncPage, MasterState, WriteAck, SyncStatus, WasSyncPort, DocCipher } from './types.js';
|
|
54
58
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/sync/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAI1E,OAAO,EAAE,UAAU,IAAI,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AACzE,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,eAAe,EACf,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,cAAc,CAAA;AAErB,YAAY,EACV,IAAI,EACJ,cAAc,EACd,OAAO,EACP,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,UAAU,EACV,WAAW,EACX,SAAS,EACV,MAAM,YAAY,CAAA"}
|
package/dist/sync/index.js
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* Collection provisioning, and its Space half alone (ensure the Space once,
|
|
18
18
|
* then thread the returned description through every collection).
|
|
19
19
|
* - `isSyncConflictError` / `isSyncNotFoundError` / `isSyncAuthError` /
|
|
20
|
-
* `isUnknownEpochError` / `isKeyUnwrapError` / `isIntegrityError`
|
|
21
|
-
* classification contract for the signals
|
|
20
|
+
* `isUnknownEpochError` / `isKeyUnwrapError` / `isIntegrityError` /
|
|
21
|
+
* `isNotSupportedError` -- the classification contract for the signals
|
|
22
22
|
* below. A consumer matches by `err.name` and never with `instanceof`,
|
|
23
23
|
* because every one of these errors is raised inside a seam the app injects
|
|
24
24
|
* (the port, the caller's `DocCipher`) and that seam can resolve to a second
|
|
@@ -37,7 +37,11 @@
|
|
|
37
37
|
* key for it, so a refresh cannot help). So is the tamper signal
|
|
38
38
|
* (`IntegrityError`: the stored body does not verify against the resource id
|
|
39
39
|
* it was read under). `EncryptionError`, the fail-closed umbrella both
|
|
40
|
-
* `KeyUnwrapError` and `IntegrityError` fall under, rides along.
|
|
40
|
+
* `KeyUnwrapError` and `IntegrityError` fall under, rides along. So does the
|
|
41
|
+
* affordance gate (`NotSupportedError`), which a guarded write raises before
|
|
42
|
+
* any request when the read it is pinned to returned no `ETag` validator -- a
|
|
43
|
+
* permanent refusal, so a replication driver stops rather than retries.
|
|
44
|
+
* The classes are exported
|
|
41
45
|
* for construction and for a caller inside one resolved copy; across a package
|
|
42
46
|
* boundary the predicates are the contract
|
|
43
47
|
* (`decisions/0001-cross-package-errors-match-by-name.md`).
|
|
@@ -51,6 +55,6 @@ export { contentCid, cidFrom, deriveSpaceId } from './cid.js';
|
|
|
51
55
|
export { isEncryptedEnvelope } from './envelope.js';
|
|
52
56
|
export { createPlaintextDocCipher } from './plaintextCipher.js';
|
|
53
57
|
export { ensureSpace, ensureSpaceAndCollection } from './provisioning.js';
|
|
54
|
-
export { isSyncAuthError, isSyncConflictError, isSyncNotFoundError, isIntegrityError, isKeyUnwrapError, isUnknownEpochError } from './predicates.js';
|
|
55
|
-
export { EncryptionError, IntegrityError, KeyUnwrapError, UnknownEpochError, WasSyncAuthError, WasSyncConflictError, WasSyncNotFoundError } from '../errors.js';
|
|
58
|
+
export { isNotSupportedError, isSyncAuthError, isSyncConflictError, isSyncNotFoundError, isIntegrityError, isKeyUnwrapError, isUnknownEpochError } from './predicates.js';
|
|
59
|
+
export { EncryptionError, IntegrityError, KeyUnwrapError, NotSupportedError, UnknownEpochError, WasSyncAuthError, WasSyncConflictError, WasSyncNotFoundError } from '../errors.js';
|
|
56
60
|
//# sourceMappingURL=index.js.map
|
package/dist/sync/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAC1E,sEAAsE;AACtE,6EAA6E;AAC7E,SAAS;AACT,OAAO,EAAE,UAAU,IAAI,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AACzE,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,eAAe,EACf,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,cAAc,CAAA"}
|
package/dist/sync/port.d.ts
CHANGED
|
@@ -28,13 +28,9 @@
|
|
|
28
28
|
* WriteAck} -- the write's raw `etag` (re-read only if the backend sent
|
|
29
29
|
* none) plus its `version` parsed out of it -- so a caller can record acked
|
|
30
30
|
* revisions immediately and echo `etag` back verbatim as a later write's
|
|
31
|
-
* `ifMatch`.
|
|
32
|
-
* `
|
|
33
|
-
*
|
|
34
|
-
* that ignores the header would turn a guarded push into a silent overwrite. A
|
|
35
|
-
* descriptor that could not be read is not evidence either way and the push
|
|
36
|
-
* goes out, so a collection that is briefly absent does not disable guarded
|
|
37
|
-
* pushes for the port's lifetime.
|
|
31
|
+
* `ifMatch`. Conditional writes are a baseline server requirement, so a write
|
|
32
|
+
* carrying `ifMatch` or `ifNoneMatch` goes out as given and the server answers
|
|
33
|
+
* a lost race with a `412`.
|
|
38
34
|
*
|
|
39
35
|
* Bypassing the codec is not bypassing the error mapper. Every failure caught
|
|
40
36
|
* here goes through the client's own `mapError` first, so the port's signals
|
package/dist/sync/port.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../../src/sync/port.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../../src/sync/port.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,EACL,gBAAgB,EAGjB,MAAM,4BAA4B,CAAA;AAYnC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,KAAK,EAIV,WAAW,EAEZ,MAAM,YAAY,CAAA;AAEnB;;;;;GAKG;AACH,OAAO,EAAE,gBAAgB,EAAE,CAAA;AAyG3B;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAW7E;AAwBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,GAAG,EACH,OAAO,EACP,YAAY,EACZ,UAAU,EACV,aAAqB,EACtB,EAAE;IACD,GAAG,EAAE,SAAS,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,KAAK,CAAA;IAClB,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,GAAG,WAAW,CA4Od"}
|
package/dist/sync/port.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KEY_EPOCH_HEADER,
|
|
1
|
+
import { KEY_EPOCH_HEADER, readEtag, writeHeaders } from '../internal/conditional.js';
|
|
2
2
|
import { resourceMeta, resourcePath } from '../internal/paths.js';
|
|
3
3
|
import { mapError, NotFoundError, PreconditionFailedError, WasSyncAuthError, WasSyncConflictError, WasSyncNotFoundError, WasServerError } from '../errors.js';
|
|
4
4
|
/**
|
|
@@ -185,10 +185,9 @@ export function createWasSyncPort({ was, spaceId, collectionId, capability, mapA
|
|
|
185
185
|
// percent-encoding and reserved/dot-segment guards as the handle API.
|
|
186
186
|
const contentPath = (id) => resourcePath(spaceId, collectionId, id);
|
|
187
187
|
const metaPath = (id) => resourceMeta(spaceId, collectionId, id);
|
|
188
|
-
// Construction is I/O-free (the codec
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
// also gates the port's precondition-bearing writes.
|
|
188
|
+
// Construction is I/O-free (the codec is a lazy thunk) and `changes()`
|
|
189
|
+
// never resolves the codec, so it ships the stored bodies verbatim -- what
|
|
190
|
+
// this codec-bypassing port requires.
|
|
192
191
|
const changesCollection = was
|
|
193
192
|
.space(spaceId)
|
|
194
193
|
.collection(collectionId, { capability });
|
|
@@ -274,11 +273,6 @@ export function createWasSyncPort({ was, spaceId, collectionId, capability, mapA
|
|
|
274
273
|
},
|
|
275
274
|
async putContent({ id, data, ifMatch, ifNoneMatch, epoch }) {
|
|
276
275
|
try {
|
|
277
|
-
await assertPreconditionEnforced({
|
|
278
|
-
features: changesCollection.features,
|
|
279
|
-
precondition: { ifMatch, ifNoneMatch },
|
|
280
|
-
operation: `Cannot write the resource "${id}"`
|
|
281
|
-
});
|
|
282
276
|
const response = await was.request({
|
|
283
277
|
capability,
|
|
284
278
|
path: contentPath(id),
|
|
@@ -297,11 +291,6 @@ export function createWasSyncPort({ was, spaceId, collectionId, capability, mapA
|
|
|
297
291
|
},
|
|
298
292
|
async deleteContent({ id, ifMatch }) {
|
|
299
293
|
try {
|
|
300
|
-
await assertPreconditionEnforced({
|
|
301
|
-
features: changesCollection.features,
|
|
302
|
-
precondition: { ifMatch },
|
|
303
|
-
operation: `Cannot delete the resource "${id}"`
|
|
304
|
-
});
|
|
305
294
|
const response = await was.request({
|
|
306
295
|
capability,
|
|
307
296
|
path: contentPath(id),
|
|
@@ -326,11 +315,6 @@ export function createWasSyncPort({ was, spaceId, collectionId, capability, mapA
|
|
|
326
315
|
},
|
|
327
316
|
async putMeta({ id, custom, ifMatch, ifNoneMatch }) {
|
|
328
317
|
try {
|
|
329
|
-
await assertPreconditionEnforced({
|
|
330
|
-
features: changesCollection.features,
|
|
331
|
-
precondition: { ifMatch, ifNoneMatch },
|
|
332
|
-
operation: `Cannot write the metadata of resource "${id}"`
|
|
333
|
-
});
|
|
334
318
|
const response = await was.request({
|
|
335
319
|
capability,
|
|
336
320
|
path: metaPath(id),
|