Types

Sessel has eleven user-facing types plus a family of Temporal types for date and time. All values belong to exactly one type at any point in time.

Type summary

Type Literal syntax Description
Number 42, 3.14, -7 Integers and decimals
String "hello", "" Text values
Boolean true, false Logical values
Null null Absence of a value
List [1, 2, 3], [] Ordered collections
Dictionary { name: "Widget" }, {} Key-value pairs
Element ${h1}.first(), new p { text: "Hello" } An HTML element
Temporal Temporal.PlainDate.from("2026-03-23") Date and time values
Instance new Project { slug: "launch" } A schema-backed instance with typed properties
Class @schema Project url(...) A schema class object with search and construct
Selector new Selector { "h1.title" } A CSS selector value that can execute scans

Types in detail

Number

Integers (42, -7) and decimals (3.14) are both Numbers. Sessel exposes them as a single type to expressions, though the underlying representation tracks integer vs. float precision. Use .Integer() to force integer parsing and .Float() to force decimal parsing.

See Number for details.

String

Text values written with double quotes: "hello", "Untitled", "". Strings support length checks, substring matching, prefix/suffix tests, and regex matching via methods.

See String for details.

Boolean

One of exactly two values: true or false. Produced by comparison and logical operators, and by methods such as .all(), .any(), .contains(), .startsWith(), .endsWith(), and .matches().

Null

The value null represents the absence of a result. Null arises when an operation cannot produce a value — for example, calling .first() on an empty list, or calling .Number() on text that is not a valid number. Null propagates through method chains until a ?? fallback provides a replacement.

List

An ordered collection of zero or more values, written with square brackets: [1, 2, 3], ["a", "b"], []. Lists can contain any type, including nested lists and dictionaries.

CSS selector expressions (${selector}) always return a List, even when only one element matches. Use .first(), .last(), or .at(n) to extract a single element.

See List for details.

Dictionary

A set of key-value pairs, written with curly braces: { name: "Widget", price: 42 }. Keys are always strings. Values can be any type. Keys can be bare identifiers or quoted strings — use quotes when the key contains hyphens or spaces.

See Dictionary for details.

Element

An HTML element. Elements come from two sources:

Both variants support the same element methods (.text(), .value(), .attr(name)).

See Element for details.

Temporal

Date and time values constructed via the Temporal.* namespace. Sessel provides eight Temporal types that mirror JavaScript's Temporal API:

Type Construction
Temporal.PlainDate Temporal.PlainDate.from("2026-03-23")
Temporal.PlainTime Temporal.PlainTime.from("14:30:00")
Temporal.PlainDateTime Temporal.PlainDateTime.from("2026-03-23T14:30:00")
Temporal.Instant Temporal.Instant.from("2026-03-23T14:30:00Z")
Temporal.ZonedDateTime Temporal.ZonedDateTime.from("2026-03-23T14:30:00[Europe/London]")
Temporal.PlainYearMonth Temporal.PlainYearMonth.from("2026-03")
Temporal.PlainMonthDay Temporal.PlainMonthDay.from("--03-23")
Temporal.Duration Temporal.Duration.from("P1Y2M3DT4H5M6S")

All Temporal values are truthy. String(date) returns the ISO 8601 representation. Use isa Temporal.PlainDate (or isa Temporal for any Temporal type) for type checking.

See Temporal for the full reference.

Instance

A schema-backed object with typed properties and methods. Instances are created by constructing a schema type with new:

@schema Project url("https://example.com/Project");

let p = new Project { slug: "launch", name: "Product Launch" };
p.name        // "Product Launch"
p isa Project // true

Instances are microdata elements under the hood — they carry an itemtype and itemprop children — but property access goes through the schema's type system, including @read resolvers, type validation, and cardinality rules. You can also write properties (p.status = "archived") and call schema-defined methods (p.escalate(2)).

Because an instance is an element, Element methods (.attr(), .children(), .parent(), .text(), and the rest) work on it directly. An instance's body can also include positional element children alongside its properties; those, and the elements generated for scalar properties, all become element children of the instance and can walk back up to it via .parent():

@schema Foo url("https://example.com/Foo");

let f = new Foo { new span { } };
f.children().first().parent().attr("itemtype")   // "https://example.com/Foo" — the child reaches the instance

(new Foo { count: 5 }).children().first().parent().attr("itemtype")   // "https://example.com/Foo" — a scalar property's child reaches it too

Class

A class object represents a schema type as a first-class value. Class values are produced by @schema declarations:

@schema UserConfig url("https://example.com/UserConfig");

UserConfig                // the class object itself
UserConfig isa Class      // true

Class objects expose three capabilities:

.search(criteria?, options?)

Returns a List of Instance values matching the given criteria.

criteria — optional. Controls which instances are returned.

Form Meaning
omitted or null All instances of this type.
Map AND filter — every key/value pair must match.
List<Map> OR filter — instances matching any of the Maps are returned.

Each Map entry becomes a :has([itemprop="key"]:value-equals("value")) CSS predicate. Values may be strings, integers, floats, or booleans.

options — optional Map. Recognized keys:

Key Type Default Meaning
"isa" Bool false When true, includes instances of the target type and every schema with the target in its parent chain (reflexive polymorphic search).

