Commit Graph
99 Commits
Author SHA1 Message Date
Zef Hemel d9f4b93795 Eliminate (most) uses of the client global
Also an opportunity to refactor the transclusion code, which had become
very messy
2026-03-10 14:02:05 +01:00
Zef Hemel 799e27a9b4 Enable biome based formatter and reformat 2026-03-10 09:38:59 +01:00
Zef Hemel cce5b669f6 Merge branch 'main' into to-node 2026-03-10 08:57:58 +01:00
Matouš Jan Fialka 9c9ca047f0 Fix: Handle tagged floats before plain object check in renderCellContent
Tagged floats (`{value: N, isFloat: true}`) were matched by
`isPlainObject` and rendered as a two-column table with "value" and
"isFloat" headers instead of as a formatted number. This is now fixed.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-03-09 12:57:53 +01:00
Matouš Jan Fialka 2aaa797bda Improve Lua Widget Rendering
Lua `${...}` expression results are rendered using two parallel paths:

- **HTML path** (display path) for visual display in the editor and
- **Markdown path** (copy path) for the *copy button* output.

Every Lua value is classified as one of:

- **Scalar:** number, string, boolean, or `nil`
- **Empty table:** `{}`
- **Array:** a `LuaTable` with only sequential integer keys (no string keys)
- **Array of tables:** an array where every element is a `LuaTable`
- **Record:** a `LuaTable` with any string key (may also have integer keys)

Scalars are rendered as inline Markdown - wiki links, tags, bold, and
other inline syntax are resolved, `nil` renders as `<span data-empty>`.

Empty tables render as `<table data-table-empty>`.

An array of tables renders as a multi-row `<table>`. Column headers
are the union of all element keys. Each cell value recurses through
the same classification.

An array of scalars (or a mix of scalars and tables) renders as `<ul>`
with one `<li>` per element. Each item value recurses.

A record renders as a single-row `<table>` with one column per key.
Cell values recurse.

Every `<td>` carries `data-table-cell-type` and optionally
`data-table-cell-empty`.  Every `<li>` carries `data-list-item-type` and
optionally `data-list-item-empty`.  The outermost element carries
`data-type`.  These attributes allow CSS styling of rendered widgets.

Nesting is **unlimited**. Tables inside cells, lists inside cells,
tables inside list items all render correctly in HTML.

Scalars render as plain text. Lua `nil` renders as `nil`. Empty tables
render as `*(empty table)*`.

An array of tables renders as a pipe-delimited Markdown table. Nested
values in cells are rendered as Lua literals via `toStringAsync()`
(e.g. `{1, 2, 3}` or `{a = 1}`), since Markdown table cells cannot
contain block-level content.

An array of scalars renders as a Markdown list (`- item`). When a list
item is itself multi-line (a sublist) it uses an empty bullet with
two-space indented content on the next line:

```
- 1
- 2
-
  - nested 1
  - nested 2
```

Nested tables or arrays **beyond the first list nesting** level are
rendered as Lua literals.

A record renders as a single-row Markdown table.

Column headers follow the order returned by `LuaTable.keys`: string keys
in their insertion order, followed by integer keys in ascending order.
For example, `{a = 1, b = 2, 3, d = 4}` produces columns `a
| b | d | 1`. This is consequence of `LuaTable` storing string keys
and the array part separately (for performance).

Arrays mixing plain scalars and record-like tables (e.g.  `{1, 2, 3,
{a=3}}`) were previously rendered as a single flat table due to an
internal JS conversion (`toJS`). They now render as a list, with record
elements shown as nested tables - which more accurately reflects their
Lua structure. This is the only divergence from the original
presentation.

To restore the old tabular layout, wrap all elements as records with
a consistent key:

