@poe-platform/safe-fs 0.1.734 → 0.1.735

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.
@@ -1,4 +1,3 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import type { FsOptions } from "../contracts/filesystem.js";
3
2
  import { type ScopedTransportBudgetFrame } from "./transport-budget.js";
4
3
  export type PlatformErrno = number;
@@ -15,7 +14,7 @@ export declare const platform: Readonly<{
15
14
  maxCollectionBytes: number;
16
15
  errno(code: string): PlatformErrno;
17
16
  callbackAuthorities: true;
18
- randomUUID: typeof randomUUID;
17
+ randomUUID: () => string;
19
18
  }>;
20
19
  export declare const comparisonContext: Readonly<{
21
20
  active(_options: FsOptions): boolean;
@@ -25,7 +25,7 @@ export const platform = Object.freeze({
25
25
  return errno;
26
26
  },
27
27
  callbackAuthorities: true,
28
- randomUUID
28
+ randomUUID: randomUUID
29
29
  });
30
30
  export const comparisonContext = Object.freeze({
31
31
  active(_options) {
@@ -6,21 +6,16 @@ export class XmlLimitError extends SyntaxError {
6
6
  }
7
7
  }
8
8
  function* find(source, needle, start) {
9
- let work = 0;
10
- for (let offset = start; offset < source.length; offset++) {
11
- if (source.startsWith(needle, offset)) {
12
- if (work)
13
- yield work;
14
- return offset;
15
- }
16
- if (++work === 512) {
17
- yield work;
18
- work = 0;
19
- }
9
+ const found = source.indexOf(needle, start);
10
+ const end = found < 0 ? source.length : found;
11
+ let remaining = end - start;
12
+ while (remaining >= 512) {
13
+ yield 512;
14
+ remaining -= 512;
20
15
  }
21
- if (work)
22
- yield work;
23
- return -1;
16
+ if (remaining > 0)
17
+ yield remaining;
18
+ return found;
24
19
  }
25
20
  const xmlNamespace = "http://www.w3.org/XML/1998/namespace";
26
21
  const xmlnsNamespace = "http://www.w3.org/2000/xmlns/";
@@ -47,6 +42,30 @@ function namePart(point) {
47
42
  || (point >= 48 && point <= 57) || (point >= 0x300 && point <= 0x36f)
48
43
  || (point >= 0x203f && point <= 0x2040);
49
44
  }
45
+ function qualifiedNameSync(name) {
46
+ let prefix = "";
47
+ let start = 0;
48
+ let first = true;
49
+ for (let offset = 0; offset < name.length;) {
50
+ const point = name.codePointAt(offset);
51
+ if (point === 58) {
52
+ if (start !== 0 || first)
53
+ invalid("invalid qualified name");
54
+ prefix = name.slice(0, offset);
55
+ start = offset + 1;
56
+ first = true;
57
+ }
58
+ else {
59
+ if (!(first ? nameStart(point) : namePart(point)))
60
+ invalid("invalid qualified name");
61
+ first = false;
62
+ }
63
+ offset += point > 0xffff ? 2 : 1;
64
+ }
65
+ if (first)
66
+ invalid("invalid qualified name");
67
+ return [prefix, name.slice(start)];
68
+ }
50
69
  function* qualifiedName(name) {
51
70
  let prefix = "";
52
71
  let start = 0;
@@ -258,67 +277,81 @@ export function* parseXmlSteps(input, limits = {}) {
258
277
  if (++contentNodes > maxContentNodes)
259
278
  throw new XmlLimitError("maxContentNodes", "XML content node limit exceeded");
260
279
  };
261
- const whitespace = function* () {
262
- let work = 0;
263
- while (offset < source.length && " \t\n\r".includes(source[offset])) {
280
+ let pendingWork = 0;
281
+ const skipWhitespace = () => {
282
+ const start = offset;
283
+ while (offset < source.length) {
284
+ const c = source.charCodeAt(offset);
285
+ if (c !== 32 && c !== 9 && c !== 10 && c !== 13)
286
+ break;
264
287
  offset++;
265
- if (++work === 512) {
266
- yield work;
267
- work = 0;
268
- }
269
288
  }
270
- if (work)
271
- yield work;
289
+ return offset - start;
272
290
  };
273
- const readName = function* () {
291
+ const scanName = () => {
274
292
  const start = offset;
275
- while (offset < source.length && !" \t\r\n/=>?".includes(source[offset])) {
293
+ while (offset < source.length) {
294
+ const c = source.charCodeAt(offset);
295
+ if (c === 32 || c === 9 || c === 13 || c === 10 || c === 47 || c === 61 || c === 62 || c === 63)
296
+ break;
276
297
  offset++;
277
- if ((offset - start) % 512 === 0)
278
- yield 512;
279
298
  }
280
- if ((offset - start) % 512)
281
- yield (offset - start) % 512;
282
299
  const name = source.slice(start, offset);
283
- yield* qualifiedName(name);
284
- return name;
300
+ const [prefix, localName] = qualifiedNameSync(name);
301
+ return [name, prefix, localName, (offset - start) * 2];
285
302
  };
286
- const appendText = function* (text, kind = "text") {
287
- admitText(text);
288
- const parent = stack.at(-1);
289
- if (parent) {
290
- parent.element.text += text;
291
- if (text.length || kind === "cdata") {
292
- admitContent();
293
- if (retainContent)
294
- parent.content.push({ kind, text });
303
+ while (offset < source.length) {
304
+ pendingWork += 1;
305
+ if (pendingWork >= 512) {
306
+ while (pendingWork >= 512) {
307
+ yield 512;
308
+ pendingWork -= 512;
295
309
  }
296
310
  }
297
- else {
298
- for (let index = 0; index < text.length; index++) {
299
- if (!" \t\r\n".includes(text[index]))
300
- invalid("text outside the root");
301
- if ((index + 1) % 512 === 0)
302
- yield 512;
303
- }
304
- if (text.length % 512)
305
- yield text.length % 512;
306
- if (text.length) {
307
- admitContent();
308
- if (retainContent)
309
- (root ? epilog : prolog).push({ kind, text });
311
+ if (source.charCodeAt(offset) !== 60) {
312
+ const next = source.indexOf("<", offset);
313
+ const endPos = next < 0 ? source.length : next;
314
+ const text = source.slice(offset, endPos);
315
+ pendingWork += (endPos - offset) + text.length;
316
+ while (pendingWork >= 512) {
317
+ yield 512;
318
+ pendingWork -= 512;
310
319
  }
311
- }
312
- };
313
- while (offset < source.length) {
314
- yield 1;
315
- if (source[offset] !== "<") {
316
- const next = yield* find(source, "<", offset);
317
- const text = source.slice(offset, next < 0 ? source.length : next);
318
- if ((yield* find(text, "]]>", 0)) >= 0)
320
+ if (text.indexOf("]]>") >= 0)
319
321
  invalid("CDATA terminator in text");
320
- yield* appendText(stack.length ? yield* entities(text) : text);
321
- offset += text.length;
322
+ const resolved = stack.length ? (text.indexOf("&") < 0 ? (pendingWork += text.length, text) : yield* entities(text)) : text;
323
+ while (pendingWork >= 512) {
324
+ yield 512;
325
+ pendingWork -= 512;
326
+ }
327
+ admitText(resolved);
328
+ const parent = stack.at(-1);
329
+ if (parent) {
330
+ parent.element.text += resolved;
331
+ if (resolved.length) {
332
+ admitContent();
333
+ if (retainContent)
334
+ parent.content.push({ kind: "text", text: resolved });
335
+ }
336
+ }
337
+ else {
338
+ for (let index = 0; index < resolved.length; index++) {
339
+ const c = resolved.charCodeAt(index);
340
+ if (c !== 32 && c !== 9 && c !== 13 && c !== 10)
341
+ invalid("text outside the root");
342
+ }
343
+ pendingWork += resolved.length;
344
+ while (pendingWork >= 512) {
345
+ yield 512;
346
+ pendingWork -= 512;
347
+ }
348
+ if (resolved.length) {
349
+ admitContent();
350
+ if (retainContent)
351
+ (root ? epilog : prolog).push({ kind: "text", text: resolved });
352
+ }
353
+ }
354
+ offset = endPos;
322
355
  }
323
356
  else if (source.startsWith("<!--", offset)) {
324
357
  const end = yield* find(source, "-->", offset + 4);
@@ -340,13 +373,24 @@ export function* parseXmlSteps(input, limits = {}) {
340
373
  const end = yield* find(source, "]]>", offset + 9);
341
374
  if (end < 0)
342
375
  invalid("unterminated CDATA");
343
- yield* appendText(source.slice(offset + 9, end), "cdata");
376
+ const cdataText = source.slice(offset + 9, end);
377
+ admitText(cdataText);
378
+ const parent = stack.at(-1);
379
+ parent.element.text += cdataText;
380
+ admitContent();
381
+ if (retainContent)
382
+ parent.content.push({ kind: "cdata", text: cdataText });
344
383
  offset = end + 3;
345
384
  }
346
385
  else if (source.startsWith("<?", offset)) {
347
386
  const start = offset;
348
387
  offset += 2;
349
- const target = yield* readName();
388
+ const [target, , , wName] = scanName();
389
+ pendingWork += wName;
390
+ while (pendingWork >= 512) {
391
+ yield 512;
392
+ pendingWork -= 512;
393
+ }
350
394
  const end = yield* find(source, "?>", offset);
351
395
  if (end < 0)
352
396
  invalid("unterminated processing instruction");
@@ -363,15 +407,15 @@ export function* parseXmlSteps(input, limits = {}) {
363
407
  invalid("invalid processing instruction");
364
408
  if (!(target.length === 3 && target.toLowerCase() === "xml")) {
365
409
  const parent = stack.at(-1);
366
- let start = 0;
367
- while (start < content.length && " \t\n\r".includes(content[start])) {
368
- start++;
369
- if (start % 512 === 0)
370
- yield 512;
410
+ let wsStart = 0;
411
+ while (wsStart < content.length && " \t\n\r".includes(content[wsStart]))
412
+ wsStart++;
413
+ pendingWork += wsStart;
414
+ while (pendingWork >= 512) {
415
+ yield 512;
416
+ pendingWork -= 512;
371
417
  }
372
- if (start % 512)
373
- yield start % 512;
374
- const text = content.slice(start);
418
+ const text = content.slice(wsStart);
375
419
  admitText(text);
376
420
  admitContent();
377
421
  if (retainContent) {
@@ -383,92 +427,129 @@ export function* parseXmlSteps(input, limits = {}) {
383
427
  else if (source.startsWith("<!", offset)) {
384
428
  invalid("DTD and entity declarations are forbidden");
385
429
  }
386
- else if (source.startsWith("</", offset)) {
430
+ else if (source.charCodeAt(offset + 1) === 47) {
387
431
  offset += 2;
388
- const name = yield* readName();
389
- yield* whitespace();
432
+ const [name, , , wName] = scanName();
433
+ pendingWork += wName + skipWhitespace();
434
+ while (pendingWork >= 512) {
435
+ yield 512;
436
+ pendingWork -= 512;
437
+ }
390
438
  if (source[offset++] !== ">" || stack.pop()?.name !== name)
391
439
  invalid("mismatched closing tag");
392
440
  }
393
441
  else {
394
442
  offset++;
395
- const name = yield* readName();
443
+ const [name, prefix, localName, wName] = scanName();
444
+ pendingWork += wName;
445
+ while (pendingWork >= 512) {
446
+ yield 512;
447
+ pendingWork -= 512;
448
+ }
396
449
  const attributes = new Map();
450
+ const attrMeta = [];
397
451
  let namespaces = stack.at(-1)?.namespaces ?? new Map([["xml", xmlNamespace]]);
398
452
  let ownsNamespaces = stack.length === 0;
399
453
  while (true) {
400
- const beforeSpace = offset;
401
- yield* whitespace();
402
- if (source[offset] === "/" || source[offset] === ">")
454
+ const ws = skipWhitespace();
455
+ pendingWork += ws;
456
+ while (pendingWork >= 512) {
457
+ yield 512;
458
+ pendingWork -= 512;
459
+ }
460
+ const ch = source.charCodeAt(offset);
461
+ if (ch === 47 || ch === 62)
403
462
  break;
404
- if (offset === beforeSpace)
463
+ if (ws === 0)
405
464
  invalid("attributes require whitespace");
406
- const attribute = yield* readName();
465
+ const [attribute, attrPrefix, attrLocal, wAttr] = scanName();
466
+ pendingWork += wAttr;
467
+ while (pendingWork >= 512) {
468
+ yield 512;
469
+ pendingWork -= 512;
470
+ }
407
471
  if (attributes.has(attribute))
408
472
  invalid("duplicate attribute");
409
473
  if (++attributeCount > maxAttributes)
410
474
  throw new XmlLimitError("maxAttributes", "XML attribute limit exceeded");
411
475
  if (attributes.size >= maxAttributesPerElement)
412
476
  throw new XmlLimitError("maxAttributesPerElement", "XML attribute limit exceeded");
413
- yield* whitespace();
477
+ pendingWork += skipWhitespace();
414
478
  if (source[offset++] !== "=")
415
479
  invalid("missing attribute equals");
416
- yield* whitespace();
480
+ pendingWork += skipWhitespace();
417
481
  const quote = source[offset++];
418
482
  if (quote !== '"' && quote !== "'")
419
483
  invalid("unquoted attribute");
420
- const end = yield* find(source, quote, offset);
484
+ const end = source.indexOf(quote, offset);
485
+ const scanLen = (end < 0 ? source.length : end) - offset;
486
+ pendingWork += scanLen;
487
+ while (pendingWork >= 512) {
488
+ yield 512;
489
+ pendingWork -= 512;
490
+ }
421
491
  if (end < 0)
422
492
  invalid("unterminated attribute");
423
493
  const raw = source.slice(offset, end);
424
- if ((yield* find(raw, "<", 0)) >= 0)
494
+ pendingWork += raw.length * 2;
495
+ while (pendingWork >= 512) {
496
+ yield 512;
497
+ pendingWork -= 512;
498
+ }
499
+ if (raw.indexOf("<") >= 0)
425
500
  invalid("less-than in attribute");
426
- let normalized = "";
427
- for (let index = 0; index < raw.length; index++) {
428
- const character = raw[index];
429
- normalized += character === "\t" || character === "\n" || character === "\r" ? " " : character;
430
- if ((index + 1) % 512 === 0)
431
- yield 512;
501
+ const normalized = /[\t\n\r]/.test(raw) ? raw.replace(/[\t\n\r]/g, " ") : raw;
502
+ const value = normalized.indexOf("&") < 0 ? (pendingWork += normalized.length, normalized) : yield* entities(normalized);
503
+ while (pendingWork >= 512) {
504
+ yield 512;
505
+ pendingWork -= 512;
432
506
  }
433
- if (raw.length % 512)
434
- yield raw.length % 512;
435
- const value = yield* entities(normalized);
436
507
  admitText(value);
437
508
  attributes.set(attribute, value);
509
+ attrMeta.push([attribute, attrPrefix, attrLocal]);
438
510
  offset = end + 1;
439
511
  if (attribute === "xmlns" || attribute.startsWith("xmlns:")) {
440
- const prefix = attribute === "xmlns" ? "" : attribute.slice(6);
441
- if (prefix === "xmlns" || value === xmlnsNamespace
442
- || (prefix === "xml") !== (value === xmlNamespace)
443
- || (prefix !== "" && value === ""))
512
+ const nsPrefix = attribute === "xmlns" ? "" : attribute.slice(6);
513
+ if (nsPrefix === "xmlns" || value === xmlnsNamespace
514
+ || (nsPrefix === "xml") !== (value === xmlNamespace)
515
+ || (nsPrefix !== "" && value === ""))
444
516
  invalid("invalid namespace binding");
445
517
  if (!ownsNamespaces) {
446
518
  const copy = new Map();
447
519
  for (const [key, uri] of namespaces) {
448
520
  copy.set(key, uri);
449
- yield 1;
521
+ pendingWork += 1;
450
522
  }
451
523
  namespaces = copy;
452
524
  ownsNamespaces = true;
453
525
  }
454
- namespaces.set(prefix, value);
526
+ namespaces.set(nsPrefix, value);
455
527
  if (namespaces.size > maxNamespaces)
456
528
  throw new XmlLimitError("maxNamespaces", "XML namespace scope limit exceeded");
457
529
  }
458
530
  }
459
- const expanded = new Set();
460
- for (const attribute of attributes.keys()) {
461
- if (attribute === "xmlns" || attribute.startsWith("xmlns:"))
462
- continue;
463
- const [prefix, localName] = yield* qualifiedName(attribute);
464
- if (prefix && !namespaces.has(prefix))
465
- invalid("unbound attribute prefix");
466
- const key = JSON.stringify([prefix ? namespaces.get(prefix) : "", localName]);
467
- if (expanded.has(key))
468
- invalid("duplicate expanded attribute");
469
- expanded.add(key);
531
+ if (attrMeta.length > 0) {
532
+ const expanded = attrMeta.length > 1 ? new Set() : undefined;
533
+ for (let i = 0; i < attrMeta.length; i++) {
534
+ const [attribute, attrPrefix, attrLocal] = attrMeta[i];
535
+ if (attribute === "xmlns" || attribute.startsWith("xmlns:"))
536
+ continue;
537
+ pendingWork += attribute.length;
538
+ if (attrPrefix && !namespaces.has(attrPrefix))
539
+ invalid("unbound attribute prefix");
540
+ if (expanded) {
541
+ const key = `${attrPrefix ? namespaces.get(attrPrefix) : ""}\0${attrLocal}`;
542
+ if (expanded.has(key))
543
+ invalid("duplicate expanded attribute");
544
+ expanded.add(key);
545
+ }
546
+ }
547
+ }
548
+ pendingWork += name.length;
549
+ while (pendingWork >= 512) {
550
+ yield 512;
551
+ pendingWork -= 512;
470
552
  }
471
- const [prefix, localName] = yield* qualifiedName(name);
472
553
  if (prefix === "xmlns" || (prefix && !namespaces.has(prefix)))
473
554
  invalid("unbound element prefix");
474
555
  if (++nodes > maxNodes)
@@ -479,12 +560,19 @@ export function* parseXmlSteps(input, limits = {}) {
479
560
  limits.onElement?.({ name, namespace, localName }, stack.at(-1)?.element, stack.length + 1);
480
561
  admitContent();
481
562
  const retainedAttributes = [];
482
- for (const [attribute, value] of retainContent ? attributes : []) {
483
- const [prefix, localName] = yield* qualifiedName(attribute);
484
- admitContent();
485
- retainedAttributes.push({ name: attribute, localName,
486
- namespace: attribute === "xmlns" || prefix === "xmlns" ? xmlnsNamespace : prefix ? namespaces.get(prefix) : "", value });
487
- yield 1;
563
+ if (retainContent) {
564
+ for (let i = 0; i < attrMeta.length; i++) {
565
+ const [attribute, attrPrefix, attrLocal] = attrMeta[i];
566
+ const value = attributes.get(attribute);
567
+ admitContent();
568
+ retainedAttributes.push({ name: attribute, localName: attrLocal,
569
+ namespace: attribute === "xmlns" || attrPrefix === "xmlns" ? xmlnsNamespace : attrPrefix ? namespaces.get(attrPrefix) : "", value });
570
+ pendingWork += attribute.length + 1;
571
+ }
572
+ }
573
+ while (pendingWork >= 512) {
574
+ yield 512;
575
+ pendingWork -= 512;
488
576
  }
489
577
  const content = retainContent ? [] : undefined;
490
578
  const element = { kind: "element", name, namespace, localName, children: [], text: "", content: content ?? emptyContent, attributes: retainContent ? retainedAttributes : emptyAttributes, namespaces: retainContent ? namespaces : emptyNamespaces, ...(root === undefined && retainContent ? { prolog, epilog, ...(declaration === undefined ? {} : { declaration }) } : {}) };
@@ -506,6 +594,10 @@ export function* parseXmlSteps(input, limits = {}) {
506
594
  stack.push({ element, content, name, namespaces });
507
595
  }
508
596
  }
597
+ if (pendingWork > 0) {
598
+ yield pendingWork;
599
+ pendingWork = 0;
600
+ }
509
601
  if (stack.length || !root)
510
602
  invalid("incomplete document");
511
603
  return root;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-fs",
3
- "version": "0.1.734",
3
+ "version": "0.1.735",
4
4
  "description": "Composable filesystem with a portable core and explicit Node adapters",
5
5
  "type": "module",
6
6
  "license": "MIT",