@hachej/boring-files 0.4.36 → 0.4.37
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 +1 -1
- package/dist/contracts.d.ts +2 -14
- package/dist/publication-input.js +2 -16
- package/dist/remote-protocol.js +0 -4
- package/dist/remote.js +0 -2
- package/dist/workspace.js +0 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Opaque resource view IDs are provider-scoped and bound to the actual backing/inc
|
|
|
8
8
|
|
|
9
9
|
`@hachej/boring-files/workspace` exports `createWorkspaceProvider({ identity, fs, journal })`: the one provider of a workspace's files ([One place for files](../../docs/architecture/FILES-GIT-EXEC.md#one-place-for-files)). It takes a Pi `FileSystem` (a disk environment, the virtual workspace or the SQLite backend below), the host's workspace identity and a journal (`createWorkspaceJournal(connection)` from `@hachej/boring-files/journal`, receipts, intents and the last 20 versions of each file in SQLite). Revisions are Git blob ids of the bytes. A conditional write checks every expected revision and precondition inside the workspace's queue, records its intent first and its receipt last; an intent without a completion looks up as `unknown` and is never replayed. Its owner closes the connection; the provider borrows it. Node currently marks `node:sqlite` experimental.
|
|
10
10
|
|
|
11
|
-
The provider accepts all-or-nothing create, replace and conditional delete, plus conditional rename through `renaming.rename`. It refuses working views
|
|
11
|
+
The provider accepts all-or-nothing create, replace and conditional delete, plus conditional rename through `renaming.rename`. It refuses working views and folder mutations before any effect. Logical paths are relative to the workspace root; a path whose real location leaves the root is denied. A single file is replaced atomically by every backend; a multi-file batch only by the SQLite backend, together with its receipt when the journal shares the database. Access control is the host's, at the workspace boundary: there are no per-file permissions inside a workspace, and external revocation fencing is not implemented.
|
|
12
12
|
|
|
13
13
|
### Agent writes and the change feed
|
|
14
14
|
|
package/dist/contracts.d.ts
CHANGED
|
@@ -91,7 +91,7 @@ export type ResourceChange = {
|
|
|
91
91
|
export interface PublicationRequest {
|
|
92
92
|
readonly operationId: string;
|
|
93
93
|
readonly changes: NonEmpty<ResourceChange>;
|
|
94
|
-
readonly atomicity: 'all-or-nothing'
|
|
94
|
+
readonly atomicity: 'all-or-nothing';
|
|
95
95
|
/** Extra exact-content/absence dependencies, in addition to change targets.
|
|
96
96
|
* Search/listing predicate evidence needs its separately qualified provider
|
|
97
97
|
* integration; this minimal contract does not pretend to implement it.
|
|
@@ -149,19 +149,7 @@ export type PublicationOutcome = {
|
|
|
149
149
|
readonly operationId: string;
|
|
150
150
|
readonly reason: string;
|
|
151
151
|
};
|
|
152
|
-
|
|
153
|
-
* Providers validate index coverage/uniqueness and target/receipt agreement.
|
|
154
|
-
*/
|
|
155
|
-
export interface PublicationItem {
|
|
156
|
-
readonly changeIndex: number;
|
|
157
|
-
readonly target: ResourceLocator;
|
|
158
|
-
readonly outcome: PublicationOutcome;
|
|
159
|
-
}
|
|
160
|
-
export type PublicationResult = PublicationOutcome | {
|
|
161
|
-
readonly kind: 'partial';
|
|
162
|
-
readonly operationId: string;
|
|
163
|
-
readonly items: NonEmpty<PublicationItem>;
|
|
164
|
-
};
|
|
152
|
+
export type PublicationResult = PublicationOutcome;
|
|
165
153
|
export interface ProviderGuarantees {
|
|
166
154
|
readonly pinnedReads: boolean;
|
|
167
155
|
readonly conditionalPublication: boolean;
|
|
@@ -79,7 +79,7 @@ export function publicationSnapshot(value) {
|
|
|
79
79
|
const input = object(value);
|
|
80
80
|
if (!Array.isArray(input.changes) || !input.changes.length)
|
|
81
81
|
throw new TypeError('Publication requires changes');
|
|
82
|
-
if (input.atomicity !== 'all-or-nothing'
|
|
82
|
+
if (input.atomicity !== 'all-or-nothing')
|
|
83
83
|
throw new TypeError('Invalid atomicity');
|
|
84
84
|
const [first, ...rest] = input.changes;
|
|
85
85
|
const changes = [change(first), ...rest.map(change)];
|
|
@@ -172,19 +172,5 @@ function publicationOutcome(value) {
|
|
|
172
172
|
}
|
|
173
173
|
/** Parse transport/provider output before treating it as acknowledgement. */
|
|
174
174
|
export function parsePublicationResult(value) {
|
|
175
|
-
|
|
176
|
-
if (input.kind !== 'partial')
|
|
177
|
-
return publicationOutcome(input);
|
|
178
|
-
if (!Array.isArray(input.items) || !input.items.length)
|
|
179
|
-
throw new TypeError('Partial publication requires items');
|
|
180
|
-
const items = input.items.map(value => {
|
|
181
|
-
const item = object(value);
|
|
182
|
-
if (typeof item.changeIndex !== 'number' || !Number.isSafeInteger(item.changeIndex) || item.changeIndex < 0)
|
|
183
|
-
throw new TypeError('Invalid change index');
|
|
184
|
-
return { changeIndex: item.changeIndex, target: locator(item.target), outcome: publicationOutcome(item.outcome) };
|
|
185
|
-
});
|
|
186
|
-
const [first, ...rest] = items;
|
|
187
|
-
if (!first || new Set(items.map(item => item.changeIndex)).size !== items.length)
|
|
188
|
-
throw new TypeError('Invalid partial publication coverage');
|
|
189
|
-
return { kind: 'partial', operationId: identifier(input.operationId), items: [first, ...rest] };
|
|
175
|
+
return publicationOutcome(object(value));
|
|
190
176
|
}
|
package/dist/remote-protocol.js
CHANGED
|
@@ -76,8 +76,6 @@ export function decodeCall(input) {
|
|
|
76
76
|
return change.kind === 'delete' ? change : { ...change, bytes: bytes(change.bytes) };
|
|
77
77
|
});
|
|
78
78
|
const request = publicationSnapshot({ ...value, changes });
|
|
79
|
-
if (request.atomicity !== 'all-or-nothing')
|
|
80
|
-
throw new TypeError('Remote per-change publication is not qualified');
|
|
81
79
|
return { kind: 'publish', value: request };
|
|
82
80
|
}
|
|
83
81
|
export function encode(value, limit) {
|
|
@@ -148,8 +146,6 @@ function outcomeAgreement(result, operationId, selected, changes, digest) {
|
|
|
148
146
|
}
|
|
149
147
|
export function publicationResult(value, operationId, selected, request, digest) {
|
|
150
148
|
const result = parsePublicationResult(value);
|
|
151
|
-
if (result.kind === 'partial')
|
|
152
|
-
throw new TypeError('Remote partial receipts are not qualified');
|
|
153
149
|
outcomeAgreement(result, operationId, selected, request?.changes, digest);
|
|
154
150
|
return result;
|
|
155
151
|
}
|
package/dist/remote.js
CHANGED
|
@@ -65,8 +65,6 @@ export function createResourceClient(options) {
|
|
|
65
65
|
const captured = publicationSnapshot(request);
|
|
66
66
|
let digest;
|
|
67
67
|
try {
|
|
68
|
-
if (captured.atomicity !== 'all-or-nothing')
|
|
69
|
-
throw new TypeError('Remote per-change publication is not qualified');
|
|
70
68
|
digest = await publicationDigest(captured);
|
|
71
69
|
}
|
|
72
70
|
catch {
|
package/dist/workspace.js
CHANGED
|
@@ -234,8 +234,6 @@ export function createWorkspaceProvider(options) {
|
|
|
234
234
|
const moving = 'from' in request ? request : undefined;
|
|
235
235
|
const publication = 'changes' in request ? request : undefined;
|
|
236
236
|
const digest = moving ? await renameDigest(moving) : await publicationDigest(publication);
|
|
237
|
-
if (publication && publication.atomicity !== 'all-or-nothing')
|
|
238
|
-
return { kind: 'unavailable', reason: 'This provider accepts all-or-nothing publication only' };
|
|
239
237
|
if (publication && publication.changes.length !== 1 && !batch)
|
|
240
238
|
return { kind: 'unavailable', reason: 'This provider cannot apply a multi-file batch atomically; only a single-file change is accepted' };
|
|
241
239
|
const changes = moving ? [{ kind: 'delete', target: { ...moving.from, revision: moving.expectedRevision } }] : publication.changes;
|