Breaking Lua API changes:

* tpl -> template.new
* define_command -> command.define
* define_event_listener -> event.listen
* tag -> index.tag

Updated in the docs
pull/1224/head
Zef Hemel 2025-01-19 14:32:11 +01:00
parent 72b4ecdc36
commit 6078452a6c
22 changed files with 275 additions and 237 deletions

View File

@ -62,7 +62,7 @@ export abstract class CommonSystem {
Object.keys(this.scriptEnv.eventHandlers).length,
"event handlers from space-script",
);
await this.spaceLuaEnv.reload(this.system, this.scriptEnv);
await this.spaceLuaEnv.reload(this.system);
} catch (e: any) {
console.error("Error loading space-script:", e.message);
}

View File

@ -11,7 +11,6 @@ import {
type PageRef,
parsePageRef,
} from "@silverbulletmd/silverbullet/lib/page_ref";
import type { ScriptEnvironment } from "$common/space_script.ts";
import type { ASTCtx } from "$common/space_lua/ast.ts";
import { buildLuaEnv } from "$common/space_lua_api.ts";
@ -24,7 +23,6 @@ export class SpaceLuaEnvironment {
*/
async reload(
system: System<any>,
scriptEnv: ScriptEnvironment,
) {
const allScripts: ScriptObject[] = await system.invokeFunction(
"index.queryObjects",
@ -36,7 +34,7 @@ export class SpaceLuaEnvironment {
}],
);
try {
this.env = buildLuaEnv(system, scriptEnv);
this.env = buildLuaEnv(system);
const tl = new LuaEnv();
tl.setLocal("_GLOBAL", this.env);
for (const script of allScripts) {

View File

@ -267,6 +267,7 @@ export function evalExpression(
sf.withCtx(e.ctx),
);
}
collection = luaValueToJS(collection);
// Check if collection is a queryable collection
if (!collection.query) {
// If not, try to convert it to JS and see if it's an array

View File

@ -104,10 +104,12 @@ Deno.test("Test comment handling", () => {
});
Deno.test("Test query parsing", () => {
parse(`_(query[[from p = tag("page") where p.name == "John" limit 10, 3]])`);
parse(`_(query[[from tag("page") select {name="hello", age=10}]])`);
parse(
`_(query[[from p = tag("page") order by p.lastModified desc, p.name]])`,
`_(query[[from p = index.tag("page") where p.name == "John" limit 10, 3]])`,
);
parse(`_(query[[from p = tag("page") order by p.lastModified]])`);
parse(`_(query[[from index.tag("page") select {name="hello", age=10}]])`);
parse(
`_(query[[from p = index.tag("page") order by p.lastModified desc, p.name]])`,
);
parse(`_(query[[from p = index.tag("page") order by p.lastModified]])`);
});

View File

@ -1,31 +1,21 @@
import {
type ILuaFunction,
jsToLuaValue,
LuaBuiltinFunction,
luaCall,
LuaEnv,
luaGet,
LuaMultiRes,
LuaRuntimeError,
LuaTable,
type LuaTable,
luaToString,
luaTypeOf,
type LuaValue,
luaValueToJS,
} from "$common/space_lua/runtime.ts";
import { stringApi } from "$common/space_lua/stdlib/string.ts";
import { tableApi } from "$common/space_lua/stdlib/table.ts";
import { osApi } from "$common/space_lua/stdlib/os.ts";
import { jsApi } from "$common/space_lua/stdlib/js.ts";
import {
interpolateLuaString,
spaceLuaApi,
} from "$common/space_lua/stdlib/space_lua.ts";
import {
findAllQueryVariables,
type LuaCollectionQuery,
type LuaQueryCollection,
} from "$common/space_lua/query_collection.ts";
import { spaceLuaApi } from "$common/space_lua/stdlib/space_lua.ts";
import { templateApi } from "$common/space_lua/stdlib/template.ts";
import { mathApi } from "$common/space_lua/stdlib/math.ts";
@ -149,68 +139,6 @@ const getmetatableFunction = new LuaBuiltinFunction((_sf, table: LuaTable) => {
return table.metatable;
});
// Non-standard
const tagFunction = new LuaBuiltinFunction(
(sf, tagName: LuaValue): LuaQueryCollection => {
const global = sf.threadLocal.get("_GLOBAL");
if (!global) {
throw new LuaRuntimeError("Global not found", sf);
}
return {
query: async (query: LuaCollectionQuery, env: LuaEnv): Promise<any[]> => {
const localVars = findAllQueryVariables(query).filter((v) =>
!global.has(v) && v !== "_"
);
const scopedVariables: Record<string, any> = {};
for (const v of localVars) {
try {
const jsonValue = await luaValueToJS(env.get(v));
// Ensure this is JSON serializable
JSON.stringify(jsonValue);
scopedVariables[v] = jsonValue;
} catch (e: any) {
console.error(
"Failed to JSON serialize variable",
v,
e,
);
throw new LuaRuntimeError(
`Failed to JSON serialize variable ${v} in query`,
sf,
);
}
}
return (await global.get("datastore").get("query_lua").call(
sf,
[
"idx",
tagName,
],
query,
scopedVariables,
)).toJSArray();
},
};
},
);
const tplFunction = new LuaBuiltinFunction(
(_sf, template: string): ILuaFunction => {
const lines = template.split("\n").map((line) =>
line.replace(/^\s{4}/, "")
);
const processed = lines.join("\n");
return new LuaBuiltinFunction(
async (sf, env: LuaTable | any) => {
if (!(env instanceof LuaTable)) {
env = jsToLuaValue(env);
}
return await interpolateLuaString(sf, processed, env);
},
);
},
);
export function luaBuildStandardEnv() {
const env = new LuaEnv();
// Top-level builtins
@ -231,9 +159,6 @@ export function luaBuildStandardEnv() {
env.set("error", errorFunction);
env.set("pcall", pcallFunction);
env.set("xpcall", xpcallFunction);
// Non-standard
env.set("tag", tagFunction);
env.set("tpl", tplFunction);
// APIs
env.set("string", stringApi);
env.set("table", tableApi);

View File

@ -1,8 +1,10 @@
import {
type ILuaFunction,
jsToLuaValue,
LuaBuiltinFunction,
LuaTable,
} from "$common/space_lua/runtime.ts";
import { interpolateLuaString } from "$common/space_lua/stdlib/space_lua.ts";
export const templateApi = new LuaTable({
each: new LuaBuiltinFunction(
@ -17,4 +19,20 @@ export const templateApi = new LuaTable({
return result.join("");
},
),
new: new LuaBuiltinFunction(
(_sf, template: string): ILuaFunction => {
const lines = template.split("\n").map((line) =>
line.replace(/^\s{4}/, "")
);
const processed = lines.join("\n");
return new LuaBuiltinFunction(
async (sf, env: LuaTable | any) => {
if (!(env instanceof LuaTable)) {
env = jsToLuaValue(env);
}
return await interpolateLuaString(sf, processed, env);
},
);
},
),
});

View File

@ -1,24 +1,18 @@
import { luaBuildStandardEnv } from "$common/space_lua/stdlib.ts";
import { parsePageRef } from "@silverbulletmd/silverbullet/lib/page_ref";
import {
jsToLuaValue,
LuaBuiltinFunction,
LuaEnv,
LuaNativeJSFunction,
LuaStackFrame,
LuaTable,
} from "$common/space_lua/runtime.ts";
import type { System } from "$lib/plugos/system.ts";
import type { ScriptEnvironment } from "$common/space_script.ts";
import type { CommandDef } from "$lib/command.ts";
export function buildLuaEnv(system: System<any>, scriptEnv: ScriptEnvironment) {
export function buildLuaEnv(system: System<any>) {
const env = new LuaEnv(luaBuildStandardEnv());
// Expose all syscalls to Lua
exposeSyscalls(env, system);
// Support defining commands and subscriptions from Lua
exposeDefinitions(env, system, scriptEnv);
return env;
}
@ -45,82 +39,7 @@ function exposeSyscalls(env: LuaEnv, system: System<any>) {
}
}
function exposeDefinitions(
env: LuaEnv,
system: System<any>,
scriptEnv: ScriptEnvironment,
) {
// Expose the command registration function to Lua via define_command({name="foo", function() ... end})
env.set(
"define_command",
new LuaBuiltinFunction(
(_sf, def: LuaTable) => {
if (def.get(1) === undefined) {
throw new Error("Callback is required");
}
if (!def.get("name")) {
throw new Error("Name is required");
}
const fn = def.get(1);
console.log(
`[Lua] Registering command '${
def.get("name")
}' (source: ${fn.body.ctx.ref})`,
);
scriptEnv.registerCommand(
{
name: def.get("name"),
key: def.get("key"),
mac: def.get("mac"),
priority: def.get("priority"),
requireMode: def.get("require_mode"),
hide: def.get("hide"),
} as CommandDef,
async (...args: any[]) => {
const tl = await buildThreadLocalEnv(system, env);
const sf = new LuaStackFrame(tl, null);
try {
return await fn.call(sf, ...args.map(jsToLuaValue));
} catch (e: any) {
await handleLuaError(e, system);
}
},
);
},
),
);
env.set(
"define_event_listener",
new LuaBuiltinFunction((_sf, def: LuaTable) => {
if (def.get(1) === undefined) {
throw new Error("Callback is required");
}
if (!def.get("event")) {
throw new Error("Event is required");
}
const fn = def.get(1);
console.log(
`[Lua] Subscribing to event '${
def.get("event")
}' (source: ${fn.body.ctx.ref})`,
);
scriptEnv.registerEventListener(
{ name: def.get("event") },
async (...args: any[]) => {
const tl = await buildThreadLocalEnv(system, env);
const sf = new LuaStackFrame(tl, null);
try {
return await fn.call(sf, ...args.map(jsToLuaValue));
} catch (e: any) {
await handleLuaError(e, system);
}
},
);
}),
);
}
function buildThreadLocalEnv(_system: System<any>, globalEnv: LuaEnv) {
export function buildThreadLocalEnv(_system: System<any>, globalEnv: LuaEnv) {
const tl = new LuaEnv();
// const currentPageMeta = await system.localSyscall(
// "editor.getCurrentPageMeta",
@ -131,7 +50,7 @@ function buildThreadLocalEnv(_system: System<any>, globalEnv: LuaEnv) {
return Promise.resolve(tl);
}
async function handleLuaError(e: any, system: System<any>) {
export async function handleLuaError(e: any, system: System<any>) {
console.error(
"Lua eval exception",
e.message,

View File

@ -20,7 +20,7 @@ type AttributeExtractorDef = {
tags: string[];
};
type EventListenerDef = {
export type EventListenerDef = {
name: string;
};

View File

@ -0,0 +1,50 @@
import type { SysCallMapping } from "$lib/plugos/system.ts";
import type { CommandDef } from "$lib/command.ts";
import { buildThreadLocalEnv, handleLuaError } from "$common/space_lua_api.ts";
import {
type ILuaFunction,
jsToLuaValue,
luaCall,
LuaStackFrame,
luaValueToJS,
} from "$common/space_lua/runtime.ts";
import type { CommonSystem } from "$common/common_system.ts";
export type CallbackCommandDef = CommandDef & {
run: ILuaFunction;
};
export function commandSyscalls(
commonSystem: CommonSystem,
): SysCallMapping {
return {
/**
* Define a Lua command
* @param def - The command definition
* @param luaCallback - The Lua callback
*/
"command.define": (
_ctx,
def: CallbackCommandDef,
) => {
console.log("Registering Lua command: ", def.name);
commonSystem.scriptEnv.registerCommand(
def,
async (...args: any[]) => {
const tl = await buildThreadLocalEnv(
commonSystem.system,
commonSystem.spaceLuaEnv.env,
);
const sf = new LuaStackFrame(tl, null);
try {
return luaValueToJS(
await luaCall(def.run, args.map(jsToLuaValue), sf),
);
} catch (e: any) {
await handleLuaError(e, commonSystem.system);
}
},
);
},
};
}

48
common/syscalls/event.ts Normal file
View File

@ -0,0 +1,48 @@
import type { SysCallMapping } from "$lib/plugos/system.ts";
import type { EventListenerDef } from "$common/space_script.ts";
import { buildThreadLocalEnv, handleLuaError } from "$common/space_lua_api.ts";
import {
type ILuaFunction,
jsToLuaValue,
luaCall,
LuaStackFrame,
luaValueToJS,
} from "$common/space_lua/runtime.ts";
import type { CommonSystem } from "$common/common_system.ts";
export type CallbackEventListener = EventListenerDef & {
run: ILuaFunction;
};
export function eventListenerSyscalls(
commonSystem: CommonSystem,
): SysCallMapping {
return {
/**
* Define a Lua event listener
*/
"event.listen": (
_ctx,
def: CallbackEventListener,
) => {
console.log("Registering Lua event listener: ", def.name);
commonSystem.scriptEnv.registerEventListener(
def,
async (...args: any[]) => {
const tl = await buildThreadLocalEnv(
commonSystem.system,
commonSystem.spaceLuaEnv.env,
);
const sf = new LuaStackFrame(tl, null);
try {
return luaValueToJS(
await luaCall(def.run, args.map(jsToLuaValue), sf),
);
} catch (e: any) {
await handleLuaError(e, commonSystem.system);
}
},
);
},
};
}

View File

@ -3,15 +3,26 @@ import type {
ObjectQuery,
ObjectValue,
} from "@silverbulletmd/silverbullet/types";
import type { SysCallMapping, System } from "$lib/plugos/system.ts";
import type { LuaCollectionQuery } from "$common/space_lua/query_collection.ts";
import type { SysCallMapping } from "$lib/plugos/system.ts";
import {
findAllQueryVariables,
type LuaCollectionQuery,
type LuaQueryCollection,
} from "$common/space_lua/query_collection.ts";
import {
type LuaEnv,
LuaRuntimeError,
type LuaStackFrame,
luaValueToJS,
} from "$common/space_lua/runtime.ts";
import type { CommonSystem } from "$common/common_system.ts";
// These are just wrappers around the system.invokeFunction calls, but they make it easier to use the index
export function indexSyscalls(system: System<any>): SysCallMapping {
export function indexSyscalls(commonSystem: CommonSystem): SysCallMapping {
return {
"index.indexObjects": (ctx, page: string, objects: ObjectValue<any>[]) => {
return system.syscall(ctx, "system.invokeFunction", [
return commonSystem.system.syscall(ctx, "system.invokeFunction", [
"index.indexObjects",
page,
objects,
@ -23,7 +34,7 @@ export function indexSyscalls(system: System<any>): SysCallMapping {
query: ObjectQuery,
ttlSecs?: number,
) => {
return system.syscall(ctx, "system.invokeFunction", [
return commonSystem.system.syscall(ctx, "system.invokeFunction", [
"index.queryObjects",
tag,
query,
@ -36,7 +47,7 @@ export function indexSyscalls(system: System<any>): SysCallMapping {
query: LuaCollectionQuery,
scopedVariables?: Record<string, any>,
) => {
return system.syscall(ctx, "system.invokeFunction", [
return commonSystem.system.syscall(ctx, "system.invokeFunction", [
"index.queryLuaObjects",
tag,
query,
@ -44,26 +55,68 @@ export function indexSyscalls(system: System<any>): SysCallMapping {
]);
},
"index.queryDeleteObjects": (ctx, tag: string, query: ObjectQuery) => {
return system.syscall(ctx, "system.invokeFunction", [
return commonSystem.system.syscall(ctx, "system.invokeFunction", [
"index.queryDeleteObjects",
tag,
query,
]);
},
"index.query": (ctx, query: KvQuery, variables?: Record<string, any>) => {
return system.syscall(ctx, "system.invokeFunction", [
return commonSystem.system.syscall(ctx, "system.invokeFunction", [
"index.query",
query,
variables,
]);
},
"index.getObjectByRef": (ctx, page: string, tag: string, ref: string) => {
return system.syscall(ctx, "system.invokeFunction", [
return commonSystem.system.syscall(ctx, "system.invokeFunction", [
"index.getObjectByRef",
page,
tag,
ref,
]);
},
"index.tag": (_ctx, tagName: string): LuaQueryCollection => {
return {
query: async (
query: LuaCollectionQuery,
env: LuaEnv,
sf: LuaStackFrame,
): Promise<any[]> => {
const global = commonSystem.spaceLuaEnv.env;
const localVars = findAllQueryVariables(query).filter((v) =>
!global.has(v) && v !== "_"
);
const scopedVariables: Record<string, any> = {};
for (const v of localVars) {
try {
const jsonValue = await luaValueToJS(env.get(v));
// Ensure this is JSON serializable
JSON.stringify(jsonValue);
scopedVariables[v] = jsonValue;
} catch (e: any) {
console.error(
"Failed to JSON serialize variable",
v,
e,
);
throw new LuaRuntimeError(
`Failed to JSON serialize variable ${v} in query`,
sf,
);
}
}
return (await global.get("datastore").get("query_lua").call(
sf,
[
"idx",
tagName,
],
query,
scopedVariables,
)).toJSArray();
},
};
},
};
}

View File

@ -42,6 +42,8 @@ import { plugPrefix } from "$common/spaces/constants.ts";
import { base64EncodedDataUrl } from "$lib/crypto.ts";
import type { ConfigContainer } from "../type/config.ts";
import { indexSyscalls } from "$common/syscalls/index.ts";
import { commandSyscalls } from "$common/syscalls/command.ts";
import { eventListenerSyscalls } from "$common/syscalls/event.ts";
const fileListInterval = 30 * 1000; // 30s
@ -122,6 +124,7 @@ export class ServerSystem extends CommonSystem {
this.system.registerSyscalls(
[],
eventSyscalls(this.eventHook),
eventListenerSyscalls(this),
spaceReadSyscalls(space, this.allKnownFiles),
assetSyscalls(this.system),
yamlSyscalls(),
@ -134,7 +137,8 @@ export class ServerSystem extends CommonSystem {
mqSyscalls(this.mq),
languageSyscalls(),
jsonschemaSyscalls(),
indexSyscalls(this.system),
indexSyscalls(this),
commandSyscalls(this),
luaSyscalls(),
templateSyscalls(this.ds),
dataStoreReadSyscalls(this.ds, this),

View File

@ -45,6 +45,8 @@ import { plugPrefix } from "$common/spaces/constants.ts";
import { jsonschemaSyscalls } from "$common/syscalls/jsonschema.ts";
import { luaSyscalls } from "$common/syscalls/lua.ts";
import { indexSyscalls } from "$common/syscalls/index.ts";
import { commandSyscalls } from "$common/syscalls/command.ts";
import { eventListenerSyscalls } from "$common/syscalls/event.ts";
const plugNameExtractRegex = /\/(.+)\.plug\.js$/;
@ -152,6 +154,7 @@ export class ClientSystem extends CommonSystem {
this.system.registerSyscalls(
[],
eventSyscalls(this.eventHook),
eventListenerSyscalls(this),
editorSyscalls(this.client),
spaceReadSyscalls(this.client),
systemSyscalls(this.system, false, this, this.client, this.client),
@ -163,7 +166,8 @@ export class ClientSystem extends CommonSystem {
clientCodeWidgetSyscalls(),
languageSyscalls(),
jsonschemaSyscalls(),
indexSyscalls(this.system),
indexSyscalls(this),
commandSyscalls(this),
luaSyscalls(),
this.client.syncMode
// In sync mode handle locally

View File

@ -1,5 +1,5 @@
This describes the APIs available in [[Space Lua]]
${template.each(query[[
from tag("page") where string.startswith(name, "API/")
from index.tag("page") where string.startswith(name, "API/")
]], render.page)}

14
website/API/command.md Normal file
View File

@ -0,0 +1,14 @@
APIs related to editor commands
### command.define(command_def)
Registers a command.
Example:
```lua
command.define {
name = "My custom command",
run = function()
editor.flash_notification "Triggered my custom command"
end
}
```

View File

@ -4,13 +4,25 @@ The Event API provides functions for working with SilverBullet's event bus syste
## Event Operations
### event.dispatch_event(event_name, data, timeout)
### event.listen(listener_def)
Register an event listener.
```lua
event.listen {
name = "my-event",
run = function(e)
print("Data", e.data)
end
}
```
### event.dispatch(event_name, data, timeout)
Triggers an event on the SilverBullet event bus. Event handlers can return values, which are accumulated and returned to the caller.
Example:
```lua
-- Simple event dispatch
event.dispatch_event("custom.event", {message = "Hello"})
event.dispatch("custom.event", {message = "Hello"})
-- Event dispatch with timeout and response handling
local responses = event.dispatch_event("data.request", {id = 123}, 5000)

View File

@ -201,23 +201,3 @@ rawset(t, "foo", "bar") -- bypasses the metamethod
print(t.foo) -- prints: "bar"
```
# Space Lua specific
## tag(name)
Returns a given [[Objects#Tags]] as a query collection, to be queried using [[Space Lua/Lua Integrated Query]].
Example:
${query[[from tag("page") limit 1]]}
## tpl(template)
Returns a template function that can be used to render a template. Conventionally, a template string is put between `[==[` and `]==]` as string delimiters.
Example:
```space-lua
examples = examples or {}
examples.say_hello = tpl[==[Hello ${name}!]==]
```
And its use: ${examples.say_hello {name="Pete"}}

View File

@ -2,7 +2,14 @@ The `index` API provides functions for interacting with SilverBullet's [[Objects
## Object Operations
### index.index_objects(page, objects)
## index.tag(name)
Returns a given [[Objects#Tags]] as a query collection, to be queried using [[Space Lua/Lua Integrated Query]].
Example:
${query[[from index.tag("page") limit 1]]}
## index.index_objects(page, objects)
Indexes an array of objects for a specific page.
Example:
@ -14,7 +21,7 @@ local objects = {
index.index_objects("my page", objects)
```
### index.query_lua_objects(tag, query, scoped_variables?)
## index.query_lua_objects(tag, query, scoped_variables?)
Queries objects using a Lua-based collection query.
Example:
@ -22,7 +29,7 @@ Example:
local tasks = index.query_lua_objects("mytask", {limit=3})
```
### index.get_object_by_ref(page, tag, ref)
## index.get_object_by_ref(page, tag, ref)
Retrieves a specific object by its reference.
Example:

View File

@ -1,10 +1,23 @@
Template functions that use the [[API/global#tpl(template)]] function.
Template functions that use the [[API/template#template.new(template)]] function.
## template.new(template)
Returns a template function that can be used to render a template. Conventionally, a template string is put between `[==[` and `]==]` as string delimiters.
Example:
```space-lua
examples = examples or {}
examples.say_hello = template.new[==[Hello ${name}!]==]
```
And its use: ${examples.say_hello {name="Pete"}}
## template.each(collection, template)
Iterates over a collection and renders a template for each item.
Example:
${template.each(query[[from tag "page" limit 3]], tpl[==[
${template.each(query[[from index.tag "page" limit 3]], template.new[==[
* ${name}
]==])}

View File

@ -1,8 +0,0 @@
Defines some core useful templates for use in [[Space Lua]]
```space-lua
render = render or {}
render.page = tpl[==[
* [[${name}]]
]==]
```

View File

@ -60,7 +60,7 @@ For example: 10 + 2 = ${adder(10, 2)} (Alt-click, or select to see the expressio
Space Lua has a feature called [[Space Lua/Lua Integrated Query]], which integrate SQL-like queries into Lua. By using this feature, you can easily replicate [[Live Queries]]. More detail in [[Space Lua/Lua Integrated Query]], but heres a small example querying the last 3 modifies pages:
${query[[
from tag "page"
from index.tag "page"
order by lastModified desc
select name
limit 3
@ -101,12 +101,12 @@ ${marquee "Finally, marqeeeeeeee!"}
Oh boy, the times we live in!
## Commands
Custom commands can be defined using `define_command`:
Custom commands can be defined using `command.define`:
```space-lua
define_command {
name = "Hello World";
function()
command.define {
name = "Hello World",
run = function()
editor.flash_notification "Hello world!"
event.dispatch("my-custom-event", {name="Pete"})
end
@ -116,15 +116,14 @@ define_command {
Try it: {[Hello World]}
## Event listeners
You can listen to events using `define_event_listener`:
You can listen to events using `event.listen`:
```space-lua
define_event_listener {
event = "my-custom-event";
function(e)
event.listen {
name = "my-custom-event";
run = function(e)
editor.flash_notification("Custom triggered: "
.. e.data.name
.. " on page " .. _CTX.pageMeta.name)
.. e.data.name)
end
}
```
@ -138,7 +137,6 @@ Space Lua currently introduces a few new features on top core Lua:
## Thread locals
Theres a magic `_CTX` global variable available from which you can access useful context-specific values. Currently the following keys are available:
* `_CTX.pageMeta` contains a reference to the loaded page metadata (can be `nil` when not yet loaded)
* `_CTX.GLOBAL` providing access to the global scope
# API

View File

@ -19,10 +19,10 @@ Unlike [[Query Language]] which operates on [[Objects]] only, LIQ can operate on
For instance, to sort a list of numbers in descending order:
${query[[from n = {1, 2, 3} order by n desc]]}
However, in most cases youll use it in conjunction with [[../API/global#tag(name)]]. Heres an example querying the 3 pages that were last modified:
However, in most cases youll use it in conjunction with [[API/index#index.tag(name)]]. Heres an example querying the 3 pages that were last modified:
${query[[
from p = tag "page"
from p = index.tag "page"
order by p.lastModified desc
select p.name
limit 3
@ -52,8 +52,8 @@ ${query[[from {1, 2, 3} select _]]}
With variable binding:
${query[[from n = {1, 2, 3} select n]]}
A more realistic example using `tag`:
${query[[from tag "page" order by lastModified select name limit 3]]}
A more realistic example using `index.tag`:
${query[[from index.tag "page" order by lastModified select name limit 3]]}
## `where <expression>`
The `where` clause allows you to filter data. When the expression evaluated to a truthy value, the item is included in the result.
@ -64,14 +64,14 @@ ${query[[from {1, 2, 3, 4, 5} where _ > 2]]}
Or to select all pages tagged with `#meta`:
${query[[from tag "page" where table.includes(tags, "meta")]]}
${query[[from index.tag "page" where table.includes(tags, "meta")]]}
## `order by <expression> [desc]`
The `order by` clause allows you to sort data, when `desc` is specified it reverts the sort order.
As an example, the last 3 modified pages:
${query[[
from tag "page"
from index.tag "page"
order by lastModified desc
select name
limit 3
@ -97,11 +97,11 @@ Double each number:
${query[[from {1, 2, 3} select _ * 2]]}
Extract just the name from pages:
${query[[from tag "page" select _.name limit 3]]}
${query[[from index.tag "page" select _.name limit 3]]}
You can also return tables or other complex values:
${query[[
from p = tag "page"
from p = index.tag "page"
select {
name = p.name,
modified = p.lastModified