```lua
-- Before (was a flat table, now a list with nested table):
${{1, 2, 3, {a=3}}}

-- After (explicit records, renders as a table):
${{{value=1}, {value=2}, {value=3}, {a=3}}}
```

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-03-09 11:45:04 +01:00
Zef Hemel bbcd9ac6aa Introduce new noFloatingPromises lint rule and fix violiations 2026-03-08 15:49:45 +01:00
Zef Hemel dbf275d75b Introduce biome as linter with lint fixes 2026-03-06 08:05:13 +01:00
Zef Hemel de32dfb0f5 Auto discover lua test files 2026-03-05 16:39:19 +01:00
Zef Hemel e218433f52 Merge remote-tracking branch 'origin/main' into to-node
# Conflicts:
#	client/space_lua/query_collection.test.ts
2026-03-04 15:47:09 +01:00
Matouš Jan Fialka 879bfb811a [LIQ] group by: Convert plain JS objects to LuaTable in results
Items from `index.tag` are stored as plain JS objects when no metatable
is defined. The `group by` clause wraps rows in `LuaTable` (`key`,
`group`) but leaves the individual `group` items as plain JS objects.
This causes `t.keys is not a function` errors when passing `row.group`
to APIs like `template.each` that expect `LuaTable` instances.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-03-04 11:47:28 +01:00
Matouš Jan Fialka df998ebc68 [LIQ] order by: Pre-compute sort keys before merge sort (Schwartzian transform)
Evaluate all `order by` expressions once per item in a decorate pass,
then compare cached keys during sort. Reduces Lua eval calls from
*O(kn log n)* to *O(kn)* where *k* is the number of sort keys.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-03-04 09:58:43 +01:00
Matouš Jan Fialka a1024212ee [LIQ] order by: Replace Quicksort with merge sort with SWO violation detection
- Add iterative async stable merge sort (*O(n log n)*).
- Replace Quicksort with the new merge sort (kept Quicksort for tables).
- Update documentation (reorganize for logical flow, add SWO section
  with invalid comparator example that really throws).

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-03-04 09:06:11 +01:00
Matouš Jan Fialka 01dd7c1db4 [Space Lua] LIQ: Add using ... support to order by
* support for custom comparators in LIQ `order by` clauses via the
  `using` keyword.

* `using` is mutually exclusive with `asc`/`desc` and accepts either
  a *named function* or an *inline anonymous function*. The comparator
  receives two values and returns true if the first should come before the
  second.

* `nulls first`/ `nulls last` works independently of `using`.

* `using` is now a reserved keyword (`kw`; this cannot be avoided but
  the chance to collide with user scripts is negligable),

* documentation updated + polished a little

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-03-03 14:47:28 +01:00
Matouš Jan Fialka b786d65307 [LIQ] Add filter(where <cond>) clause for per-row aggregate filtering
Implements `filter(where <cond>)` for aggregate functions in LIQ
queries which allows per-row conditional filtering within grouped
aggregates. Also extends test suite and documentation.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-03-03 08:29:18 +01:00
Matouš Jan Fialka e35b61d050 Rename LUA_SQL_NULL to LIQ_NULL
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-03-02 14:07:07 +01:00
Matouš Jan Fialka ff6854f6ee [LIQ] Add nulls first / nulls last to order by
Extend the `order by` clause with optional `nulls first` and `nulls
last` modifiers following standard SQL semantics. Each sort key can
independently specify where `nil` values should appear in the result. By
default, ascending sorts place nulls last and descending sorts place
nulls first. An explicit nulls first or nulls last overrides this
default. The `asc` keyword is also now accepted as an explicit
alternative to the default ascending direction. Documentation and tests
were updated accordingly.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-03-02 12:34:25 +01:00
Zef Hemel 380b5d90ca More Deno cleanup 2026-02-27 16:25:34 +01:00
Matouš Jan Fialka 8f193dc402 [LIQ] Fix: preserve queryable collections in query evaluation
Check for already-queryable objects (e.g. `DataStoreQueryCollection`)
before `LuaTable` extraction or `luaValueToJS` conversion, preventing
them from being destroyed by the collection normalization pipeline.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-27 15:44:26 +01:00
Matouš Jan Fialka cad71aa586 [LIQ] Fix: group by keys and collection handling
- Fix `group by` key building: populate array indices from
  `keyParts` instead of only from `keyRecord`, which was empty for
  some expressions (e.g. `tags[1]`).

- Fix collection handling: treat record-like `LuaTables` (no array part)
  as items instead of extracting zero items.

- Update test suite.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-27 15:30:40 +01:00
Zef Hemel 803c5c478d Merge remote-tracking branch 'origin/main' into to-node 2026-02-27 15:05:49 +01:00
Matouš Jan Fialka bc2ae5e46f [LIQ] Miscellaneous bugfixes
* Fix `LUA_SQL_NULL` leaking into `order by` comparisons

  Note: SQL `NULLS LAST` is now default behaviour!

* Fix `objectVariable` unset for empty groups

* Fix non-grouped `having` used wrong environment builder

  Note: Queries similar to the following one now works correctly:

  ```sql
  ${query[[
    from
      tags.page
    having
      name == 'index'
  ]]}
  ```

* Fix `generateKey` did not handle `LUA_SQL_NULL`

* Refactor/deduplicate duplicated `order by` comparator

