@poe-platform/safe-js 0.1.729 → 0.1.731

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.
Files changed (100) hide show
  1. package/README.md +5 -5
  2. package/dist/safe-js/chunks/{chunk-SJZWSPWD.js → chunk-2GVBG23M.js} +1 -1
  3. package/dist/safe-js/chunks/{chunk-SJZWSPWD.js.map → chunk-2GVBG23M.js.map} +1 -1
  4. package/dist/safe-js/chunks/{chunk-VIMROAQA.js → chunk-E4XLBT7R.js} +2 -2
  5. package/dist/safe-js/chunks/chunk-F6CV74HU.js +114 -0
  6. package/dist/safe-js/chunks/chunk-F6CV74HU.js.map +7 -0
  7. package/dist/safe-js/chunks/{chunk-OZLTLIHX.js → chunk-G6WTEIJJ.js} +2 -2
  8. package/dist/safe-js/chunks/{chunk-XX57YCGZ.js → chunk-GNEORAQI.js} +55 -50
  9. package/dist/safe-js/chunks/chunk-GNEORAQI.js.map +7 -0
  10. package/dist/safe-js/chunks/chunk-GVKXZW53.js +76 -0
  11. package/dist/safe-js/chunks/chunk-GVKXZW53.js.map +7 -0
  12. package/dist/safe-js/chunks/{chunk-2S4YRXI6.js → chunk-IRDQDZ5R.js} +4 -4
  13. package/dist/safe-js/chunks/{chunk-MRULOD5J.js → chunk-K4T4EBED.js} +59 -38
  14. package/dist/safe-js/chunks/chunk-K4T4EBED.js.map +7 -0
  15. package/dist/safe-js/chunks/{chunk-MFCEBCGV.js → chunk-KETIFEA7.js} +17 -8
  16. package/dist/safe-js/chunks/chunk-KETIFEA7.js.map +7 -0
  17. package/dist/safe-js/chunks/{chunk-S5YDMMM3.js → chunk-MDIWQFYU.js} +95 -169
  18. package/dist/safe-js/chunks/{chunk-S5YDMMM3.js.map → chunk-MDIWQFYU.js.map} +4 -4
  19. package/dist/safe-js/chunks/{chunk-BRBOVGNC.js → chunk-MGHE7ZFI.js} +78 -67
  20. package/dist/safe-js/chunks/chunk-MGHE7ZFI.js.map +7 -0
  21. package/dist/safe-js/chunks/{chunk-EN724VH3.js → chunk-O4IDRXW7.js} +4 -4
  22. package/dist/safe-js/chunks/{chunk-EA7AHM2X.js → chunk-PSVV3IG2.js} +3 -3
  23. package/dist/safe-js/chunks/{chunk-Y5EZYHU6.js → chunk-QKO4FATZ.js} +33 -5
  24. package/dist/safe-js/chunks/chunk-QKO4FATZ.js.map +7 -0
  25. package/dist/safe-js/chunks/{chunk-EG3QWREA.js → chunk-RULU2F5J.js} +2 -2
  26. package/dist/safe-js/chunks/chunk-UQPYTZOZ.js +72 -0
  27. package/dist/safe-js/chunks/chunk-UQPYTZOZ.js.map +7 -0
  28. package/dist/safe-js/chunks/{chunk-LRSWTDIV.js → chunk-W4SWUHCQ.js} +58 -23
  29. package/dist/safe-js/chunks/chunk-W4SWUHCQ.js.map +7 -0
  30. package/dist/safe-js/chunks/{chunk-W3IY6FKO.js → chunk-WS2FJBH6.js} +2 -2
  31. package/dist/safe-js/chunks/{chunk-MVLDRXWS.js → chunk-X3CKAZQ4.js} +3 -3
  32. package/dist/safe-js/chunks/chunk-YUQFFRUF.js +26 -0
  33. package/dist/safe-js/chunks/chunk-YUQFFRUF.js.map +7 -0
  34. package/dist/safe-js/chunks/{cli-runtime-6VVBO2SS.js → cli-runtime-NUEFUM64.js} +51 -30
  35. package/dist/safe-js/chunks/cli-runtime-NUEFUM64.js.map +7 -0
  36. package/dist/safe-js/chunks/{dump-UNYK24YS.js → dump-RGRC3CG3.js} +5 -4
  37. package/dist/safe-js/chunks/harness-UMOMFXDT.js +11 -0
  38. package/dist/safe-js/chunks/mcp-S672263Q.js +12 -0
  39. package/dist/safe-js/chunks/migration-file-KRXQYI3Q.js +15 -0
  40. package/dist/safe-js/chunks/restore-GEO7VN2R.js +14 -0
  41. package/dist/safe-js/chunks/run-ZSFX66RN.js +20 -0
  42. package/dist/safe-js/chunks/runtime-modules-A3U2DKFL.js +11 -0
  43. package/dist/safe-js/chunks/source-files-L3SGK63Z.js +10 -0
  44. package/dist/safe-js/cli-runtime.d.ts +2 -0
  45. package/dist/safe-js/cli.js +4 -4
  46. package/dist/safe-js/core.d.ts +1 -0
  47. package/dist/safe-js/core.js +15 -8
  48. package/dist/safe-js/error/shape.js +170 -0
  49. package/dist/safe-js/index.js +42 -33
  50. package/dist/safe-js/index.js.map +2 -2
  51. package/dist/safe-js/interp/budget.js +600 -0
  52. package/dist/safe-js/interp/internal-symbols.js +1 -0
  53. package/dist/safe-js/interp/promise-replay.d.ts +1 -1
  54. package/dist/safe-js/interp/template-cache.js +7 -0
  55. package/dist/safe-js/migration-file.d.ts +2 -0
  56. package/dist/safe-js/modules/fs-config.d.ts +3 -1
  57. package/dist/safe-js/modules/fs.d.ts +8 -4
  58. package/dist/safe-js/modules/fs.js +16 -0
  59. package/dist/safe-js/modules/source-files.d.ts +3 -2
  60. package/dist/safe-js/platform/context.js +58 -0
  61. package/dist/safe-js/platform/node.js +64 -0
  62. package/dist/safe-js/platform/types.js +63 -0
  63. package/dist/safe-js/platform/workerd.js +39 -0
  64. package/dist/safe-js/runner/run-harness.d.ts +2 -0
  65. package/dist/safe-js/snapshot/backend.d.ts +2 -0
  66. package/package.json +9 -2
  67. package/dist/safe-js/chunks/chunk-BRBOVGNC.js.map +0 -7
  68. package/dist/safe-js/chunks/chunk-FCCA27RC.js +0 -69
  69. package/dist/safe-js/chunks/chunk-FCCA27RC.js.map +0 -7
  70. package/dist/safe-js/chunks/chunk-LRSWTDIV.js.map +0 -7
  71. package/dist/safe-js/chunks/chunk-MFCEBCGV.js.map +0 -7
  72. package/dist/safe-js/chunks/chunk-MRULOD5J.js.map +0 -7
  73. package/dist/safe-js/chunks/chunk-XX57YCGZ.js.map +0 -7
  74. package/dist/safe-js/chunks/chunk-Y5EZYHU6.js.map +0 -7
  75. package/dist/safe-js/chunks/cli-runtime-6VVBO2SS.js.map +0 -7
  76. package/dist/safe-js/chunks/fs-AOV77RQC.js +0 -13
  77. package/dist/safe-js/chunks/harness-ZIEO7NAV.js +0 -10
  78. package/dist/safe-js/chunks/mcp-WBM7ZOAN.js +0 -11
  79. package/dist/safe-js/chunks/migration-file-3SWGNDS2.js +0 -12
  80. package/dist/safe-js/chunks/restore-FCTWPLU3.js +0 -13
  81. package/dist/safe-js/chunks/run-ESJHEXCE.js +0 -17
  82. package/dist/safe-js/chunks/runtime-modules-7V6BWCRK.js +0 -10
  83. package/dist/safe-js/chunks/source-files-73OHLNGH.js +0 -8
  84. /package/dist/safe-js/chunks/{chunk-VIMROAQA.js.map → chunk-E4XLBT7R.js.map} +0 -0
  85. /package/dist/safe-js/chunks/{chunk-OZLTLIHX.js.map → chunk-G6WTEIJJ.js.map} +0 -0
  86. /package/dist/safe-js/chunks/{chunk-2S4YRXI6.js.map → chunk-IRDQDZ5R.js.map} +0 -0
  87. /package/dist/safe-js/chunks/{chunk-EN724VH3.js.map → chunk-O4IDRXW7.js.map} +0 -0
  88. /package/dist/safe-js/chunks/{chunk-EA7AHM2X.js.map → chunk-PSVV3IG2.js.map} +0 -0
  89. /package/dist/safe-js/chunks/{chunk-EG3QWREA.js.map → chunk-RULU2F5J.js.map} +0 -0
  90. /package/dist/safe-js/chunks/{chunk-W3IY6FKO.js.map → chunk-WS2FJBH6.js.map} +0 -0
  91. /package/dist/safe-js/chunks/{chunk-MVLDRXWS.js.map → chunk-X3CKAZQ4.js.map} +0 -0
  92. /package/dist/safe-js/chunks/{dump-UNYK24YS.js.map → dump-RGRC3CG3.js.map} +0 -0
  93. /package/dist/safe-js/chunks/{fs-AOV77RQC.js.map → harness-UMOMFXDT.js.map} +0 -0
  94. /package/dist/safe-js/chunks/{harness-ZIEO7NAV.js.map → mcp-S672263Q.js.map} +0 -0
  95. /package/dist/safe-js/chunks/{mcp-WBM7ZOAN.js.map → migration-file-KRXQYI3Q.js.map} +0 -0
  96. /package/dist/safe-js/chunks/{migration-file-3SWGNDS2.js.map → restore-GEO7VN2R.js.map} +0 -0
  97. /package/dist/safe-js/chunks/{restore-FCTWPLU3.js.map → run-ZSFX66RN.js.map} +0 -0
  98. /package/dist/safe-js/chunks/{run-ESJHEXCE.js.map → runtime-modules-A3U2DKFL.js.map} +0 -0
  99. /package/dist/safe-js/chunks/{runtime-modules-7V6BWCRK.js.map → source-files-L3SGK63Z.js.map} +0 -0
  100. /package/dist/safe-js/{chunks/source-files-73OHLNGH.js.map → modules/fs.js.map} +0 -0
