@nimbus-sh/platform 0.13.0 → 0.14.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/dist/utf8.d.ts +8 -0
- package/dist/utf8.d.ts.map +1 -1
- package/dist/utf8.js +10 -4
- package/dist/w7-frame.d.ts +45 -4
- package/dist/w7-frame.d.ts.map +1 -1
- package/dist/w7-frame.js +47 -13
- package/package.json +2 -2
- package/src/utf8.ts +10 -4
- package/src/w7-frame.ts +81 -23
package/dist/utf8.d.ts
CHANGED
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
* TextEncoder would write: one to four per code point, and three for a
|
|
9
9
|
* lone surrogate, which it writes as U+FFFD.
|
|
10
10
|
*/
|
|
11
|
+
/**
|
|
12
|
+
* From this many UTF-16 units, a string is counted natively (longUtf8Length),
|
|
13
|
+
* into a scratch buffer of SCRATCH_BYTES. The resident store's count on the
|
|
14
|
+
* launch's captures (worker vfs/resident/cell-cost.ts) keeps the same policy,
|
|
15
|
+
* and tests/unit/utf8-length.mjs holds both to TextEncoder.
|
|
16
|
+
*/
|
|
17
|
+
export declare const UTF8_LONG_UNITS = 256;
|
|
18
|
+
export declare const UTF8_SCRATCH_BYTES: number;
|
|
11
19
|
export declare function utf8Length(text: string): number;
|
|
12
20
|
/**
|
|
13
21
|
* `a` against `b` (negative, 0, positive) as their UTF-8 bytes order them,
|
package/dist/utf8.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utf8.d.ts","sourceRoot":"","sources":["../src/utf8.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"utf8.d.ts","sourceRoot":"","sources":["../src/utf8.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,eAAO,MAAM,kBAAkB,QAAa,CAAC;AAK7C,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsB/C;AA6BD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAUxD"}
|
package/dist/utf8.js
CHANGED
|
@@ -8,13 +8,19 @@
|
|
|
8
8
|
* TextEncoder would write: one to four per code point, and three for a
|
|
9
9
|
* lone surrogate, which it writes as U+FFFD.
|
|
10
10
|
*/
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* From this many UTF-16 units, a string is counted natively (longUtf8Length),
|
|
13
|
+
* into a scratch buffer of SCRATCH_BYTES. The resident store's count on the
|
|
14
|
+
* launch's captures (worker vfs/resident/cell-cost.ts) keeps the same policy,
|
|
15
|
+
* and tests/unit/utf8-length.mjs holds both to TextEncoder.
|
|
16
|
+
*/
|
|
17
|
+
export const UTF8_LONG_UNITS = 256;
|
|
18
|
+
export const UTF8_SCRATCH_BYTES = 256 * 1024;
|
|
13
19
|
const encoder = new TextEncoder();
|
|
14
20
|
/** Where longUtf8Length's encodes land: written, never read. */
|
|
15
21
|
let scratch;
|
|
16
22
|
export function utf8Length(text) {
|
|
17
|
-
if (text.length >=
|
|
23
|
+
if (text.length >= UTF8_LONG_UNITS)
|
|
18
24
|
return longUtf8Length(text);
|
|
19
25
|
let bytes = text.length;
|
|
20
26
|
for (let index = 0; index < text.length; index++) {
|
|
@@ -47,7 +53,7 @@ export function utf8Length(text) {
|
|
|
47
53
|
* a surrogate pair, so each slice starts on a code point.
|
|
48
54
|
*/
|
|
49
55
|
function longUtf8Length(text) {
|
|
50
|
-
scratch ??= new Uint8Array(
|
|
56
|
+
scratch ??= new Uint8Array(UTF8_SCRATCH_BYTES);
|
|
51
57
|
let bytes = 0;
|
|
52
58
|
for (let read = 0; read < text.length;) {
|
|
53
59
|
const done = encoder.encodeInto(read === 0 ? text : text.slice(read), scratch);
|
package/dist/w7-frame.d.ts
CHANGED
|
@@ -70,14 +70,24 @@ export interface BatchWritePayload {
|
|
|
70
70
|
* chmod, chown and utimes each make. An owner side that is null keeps what
|
|
71
71
|
* the file has when the change lands (chown's -1).
|
|
72
72
|
*/
|
|
73
|
+
/**
|
|
74
|
+
* A time utimes and futimes set, as utimensat(2) takes it: ms since the
|
|
75
|
+
* epoch (explicit: only the owner may set it), 'now' (UTIME_NOW: the
|
|
76
|
+
* clock of the filesystem that commits the change, as it commits it) or
|
|
77
|
+
* 'omit' (UTIME_OMIT: left as it is then). 'now' and 'omit' need the owner or write
|
|
78
|
+
* permission. Carried as it is to the filesystem that commits the change,
|
|
79
|
+
* which alone resolves it (posix-access.ts, timeAt) and judges it
|
|
80
|
+
* (utimesRefusal, explicitTimes).
|
|
81
|
+
*/
|
|
82
|
+
export type FsTime = number | 'now' | 'omit';
|
|
73
83
|
export type W7Attrs = {
|
|
74
84
|
mode: number;
|
|
75
85
|
} | {
|
|
76
86
|
uid: number | null;
|
|
77
87
|
gid: number | null;
|
|
78
88
|
} | {
|
|
79
|
-
atime:
|
|
80
|
-
mtime:
|
|
89
|
+
atime: FsTime;
|
|
90
|
+
mtime: FsTime;
|
|
81
91
|
};
|
|
82
92
|
/**
|
|
83
93
|
* A process's filesystem call as a record (v4), applied by the session's own
|
|
@@ -100,6 +110,20 @@ export declare function openAccessMask(call: {
|
|
|
100
110
|
access: W7OpenAccess;
|
|
101
111
|
truncate?: true;
|
|
102
112
|
}): number;
|
|
113
|
+
/**
|
|
114
|
+
* What a process decided a call on from its own view of the name: the file
|
|
115
|
+
* it expected there (its number on the session's filesystem), none (null),
|
|
116
|
+
* or the file its open of description `made` reached, for a file that open
|
|
117
|
+
* made and the process has no number of yet (it keeps that description
|
|
118
|
+
* open until it has). Where the name leads elsewhere now (a stale view: a
|
|
119
|
+
* peer replaced the name, or a link there leads to another file; or that
|
|
120
|
+
* open was refused), the session refuses the call before any effect:
|
|
121
|
+
* ENOENT where it expected a file, EEXIST where it expected none, EXDEV
|
|
122
|
+
* where the name leads to a mount.
|
|
123
|
+
*/
|
|
124
|
+
export type W7Expected = number | null | {
|
|
125
|
+
readonly made: string;
|
|
126
|
+
};
|
|
103
127
|
export type W7Call =
|
|
104
128
|
/**
|
|
105
129
|
* `ino`, on a call that makes a name (writeFile, appendFile, mkdir,
|
|
@@ -169,9 +193,11 @@ export type W7Call =
|
|
|
169
193
|
} | {
|
|
170
194
|
call: 'unlink';
|
|
171
195
|
path: string;
|
|
196
|
+
expect?: W7Expected;
|
|
172
197
|
} | {
|
|
173
198
|
call: 'rmdir';
|
|
174
199
|
path: string;
|
|
200
|
+
expect?: W7Expected;
|
|
175
201
|
} | {
|
|
176
202
|
call: 'symlink';
|
|
177
203
|
path: string;
|
|
@@ -245,6 +271,7 @@ export type W7Call =
|
|
|
245
271
|
nofollow?: true;
|
|
246
272
|
description?: string;
|
|
247
273
|
fchmod?: number;
|
|
274
|
+
expect?: W7Expected;
|
|
248
275
|
}
|
|
249
276
|
/** close(2) of the session's open description `description` (an `open` call's). */
|
|
250
277
|
| {
|
|
@@ -275,18 +302,26 @@ export type W7Op = {
|
|
|
275
302
|
type: 'file';
|
|
276
303
|
inode: BatchInodeEntry;
|
|
277
304
|
source: AsyncIterable<Uint8Array>;
|
|
278
|
-
}
|
|
305
|
+
}
|
|
306
|
+
/** `expect`, `expectTo`: the files the process expected at `from` and at `to` (W7Expected). */
|
|
307
|
+
| {
|
|
279
308
|
type: 'rename';
|
|
280
309
|
from: string;
|
|
281
310
|
to: string;
|
|
311
|
+
expect?: W7Expected;
|
|
312
|
+
expectTo?: W7Expected;
|
|
282
313
|
} | {
|
|
283
314
|
type: 'truncate';
|
|
284
315
|
path: string;
|
|
285
316
|
size: number;
|
|
286
|
-
}
|
|
317
|
+
}
|
|
318
|
+
/** `description`: through the session's open description of that id (fchmod, fchown, futimens): its file, whatever names it now. */
|
|
319
|
+
| {
|
|
287
320
|
type: 'setattr';
|
|
288
321
|
path: string;
|
|
289
322
|
attrs: W7Attrs;
|
|
323
|
+
description?: string;
|
|
324
|
+
expect?: W7Expected;
|
|
290
325
|
} | {
|
|
291
326
|
type: 'call';
|
|
292
327
|
call: W7Call;
|
|
@@ -369,6 +404,8 @@ export type W7DecodedRecord = {
|
|
|
369
404
|
type: 'rename';
|
|
370
405
|
from: string;
|
|
371
406
|
to: string;
|
|
407
|
+
expect?: W7Expected;
|
|
408
|
+
expectTo?: W7Expected;
|
|
372
409
|
} | {
|
|
373
410
|
type: 'truncate';
|
|
374
411
|
path: string;
|
|
@@ -377,6 +414,8 @@ export type W7DecodedRecord = {
|
|
|
377
414
|
type: 'setattr';
|
|
378
415
|
path: string;
|
|
379
416
|
attrs: W7Attrs;
|
|
417
|
+
description?: string;
|
|
418
|
+
expect?: W7Expected;
|
|
380
419
|
} | {
|
|
381
420
|
type: 'call';
|
|
382
421
|
call: W7PathCall;
|
|
@@ -409,5 +448,7 @@ export declare function encodeWriteBatch(payload: BatchWritePayload): Promise<Ui
|
|
|
409
448
|
* and before its payload bytes are read or copied.
|
|
410
449
|
*/
|
|
411
450
|
export declare function decodeWriteBatchStream(stream: ReadableStream<Uint8Array>, options?: W7DecodeOptions): Promise<W7DecodedStream>;
|
|
451
|
+
/** `value` as an FsTime (a finite number of milliseconds, 'now' or 'omit'); else an error naming `label`. */
|
|
452
|
+
export declare function parseFsTime(value: unknown, label: string): FsTime;
|
|
412
453
|
export {};
|
|
413
454
|
//# sourceMappingURL=w7-frame.d.ts.map
|
package/dist/w7-frame.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"w7-frame.d.ts","sourceRoot":"","sources":["../src/w7-frame.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"w7-frame.d.ts","sourceRoot":"","sources":["../src/w7-frame.ts"],"names":[],"mappings":"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"}
|
package/dist/w7-frame.js
CHANGED
|
@@ -385,7 +385,7 @@ v3) {
|
|
|
385
385
|
throw new Error(`w7-frame: unknown record tag ${envelope.tag}`);
|
|
386
386
|
summary.pathCount++;
|
|
387
387
|
summary.opCount++;
|
|
388
|
-
const value = parseObject(payload, 'call', ['call', 'path'], ['mode', 'target', 'from', 'ino', 'size', 'existing', 'recursive', 'force', 'uid', 'gid', 'atime', 'mtime', 'umask', 'access', 'create', 'truncate', 'exclusive', 'nofollow', 'description', 'fchmod']);
|
|
388
|
+
const value = parseObject(payload, 'call', ['call', 'path'], ['mode', 'target', 'from', 'ino', 'size', 'existing', 'recursive', 'force', 'uid', 'gid', 'atime', 'mtime', 'umask', 'access', 'create', 'truncate', 'exclusive', 'nofollow', 'description', 'fchmod', 'expect']);
|
|
389
389
|
yield { type: 'call', call: parsePathCall(value, (path, label) => ownedPaths.claim(canonicalPath(path, label))) };
|
|
390
390
|
break;
|
|
391
391
|
}
|
|
@@ -397,20 +397,23 @@ v3) {
|
|
|
397
397
|
summary.pathCount++;
|
|
398
398
|
summary.opCount++;
|
|
399
399
|
if (envelope.tag === RecordTag.Rename) {
|
|
400
|
-
const value = parseObject(payload, 'rename', ['from', 'to']);
|
|
400
|
+
const value = parseObject(payload, 'rename', ['from', 'to'], ['expect', 'expectTo']);
|
|
401
401
|
const from = ownedPaths.claim(canonicalPath(value.from, 'rename source'));
|
|
402
402
|
const to = ownedPaths.claim(canonicalPath(value.to, 'rename target'));
|
|
403
|
-
yield { type: 'rename', from, to };
|
|
403
|
+
yield { type: 'rename', from, to, ...expectations(value, 'rename') };
|
|
404
404
|
}
|
|
405
405
|
else if (envelope.tag === RecordTag.Truncate) {
|
|
406
406
|
const value = parseObject(payload, 'truncate', ['path', 'size']);
|
|
407
407
|
yield { type: 'truncate', path: ownedPaths.claim(canonicalPath(value.path, 'truncate path')), size: safeInteger(value.size, 'truncate size') };
|
|
408
408
|
}
|
|
409
409
|
else {
|
|
410
|
-
const value = parseObject(payload, 'setattr', ['path'], ['mode', 'uid', 'gid', 'atime', 'mtime']);
|
|
410
|
+
const value = parseObject(payload, 'setattr', ['path'], ['mode', 'uid', 'gid', 'atime', 'mtime', 'description', 'expect']);
|
|
411
411
|
const path = ownedPaths.claim(canonicalPath(value.path, 'setattr path'));
|
|
412
|
-
const { path: _path, ...attrs } = value;
|
|
413
|
-
yield {
|
|
412
|
+
const { path: _path, description, expect: _expect, ...attrs } = value;
|
|
413
|
+
yield {
|
|
414
|
+
type: 'setattr', path, attrs: parseAttrs(attrs, 'setattr'),
|
|
415
|
+
...(description === undefined ? {} : { description: descriptionId(description, 'setattr description') }), ...expectations(value, 'setattr'),
|
|
416
|
+
};
|
|
414
417
|
}
|
|
415
418
|
break;
|
|
416
419
|
}
|
|
@@ -483,7 +486,7 @@ async function* encodeRecords(batchId, ops) {
|
|
|
483
486
|
case 'rename':
|
|
484
487
|
state.summary.pathCount++;
|
|
485
488
|
state.summary.opCount++;
|
|
486
|
-
yield encodeMetadataRecord(RecordTag.Rename, { from: op.from, to: op.to }, state);
|
|
489
|
+
yield encodeMetadataRecord(RecordTag.Rename, { from: op.from, to: op.to, ...expectations(op, 'rename') }, state);
|
|
487
490
|
break;
|
|
488
491
|
case 'truncate':
|
|
489
492
|
state.summary.pathCount++;
|
|
@@ -493,7 +496,7 @@ async function* encodeRecords(batchId, ops) {
|
|
|
493
496
|
case 'setattr':
|
|
494
497
|
state.summary.pathCount++;
|
|
495
498
|
state.summary.opCount++;
|
|
496
|
-
yield encodeMetadataRecord(RecordTag.SetAttr, { path: op.path, ...op.attrs }, state);
|
|
499
|
+
yield encodeMetadataRecord(RecordTag.SetAttr, { path: op.path, ...op.attrs, ...(op.description === undefined ? {} : { description: op.description }), ...expectations(op, 'setattr') }, state);
|
|
497
500
|
break;
|
|
498
501
|
case 'call':
|
|
499
502
|
state.summary.pathCount++;
|
|
@@ -702,11 +705,16 @@ function prepareOps(payload, batchId) {
|
|
|
702
705
|
kind: 'rename',
|
|
703
706
|
from: ownedPaths.claim(canonicalPath(op.from, 'rename source')),
|
|
704
707
|
to: ownedPaths.claim(canonicalPath(op.to, 'rename target')),
|
|
708
|
+
...expectations(op, 'rename'),
|
|
705
709
|
};
|
|
706
710
|
case 'truncate':
|
|
707
711
|
return { kind: 'truncate', path: ownedPaths.claim(canonicalPath(op.path, 'truncate path')), size: safeInteger(op.size, 'truncate size') };
|
|
708
712
|
case 'setattr':
|
|
709
|
-
return {
|
|
713
|
+
return {
|
|
714
|
+
kind: 'setattr', path: ownedPaths.claim(canonicalPath(op.path, 'setattr path')), attrs: parseAttrs(op.attrs, 'setattr'),
|
|
715
|
+
...(op.description === undefined ? {} : { description: descriptionId(op.description, 'setattr description') }),
|
|
716
|
+
...expectations(op, 'setattr'),
|
|
717
|
+
};
|
|
710
718
|
case 'call': {
|
|
711
719
|
const call = op.call;
|
|
712
720
|
if ('data' in call) {
|
|
@@ -747,9 +755,17 @@ function parseAttrs(value, label) {
|
|
|
747
755
|
if (keys === 'gid,uid')
|
|
748
756
|
return { uid: ownerId(value.uid, `${label} uid`), gid: ownerId(value.gid, `${label} gid`) };
|
|
749
757
|
if (keys === 'atime,mtime')
|
|
750
|
-
return { atime:
|
|
758
|
+
return { atime: parseFsTime(value.atime, `w7-frame: ${label} atime`), mtime: parseFsTime(value.mtime, `w7-frame: ${label} mtime`) };
|
|
751
759
|
throw new Error(`w7-frame: ${label} changes the mode, the owner or the times, one of them: got ${keys || 'nothing'}`);
|
|
752
760
|
}
|
|
761
|
+
/** `value` as an FsTime (a finite number of milliseconds, 'now' or 'omit'); else an error naming `label`. */
|
|
762
|
+
export function parseFsTime(value, label) {
|
|
763
|
+
if (value === 'now' || value === 'omit')
|
|
764
|
+
return value;
|
|
765
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
766
|
+
return value;
|
|
767
|
+
throw new Error(`${label} must be a time: milliseconds, 'now' or 'omit'`);
|
|
768
|
+
}
|
|
753
769
|
function parseDelete(bytes) {
|
|
754
770
|
const value = parseObject(bytes, 'delete', ['path']);
|
|
755
771
|
return { path: canonicalPath(value.path, 'delete path') };
|
|
@@ -822,9 +838,9 @@ function parsePathCall(value, path) {
|
|
|
822
838
|
}
|
|
823
839
|
case 'unlink':
|
|
824
840
|
case 'rmdir':
|
|
825
|
-
if (keys !== 'call,path')
|
|
841
|
+
if (keys !== 'call,path' && keys !== 'call,expect,path')
|
|
826
842
|
break;
|
|
827
|
-
return { call: value.call, path: path(value.path, `${value.call} path`) };
|
|
843
|
+
return { call: value.call, path: path(value.path, `${value.call} path`), ...expectations(value, value.call) };
|
|
828
844
|
case 'symlink':
|
|
829
845
|
if (keys !== 'call,path,target' && keys !== 'call,ino,path,target')
|
|
830
846
|
break;
|
|
@@ -868,7 +884,7 @@ function parsePathCall(value, path) {
|
|
|
868
884
|
case 'open': {
|
|
869
885
|
const flags = ['create', 'exclusive', 'nofollow', 'truncate'];
|
|
870
886
|
let required = keys;
|
|
871
|
-
for (const flag of ['create', 'description', 'exclusive', 'fchmod', 'ino', 'nofollow', 'truncate', 'umask'])
|
|
887
|
+
for (const flag of ['create', 'description', 'exclusive', 'expect', 'fchmod', 'ino', 'nofollow', 'truncate', 'umask'])
|
|
872
888
|
required = required.replace(`,${flag}`, '');
|
|
873
889
|
if (required !== 'access,call,mode,path')
|
|
874
890
|
break;
|
|
@@ -889,6 +905,7 @@ function parsePathCall(value, path) {
|
|
|
889
905
|
...(value.nofollow === true ? { nofollow: true } : {}),
|
|
890
906
|
...(value.description === undefined ? {} : { description: descriptionId(value.description, 'open description') }),
|
|
891
907
|
...(value.fchmod === undefined ? {} : { fchmod: u32(value.fchmod, 'open fchmod') }),
|
|
908
|
+
...expectations(value, 'open'),
|
|
892
909
|
};
|
|
893
910
|
}
|
|
894
911
|
case 'lutimes':
|
|
@@ -960,6 +977,23 @@ function inodeNumber(value, label) {
|
|
|
960
977
|
throw new Error(`w7-frame: ${label} must be at least 2`);
|
|
961
978
|
return ino;
|
|
962
979
|
}
|
|
980
|
+
/** A record's expectations (W7Expected), checked: `expect`, and a rename's `expectTo`. */
|
|
981
|
+
function expectations(value, label) {
|
|
982
|
+
const expected = (field, name) => {
|
|
983
|
+
if (field === null)
|
|
984
|
+
return null;
|
|
985
|
+
if (isObject(field) && !Array.isArray(field)) {
|
|
986
|
+
if (Object.keys(field).join(',') !== 'made')
|
|
987
|
+
throw new Error(`w7-frame: ${label} ${name} is a number, null or { made }`);
|
|
988
|
+
return { made: descriptionId(field.made, `${label} ${name} made`) };
|
|
989
|
+
}
|
|
990
|
+
return inodeNumber(field, `${label} ${name}`);
|
|
991
|
+
};
|
|
992
|
+
return {
|
|
993
|
+
...(value.expect === undefined ? {} : { expect: expected(value.expect, 'expect') }),
|
|
994
|
+
...(value.expectTo === undefined ? {} : { expectTo: expected(value.expectTo, 'expectTo') }),
|
|
995
|
+
};
|
|
996
|
+
}
|
|
963
997
|
function parseObject(bytes, label, required, optional = []) {
|
|
964
998
|
let value;
|
|
965
999
|
try {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nimbus-sh/platform",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The measured Cloudflare platform truths Nimbus is built on
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"description": "The measured Cloudflare platform truths Nimbus is built on — error taxonomy, limits tables, budget coordination, and wire-format primitives, with zero runtime dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
7
7
|
"workers",
|
package/src/utf8.ts
CHANGED
|
@@ -9,14 +9,20 @@
|
|
|
9
9
|
* lone surrogate, which it writes as U+FFFD.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* From this many UTF-16 units, a string is counted natively (longUtf8Length),
|
|
14
|
+
* into a scratch buffer of SCRATCH_BYTES. The resident store's count on the
|
|
15
|
+
* launch's captures (worker vfs/resident/cell-cost.ts) keeps the same policy,
|
|
16
|
+
* and tests/unit/utf8-length.mjs holds both to TextEncoder.
|
|
17
|
+
*/
|
|
18
|
+
export const UTF8_LONG_UNITS = 256;
|
|
19
|
+
export const UTF8_SCRATCH_BYTES = 256 * 1024;
|
|
14
20
|
const encoder = new TextEncoder();
|
|
15
21
|
/** Where longUtf8Length's encodes land: written, never read. */
|
|
16
22
|
let scratch: Uint8Array | undefined;
|
|
17
23
|
|
|
18
24
|
export function utf8Length(text: string): number {
|
|
19
|
-
if (text.length >=
|
|
25
|
+
if (text.length >= UTF8_LONG_UNITS) return longUtf8Length(text);
|
|
20
26
|
let bytes = text.length;
|
|
21
27
|
for (let index = 0; index < text.length; index++) {
|
|
22
28
|
const unit = text.charCodeAt(index);
|
|
@@ -45,7 +51,7 @@ export function utf8Length(text: string): number {
|
|
|
45
51
|
* a surrogate pair, so each slice starts on a code point.
|
|
46
52
|
*/
|
|
47
53
|
function longUtf8Length(text: string): number {
|
|
48
|
-
scratch ??= new Uint8Array(
|
|
54
|
+
scratch ??= new Uint8Array(UTF8_SCRATCH_BYTES);
|
|
49
55
|
let bytes = 0;
|
|
50
56
|
for (let read = 0; read < text.length;) {
|
|
51
57
|
const done = encoder.encodeInto(read === 0 ? text : text.slice(read), scratch);
|
package/src/w7-frame.ts
CHANGED
|
@@ -85,10 +85,21 @@ export interface BatchWritePayload {
|
|
|
85
85
|
* chmod, chown and utimes each make. An owner side that is null keeps what
|
|
86
86
|
* the file has when the change lands (chown's -1).
|
|
87
87
|
*/
|
|
88
|
+
/**
|
|
89
|
+
* A time utimes and futimes set, as utimensat(2) takes it: ms since the
|
|
90
|
+
* epoch (explicit: only the owner may set it), 'now' (UTIME_NOW: the
|
|
91
|
+
* clock of the filesystem that commits the change, as it commits it) or
|
|
92
|
+
* 'omit' (UTIME_OMIT: left as it is then). 'now' and 'omit' need the owner or write
|
|
93
|
+
* permission. Carried as it is to the filesystem that commits the change,
|
|
94
|
+
* which alone resolves it (posix-access.ts, timeAt) and judges it
|
|
95
|
+
* (utimesRefusal, explicitTimes).
|
|
96
|
+
*/
|
|
97
|
+
export type FsTime = number | 'now' | 'omit';
|
|
98
|
+
|
|
88
99
|
export type W7Attrs =
|
|
89
100
|
| { mode: number }
|
|
90
101
|
| { uid: number | null; gid: number | null }
|
|
91
|
-
| { atime:
|
|
102
|
+
| { atime: FsTime; mtime: FsTime };
|
|
92
103
|
|
|
93
104
|
/**
|
|
94
105
|
* A process's filesystem call as a record (v4), applied by the session's own
|
|
@@ -116,6 +127,19 @@ export function openAccessMask(call: { access: W7OpenAccess; truncate?: true }):
|
|
|
116
127
|
return (rights.read ? 4 : 0) | (rights.write || call.truncate === true ? 2 : 0);
|
|
117
128
|
}
|
|
118
129
|
|
|
130
|
+
/**
|
|
131
|
+
* What a process decided a call on from its own view of the name: the file
|
|
132
|
+
* it expected there (its number on the session's filesystem), none (null),
|
|
133
|
+
* or the file its open of description `made` reached, for a file that open
|
|
134
|
+
* made and the process has no number of yet (it keeps that description
|
|
135
|
+
* open until it has). Where the name leads elsewhere now (a stale view: a
|
|
136
|
+
* peer replaced the name, or a link there leads to another file; or that
|
|
137
|
+
* open was refused), the session refuses the call before any effect:
|
|
138
|
+
* ENOENT where it expected a file, EEXIST where it expected none, EXDEV
|
|
139
|
+
* where the name leads to a mount.
|
|
140
|
+
*/
|
|
141
|
+
export type W7Expected = number | null | { readonly made: string };
|
|
142
|
+
|
|
119
143
|
export type W7Call =
|
|
120
144
|
/**
|
|
121
145
|
* `ino`, on a call that makes a name (writeFile, appendFile, mkdir,
|
|
@@ -144,8 +168,8 @@ export type W7Call =
|
|
|
144
168
|
| { call: 'ftruncate'; path: string; ino?: number; description?: string; size: number }
|
|
145
169
|
/** `existing: 'ok'`: a directory already there answers success, as `mkdir -p` takes it (anything else there is still EEXIST). */
|
|
146
170
|
| { call: 'mkdir'; path: string; mode: number; ino?: number; umask?: number; existing?: 'ok' }
|
|
147
|
-
| { call: 'unlink'; path: string }
|
|
148
|
-
| { call: 'rmdir'; path: string }
|
|
171
|
+
| { call: 'unlink'; path: string; expect?: W7Expected }
|
|
172
|
+
| { call: 'rmdir'; path: string; expect?: W7Expected }
|
|
149
173
|
| { call: 'symlink'; path: string; target: string; ino?: number }
|
|
150
174
|
/**
|
|
151
175
|
* copyfile as the session copies a file: the file \`from\` names (a link
|
|
@@ -182,7 +206,7 @@ export type W7Call =
|
|
|
182
206
|
* open: refused (EPERM: another's file), the open is refused and keeps no
|
|
183
207
|
* description, so a write through it is EBADF.
|
|
184
208
|
*/
|
|
185
|
-
| { call: 'open'; path: string; mode: number; access: W7OpenAccess; ino?: number; umask?: number; create?: true; truncate?: true; exclusive?: true; nofollow?: true; description?: string; fchmod?: number }
|
|
209
|
+
| { call: 'open'; path: string; mode: number; access: W7OpenAccess; ino?: number; umask?: number; create?: true; truncate?: true; exclusive?: true; nofollow?: true; description?: string; fchmod?: number; expect?: W7Expected }
|
|
186
210
|
/** close(2) of the session's open description `description` (an `open` call's). */
|
|
187
211
|
| { call: 'close'; path: string; description: string };
|
|
188
212
|
|
|
@@ -197,9 +221,11 @@ export type W7Op =
|
|
|
197
221
|
| { type: 'directory'; inode: BatchInodeEntry }
|
|
198
222
|
| { type: 'file'; inode: BatchInodeEntry; data: Uint8Array }
|
|
199
223
|
| { type: 'file'; inode: BatchInodeEntry; source: AsyncIterable<Uint8Array> }
|
|
200
|
-
|
|
224
|
+
/** `expect`, `expectTo`: the files the process expected at `from` and at `to` (W7Expected). */
|
|
225
|
+
| { type: 'rename'; from: string; to: string; expect?: W7Expected; expectTo?: W7Expected }
|
|
201
226
|
| { type: 'truncate'; path: string; size: number }
|
|
202
|
-
|
|
227
|
+
/** `description`: through the session's open description of that id (fchmod, fchown, futimens): its file, whatever names it now. */
|
|
228
|
+
| { type: 'setattr'; path: string; attrs: W7Attrs; description?: string; expect?: W7Expected }
|
|
203
229
|
| { type: 'call'; call: W7Call };
|
|
204
230
|
|
|
205
231
|
export const W7_MAGIC = new Uint8Array([0x4e, 0x57, 0x37, 0x04]);
|
|
@@ -350,9 +376,9 @@ export type W7DecodedRecord =
|
|
|
350
376
|
chunkCount: number;
|
|
351
377
|
check: number;
|
|
352
378
|
}
|
|
353
|
-
| { type: 'rename'; from: string; to: string }
|
|
379
|
+
| { type: 'rename'; from: string; to: string; expect?: W7Expected; expectTo?: W7Expected }
|
|
354
380
|
| { type: 'truncate'; path: string; size: number }
|
|
355
|
-
| { type: 'setattr'; path: string; attrs: W7Attrs }
|
|
381
|
+
| { type: 'setattr'; path: string; attrs: W7Attrs; description?: string; expect?: W7Expected }
|
|
356
382
|
| { type: 'call'; call: W7PathCall }
|
|
357
383
|
| { type: 'batch-end'; summary: W7BatchSummary };
|
|
358
384
|
|
|
@@ -367,9 +393,9 @@ type EncoderOp =
|
|
|
367
393
|
| { kind: 'delete'; path: string }
|
|
368
394
|
| { kind: 'directory'; inode: W7DirectoryInode }
|
|
369
395
|
| { kind: 'file'; file: EncoderFile }
|
|
370
|
-
| { kind: 'rename'; from: string; to: string }
|
|
396
|
+
| { kind: 'rename'; from: string; to: string; expect?: W7Expected; expectTo?: W7Expected }
|
|
371
397
|
| { kind: 'truncate'; path: string; size: number }
|
|
372
|
-
| { kind: 'setattr'; path: string; attrs: W7Attrs }
|
|
398
|
+
| { kind: 'setattr'; path: string; attrs: W7Attrs; description?: string; expect?: W7Expected }
|
|
373
399
|
| { kind: 'call'; call: W7PathCall };
|
|
374
400
|
|
|
375
401
|
interface EncoderFile {
|
|
@@ -712,7 +738,7 @@ async function* decodeRecords(
|
|
|
712
738
|
if (v3) throw new Error(`w7-frame: unknown record tag ${envelope.tag}`);
|
|
713
739
|
summary.pathCount++;
|
|
714
740
|
summary.opCount++;
|
|
715
|
-
const value = parseObject(payload, 'call', ['call', 'path'], ['mode', 'target', 'from', 'ino', 'size', 'existing', 'recursive', 'force', 'uid', 'gid', 'atime', 'mtime', 'umask', 'access', 'create', 'truncate', 'exclusive', 'nofollow', 'description', 'fchmod']);
|
|
741
|
+
const value = parseObject(payload, 'call', ['call', 'path'], ['mode', 'target', 'from', 'ino', 'size', 'existing', 'recursive', 'force', 'uid', 'gid', 'atime', 'mtime', 'umask', 'access', 'create', 'truncate', 'exclusive', 'nofollow', 'description', 'fchmod', 'expect']);
|
|
716
742
|
yield { type: 'call', call: parsePathCall(value, (path, label) => ownedPaths.claim(canonicalPath(path, label))) };
|
|
717
743
|
break;
|
|
718
744
|
}
|
|
@@ -723,18 +749,21 @@ async function* decodeRecords(
|
|
|
723
749
|
summary.pathCount++;
|
|
724
750
|
summary.opCount++;
|
|
725
751
|
if (envelope.tag === RecordTag.Rename) {
|
|
726
|
-
const value = parseObject(payload, 'rename', ['from', 'to']);
|
|
752
|
+
const value = parseObject(payload, 'rename', ['from', 'to'], ['expect', 'expectTo']);
|
|
727
753
|
const from = ownedPaths.claim(canonicalPath(value.from, 'rename source'));
|
|
728
754
|
const to = ownedPaths.claim(canonicalPath(value.to, 'rename target'));
|
|
729
|
-
yield { type: 'rename', from, to };
|
|
755
|
+
yield { type: 'rename', from, to, ...expectations(value, 'rename') };
|
|
730
756
|
} else if (envelope.tag === RecordTag.Truncate) {
|
|
731
757
|
const value = parseObject(payload, 'truncate', ['path', 'size']);
|
|
732
758
|
yield { type: 'truncate', path: ownedPaths.claim(canonicalPath(value.path, 'truncate path')), size: safeInteger(value.size, 'truncate size') };
|
|
733
759
|
} else {
|
|
734
|
-
const value = parseObject(payload, 'setattr', ['path'], ['mode', 'uid', 'gid', 'atime', 'mtime']);
|
|
760
|
+
const value = parseObject(payload, 'setattr', ['path'], ['mode', 'uid', 'gid', 'atime', 'mtime', 'description', 'expect']);
|
|
735
761
|
const path = ownedPaths.claim(canonicalPath(value.path, 'setattr path'));
|
|
736
|
-
const { path: _path, ...attrs } = value;
|
|
737
|
-
yield {
|
|
762
|
+
const { path: _path, description, expect: _expect, ...attrs } = value;
|
|
763
|
+
yield {
|
|
764
|
+
type: 'setattr', path, attrs: parseAttrs(attrs, 'setattr'),
|
|
765
|
+
...(description === undefined ? {} : { description: descriptionId(description, 'setattr description') }), ...expectations(value, 'setattr'),
|
|
766
|
+
};
|
|
738
767
|
}
|
|
739
768
|
break;
|
|
740
769
|
}
|
|
@@ -803,7 +832,7 @@ async function* encodeRecords(batchId: string, ops: readonly EncoderOp[]): Async
|
|
|
803
832
|
case 'rename':
|
|
804
833
|
state.summary.pathCount++;
|
|
805
834
|
state.summary.opCount++;
|
|
806
|
-
yield encodeMetadataRecord(RecordTag.Rename, { from: op.from, to: op.to }, state);
|
|
835
|
+
yield encodeMetadataRecord(RecordTag.Rename, { from: op.from, to: op.to, ...expectations(op, 'rename') }, state);
|
|
807
836
|
break;
|
|
808
837
|
case 'truncate':
|
|
809
838
|
state.summary.pathCount++;
|
|
@@ -813,7 +842,7 @@ async function* encodeRecords(batchId: string, ops: readonly EncoderOp[]): Async
|
|
|
813
842
|
case 'setattr':
|
|
814
843
|
state.summary.pathCount++;
|
|
815
844
|
state.summary.opCount++;
|
|
816
|
-
yield encodeMetadataRecord(RecordTag.SetAttr, { path: op.path, ...op.attrs }, state);
|
|
845
|
+
yield encodeMetadataRecord(RecordTag.SetAttr, { path: op.path, ...op.attrs, ...(op.description === undefined ? {} : { description: op.description }), ...expectations(op, 'setattr') }, state);
|
|
817
846
|
break;
|
|
818
847
|
case 'call':
|
|
819
848
|
state.summary.pathCount++;
|
|
@@ -1020,11 +1049,16 @@ function prepareOps(payload: BatchWritePayload, batchId: string): EncoderOp[] {
|
|
|
1020
1049
|
kind: 'rename',
|
|
1021
1050
|
from: ownedPaths.claim(canonicalPath(op.from, 'rename source')),
|
|
1022
1051
|
to: ownedPaths.claim(canonicalPath(op.to, 'rename target')),
|
|
1052
|
+
...expectations(op, 'rename'),
|
|
1023
1053
|
};
|
|
1024
1054
|
case 'truncate':
|
|
1025
1055
|
return { kind: 'truncate', path: ownedPaths.claim(canonicalPath(op.path, 'truncate path')), size: safeInteger(op.size, 'truncate size') };
|
|
1026
1056
|
case 'setattr':
|
|
1027
|
-
return {
|
|
1057
|
+
return {
|
|
1058
|
+
kind: 'setattr', path: ownedPaths.claim(canonicalPath(op.path, 'setattr path')), attrs: parseAttrs(op.attrs as Record<string, unknown>, 'setattr'),
|
|
1059
|
+
...(op.description === undefined ? {} : { description: descriptionId(op.description, 'setattr description') }),
|
|
1060
|
+
...expectations(op, 'setattr'),
|
|
1061
|
+
};
|
|
1028
1062
|
case 'call': {
|
|
1029
1063
|
const call = op.call;
|
|
1030
1064
|
if ('data' in call) {
|
|
@@ -1060,10 +1094,17 @@ function parseAttrs(value: Record<string, unknown>, label: string): W7Attrs {
|
|
|
1060
1094
|
const keys = Object.keys(value).sort().join(',');
|
|
1061
1095
|
if (keys === 'mode') return { mode: u32(value.mode, `${label} mode`) };
|
|
1062
1096
|
if (keys === 'gid,uid') return { uid: ownerId(value.uid, `${label} uid`), gid: ownerId(value.gid, `${label} gid`) };
|
|
1063
|
-
if (keys === 'atime,mtime') return { atime:
|
|
1097
|
+
if (keys === 'atime,mtime') return { atime: parseFsTime(value.atime, `w7-frame: ${label} atime`), mtime: parseFsTime(value.mtime, `w7-frame: ${label} mtime`) };
|
|
1064
1098
|
throw new Error(`w7-frame: ${label} changes the mode, the owner or the times, one of them: got ${keys || 'nothing'}`);
|
|
1065
1099
|
}
|
|
1066
1100
|
|
|
1101
|
+
/** `value` as an FsTime (a finite number of milliseconds, 'now' or 'omit'); else an error naming `label`. */
|
|
1102
|
+
export function parseFsTime(value: unknown, label: string): FsTime {
|
|
1103
|
+
if (value === 'now' || value === 'omit') return value;
|
|
1104
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
1105
|
+
throw new Error(`${label} must be a time: milliseconds, 'now' or 'omit'`);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1067
1108
|
function parseDelete(bytes: Uint8Array): DeleteMetadata {
|
|
1068
1109
|
const value = parseObject(bytes, 'delete', ['path']);
|
|
1069
1110
|
return { path: canonicalPath(value.path, 'delete path') };
|
|
@@ -1139,8 +1180,8 @@ function parsePathCall(value: Record<string, unknown>, path: (value: unknown, la
|
|
|
1139
1180
|
}
|
|
1140
1181
|
case 'unlink':
|
|
1141
1182
|
case 'rmdir':
|
|
1142
|
-
if (keys !== 'call,path') break;
|
|
1143
|
-
return { call: value.call, path: path(value.path, `${value.call} path`) };
|
|
1183
|
+
if (keys !== 'call,path' && keys !== 'call,expect,path') break;
|
|
1184
|
+
return { call: value.call, path: path(value.path, `${value.call} path`), ...expectations(value, value.call) };
|
|
1144
1185
|
case 'symlink':
|
|
1145
1186
|
if (keys !== 'call,path,target' && keys !== 'call,ino,path,target') break;
|
|
1146
1187
|
return {
|
|
@@ -1177,7 +1218,7 @@ function parsePathCall(value: Record<string, unknown>, path: (value: unknown, la
|
|
|
1177
1218
|
case 'open': {
|
|
1178
1219
|
const flags = ['create', 'exclusive', 'nofollow', 'truncate'] as const;
|
|
1179
1220
|
let required = keys;
|
|
1180
|
-
for (const flag of ['create', 'description', 'exclusive', 'fchmod', 'ino', 'nofollow', 'truncate', 'umask']) required = required.replace(`,${flag}`, '');
|
|
1221
|
+
for (const flag of ['create', 'description', 'exclusive', 'expect', 'fchmod', 'ino', 'nofollow', 'truncate', 'umask']) required = required.replace(`,${flag}`, '');
|
|
1181
1222
|
if (required !== 'access,call,mode,path') break;
|
|
1182
1223
|
for (const flag of flags) {
|
|
1183
1224
|
if (value[flag] !== undefined && value[flag] !== true) throw new Error(`w7-frame: open ${flag} is true or absent`);
|
|
@@ -1194,6 +1235,7 @@ function parsePathCall(value: Record<string, unknown>, path: (value: unknown, la
|
|
|
1194
1235
|
...(value.nofollow === true ? { nofollow: true as const } : {}),
|
|
1195
1236
|
...(value.description === undefined ? {} : { description: descriptionId(value.description, 'open description') }),
|
|
1196
1237
|
...(value.fchmod === undefined ? {} : { fchmod: u32(value.fchmod, 'open fchmod') }),
|
|
1238
|
+
...expectations(value, 'open'),
|
|
1197
1239
|
};
|
|
1198
1240
|
}
|
|
1199
1241
|
case 'lutimes':
|
|
@@ -1268,6 +1310,22 @@ function inodeNumber(value: unknown, label: string): number {
|
|
|
1268
1310
|
return ino;
|
|
1269
1311
|
}
|
|
1270
1312
|
|
|
1313
|
+
/** A record's expectations (W7Expected), checked: `expect`, and a rename's `expectTo`. */
|
|
1314
|
+
function expectations(value: { expect?: unknown; expectTo?: unknown }, label: string): { expect?: W7Expected; expectTo?: W7Expected } {
|
|
1315
|
+
const expected = (field: unknown, name: string): W7Expected => {
|
|
1316
|
+
if (field === null) return null;
|
|
1317
|
+
if (isObject(field) && !Array.isArray(field)) {
|
|
1318
|
+
if (Object.keys(field).join(',') !== 'made') throw new Error(`w7-frame: ${label} ${name} is a number, null or { made }`);
|
|
1319
|
+
return { made: descriptionId(field.made, `${label} ${name} made`) };
|
|
1320
|
+
}
|
|
1321
|
+
return inodeNumber(field, `${label} ${name}`);
|
|
1322
|
+
};
|
|
1323
|
+
return {
|
|
1324
|
+
...(value.expect === undefined ? {} : { expect: expected(value.expect, 'expect') }),
|
|
1325
|
+
...(value.expectTo === undefined ? {} : { expectTo: expected(value.expectTo, 'expectTo') }),
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1271
1329
|
function parseObject(
|
|
1272
1330
|
bytes: Uint8Array,
|
|
1273
1331
|
label: string,
|