Unknown keys raise a TypeError. "isa" must be quoted because isa is a reserved keyword in Sessel.

Return value: List<Instance>. When "isa" is true, the list is heterogeneous — each instance carries its actual $schema, not the queried type.

CSS equivalent: For polymorphic type matching in raw CSS selectors (HTTP Range headers, resource bindings, ${} literals), use the :isa() pseudo-class instead: :isa('https://example.com/UserConfig').

@schema UserConfig url("https://example.com/UserConfig");

// All instances
UserConfig.search()

// AND filter: org-id must be "abc"
UserConfig.search({ "org-id": "abc" })

// OR filter: match hostname OR alias
UserConfig.search([{ hostname: "foo.com" }, { alias: "foo.com" }])

// Polymorphic: UserConfig + all descendant types
UserConfig.search({}, { "isa": true })

// OR + polymorphic
UserConfig.search([{ hostname: "x" }, { alias: "x" }], { "isa": true })

// Construct an instance from a variable class reference
let cls = UserConfig;
cls.construct({ "org-id": "abc", theme: "dark" })

Selector

A CSS selector wrapped in a value. Unlike the ${...} selector literal (which executes immediately), a Selector is inert until you call .execute().

let sel = new Selector { "h1.title" };
sel.toString()              // "h1.title"
sel.execute()               // same as ${h1.title} from self
sel.execute("/about.html")  // same as ${h1.title} from "/about.html"

The Selector type is useful when you need to build or pass selectors as data — for example, storing a selector in a variable and executing it later against different documents. The .execute() method accepts an optional path argument (string or list of strings) that works like the from clause.

Document

A Document is a special Element returned by Pagelove.GET() for HTML resources. It extends Element with metadata from storage.

Method Description
.metadata() Storage metadata as Dictionary (mimetype, etag, created, modified, size, version)
All Element methods Inherited

Static method: Document.parse(htmlString) parses an HTML string into a mutable ConstructedElement tree. The result can be inspected, modified, and used in composition like any other constructed element.

let el = Document.parse("<div class='card'><p>Hello</p></div>");
el.${p}.first()?.text()    // "Hello"
el.attr("class")            // "card"

Document.parse() is the controlled entry point for HTML strings from external sources.

Blob

Returned by Pagelove.GET() for non-HTML resources (images, scripts, etc.). Not an Element — has no DOM tree.

Method Description
.metadata() Storage metadata as Dictionary
.path() Resource path

Reflection API

Reflection capabilities are accessed through the Sessel schema, explicitly imported:

@schema Sessel url("https://pagelove.org/Sessel");

Sessel.stored(self, "name")      // raw itemprop read, bypasses dispatch
Sessel.properties(self)           // list all itemprop names
Sessel.schemaOf(self)            // schema URL string
Method Description
Sessel.stored(element, propName) Reads the raw itemprop value, bypassing the dispatch chain (no schema methods, no resolvers, no computed properties). Returns null if no itemprop exists.
Sessel.properties(element) Returns a List of all itemprop names on the element.
Sessel.schemaOf(element) Returns the schema URL string (itemtype attribute) or null.

Reflection is deliberately not built into the language surface. The explicit @schema import signals that meta-level operations are being performed.

Truthiness

Conditions in if expressions, the ternary operator (? :), and logical operators (&&, ||) test for truthiness. Only these values are falsy:

Value Type
false Boolean
null Null
0 Number (integer zero)
0.0 Number (float zero)
"" String (empty)
[] List (empty)
{} Dictionary (empty)

Everything else is truthy, including non-zero numbers, non-empty strings, non-empty lists, non-empty dictionaries, all elements, all Temporal values, all Instances, all Class objects, and all Selectors.

Type coercion

Methods that convert between types return null if the conversion fails. They never throw.

Method Input Output Notes
.Number() String Number or null Smart parse: "42" returns integer, "3.14" returns float
.Integer() String, Float Integer or null Truncates floats toward zero; fails if string has a decimal part
.Float() String, Integer Float or null Always returns a float, even for "42"
.String() Any String Always succeeds
.Bool() Any Boolean Converts using truthiness rules
${[itemprop="price"]}.first().value().Number()   // "3.99" → 3.99 (float), "42" → 42 (integer)
${[itemprop="count"]}.first().value().Integer()  // "5" → 5, "5.0" → null
${[itemprop="rating"]}.first().value().Float()   // "42" → 42.0 (always float)
${div.item}.count().String()                   // 4 → "4"
0.Bool()                                       // false
"hello".Bool()                                 // true

Null propagates through coercion — if the input to .Number() is already null, the output is also null. Use ?? to provide a fallback:

${#count}.first().value().Number() ?? 0

Type Namespaces

The capitalized type names Number, Integer, Float, String, Bool, Null, Element, List, and Map are first-class values in Sessel. They provide access to static methods.

Static methods

Type namespaces expose static methods via dot notation:

Integer.random(1, 100)       // random integer in [1, 100]
Float.random(0.0, 1.0)       // random float in [0.0, 1.0)
Number.random(1, 10)          // infers Integer or Float from argument types

See Number for details on random number generation.

Name resolution

Type namespace names are resolved after variables and lambda bindings. A let binding shadows the type name:

let String = "hello";
String                         // "hello" (the variable, not the type namespace)