@@ -0,0 +1,600 @@
1
+ import { replaceErrorStack } from "../error/shape.js";
2
+ import { releaseTemplateObjects } from "./template-cache.js";
3
+ export const REGEX_COMPILE_LIMITS = Object.freeze({
4
+ sourceLength: Infinity,
5
+ flagsLength: Infinity,
6
+ depth: Infinity,
7
+ allocations: Infinity
8
+ });
9
+ const DEADLINE_CHECK_INTERVAL = 1_024;
10
+ export class SandboxError extends Error {
11
+ code;
12
+ budget;
13
+ current;
14
+ limit;
15
+ constructor(input) {
16
+ super(input === "aborted"
17
+ ? "aborted"
18
+ : input === "reentry"
19
+ ? "Sandbox object is already running."
20
+ : `Sandbox budget exceeded for ${input.budget}: ${input.current} > ${input.limit}.`);
21
+ this.name = "SandboxError";
22
+ replaceErrorStack(this);
23
+ if (input === "aborted") {
24
+ this.code = "aborted";
25
+ return;
26
+ }
27
+ if (input === "reentry") {
28
+ this.code = "reentry";
29
+ return;
30
+ }
31
+ this.code = "budgetExceeded";
32
+ this.budget = input.budget;
33
+ this.current = input.current;
34
+ this.limit = input.limit;
35
+ }
36
+ }
37
+ export function isFatalSandboxError(error) {
38
+ return (error instanceof SandboxError && (error.code === "budgetExceeded" || error.code === "reentry"));
39
+ }
40
+ class BudgetAccounting {
41
+ deadline;
42
+ limits;
43
+ stepsUsed = 0;
44
+ peakCallDepth = 0;
45
+ currentDataSize = 0;
46
+ peakDataSize = 0;
47
+ currentCallDepth = 0;
48
+ allChecksSuspended = 0;
49
+ deadlineChecksSuspended = 0;
50
+ visitsUntilDeadlineCheck = DEADLINE_CHECK_INTERVAL;
51
+ pendingDeadlines;
52
+ earliestPendingDeadline;
53
+ retainedDataSize = 0;
54
+ retainedData = new Map();
55
+ retainedValueSources = new Map();
56
+ compileGeneration = 0;
57
+ activeCompileOwner;
58
+ defaultCompileOwner;
59
+ compileUses = 0;
60
+ provisionalScopes = 0;
61
+ compileTickets = new Map();
62
+ completedCompileTickets = new Set();
63
+ reconciliationHolds = 0;
64
+ deferredCompileDiscards = new Set();
65
+ realmViews;
66
+ constructor(options = {}) {
67
+ this.deadline = normalizeDeadline(options.deadline);
68
+ this.limits = Object.freeze({
69
+ maxSteps: normalizeLimit("maxSteps", options.maxSteps),
70
+ maxCallDepth: normalizeLimit("maxCallDepth", options.maxCallDepth),
71
+ stringLength: normalizeLimit("stringLength", options.stringLength),
72
+ ...(options.regexSourceLength === undefined ? {} : {
73
+ regexSourceLength: normalizeRegexLimit("regexSourceLength", options.regexSourceLength)
74
+ }),
75
+ ...(options.regexCompileAllocations === undefined ? {} : {
76
+ regexCompileAllocations: normalizeRegexLimit("regexCompileAllocations", options.regexCompileAllocations)
77
+ }),
78
+ arrayLength: normalizeLimit("arrayLength", options.arrayLength),
79
+ dataSize: normalizeLimit("dataSize", options.dataSize)
80
+ });
81
+ }
82
+ }
83
+ export class Budget {
84
+ accounting;
85
+ realmView = false;
86
+ realmLease = false;
87
+ constructor(options = {}) {
88
+ this.accounting = new BudgetAccounting(options);
89
+ }
90
+ get deadline() { return this.accounting.deadline; }
91
+ get limits() { return this.accounting.limits; }
92
+ get stepsUsed() { return this.accounting.stepsUsed; }
93
+ set stepsUsed(value) { this.accounting.stepsUsed = value; }
94
+ get peakCallDepth() { return this.accounting.peakCallDepth; }
95
+ set peakCallDepth(value) { this.accounting.peakCallDepth = value; }
96
+ get currentDataSize() { return this.accounting.currentDataSize; }
97
+ set currentDataSize(value) { this.accounting.currentDataSize = value; }
98
+ get peakDataSize() { return this.accounting.peakDataSize; }
99
+ set peakDataSize(value) { this.accounting.peakDataSize = value; }
100
+ get currentCallDepth() { return this.accounting.currentCallDepth; }
101
+ set currentCallDepth(value) { this.accounting.currentCallDepth = value; }
102
+ // Pending host operations keep their elapsed deadline active during sampled
103
+ // synchronous work. Linked private records need no observable native hooks.
104
+ acquireDeadline(deadline, onExceeded) {
105
+ if (!Number.isFinite(deadline))
106
+ throw new TypeError("Deadline must be a finite timestamp.");
107
+ const accounting = this.accounting;
108
+ const pending = {
109
+ deadline, onExceeded,
110
+ next: accounting.pendingDeadlines, active: true
111
+ };
112
+ if (pending.next !== undefined)
113
+ pending.next.previous = pending;
114
+ accounting.pendingDeadlines = pending;
115
+ if (accounting.earliestPendingDeadline === undefined ||
116
+ pending.deadline < accounting.earliestPendingDeadline.deadline)
117
+ accounting.earliestPendingDeadline = pending;
118
+ return () => {
119
+ if (!pending.active)
120
+ return;
121
+ pending.active = false;
122
+ if (pending.previous === undefined)
123
+ accounting.pendingDeadlines = pending.next;
124
+ else
125
+ pending.previous.next = pending.next;
126
+ if (pending.next !== undefined)
127
+ pending.next.previous = pending.previous;
128
+ pending.previous = pending.next = undefined;
129
+ pending.onExceeded = undefined;
130
+ if (accounting.earliestPendingDeadline !== pending)
131
+ return;
132
+ accounting.earliestPendingDeadline = undefined;
133
+ for (let next = accounting.pendingDeadlines; next !== undefined; next = next.next)
134
+ if (accounting.earliestPendingDeadline === undefined ||
135
+ next.deadline < accounting.earliestPendingDeadline.deadline)
136
+ accounting.earliestPendingDeadline = next;
137
+ };
138
+ }
139
+ forkRealm() {
140
+ // Realm-indexed caches use the view identity; all limits and usage stay shared.
141
+ const view = new Budget();
142
+ view.accounting = this.accounting;
143
+ view.realmView = true;
144
+ const views = this.accounting.realmViews ??= new Set([new WeakRef(this)]);
145
+ views.add(new WeakRef(view));
146
+ return view;
147
+ }
148
+ acquireRealmOwner() {
149
+ // Only explicit realm views share a live compile owner; each view still
150
+ // owns distinct realm-indexed caches and at most one live realm.
151
+ if (this.realmLease)
152
+ throw new SandboxError("reentry");
153
+ const lease = this.acquireCompileOwner(!this.realmView, this.realmView ? this.accounting.activeCompileOwner : undefined);
154
+ this.realmLease = true;
155
+ let released = false;
156
+ return {
157
+ owner: lease.owner,
158
+ release: () => {
159
+ if (released)
160
+ return;
161
+ released = true;
162
+ this.realmLease = false;
163
+ lease.release();
164
+ },
165
+ };
166
+ }
167
+ visitNode(units = 1) {
168
+ if (!Number.isSafeInteger(units) || units < 0) {
169
+ throw new Error("units must be a non-negative safe integer.");
170
+ }
171
+ if (units === 0)
172
+ return;
173
+ this.accounting.stepsUsed += units;
174
+ this.checkSampledDeadline(units);
175
+ if (this.accounting.allChecksSuspended === 0 &&
176
+ this.accounting.limits.maxSteps !== undefined &&
177
+ this.accounting.stepsUsed > this.accounting.limits.maxSteps) {
178
+ throw new SandboxError({
179
+ budget: "steps",
180
+ current: this.accounting.stepsUsed,
181
+ limit: this.accounting.limits.maxSteps
182
+ });
183
+ }
184
+ }
185
+ allocateString(value) {
186
+ this.allocateStringLength(value.length);
187
+ return value;
188
+ }
189
+ allocateStringLength(length) {
190
+ if (this.accounting.allChecksSuspended === 0 &&
191
+ this.accounting.limits.stringLength !== undefined &&
192
+ length > this.accounting.limits.stringLength) {
193
+ throw new SandboxError({
194
+ budget: "stringLength",
195
+ current: length,
196
+ limit: this.accounting.limits.stringLength
197
+ });
198
+ }
199
+ }
200
+ reserveString(length) {
201
+ if (!Number.isSafeInteger(length) || length < 0) {
202
+ throw new RangeError("string length must be a nonnegative safe integer");
203
+ }
204
+ if (this.accounting.allChecksSuspended === 0 && this.accounting.limits.stringLength !== undefined
205
+ && length > this.accounting.limits.stringLength) {
206
+ throw new SandboxError({ budget: "stringLength", current: length, limit: this.accounting.limits.stringLength });
207
+ }
208
+ return this.provisionDataUsage(length);
209
+ }
210
+ allocateArrayLength(length) {
211
+ if (this.accounting.allChecksSuspended === 0 &&
212
+ this.accounting.limits.arrayLength !== undefined &&
213
+ length > this.accounting.limits.arrayLength) {
214
+ throw new SandboxError({
215
+ budget: "arrayLength",
216
+ current: length,
217
+ limit: this.accounting.limits.arrayLength
218
+ });
219
+ }
220
+ }
221
+ allocateCollectionEntries(count) {
222
+ this.allocateArrayLength(count);
223
+ }
224
+ reconcileDataUsage(usage) {
225
+ const measured = usage + this.accounting.retainedDataSize;
226
+ const total = this.reconciliationDeferred ? Math.max(measured, this.accounting.currentDataSize) : measured;
227
+ this.checkDataUsage(total);
228
+ this.accounting.currentDataSize = total;
229
+ this.accounting.peakDataSize = Math.max(this.accounting.peakDataSize, total);
230
+ }
231
+ setRetainedDataUsage(owner, usage) {
232
+ if (!Number.isSafeInteger(usage) || usage < 0) {
233
+ throw new TypeError("Retained data usage must be a non-negative safe integer.");
234
+ }
235
+ const delta = usage - (this.accounting.retainedData.get(owner) ?? 0);
236
+ const total = this.accounting.currentDataSize + delta;
237
+ this.checkDataUsage(total);
238
+ if (usage === 0)
239
+ this.accounting.retainedData.delete(owner);
240
+ else
241
+ this.accounting.retainedData.set(owner, usage);
242
+ this.accounting.retainedDataSize += delta;
243
+ this.accounting.currentDataSize = total;
244
+ this.accounting.peakDataSize = Math.max(this.accounting.peakDataSize, total);
245
+ }
246
+ setRetainedValues(owner, values) {
247
+ if (values === undefined)
248
+ this.accounting.retainedValueSources.delete(owner);
249
+ else
250
+ this.accounting.retainedValueSources.set(owner, values);
251
+ }
252
+ get reconciliationDeferred() {
253
+ return this.accounting.reconciliationHolds > 0;
254
+ }
255
+ deferReconciliation() {
256
+ this.accounting.reconciliationHolds++;
257
+ const generation = this.accounting.compileGeneration;
258
+ let released = false;
259
+ return () => {
260
+ if (released)
261
+ return;
262
+ released = true;
263
+ if (generation !== this.accounting.compileGeneration)
264
+ return;
265
+ if (--this.accounting.reconciliationHolds !== 0)
266
+ return;
267
+ for (const ticket of this.accounting.deferredCompileDiscards)
268
+ this.accounting.completedCompileTickets.add(ticket);
269
+ this.accounting.deferredCompileDiscards.clear();
270
+ };
271
+ }
272
+ *retainedValues() {
273
+ for (const values of this.accounting.retainedValueSources.values()) {
274
+ const retained = values();
275
+ if (retained !== undefined)
276
+ yield* retained;
277
+ }
278
+ }
279
+ acquireCompileOwner(reset = false, owner) {
280
+ if ((owner !== undefined &&
281
+ (owner.budget.accounting !== this.accounting || owner.generation !== this.accounting.compileGeneration)) ||
282
+ (this.accounting.activeCompileOwner !== undefined && this.accounting.activeCompileOwner !== owner) ||
283
+ (reset && (this.accounting.compileUses !== 0 || owner !== undefined))) {
284
+ throw new SandboxError("reentry");
285
+ }
286
+ if (reset)
287
+ this.reset();
288
+ const selected = owner ?? (this.accounting.defaultCompileOwner ??= Object.freeze({ budget: this, generation: this.accounting.compileGeneration }));
289
+ this.accounting.activeCompileOwner = selected;
290
+ this.accounting.compileUses += 1;
291
+ let released = false;
292
+ return {
293
+ owner: selected,
294
+ release: () => {
295
+ if (released)
296
+ return;
297
+ released = true;
298
+ this.accounting.compileUses -= 1;
299
+ if (this.accounting.compileUses === 0)
300
+ this.accounting.activeCompileOwner = undefined;
301
+ }
302
+ };
303
+ }
304
+ createCompileTicket(owner) {
305
+ if (owner.budget.accounting !== this.accounting ||
306
+ owner.generation !== this.accounting.compileGeneration ||
307
+ this.accounting.activeCompileOwner !== owner) {
308
+ throw new SandboxError("reentry");
309
+ }
310
+ const ticket = Object.freeze({ owner });
311
+ this.accounting.compileTickets.set(ticket, 0);
312
+ return ticket;
313
+ }
314
+ compileTicketUsage(ticket) {
315
+ return ticket.owner.generation === this.accounting.compileGeneration
316
+ ? (this.accounting.compileTickets.get(ticket) ?? 0)
317
+ : 0;
318
+ }
319
+ resizeCompileTicket(ticket, usage) {
320
+ if (ticket.owner.generation !== this.accounting.compileGeneration || !this.accounting.compileTickets.has(ticket)) {
321
+ throw new SandboxError("reentry");
322
+ }
323
+ if (this.reconciliationDeferred)
324
+ usage = Math.max(usage, this.compileTicketUsage(ticket));
325
+ this.setRetainedDataUsage(ticket, usage);
326
+ this.accounting.compileTickets.set(ticket, usage);
327
+ }
328
+ discardCompileTicket(ticket) {
329
+ if (ticket.owner.generation !== this.accounting.compileGeneration)
330
+ return;
331
+ const usage = this.accounting.compileTickets.get(ticket);
332
+ if (usage === undefined)
333
+ return;
334
+ if (this.reconciliationDeferred) {
335
+ this.accounting.deferredCompileDiscards.add(ticket);
336
+ return;
337
+ }
338
+ this.accounting.compileTickets.delete(ticket);
339
+ this.accounting.completedCompileTickets.delete(ticket);
340
+ this.accounting.retainedData.delete(ticket);
341
+ this.accounting.retainedDataSize -= usage;
342
+ this.accounting.currentDataSize -= usage;
343
+ }
344
+ reconcileCompileData(usage, included, transferred = included, retainedOwner, complete = false) {
345
+ if (this.reconciliationDeferred) {
346
+ if (retainedOwner !== undefined)
347
+ this.setRetainedDataUsage(retainedOwner, usage);
348
+ else {
349
+ // Keep tickets owned while another job can still use them. Their bytes
350
+ // already appear in the measured graph, so do not charge them twice.
351
+ let includedUsage = 0;
352
+ for (const ticket of included)
353
+ includedUsage += this.compileTicketUsage(ticket);
354
+ const measured = usage + this.accounting.retainedDataSize - includedUsage;
355
+ const total = Math.max(measured, this.accounting.currentDataSize);
356
+ this.checkDataUsage(total);
357
+ this.accounting.currentDataSize = total;
358
+ this.accounting.peakDataSize = Math.max(this.accounting.peakDataSize, total);
359
+ }
360
+ return new Set();
361
+ }
362
+ let includedUsage = 0;
363
+ let transferredUsage = 0;
364
+ let discardedUsage = 0;
365
+ const releasing = [];
366
+ const retained = new Set();
367
+ for (const ticket of included) {
368
+ const charge = this.compileTicketUsage(ticket);
369
+ includedUsage += charge;
370
+ if (charge > 0 &&
371
+ transferred.has(ticket) &&
372
+ (this.accounting.provisionalScopes === 0 || retainedOwner !== undefined)) {
373
+ releasing.push(ticket);
374
+ transferredUsage += charge;
375
+ }
376
+ else if (charge > 0 && complete && transferred.has(ticket)) {
377
+ retained.add(ticket);
378
+ }
379
+ }
380
+ if (retainedOwner === undefined) {
381
+ for (const ticket of this.accounting.completedCompileTickets) {
382
+ if (included.has(ticket))
383
+ continue;
384
+ releasing.push(ticket);
385
+ discardedUsage += this.compileTicketUsage(ticket);
386
+ }
387
+ }
388
+ const oldOwnerUsage = retainedOwner === undefined ? 0 : (this.accounting.retainedData.get(retainedOwner) ?? 0);
389
+ const nextRetained = this.accounting.retainedDataSize -
390
+ transferredUsage -
391
+ discardedUsage +
392
+ (retainedOwner === undefined ? 0 : usage - oldOwnerUsage);
393
+ const measured = retainedOwner === undefined
394
+ ? usage - includedUsage + transferredUsage
395
+ : this.accounting.currentDataSize - this.accounting.retainedDataSize;
396
+ const total = measured + nextRetained;
397
+ this.checkDataUsage(total);
398
+ for (const ticket of releasing) {
399
+ this.accounting.compileTickets.delete(ticket);
400
+ this.accounting.completedCompileTickets.delete(ticket);
401
+ this.accounting.retainedData.delete(ticket);
402
+ }
403
+ if (retainedOwner !== undefined) {
404
+ if (usage === 0)
405
+ this.accounting.retainedData.delete(retainedOwner);
406
+ else
407
+ this.accounting.retainedData.set(retainedOwner, usage);
408
+ }
409
+ this.accounting.retainedDataSize = nextRetained;
410
+ this.accounting.currentDataSize = total;
411
+ this.accounting.peakDataSize = Math.max(this.accounting.peakDataSize, total);
412
+ for (const ticket of retained)
413
+ this.accounting.completedCompileTickets.add(ticket);
414
+ return retained;
415
+ }
416
+ chargeDataUsage(usage) {
417
+ const total = this.accounting.currentDataSize + usage;
418
+ this.checkDataUsage(total);
419
+ this.accounting.currentDataSize = total;
420
+ this.accounting.peakDataSize = Math.max(this.accounting.peakDataSize, total);
421
+ }
422
+ provisionDataUsage(usage) {
423
+ const previous = this.accounting.currentDataSize;
424
+ const previousRetained = this.accounting.retainedDataSize;
425
+ const next = previous + usage;
426
+ this.checkDataUsage(next);
427
+ this.accounting.currentDataSize = next;
428
+ this.accounting.peakDataSize = Math.max(this.accounting.peakDataSize, next);
429
+ const generation = this.accounting.compileGeneration;
430
+ this.accounting.provisionalScopes += 1;
431
+ let released = false;
432
+ return (commit = false) => {
433
+ if (released)
434
+ return;
435
+ released = true;
436
+ if (generation !== this.accounting.compileGeneration)
437
+ return;
438
+ this.accounting.provisionalScopes -= 1;
439
+ if (!commit) {
440
+ const restored = previous + this.accounting.retainedDataSize - previousRetained;
441
+ this.accounting.currentDataSize = this.reconciliationDeferred
442
+ ? Math.max(restored, this.accounting.currentDataSize) : restored;
443
+ }
444
+ };
445
+ }
446
+ enterCall() {
447
+ return this.enterDepth();
448
+ }
449
+ enterAwait() {
450
+ return this.enterDepth();
451
+ }
452
+ reset() {
453
+ if (this.accounting.compileUses !== 0)
454
+ throw new SandboxError("reentry");
455
+ releaseTemplateObjects(this);
456
+ for (const reference of this.accounting.realmViews ?? []) {
457
+ const view = reference.deref();
458
+ if (view === undefined)
459
+ this.accounting.realmViews.delete(reference);
460
+ else if (view !== this)
461
+ releaseTemplateObjects(view);
462
+ }
463
+ this.accounting.compileGeneration += 1;
464
+ this.accounting.defaultCompileOwner = undefined;
465
+ this.accounting.provisionalScopes = 0;
466
+ this.accounting.compileTickets.clear();
467
+ this.accounting.completedCompileTickets.clear();
468
+ this.accounting.reconciliationHolds = 0;
469
+ this.accounting.deferredCompileDiscards.clear();
470
+ this.accounting.stepsUsed = 0;
471
+ this.accounting.peakCallDepth = 0;
472
+ this.accounting.currentCallDepth = 0;
473
+ this.accounting.currentDataSize = 0;
474
+ this.accounting.peakDataSize = 0;
475
+ this.accounting.retainedDataSize = 0;
476
+ this.accounting.retainedData.clear();
477
+ this.accounting.retainedValueSources.clear();
478
+ this.accounting.allChecksSuspended = 0;
479
+ this.accounting.deadlineChecksSuspended = 0;
480
+ this.accounting.visitsUntilDeadlineCheck = DEADLINE_CHECK_INTERVAL;
481
+ }
482
+ suspendChecks() {
483
+ this.accounting.allChecksSuspended += 1;
484
+ let resumed = false;
485
+ return () => {
486
+ if (resumed) {
487
+ return;
488
+ }
489
+ resumed = true;
490
+ this.accounting.allChecksSuspended -= 1;
491
+ };
492
+ }
493
+ suspendDeadlineChecks() {
494
+ this.accounting.deadlineChecksSuspended += 1;
495
+ let resumed = false;
496
+ return () => {
497
+ if (resumed) {
498
+ return;
499
+ }
500
+ resumed = true;
501
+ this.accounting.deadlineChecksSuspended -= 1;
502
+ };
503
+ }
504
+ checkDeadline() {
505
+ const pending = this.accounting.earliestPendingDeadline;
506
+ const deadline = this.accounting.deadline === undefined ? pending?.deadline
507
+ : Math.min(this.accounting.deadline, pending?.deadline ?? Infinity);
508
+ if (this.accounting.allChecksSuspended > 0 ||
509
+ this.accounting.deadlineChecksSuspended > 0 ||
510
+ deadline === undefined) {
511
+ return;
512
+ }
513
+ const now = Date.now();
514
+ if (now <= deadline) {
515
+ return;
516
+ }
517
+ const reason = new SandboxError({
518
+ budget: "deadline",
519
+ current: now,
520
+ limit: deadline
521
+ });
522
+ if (pending?.deadline === deadline) {
523
+ const notify = pending.onExceeded;
524
+ pending.onExceeded = undefined;
525
+ notify?.(reason);
526
+ }
527
+ throw reason;
528
+ }
529
+ checkDataUsage(usage) {
530
+ if (this.accounting.allChecksSuspended === 0 &&
531
+ this.accounting.limits.dataSize !== undefined &&
532
+ usage > this.accounting.limits.dataSize) {
533
+ throw new SandboxError({
534
+ budget: "dataSize",
535
+ current: usage,
536
+ limit: this.accounting.limits.dataSize
537
+ });
538
+ }
539
+ }
540
+ checkSampledDeadline(units) {
541
+ if (this.accounting.allChecksSuspended > 0 ||
542
+ this.accounting.deadlineChecksSuspended > 0 ||
543
+ (this.accounting.deadline === undefined && this.accounting.earliestPendingDeadline === undefined)) {
544
+ return;
545
+ }
546
+ this.accounting.visitsUntilDeadlineCheck -= units;
547
+ if (this.accounting.visitsUntilDeadlineCheck > 0) {
548
+ return;
549
+ }
550
+ this.accounting.visitsUntilDeadlineCheck =
551
+ DEADLINE_CHECK_INTERVAL - (-this.accounting.visitsUntilDeadlineCheck % DEADLINE_CHECK_INTERVAL);
552
+ this.checkDeadline();
553
+ }
554
+ enterDepth() {
555
+ const nextDepth = this.accounting.currentCallDepth + 1;
556
+ if (this.accounting.allChecksSuspended === 0 &&
557
+ this.accounting.limits.maxCallDepth !== undefined &&
558
+ nextDepth > this.accounting.limits.maxCallDepth) {
559
+ throw new SandboxError({
560
+ budget: "callDepth",
561
+ current: nextDepth,
562
+ limit: this.accounting.limits.maxCallDepth
563
+ });
564
+ }
565
+ this.accounting.currentCallDepth = nextDepth;
566
+ if (nextDepth > this.accounting.peakCallDepth) {
567
+ this.accounting.peakCallDepth = nextDepth;
568
+ }
569
+ let left = false;
570
+ return () => {
571
+ if (left) {
572
+ return;
573
+ }
574
+ left = true;
575
+ this.accounting.currentCallDepth -= 1;
576
+ };
577
+ }
578
+ }
579
+ function normalizeDeadline(deadline) {
580
+ if (deadline === undefined || deadline === Infinity) {
581
+ return undefined;
582
+ }
583
+ return deadline instanceof Date ? deadline.getTime() : deadline;
584
+ }
585
+ function normalizeLimit(name, value) {
586
+ if (value === undefined || value === Infinity) {
587
+ return undefined;
588
+ }
589
+ if (!Number.isInteger(value) || value < 0) {
590
+ throw new Error(`${name} must be a non-negative integer.`);
591
+ }
592
+ return value;
593
+ }
594
+ function normalizeRegexLimit(name, value) {
595
+ if (value === undefined || value === Infinity)
596
+ return undefined;
597
+ if (!Number.isSafeInteger(value) || value < 1)
598
+ throw new RangeError(`${name} must be a positive safe integer.`);
599
+ return value;
600
+ }
@@ -0,0 +1 @@
1
+ export const internalSymbols = new Set();
@@ -1,4 +1,4 @@
1
- import { AsyncLocalStorage } from "node:async_hooks";
1
+ import { AsyncLocalStorage } from "#safe-js-platform";
2
2
  import { type Budget } from "./budget.js";