* Add `isSqlNull` helper

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-27 08:37:00 +01:00
Matouš Jan Fialka f7dc242790 [LIQ] Fix group by: order by with select aliases, variable binding in having and nil column ordering
1. Fix `order by` inability to access `select` output aliases and
   aggregate functions after `group by`.

2. Introduce `LUA_SQL_NULL` sentinel to preserve column ordering when
   `select` expressions evaluate to `nil`.

   Note: In Lua, setting a table key to `nil` deletes it so rows with
   `nil` values lose those keys and produced inconsistent insertion
   order across result tables.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-26 15:32:28 +01:00
Zef HemelandGitHub 924f7cacc3 Merge pull request #1852 from mjf/space-lua-add-frexp-and-ldexp
[Space Lua] Add `math.frexp` and `math.ldexp` and tests
2026-02-25 10:38:06 +01:00
Zef HemelandGitHub a347487375 Merge branch 'main' into space-lua-fixtures 2026-02-25 10:36:32 +01:00
13c61ebab7 [LIQ] Extend with group by and having with aggregators (#1843)
* [LIQ] Extend with `group by` and `having` support

Also update documentation with extensive examples and add some parser
tests.

Note: `make generate` JS files included.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>

* Merge `liq-add-support-for-aggregators` branch

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>

* Fix link, remove `_` example

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>

* Wrap example LIQ queries inside `query [[ .. ]]` and replace `sql` code block type to `lua`

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>

* Example self-consistency fix

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>

* Fixed some queries

* Add experimental tags to LIQ aggregation/grouping

---------

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
Co-authored-by: Zef Hemel <zef@zef.me>
2026-02-25 10:35:31 +01:00
Matouš Jan FialkaandGitHub fd4ff8518d [Space Lua] Implement string.pack, string.unpack and string.packsize (#1851)
The three binary-packing functions from Lua are now implemented in
a dedicated `string_pack.ts` module and wired into `stringApi`.

Format parsing follows the exact option set of Lua:

* signed and unsigned integers of configurable width
  (`b`/`B`/`h`/`H`/`l`/`L`/`j`/`J`/`T`/`i`/`I`),
* IEEE 754 single and double precision floats (`f`/`d`/`n`),
* fixed-length strings (`c`),
* length-prefixed strings (`s`),
* zero-terminated strings (`z`),
* padding bytes (`x`),
* alignment padding (`X`), and
* endianness/alignment modifiers (`<`/`>`/`=`/`!`).

`string.packsize` rejects variable-length formats (`s` and `z`) as Lua
requires.

`string.unpack` returns all unpacked values followed by the
next position integer, matching the Lua contract exactly.

Test suite extended in separates `string_pack_test.lua` file. Passes
both Lua and Space Lua execution.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-25 09:29:14 +01:00
Matouš Jan FialkaandGitHub cebc834469 FIX: Skip nil results in query select to prevent null rows corrupting renderer (#1853)
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-25 09:27:24 +01:00
Matouš Jan Fialka 1f36103338 Add math.frexp and math.ldexp and tests
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-23 14:55:04 +01:00
Matouš Jan Fialka ed8ce174fc [Space Lua] Add _VERSION environment variable
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-23 13:01:50 +01:00
Matouš Jan Fialka 390fe384c1 [Space Lua] Remove unpack from core language test suite
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-23 08:24:28 +01:00
Matouš Jan Fialka cc931319d7 [Space Lua] Align table.pack and table.unpack with Lua semantics
`table.pack` now routes element writes through `luaSet` so `__newindex`
metamethods are honoured, matching the exact Lua 5.4 semantics.  The `n`
field is still written with `rawSet`, also matching Lua.

`table.unpack` now returns an empty `LuaMultiRes` instead of `null` for
an empty range `(i > j)`. Returning `null` would inject a single `nil`
argument at the call site; an empty `LuaMultiRes` correctly contributes
zero values, matching the exact Lua semantics.

The global `unpack` shim (for Lua 5.1 compatibility) has been removed
from `luaBuildStandardEnv`. Users who need the short alias can set it up
in `space-script` with a single line stating `unpack = table.unpack`.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-23 08:16:09 +01:00
Matouš Jan Fialka 11347fee72 Merge on main
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-23 07:33:51 +01:00
Matouš Jan FialkaandGitHub 3bb80ece3f [Space Lua]: Implement math.random and math.randomseed (#1845) 2026-02-21 17:56:49 +01:00
Matouš Jan Fialka ef7b128793 [Space Lua] Fix: math.tointeger coerces strings like Lua; fix test
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-20 15:25:24 +01:00
Matouš Jan Fialka c3b747b0d5 [Space Lua] Fix: t[nil] read returns nil, not an error
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-20 15:06:22 +01:00
Matouš Jan Fialka 91927a7314 [Space Lua] Fix: Table index is nil/NaN raises error
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-20 11:27:55 +01:00
Matouš Jan Fialka 559fc0b94d [Space Lua] Implement math.tointeger
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-20 11:26:59 +01:00
Matouš Jan Fialka 3b29aa8497 [Space Lua] # operator respects __len metamethod via luaLen
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-20 11:03:28 +01:00
Matouš Jan Fialka f93a306a66 [Space Lua] Guarantee that math.modf returns integer and tagged float
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-20 09:36:10 +01:00
Matouš Jan Fialka 77d37090ca [Space Lua] Fix: string.gsub table repl unwraps tagged float before String
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-20 09:15:52 +01:00
Matouš Jan Fialka 4d30baa2f2 [Space Lua] Fix: table.sort comparator returns 0 for ties, not 1
Returning `1` when the comparator yields `false`/`nil` declares `a > b`
for equal elements. `asyncQuickSort` tolerates this today but any future
sort algorithm change could corrupt results. Changed to `0` so ties are
neutral.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-20 09:04:04 +01:00
Matouš Jan Fialka af0086fcfb [Space Lua] Implement table.move
Implement `table.move(a1, f, e, t [,a2])`.

The implementation copies elements `a1[f..e]` to `a2[t..t+(e-f)]`
defaulting `a2` to `a1` and returning `a2` as follows:

* empty ranges (`e < f`) are "no-op",

* for in-table copies where `t > f` loop backwards to prevent unread
  source slots from being clobbered before they are read (note: the only
  case where overlap is destructive,

* cross-table copies always run forwards since there is no aliasing.

Add test suite that covers:

* non-overlapping copies,
* cross-table copies,
* both overlap directions,
* same position identity,
* empty ranges, and
* single element moves.

Each of the above is tested with both scalar and nested-table values.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-20 08:28:24 +01:00
Matouš Jan Fialka 8cecd0985c [Space Lua] tostring() respects __tostring metamethod with live stack frame
`tostring()` was delegating to `luaToString()` which calls `__tostring`
with a dead stack frame, breaking metamethods that call back into
globals. Now `tostringFunction` receives the live stack frame, looks up
`__tostring` via `rawGet`, calls it through `luaCall` with the live
frame, and validates the return is a string.

Also add tests covering the basic case, the nested-call case, and the
error path for a non-string return.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-20 07:59:16 +01:00
Matouš Jan Fialka d2eb8fe6ec [SpaceLua] Use luaFormatNumber for number-to-string coercion in .. and table.concat
Replace raw `String(v)` calls with `luaFormatNumber` in the concatenation
operator and `table.concat`, so that floats are formatted according to Lua
semantics rather than JavaScript's. This fixes cases like `10.8*22..""`
producing `237.60000000000002` instead of `237.6`, and ensures
integer-valued floats carry the `.0` suffix as Lua requires.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-20 07:31:32 +01:00
Zef Hemel 99bbb37ff0 Fix make check 2026-02-15 08:35:06 +01:00
Zef Hemel f951d6e74c Merge branch 'main' of github.com:silverbulletmd/silverbullet into to-node 2026-02-14 22:10:42 +01:00
Zef Hemel 02bd8ed362 Migration Deno -> Node.js
* First pass
* Now using vitest for tests
2026-02-14 22:08:35 +01:00
Matouš Jan FialkaandGitHub 3ddfab51e8 [Space Lua] Extend os (#1836) 2026-02-13 20:05:08 +01:00
Matouš Jan FialkaandGitHub e0182b3bb4 [Space Lua] Implement native Lua pattern matching engine (#1838)
* Add Lua *pattern matching engine* (`pattern.ts`) that directly ports
  `lstrlib.c` semantics.

* Integrate the new engine into the `string` API for `find`, `match`,
  `gmatch`, and `gsub` functions.

* Add a pattern test suite (`pattern_test.lua`) covering character
  classes, repetition quantifiers, edge cases as well as error
  conditions.

* Refactor `string_test.lua` to remove duplicated pattern tests that
  are now covered by the pattern test suite.

* Fix `string.rep` separator support.

* Fix `string.sub` index handling.

* Remove the obsolete `string_test2.lua` file.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-13 14:18:55 +01:00
Zef Hemel 8592b3add6 Lua snake_case -> camelCase naming in tests 2026-02-12 17:32:28 +01:00