@silverbulletmd/silverbullet 2.10.0 → 2.12.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.
Files changed (105) hide show
  1. package/README.md +6 -6
  2. package/client/asset_bundle/bundle.ts +0 -1
  3. package/client/config.ts +343 -0
  4. package/client/data/kv_primitives.ts +2 -2
  5. package/client/markdown_parser/constants.ts +5 -1
  6. package/client/plugos/hooks/command.ts +8 -6
  7. package/client/plugos/hooks/event.ts +60 -24
  8. package/client/plugos/hooks/mq.ts +0 -3
  9. package/client/plugos/hooks/slash_command.ts +3 -20
  10. package/client/plugos/hooks/syscall.ts +0 -4
  11. package/client/plugos/manifest_cache.ts +2 -34
  12. package/client/plugos/plug.ts +0 -3
  13. package/client/plugos/plug_compile.ts +3 -13
  14. package/client/plugos/sandboxes/sandbox.ts +2 -2
  15. package/client/plugos/sandboxes/worker_sandbox.ts +19 -9
  16. package/client/plugos/syscalls/capture.ts +104 -0
  17. package/client/plugos/syscalls/editor.ts +197 -56
  18. package/client/plugos/syscalls/event.ts +12 -9
  19. package/client/plugos/syscalls/fetch.ts +0 -4
  20. package/client/plugos/syscalls/icon.ts +46 -0
  21. package/client/plugos/syscalls/index.ts +33 -13
  22. package/client/plugos/syscalls/jsonschema.ts +0 -1
  23. package/client/plugos/syscalls/mq.ts +0 -1
  24. package/client/plugos/syscalls/navigator.ts +138 -0
  25. package/client/plugos/syscalls/search.ts +44 -0
  26. package/client/plugos/syscalls/shell.ts +0 -1
  27. package/client/plugos/syscalls/space.ts +250 -7
  28. package/client/plugos/syscalls/sync.ts +70 -4
  29. package/client/plugos/syscalls/system.ts +39 -0
  30. package/client/plugos/system.ts +9 -7
  31. package/client/plugos/types.ts +6 -6
  32. package/client/plugos/worker_runtime.ts +0 -2
  33. package/client/space_lua/aggregates.ts +6 -22
  34. package/client/space_lua/ast.ts +0 -5
  35. package/client/space_lua/ast_narrow.ts +0 -5
  36. package/client/space_lua/budget.ts +113 -0
  37. package/client/space_lua/budget_ui.ts +49 -0
  38. package/client/space_lua/eval.ts +113 -88
  39. package/client/space_lua/labels.ts +0 -4
  40. package/client/space_lua/numeric.ts +2 -7
  41. package/client/space_lua/parse.ts +0 -17
  42. package/client/space_lua/quarantine.ts +127 -0
  43. package/client/space_lua/query_collection.ts +18 -51
  44. package/client/space_lua/query_env.ts +0 -2
  45. package/client/space_lua/render_lua_markdown.ts +2 -7
  46. package/client/space_lua/render_widget.ts +18 -1
  47. package/client/space_lua/runtime.ts +64 -77
  48. package/client/space_lua/stdlib/format.ts +0 -12
  49. package/client/space_lua/stdlib/js.ts +0 -2
  50. package/client/space_lua/stdlib/load.ts +0 -1
  51. package/client/space_lua/stdlib/math.ts +0 -7
  52. package/client/space_lua/stdlib/net.ts +0 -4
  53. package/client/space_lua/stdlib/os.ts +0 -10
  54. package/client/space_lua/stdlib/pattern.ts +18 -23
  55. package/client/space_lua/stdlib/space_lua.ts +1 -2
  56. package/client/space_lua/stdlib/string_pack.ts +4 -7
  57. package/client/space_lua/stdlib/table.ts +0 -5
  58. package/client/space_lua/stdlib.ts +18 -31
  59. package/client/space_lua/tonumber.ts +0 -9
  60. package/dist/plug-compile.js +3 -4
  61. package/package.json +52 -41
  62. package/plug-api/lib/async.ts +1 -6
  63. package/plug-api/lib/collation.ts +22 -0
  64. package/plug-api/lib/crypto.ts +16 -15
  65. package/plug-api/lib/dates.ts +33 -0
  66. package/plug-api/lib/fuzzy.ts +282 -0
  67. package/plug-api/lib/json.ts +0 -6
  68. package/plug-api/lib/limited_map.ts +0 -2
  69. package/plug-api/lib/link_write.ts +27 -0
  70. package/plug-api/lib/ref.ts +28 -5
  71. package/plug-api/lib/resolve.ts +1 -9
  72. package/plug-api/lib/resolve_path.ts +290 -0
  73. package/plug-api/lib/shortcut.ts +15 -0
  74. package/plug-api/lib/tags.ts +0 -3
  75. package/plug-api/lib/transclusion.ts +28 -1
  76. package/plug-api/lib/tree.ts +0 -5
  77. package/plug-api/lib/yaml.ts +5 -44
  78. package/plug-api/syscall.ts +1 -5
  79. package/plug-api/syscalls/config.ts +2 -2
  80. package/plug-api/syscalls/editor.ts +75 -213
  81. package/plug-api/syscalls/icon.ts +18 -0
  82. package/plug-api/syscalls/index.ts +22 -4
  83. package/plug-api/syscalls/search.ts +11 -0
  84. package/plug-api/syscalls/space.ts +122 -5
  85. package/plug-api/syscalls/sync.ts +31 -2
  86. package/plug-api/syscalls/system.ts +46 -0
  87. package/plug-api/syscalls.ts +2 -0
  88. package/plug-api/system_mock.ts +13 -0
  89. package/plug-api/types/client.ts +20 -4
  90. package/plug-api/types/datastore.ts +11 -2
  91. package/plug-api/types/index.ts +10 -0
  92. package/plug-api/types/manifest.ts +7 -0
  93. package/plug-api/types/profile.ts +13 -0
  94. package/plug-api/types/revisions.ts +67 -0
  95. package/plug-api/ui/description.ts +64 -0
  96. package/plug-api/ui/hover.ts +69 -0
  97. package/plug-api/ui/index.ts +58 -2
  98. package/plug-api/ui/scroll.ts +30 -0
  99. package/plug-api/ui/tree_model.ts +285 -0
  100. package/plug-api/ui/tree_types.ts +67 -0
  101. package/plug-api/ui/use_fit_collapse.ts +67 -0
  102. package/plugs/builtin_plugs.ts +0 -1
  103. package/plugs/index/types.ts +9 -0
  104. package/plug-api/lib/memory_cache.ts +0 -21
  105. /package/plug-api/{ui → lib}/panel_styles.ts +0 -0