3
3
  type ReplayEvent = {
4
4
  kind: "promise";
@@ -0,0 +1,7 @@
1
+ export const templateRealms = new WeakMap();
2
+ export function releaseTemplateObjects(budget) {
3
+ const realm = templateRealms.get(budget);
4
+ if (realm !== undefined)
5
+ budget.setRetainedValues(realm, undefined);
6
+ templateRealms.delete(budget);
7
+ }
@@ -1,4 +1,6 @@
1
+ import { type FileSystem } from "@poe-platform/safe-fs/core";
1
2
  export type SnapshotMigrationFileOptions = {
3
+ adapter?: FileSystem;
2
4
  snapshotPath: string;
3
5
  sourcePath: string;
4
6
  targetSourcePath?: string;
@@ -4,9 +4,11 @@ export type FsConfig = {
4
4
  readonly adapter: FileSystemConfig;
5
5
  readonly root?: string;
6
6
  readonly cwd?: string;
7
+ readonly readFileMaxBytes?: number;
8
+ readonly hostReadMemoryLimit?: number;
7
9
  };
8
10
  export type ResolveFsConfigOptions = {
9
11
  readonly registry?: FileSystemAdapterRegistry;
10
12
  };
11
13
  export declare function parseFsConfig(json: string): FsConfig;
12
- export declare function resolveFsConfig(config: FsConfig, options?: ResolveFsConfigOptions): Promise<Required<Pick<FsModuleOptions, "adapter">> & Pick<FsModuleOptions, "root" | "cwd">>;
14
+ export declare function resolveFsConfig(config: FsConfig, options?: ResolveFsConfigOptions): Promise<Required<Pick<FsModuleOptions, "adapter">> & Omit<FsConfig, "adapter">>;
@@ -1,5 +1,6 @@
1
- import { type PathLike } from "node:fs";
2
- import * as nodeFsPromises from "node:fs/promises";
1
+ import { hostFs as nodeFsPromises } from "#safe-js-platform";
2
+ import type { FsBridgeEncoding } from "@poe-platform/safe-fs/core";
3
+ type PathLike = string | Uint8Array | URL;
3
4
  import { type FileSystem } from "@poe-platform/safe-fs/core";
4
5
  type FsOperationName = "access" | "appendFile" | "chmod" | "copyFile" | "cp" | "link" | "lstat" | "mkdir" | "mkdtemp" | "readFile" | "readdir" | "readlink" | "realpath" | "rename" | "rm" | "rmdir" | "stat" | "symlink" | "truncate" | "utimes" | "writeFile";
5
6
  type FsPassthroughName = Exclude<FsOperationName, "lstat" | "mkdtemp" | "readFile" | "readdir" | "readlink" | "realpath" | "stat">;
@@ -27,7 +28,7 @@ export type SandboxDirent = {
27
28
  readonly name: string;
28
29
  readonly parentPath: string;
29
30
  } & FileTypePredicates;
30
- type StringEncoding = NodeJS.BufferEncoding;
31
+ type StringEncoding = FsBridgeEncoding;
31
32
  type EncodingOptions = StringEncoding | {
32
33
  encoding: StringEncoding;
33
34
  };
@@ -43,6 +44,9 @@ type StatOptions = {
43
44
  bigint?: false;
44
45
  };
45
46
  export type FsModuleOptions = {
47
+ hostReadMemoryLimit?: number;
48
+ readFileMaxBytes?: number;
49
+ } & ({
46
50
  root?: string;
47
51
  fs?: FsImplementation;
48
52
  adapter?: never;
@@ -54,7 +58,7 @@ export type FsModuleOptions = {
54
58
  cwd?: string;
55
59
  signal?: AbortSignal;
56
60
  fs?: never;
57
- };
61
+ });
58
62
  export type FsModule = Pick<FsImplementation, FsPassthroughName> & {
59
63
  readFile(path: PathLike, options: ReadFileOptions): Promise<string>;
60
64
  readlink(path: PathLike, options?: EncodingOptions): Promise<string>;