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:
- Queried elements — obtained via a CSS selector expression such as
${h1}.first(). These are immutable snapshots from the document store. - Constructed elements — built with
new p { text: "Hello" }. These are mutable and can be modified with setter methods before use.
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?)— find all instances of this type across the document store..construct(properties)— create a new instance programmatically. Equivalent tonew UserConfig { ... }but works when the class is held in a variable.- Static methods — user-defined methods marked
static: trueon the schema are called on the class object rather than on instances.
.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)