@@ -12,7 +12,6 @@ export const netApi = new LuaTable({
12
12
  url: string,
13
13
  options: ProxyFetchRequest = {},
14
14
  ): Promise<ProxyFetchResponse> => {
15
- // JSONify any non-serializable body
16
15
  if (
17
16
  options?.body &&
18
17
  typeof options.body !== "string" &&
@@ -40,7 +39,6 @@ export const netApi = new LuaTable({
40
39
  body: await resp.text(),
41
40
  };
42
41
  }
43
- // Do sensible things with the body based on the content type
44
42
  // Read as ArrayBuffer first to safely handle empty responses (e.g.
45
43
  // PUT/DELETE returning 204 with Content-Type: application/json).
46
44
  // resp.arrayBuffer() never throws on an empty body, whereas
@@ -138,10 +136,8 @@ export const netApi = new LuaTable({
138
136
  }),
139
137
  });
140
138
 
141
- // Utility functions
142
139
  function buildProxyUrl(client: Client, url: string) {
143
140
  url = url.replace(/^https?:\/\//, "");
144
- // Strip off the /.fs and replace with /.proxy
145
141
  return (
146
142
  client.httpSpacePrimitives.url.slice(0, -fsEndpoint.length) +
147
143
  "/.proxy/" +
@@ -147,7 +147,6 @@ function buildSpecMap(d: Date, utc: boolean): Record<string, () => string> {
147
147
  const dow = () => wd(d, utc);
148
148
 
149
149
  return {
150
- // Date
151
150
  Y: () => yr(d, utc).toString(),
152
151
  y: () => pad2(yr(d, utc) % 100),
153
152
  C: () => pad2(Math.floor(yr(d, utc) / 100)),
@@ -156,14 +155,12 @@ function buildSpecMap(d: Date, utc: boolean): Record<string, () => string> {
156
155
  e: () => da(d, utc).toString().padStart(2, " "),
157
156
  j: () => pad3(dayOfYear(d, utc)),
158
157
 
159
- // Time
160
158
  H: () => pad2(h()),
161
159
  I: () => pad2(h12()),
162
160
  M: () => pad2(mi(d, utc)),
163
161
  S: () => pad2(sc(d, utc)),
164
162
  p: () => (h() >= 12 ? "PM" : "AM"),
165
163
 
166
- // Weekday
167
164
  A: () =>
168
165
  d.toLocaleString("en-US", {
169
166
  weekday: "long",
@@ -177,7 +174,6 @@ function buildSpecMap(d: Date, utc: boolean): Record<string, () => string> {
177
174
  w: () => dow().toString(),
178
175
  u: () => (dow() === 0 ? 7 : dow()).toString(),
179
176
 
180
- // Month name
181
177
  b: () =>
182
178
  d.toLocaleString("en-US", {
183
179
  month: "short",
@@ -194,14 +190,12 @@ function buildSpecMap(d: Date, utc: boolean): Record<string, () => string> {
194
190
  ...(utc ? { timeZone: "UTC" } : {}),
195
191
  }),
196
192
 
197
- // Week number
198
193
  U: () => pad2(weekNumber(d, utc, 0, false)),
199
194
  W: () => pad2(weekNumber(d, utc, 1, false)),
200
195
  V: () => pad2(weekNumber(d, utc, 1, true)),
201
196
  G: () => isoWeekYear(d, utc).toString(),
202
197
  g: () => pad2(isoWeekYear(d, utc) % 100),
203
198
 
204
- // Composite specifiers
205
199
  c: () =>
206
200
  d.toLocaleString("en-US", {
207
201
  ...(utc ? { timeZone: "UTC" } : {}),
@@ -225,14 +219,11 @@ function buildSpecMap(d: Date, utc: boolean): Record<string, () => string> {
225
219
  h() >= 12 ? "PM" : "AM"
226
220
  }`,
227
221
 
228
- // Epoch
229
222
  s: () => Math.floor(d.getTime() / 1000).toString(),
230
223
 
231
- // Whitespace
232
224
  n: () => "\n",
233
225
  t: () => "\t",
234
226
 
235
- // Timezone
236
227
  Z: () => {
237
228
  if (utc) return "UTC";
238
229
  const match = d.toTimeString().match(/\((.*)\)/);
@@ -246,7 +237,6 @@ function buildSpecMap(d: Date, utc: boolean): Record<string, () => string> {
246
237
  return `${sign}${pad2(Math.floor(abs / 60))}${pad2(abs % 60)}`;
247
238
  },
248
239
 
249
- // Literal
250
240
  "%": () => "%",
251
241
  };
252
242
  }
@@ -1,5 +1,3 @@
1
- // Lua Optimized Pattern Matching Engine Implementation
2
-
3
1
  const CH_ESC = 37; // '%'
4
2
  const CH_OPEN = 40; // '('
5
3
  const CH_CLOSE = 41; // ')'
@@ -34,27 +32,27 @@ const MAX_MATCH_DEPTH = 200;
34
32
  const CAP_UNFINISHED = -1;
35
33
  const CAP_POSITION = -2;
36
34
 
37
- interface Capture {
35
+ type Capture = {
38
36
  init: number;
39
37
  len: number;
40
- }
38
+ };
41
39
 
42
- export interface MatchState {
43
- src: string; // original source string (for substring extraction)
44
- s: Uint8Array; // source bytes
40
+ export type MatchState = {
41
+ src: string;
42
+ s: Uint16Array;
45
43
  slen: number;
46
- p: Uint8Array; // pattern bytes
44
+ p: Uint16Array;
47
45
  plen: number;
48
46
  level: number;
49
47
  capture: Capture[]; // pre-allocated, length `MAX_CAPTURES`
50
48
  matchdepth: number;
51
- }
49
+ };
52
50
 
53
- function toBytes(s: string): Uint8Array {
51
+ function toCodeUnits(s: string): Uint16Array {
54
52
  const len = s.length;
55
- const arr = new Uint8Array(len);
53
+ const arr = new Uint16Array(len);
56
54
  for (let i = 0; i < len; i++) {
57
- arr[i] = s.charCodeAt(i) & 0xff;
55
+ arr[i] = s.charCodeAt(i);
58
56
  }
59
57
  return arr;
60
58
  }
@@ -108,7 +106,7 @@ function matchClass(c: number, cl: number): boolean {
108
106
  return cl >= 97 && cl <= 122 ? res : !res;
109
107
  }
110
108
 
111
- function classEnd(p: Uint8Array, plen: number, pi: number): number {
109
+ function classEnd(p: Uint16Array, plen: number, pi: number): number {
112
110
  const ch = p[pi];
113
111
  pi++;
114
112
  if (ch === CH_ESC) {
@@ -138,7 +136,7 @@ function classEnd(p: Uint8Array, plen: number, pi: number): number {
138
136
 
139
137
  function matchBracketClass(
140
138
  c: number,
141
- p: Uint8Array,
139
+ p: Uint16Array,
142
140
  pi: number,
143
141
  ec: number,
144
142
  ): boolean {
@@ -404,8 +402,8 @@ function match(ms: MatchState, si: number, pi: number): number {
404
402
  }
405
403
 
406
404
  function createMatchState(s: string, p: string): MatchState {
407
- const sb = toBytes(s);
408
- const pb = toBytes(p);
405
+ const sb = toCodeUnits(s);
406
+ const pb = toCodeUnits(p);
409
407
  const capture: Capture[] = new Array(MAX_CAPTURES);
410
408
  for (let i = 0; i < MAX_CAPTURES; i++) {
411
409
  capture[i] = { init: 0, len: 0 };
@@ -434,15 +432,13 @@ function noSpecials(p: string): boolean {
434
432
  return true;
435
433
  }
436
434
 
437
- // Public API
438
-
439
435
  export type CaptureResult = { s: string } | { position: number };
440
436
 
441
- interface RawCapture {
437
+ type RawCapture = {
442
438
  kind: 0 | 1; // 0 = string slice, 1 = position
443
439
  start: number;
444
440
  len: number; // kind = 0: substring length; kind = 1: 1-based position
445
- }
441
+ };
446
442
 
447
443
  function getOneRawCapture(
448
444
  ms: MatchState,
@@ -615,7 +611,6 @@ function expandReplacementString(
615
611
  throw new Error("invalid use of '%' in replacement string");
616
612
  }
617
613
  } else {
618
- // Collect consecutive literal characters
619
614
  let j = i + 1;
620
615
  while (j < repl.length && repl.charCodeAt(j) !== CH_ESC) j++;
621
616
  parts.push(repl.substring(i, j));
@@ -627,13 +622,13 @@ function expandReplacementString(
627
622
  return parts.join("");
628
623
  }
629
624
 
630
- export interface GsubCallbacks {
625
+ export type GsubCallbacks = {
631
626
  replString?: string;
632
627
  replFunction?: (
633
628
  ...captures: CaptureResult[]
634
629
  ) => Promise<string | null | undefined> | string | null | undefined;
635
630
  replTable?: (key: string) => string | null | undefined;
636
- }
631
+ };
637
632
 
638
633
  export async function patternGsub(
639
634
  s: string,
@@ -180,7 +180,6 @@ export async function interpolateLuaString(
180
180
 
181
181
  result += template.slice(currentIndex, startIndex);
182
182
 
183
- // Find matching closing brace by counting nesting
184
183
  let nestLevel = 1;
185
184
  let endIndex = startIndex + 2;
186
185
  while (nestLevel > 0 && endIndex < template.length) {
@@ -519,7 +518,7 @@ export const spaceluaApi = new LuaTable({
519
518
  */
520
519
  baseUrl: new LuaBuiltinFunction({
521
520
  callback: () => {
522
- //NOTE: Removing trailing slash to stay compatible with original code: `location.protocol + "//" + location.host;`
521
+ // The origin contract excludes a trailing slash.
523
522
  return document.baseURI.replace(/\/*$/, "");
524
523
  },
525
524
  description:
@@ -28,16 +28,16 @@ type KOption =
28
28
  | "paddalign"
29
29
  | "nop";
30
30
 
31
- interface ParsedOption {
31
+ type ParsedOption = {
32
32
  opt: KOption;
33
33
  size: number; // byte width
34
34
  ntoalign: number; // padding bytes before this field
35
- }
35
+ };
36
36
 
37
- interface Header {
37
+ type Header = {
38
38
  islittle: boolean;
39
39
  maxalign: number;
40
- }
40
+ };
41
41
 
42
42
  function makeHeader(): Header {
43
43
  return { islittle: NATIVE_LITTLE, maxalign: NATIVE_MAXALIGN };
@@ -145,7 +145,6 @@ function getOption(
145
145
  }
146
146
  }
147
147
 
148
- // Compute alignment padding
149
148
  function getDetails(
150
149
  fmt: string,
151
150
  pos: number,
@@ -264,7 +263,6 @@ export const strPackFn = new LuaBuiltinFunction({
264
263
  let opt: ParsedOption;
265
264
  [opt, pos] = getDetails(fmt, pos, h, totalsize);
266
265
 
267
- // alignment padding
268
266
  if (opt.ntoalign > 0) {
269
267
  parts.push(new Uint8Array(opt.ntoalign));
270
268
  totalsize += opt.ntoalign;
@@ -349,7 +347,6 @@ export const strPackFn = new LuaBuiltinFunction({
349
347
  }
350
348
  }
351
349
 
352
- // Concatenate all parts into one binary string (latin-1 encoding)
353
350
  let total = 0;
354
351
  for (const p of parts) total += p.length;
355
352
  const out = new Uint8Array(total);
@@ -274,12 +274,10 @@ export const tableApi = new LuaTable({
274
274
  t: number,
275
275
  a2?: LuaTable | any[],
276
276
  ) => {
277
- // a2 defaults to a1
278
277
  if (a2 === undefined || a2 === null) {
279
278
  a2 = a1;
280
279
  }
281
280
 
282
- // Empty range: nothing to do, return destination
283
281
  if (e < f) {
284
282
  return a2;
285
283
  }
@@ -422,7 +420,6 @@ export const tableApi = new LuaTable({
422
420
  return false;
423
421
  }
424
422
  if (tbl instanceof LuaTable) {
425
- // Iterate over the table
426
423
  for (const key of tbl.keys()) {
427
424
  if (luaEquals(tbl.get(key), value)) {
428
425
  return true;
@@ -459,9 +456,7 @@ export const tableApi = new LuaTable({
459
456
  tbl: LuaTable | Record<string, any>,
460
457
  ...keys: LuaValue[]
461
458
  ) => {
462
- // Normalize arguments
463
459
  if (Array.isArray(keys[0])) {
464
- // First argument is key array, let's unpack
465
460
  keys = keys[0];
466
461
  } else if (keys[0] instanceof LuaTable) {
467
462
  keys = keys[0].toJSArray();
@@ -1,3 +1,4 @@
1
+ import { LuaBudgetStopped } from "./budget.ts";
1
2
  import {
2
3
  getMetatable,
3
4
  type ILuaFunction,
@@ -101,7 +102,6 @@ const ipairsFunction = new LuaBuiltinFunction({
101
102
 
102
103
  const pairsFunction = new LuaBuiltinFunction({
103
104
  callback: (sf, t: LuaTable | any[] | Record<string, any>) => {
104
- // Respect `__pairs` metamethod for Lua tables
105
105
  if (isLuaTable(t)) {
106
106
  const mt = (t as any).metatable as LuaTable | null | undefined;
107
107
  if (mt) {
@@ -295,27 +295,24 @@ async function pcallBoundary(
295
295
  const msg = errMsgOf(e);
296
296
  try {
297
297
  await luaCloseFromMark(sf, mark, msg);
298
- return { ok: false, message: msg };
299
298
  } catch (closeErr: any) {
300
- return { ok: false, message: errMsgOf(closeErr) };
299
+ if (!(e instanceof LuaBudgetStopped)) {
300
+ return { ok: false, message: errMsgOf(closeErr) };
301
+ }
302
+ }
303
+ // A user-initiated stop is not a recoverable Lua error.
304
+ if (e instanceof LuaBudgetStopped) {
305
+ throw e;
301
306
  }
307
+ return { ok: false, message: msg };
302
308
  }
303
309
  }
304
310
 
305
311
  const pcallFunction = new LuaBuiltinFunction({
306
312
  callback: async (sf, fn: ILuaFunction, ...args) => {
307
- // To-be-closed variables must be closed when unwinding to the
308
- // protected call boundary. Space Lua uses a per-thread close
309
- // stack, so we snapshot its length and close anything pushed
310
- // after that.
311
- //
312
- // The protected call boundary must be established *before*
313
- // evaluating the function and its arguments. Otherwise, any
314
- // `<close>` locals created while evaluating `pcall`'s arguments
315
- // will be wrongly treated as "inside" the protected call, and
316
- // `pcall` may end up closing them (or affecting close ordering).
317
- //
318
- // `threadState` is read-only on the stack frame; do not reassign!
313
+ // Snapshot the close stack before evaluating the function and arguments,
314
+ // so argument-owned <close> locals stay outside the protected boundary.
315
+ // Do not reassign the frame’s read-only threadState.
319
316
  const res = await pcallBoundary(sf, fn, args);
320
317
  if (res.ok) {
321
318
  return new LuaMultiRes([true, ...res.values]);
@@ -345,7 +342,6 @@ const xpcallFunction = new LuaBuiltinFunction({
345
342
  errorHandler: ILuaFunction,
346
343
  ...args
347
344
  ) => {
348
- // Same semantic as `pcall` (see comments there)
349
345
  const res = await pcallBoundary(sf, fn, args);
350
346
  if (res.ok) {
351
347
  return new LuaMultiRes([true, ...res.values]);
@@ -505,6 +501,12 @@ const dofileFunction = new LuaBuiltinFunction({
505
501
  const env = new LuaEnv(global);
506
502
  await evalStatement(parsedExpr, env, sf.withCtx(parsedExpr.ctx));
507
503
  } catch (e: any) {
504
+ // A user-initiated stop must keep its identity so an enclosing
505
+ // pcall/xpcall still recognizes and rethrows it, rather than being
506
+ // rewrapped into a recoverable LuaRuntimeError.
507
+ if (e instanceof LuaBudgetStopped) {
508
+ throw e;
509
+ }
508
510
  throw new LuaRuntimeError(
509
511
  `Error evaluating "${filename}": ${e.message}`,
510
512
  sf,
@@ -582,30 +584,24 @@ const nextFunction = new LuaBuiltinFunction({
582
584
  index: number | null = null,
583
585
  ) => {
584
586
  if (!table) {
585
- // When nil value
586
587
  return null;
587
588
  }
588
589
  const keys = luaKeys(table);
589
590
 
590
- // Empty table -> null return value
591
591
  if (keys.length === 0) {
592
592
  return null;
593
593
  }
594
594
 
595
595
  if (index === null) {
596
- // Return the first key, value
597
596
  const key = keys[0];
598
597
  return new LuaMultiRes([key, luaGet(table, key, sf.astCtx ?? null, sf)]);
599
598
  }
600
- // Find index in the key list
601
599
  const idx = keys.indexOf(index);
602
600
  if (idx === -1) {
603
- // Not found
604
601
  throw new LuaRuntimeError("invalid key to 'next': key not found", sf);
605
602
  }
606
603
  const key = keys[idx + 1];
607
604
  if (key === undefined) {
608
- // When called with the last key, should return nil
609
605
  return null;
610
606
  }
611
607
  return new LuaMultiRes([key, luaGet(table, key, sf.astCtx ?? null, sf)]);
@@ -622,7 +618,6 @@ const nextFunction = new LuaBuiltinFunction({
622
618
  ],
623
619
  });
624
620
 
625
- // Non-standard, but useful
626
621
  const someFunction = new LuaBuiltinFunction({
627
622
  callback: async (_sf, value: any) => {
628
623
  switch (await luaTypeOf(value)) {
@@ -691,13 +686,11 @@ function annotateBuiltinApi(
691
686
 
692
687
  export function luaBuildStandardEnv() {
693
688
  const env = new LuaEnv();
694
- // _G global
695
689
  env.set("_G", env);
696
690
  // Lua version string - for now it signals Lua 5.4 compatibility with
697
691
  // selective 5.5 features; kept non-standard so callers can distinguish
698
692
  // Space Lua from a plain Lua runtime.
699
693
  env.set("_VERSION", "Lua 5.4+");
700
- // Top-level builtins
701
694
  env.set("print", printFunction);
702
695
  env.set("assert", assertFunction);
703
696
  env.set("type", typeFunction);
@@ -705,10 +698,8 @@ export function luaBuildStandardEnv() {
705
698
  env.set("tonumber", tonumberFunction);
706
699
  env.set("select", selectFunction);
707
700
  env.set("next", nextFunction);
708
- // Iterators
709
701
  env.set("pairs", pairsFunction);
710
702
  env.set("ipairs", ipairsFunction);
711
- // meta table stuff
712
703
  env.set("setmetatable", setmetatableFunction);
713
704
  env.set("getmetatable", getmetatableFunction);
714
705
  env.set("rawlen", rawlenFunction);
@@ -716,19 +707,15 @@ export function luaBuildStandardEnv() {
716
707
  env.set("rawget", rawgetFunction);
717
708
  env.set("rawequal", rawequalFunction);
718
709
  env.set("dofile", dofileFunction);
719
- // Error handling
720
710
  env.set("error", errorFunction);
721
711
  env.set("pcall", pcallFunction);
722
712
  env.set("xpcall", xpcallFunction);
723
- // Evaluation
724
713
  env.set("load", loadFunction);
725
- // APIs
726
714
  env.set("string", stringApi);
727
715
  env.set("table", tableApi);
728
716
  env.set("os", osApi);
729
717
  env.set("js", jsApi);
730
718
  env.set("math", mathApi);
731
- // Non-standard
732
719
  env.set("each", eachFunction);
733
720
  env.set("spacelua", spaceluaApi);
734
721
  env.set("encoding", encodingApi);
@@ -142,7 +142,6 @@ function parseInt(s: string): { ok: boolean; value: number } {
142
142
  let acc = 0;
143
143
  let any = false;
144
144
 
145
- // hex?
146
145
  if (s.charCodeAt(i) === 48 && i + 1 < n) {
147
146
  // '0'
148
147
  const x = s.charCodeAt(i + 1);
@@ -179,7 +178,6 @@ function parseInt(s: string): { ok: boolean; value: number } {
179
178
  }
180
179
  }
181
180
 
182
- // decimal integer
183
181
  while (i < n) {
184
182
  const c = s.charCodeAt(i);
185
183
  if (c < 48 || c > 57) {
@@ -221,7 +219,6 @@ function parseDecFloat(s: string): { ok: boolean; value: number } {
221
219
  let fracAny = false;
222
220
  let val = 0;
223
221
 
224
- // integer part
225
222
  while (i < n) {
226
223
  const c = s.charCodeAt(i);
227
224
  if (c < 48 || c > 57) {
@@ -233,7 +230,6 @@ function parseDecFloat(s: string): { ok: boolean; value: number } {
233
230
  i++;
234
231
  }
235
232
 
236
- // fractional part
237
233
  if (i < n) {
238
234
  if (s.charCodeAt(i) === 46) {
239
235
  // '.'
@@ -253,7 +249,6 @@ function parseDecFloat(s: string): { ok: boolean; value: number } {
253
249
  }
254
250
  }
255
251
 
256
- // exponent
257
252
  let exp = 0;
258
253
  let hasExp = false;
259
254
  if (i < n) {
@@ -347,7 +342,6 @@ function parseHexFloat(s: string): { ok: boolean; value: number } {
347
342
  let anyHex = false;
348
343
  let sawDot = false;
349
344
 
350
- // integer hex digits
351
345
  while (i < n) {
352
346
  const c = s.charCodeAt(i);
353
347
  let d = -1;
@@ -371,7 +365,6 @@ function parseHexFloat(s: string): { ok: boolean; value: number } {
371
365
  i++;
372
366
  }
373
367
 
374
- // optional fractional part
375
368
  if (i < n) {
376
369
  if (s.charCodeAt(i) === 46) {
377
370
  // '.'
@@ -444,7 +437,6 @@ function parseHexFloat(s: string): { ok: boolean; value: number } {
444
437
  return { ok: false, value: 0 };
445
438
  }
446
439
  } else {
447
- // no exponent marker
448
440
  if (!sawDot) {
449
441
  // without dot we must reject so parseInt can claim it as integer
450
442
  return { ok: false, value: 0 };
@@ -456,7 +448,6 @@ function parseHexFloat(s: string): { ok: boolean; value: number } {
456
448
  exp = 0; // implicit p0
457
449
  }
458
450
  } else {
459
- // end of string
460
451
  if (!sawDot) {
461
452
  return { ok: false, value: 0 };
462
453
  }
@@ -4,7 +4,7 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // version.ts
7
- var version = "2.10.0";
7
+ var version = "2.12.0";
8
8
 
9
9
  // client/plugos/plug_compile.ts
10
10
  import * as path from "node:path";
@@ -1330,7 +1330,7 @@ var workerRuntimePlugin = {
1330
1330
  namespace: "worker-runtime"
1331
1331
  }));
1332
1332
  build2.onLoad({ filter: /.*/, namespace: "worker-runtime" }, () => ({
1333
- contents: '// plug-api/lib/crypto.ts\nfunction base64Decode(s) {\n const binString = atob(s);\n const len = binString.length;\n const bytes = new Uint8Array(len);\n for (let i = 0; i < len; i++) {\n bytes[i] = binString.charCodeAt(i);\n }\n return bytes;\n}\nfunction base64Encode(buffer) {\n if (typeof buffer === "string") {\n buffer = new TextEncoder().encode(buffer);\n }\n let binary = "";\n const len = buffer.byteLength;\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(buffer[i]);\n }\n return btoa(binary);\n}\nvar fixedCounter = new Uint8Array(16);\n\n// client/lib/logger.ts\nvar Logger = class {\n constructor(prefix = "", maxCaptureSize = 1e3) {\n this.prefix = prefix;\n this.maxCaptureSize = maxCaptureSize;\n this.prefix = prefix;\n this.originalConsole = {\n log: console.log.bind(console),\n info: console.info.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n debug: console.debug.bind(console)\n };\n this.patchConsole();\n }\n originalConsole;\n logBuffer = [];\n patchConsole() {\n const createPatchedMethod = (level) => {\n return (...args) => {\n const prefixedArgs = this.prefix ? [this.prefix, ...args] : args;\n this.originalConsole[level](...prefixedArgs);\n this.captureLog(level, args);\n };\n };\n console.log = createPatchedMethod("log");\n console.info = createPatchedMethod("info");\n console.warn = createPatchedMethod("warn");\n console.error = createPatchedMethod("error");\n console.debug = createPatchedMethod("debug");\n }\n captureLog(level, args) {\n const entry = {\n level,\n timestamp: Date.now(),\n message: args.map((arg) => {\n if (typeof arg === "string") {\n return arg;\n }\n try {\n return JSON.stringify(arg);\n } catch {\n return String(arg);\n }\n }).join(" ")\n };\n this.logBuffer.push(entry);\n if (this.logBuffer.length > this.maxCaptureSize) {\n this.logBuffer.shift();\n }\n }\n /**\n * Posts all buffered logs to a server endpoint\n */\n async postToServer(logEndpoint, source) {\n const logs = this.logBuffer;\n if (logs.length > 0) {\n const logCopy = [...this.logBuffer];\n this.logBuffer = [];\n try {\n const resp = await fetch(logEndpoint, {\n method: "POST",\n headers: {\n "Content-Type": "application/json"\n },\n body: JSON.stringify(logCopy.map((entry) => ({ ...entry, source })))\n });\n if (!resp.ok) {\n throw new Error("Failed to post logs to server");\n }\n } catch (e) {\n console.warn("Could not post logs to server", e.message);\n this.logBuffer.unshift(...logCopy);\n }\n }\n }\n};\nvar globalLogger;\nfunction initLogger(prefix = "") {\n globalLogger = new Logger(prefix);\n return globalLogger;\n}\n\n// client/plugos/worker_runtime.ts\nvar workerPostMessage = (_msg) => {\n throw new Error("Not initialized yet");\n};\nvar runningAsWebWorker = typeof window === "undefined" && // @ts-expect-error: globalThis\ntypeof globalThis.WebSocketPair === "undefined";\nvar pendingRequests = /* @__PURE__ */ new Map();\nvar syscallReqId = 0;\nif (runningAsWebWorker) {\n globalThis.syscall = async (name, ...args) => {\n return await new Promise((resolve, reject) => {\n syscallReqId++;\n pendingRequests.set(syscallReqId, { resolve, reject });\n workerPostMessage({\n type: "sys",\n id: syscallReqId,\n name,\n args\n });\n });\n };\n}\nfunction setupMessageListener(functionMapping, manifest, postMessageFn) {\n if (!runningAsWebWorker) {\n return;\n }\n workerPostMessage = postMessageFn;\n self.addEventListener("message", (event) => {\n (async () => {\n const data = event.data;\n switch (data.type) {\n case "inv":\n {\n const fn = functionMapping[data.name];\n if (!fn) {\n throw new Error(`Function not loaded: ${data.name}`);\n }\n try {\n const result = await Promise.resolve(fn(...data.args || []));\n workerPostMessage({\n type: "invr",\n id: data.id,\n result\n });\n } catch (e) {\n console.error(\n "An exception was thrown as a result of invoking function",\n data.name,\n "error:",\n e.message\n );\n workerPostMessage({\n type: "invr",\n id: data.id,\n error: e.message\n });\n }\n }\n break;\n case "sysr":\n {\n const syscallId = data.id;\n const lookup = pendingRequests.get(syscallId);\n if (!lookup) {\n throw Error("Invalid request id");\n }\n pendingRequests.delete(syscallId);\n if (data.error) {\n lookup.reject(new Error(data.error));\n } else {\n lookup.resolve(data.result);\n }\n }\n break;\n }\n })().catch(console.error);\n });\n workerPostMessage({\n type: "manifest",\n manifest\n });\n initLogger(`[${manifest.name} plug]`);\n}\nasync function sandboxFetch(reqInfo, options) {\n if (typeof reqInfo !== "string") {\n const body = new Uint8Array(await reqInfo.arrayBuffer());\n const encodedBody = body.length > 0 ? base64Encode(body) : void 0;\n options = {\n method: reqInfo.method,\n headers: Object.fromEntries(reqInfo.headers.entries()),\n base64Body: encodedBody\n };\n reqInfo = reqInfo.url;\n }\n return syscall("sandboxFetch.fetch", reqInfo, options);\n}\nglobalThis.nativeFetch = globalThis.fetch;\nfunction monkeyPatchFetch() {\n globalThis.fetch = async (reqInfo, init) => {\n const encodedBody = init?.body ? base64Encode(\n new Uint8Array(await new Response(init.body).arrayBuffer())\n ) : void 0;\n const r = await sandboxFetch(\n reqInfo,\n init && {\n method: init.method,\n headers: init.headers,\n base64Body: encodedBody\n }\n );\n return new Response(\n r.base64Body ? base64Decode(r.base64Body) : null,\n {\n status: r.status,\n headers: r.headers\n }\n );\n };\n}\nif (runningAsWebWorker) {\n monkeyPatchFetch();\n}\nexport {\n monkeyPatchFetch,\n sandboxFetch,\n setupMessageListener\n};\n',
1333
+ contents: '// plug-api/lib/crypto.ts\nfunction base64Decode(s) {\n const binString = atob(s);\n const len = binString.length;\n const bytes = new Uint8Array(len);\n for (let i = 0; i < len; i++) {\n bytes[i] = binString.charCodeAt(i);\n }\n return bytes;\n}\nfunction base64Encode(buffer) {\n if (typeof buffer === "string") {\n buffer = new TextEncoder().encode(buffer);\n }\n let binary = "";\n const len = buffer.byteLength;\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(buffer[i]);\n }\n return btoa(binary);\n}\nvar fixedCounter = new Uint8Array(16);\n\n// client/lib/logger.ts\nvar Logger = class {\n constructor(prefix = "", maxCaptureSize = 1e3) {\n this.prefix = prefix;\n this.maxCaptureSize = maxCaptureSize;\n this.prefix = prefix;\n this.originalConsole = {\n log: console.log.bind(console),\n info: console.info.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n debug: console.debug.bind(console)\n };\n this.patchConsole();\n }\n prefix;\n maxCaptureSize;\n originalConsole;\n logBuffer = [];\n patchConsole() {\n const createPatchedMethod = (level) => {\n return (...args) => {\n const prefixedArgs = this.prefix ? [this.prefix, ...args] : args;\n this.originalConsole[level](...prefixedArgs);\n this.captureLog(level, args);\n };\n };\n console.log = createPatchedMethod("log");\n console.info = createPatchedMethod("info");\n console.warn = createPatchedMethod("warn");\n console.error = createPatchedMethod("error");\n console.debug = createPatchedMethod("debug");\n }\n captureLog(level, args) {\n const entry = {\n level,\n timestamp: Date.now(),\n message: args.map((arg) => {\n if (typeof arg === "string") {\n return arg;\n }\n try {\n return JSON.stringify(arg);\n } catch {\n return String(arg);\n }\n }).join(" ")\n };\n this.logBuffer.push(entry);\n if (this.logBuffer.length > this.maxCaptureSize) {\n this.logBuffer.shift();\n }\n }\n /**\n * Posts all buffered logs to a server endpoint\n */\n async postToServer(logEndpoint, source) {\n const logs = this.logBuffer;\n if (logs.length > 0) {\n const logCopy = [...this.logBuffer];\n this.logBuffer = [];\n try {\n const resp = await fetch(logEndpoint, {\n method: "POST",\n headers: {\n "Content-Type": "application/json"\n },\n body: JSON.stringify(logCopy.map((entry) => ({ ...entry, source })))\n });\n if (!resp.ok) {\n throw new Error("Failed to post logs to server");\n }\n } catch (e) {\n console.warn("Could not post logs to server", e.message);\n this.logBuffer.unshift(...logCopy);\n }\n }\n }\n};\nvar globalLogger;\nfunction initLogger(prefix = "") {\n globalLogger = new Logger(prefix);\n return globalLogger;\n}\n\n// client/plugos/worker_runtime.ts\nvar workerPostMessage = (_msg) => {\n throw new Error("Not initialized yet");\n};\nvar runningAsWebWorker = typeof window === "undefined" && // @ts-expect-error: globalThis\ntypeof globalThis.WebSocketPair === "undefined";\nvar pendingRequests = /* @__PURE__ */ new Map();\nvar syscallReqId = 0;\nif (runningAsWebWorker) {\n globalThis.syscall = async (name, ...args) => {\n return await new Promise((resolve, reject) => {\n syscallReqId++;\n pendingRequests.set(syscallReqId, { resolve, reject });\n workerPostMessage({\n type: "sys",\n id: syscallReqId,\n name,\n args\n });\n });\n };\n}\nfunction setupMessageListener(functionMapping, manifest, postMessageFn) {\n if (!runningAsWebWorker) {\n return;\n }\n workerPostMessage = postMessageFn;\n self.addEventListener("message", (event) => {\n (async () => {\n const data = event.data;\n switch (data.type) {\n case "inv":\n {\n const fn = functionMapping[data.name];\n if (!fn) {\n throw new Error(`Function not loaded: ${data.name}`);\n }\n try {\n const result = await Promise.resolve(fn(...data.args || []));\n workerPostMessage({\n type: "invr",\n id: data.id,\n result\n });\n } catch (e) {\n console.error(\n "An exception was thrown as a result of invoking function",\n data.name,\n "error:",\n e.message\n );\n workerPostMessage({\n type: "invr",\n id: data.id,\n error: e.message\n });\n }\n }\n break;\n case "sysr":\n {\n const syscallId = data.id;\n const lookup = pendingRequests.get(syscallId);\n if (!lookup) {\n throw Error("Invalid request id");\n }\n pendingRequests.delete(syscallId);\n if (data.error) {\n lookup.reject(new Error(data.error));\n } else {\n lookup.resolve(data.result);\n }\n }\n break;\n }\n })().catch(console.error);\n });\n workerPostMessage({\n type: "manifest",\n manifest\n });\n initLogger(`[${manifest.name} plug]`);\n}\nasync function sandboxFetch(reqInfo, options) {\n if (typeof reqInfo !== "string") {\n const body = new Uint8Array(await reqInfo.arrayBuffer());\n const encodedBody = body.length > 0 ? base64Encode(body) : void 0;\n options = {\n method: reqInfo.method,\n headers: Object.fromEntries(reqInfo.headers.entries()),\n base64Body: encodedBody\n };\n reqInfo = reqInfo.url;\n }\n return syscall("sandboxFetch.fetch", reqInfo, options);\n}\nglobalThis.nativeFetch = globalThis.fetch;\nfunction monkeyPatchFetch() {\n globalThis.fetch = async (reqInfo, init) => {\n const encodedBody = init?.body ? base64Encode(\n new Uint8Array(await new Response(init.body).arrayBuffer())\n ) : void 0;\n const r = await sandboxFetch(\n reqInfo,\n init && {\n method: init.method,\n headers: init.headers,\n base64Body: encodedBody\n }\n );\n return new Response(\n r.base64Body ? base64Decode(r.base64Body) : null,\n {\n status: r.status,\n headers: r.headers\n }\n );\n };\n}\nif (runningAsWebWorker) {\n monkeyPatchFetch();\n}\nexport {\n monkeyPatchFetch,\n sandboxFetch,\n setupMessageListener\n};\n',
1334
1334
  loader: "js"
1335
1335
  }));
1336
1336
  } else {
@@ -1371,8 +1371,7 @@ ${Object.entries(manifest.functions).map(([funcName, def]) => {
1371
1371
  }
1372
1372
  let [filePath, jsFunctionName] = def.path.split(":");
1373
1373
  filePath = path.join(rootPath, filePath);
1374
- return `import {${jsFunctionName} as ${funcName}} from "${// Replacing \ with / for Windows
1375
- path.resolve(filePath).replaceAll("\\", "\\\\")}";
1374
+ return `import {${jsFunctionName} as ${funcName}} from "${path.resolve(filePath).replaceAll("\\", "\\\\")}";
1376
1375
  `;
1377
1376
  }).join("")}
1378
1377