1use std::collections::BTreeMap;
14
15use crate::{
16 Dispatch, PackageDeclaration, Param, PropertySig, Span, Type, TypeKind, TypeSymbol, ValueKind,
17 ValueSymbol,
18};
19
20use super::{MAX_LIST_LIMIT, MODULE_NAME};
21
22const SESSION_GET: &str = "get";
26
27pub fn is_checked_get(mangled: &crate::MangledName) -> bool {
34 *mangled == crate::mangle::package_symbol(MODULE_NAME, SESSION_GET)
35}
36
37const GET_TYPE_PARAM: &str = "T";
39
40pub fn package_declaration() -> PackageDeclaration {
41 let mut defs = PackageDeclaration::with_package(MODULE_NAME);
42 insert_entry_interface(&mut defs);
43 insert_page_interface(&mut defs);
44 insert_generic_fn(
45 &mut defs,
46 SESSION_GET,
47 vec![GET_TYPE_PARAM.to_string()],
48 vec![Param::new("key", Type::String)],
49 Type::union(vec![
50 Type::TypeVar(GET_TYPE_PARAM.to_string()),
51 Type::Undefined,
52 ]),
53 "/**\n * Read a session value, checked against `T`. The stored value is tested \
54 structurally — every field, element, and union arm — and a mismatch throws a \
55 catchable `TypeError` rather than handing back a wrongly-typed value, so \
56 `get<Progress>(\"progress\")` returns a real `Progress`, `undefined` for a missing key, or throws.\n *\n \
57 * Returns `T | undefined`: a missing key returns `undefined`; a stored `null` \
58 is preserved when `T` permits it and otherwise throws a `TypeError`.\n *\n * `T` must be a type the runtime can \
59 verify: object, array, tuple, union, and primitive shapes. A bare `unknown`, a \
60 class, or an interface with methods is rejected at compile time. A generic \
61 may pass its own type parameter, or a type built from it (`get<T>` or \
62 `get<T[]>` inside `function load<T>`): the stored value is checked against \
63 the type its caller names.\n * @param key \
64 Session key. Exact UTF-16 \
65 code units; no normalization, no path semantics.\n * @capability session.read \
66 { key: $key }\n */",
67 );
68 defs.runtime_generics
71 .insert(crate::mangle::package_symbol(MODULE_NAME, SESSION_GET));
72 insert_fn(
73 &mut defs,
74 "has",
75 vec![Param::new("key", Type::String)],
76 Type::Boolean,
77 "/**\n * Whether the session holds an entry for `key`. A stored `null` is an \
78 entry and answers `true`.\n * @param key Session key.\n * @capability \
79 session.read { key: $key }\n */",
80 );
81 insert_fn(
82 &mut defs,
83 "set",
84 vec![
85 Param::new("key", Type::String),
86 Param::new("value", Type::Unknown),
87 ],
88 Type::Void,
89 "/**\n * Store `value` under `key`, replacing any previous entry. `value` must \
90 be JSON-compatible data — `null`, a boolean, a finite number, a string, an \
91 array, or a plain object of those. A function, a `Map`/`Set`, a host handle, \
92 or a value reachable from itself is rejected and the previous entry is left \
93 intact. Traps if the value or the session exceeds its configured size \
94 limits.\n * @param key Session key.\n * @param value The data to store.\n * \
95 @capability session.write { key: $key }\n */",
96 );
97 insert_fn(
98 &mut defs,
99 "remove",
100 vec![Param::new("key", Type::String)],
101 Type::Boolean,
102 "/**\n * Delete `key`. Returns `true` when an entry existed.\n * @param key \
103 Session key.\n * @capability session.remove { key: $key }\n */",
104 );
105 insert_fn(
106 &mut defs,
107 "list",
108 vec![
109 Param::new("prefix", Type::String),
110 Param::new("limit", Type::Number),
111 Param::with_default(
112 "cursor",
113 Type::union(vec![Type::String, Type::Undefined]),
114 crate::DefaultValue::Undefined,
115 ),
116 ],
117 page_type(),
118 &format!(
119 "/**\n * Enumerate session keys starting with `prefix`, in UTF-16 code-unit \
120 order. Returns metadata only — `key` and `sizeBytes` — never the stored \
121 values; read one with `get`. Pass `\"\"` to list every key.\n *\n * Each \
122 page holds at most `limit` entries. `nextCursor` is a `string` when the \
123 listing is unfinished and `undefined` only when no further matching key remains, \
124 so page until it is `undefined` rather than until a page is short: a page can be \
125 short because the scan bound was reached, not because the keys ran out. Pass \
126 the previous page's `nextCursor` back unchanged; a cursor minted for a \
127 different prefix, or one this runtime did not issue, throws.\n *\n * Each \
128 page reflects the store as it is when the page is read, not a snapshot: a \
129 key written behind a cursor is not seen until the listing restarts.\n *\n * \
130 Keys the policy does not permit reading are omitted, and neither the entry \
131 count nor the cursor reveals them.\n * @param prefix Key prefix, matched as \
132 exact UTF-16 code units — no normalization, no path semantics. `\"\"` \
133 matches every key.\n * @param limit Maximum entries in the page; 1 to \
134 {MAX_LIST_LIMIT}. Outside that range throws.\n * @param cursor The previous \
135 page's `nextCursor`; omit it to start at the first key.\n * @capability \
136 session.list {{ prefix: $prefix }}\n * @capability \
137 session.read {{ key: $key }} per candidate key\n */"
138 ),
139 );
140 insert_fn(
141 &mut defs,
142 "registerFinalizer",
143 vec![
144 Param::new("key", Type::String),
145 Param::new("value", Type::Unknown),
146 ],
147 Type::Void,
148 "/** Register durable cleanup using this package's finalizer(key, value). Packages only. Keys are automatically namespaced by the engine; duplicate live keys are rejected.\n * @capability session.finalize { key: $key }\n */",
149 );
150 insert_fn(
151 &mut defs,
152 "unregisterFinalizer",
153 vec![Param::new("key", Type::String)],
154 Type::Void,
155 "/** Forget cleanup after releasing the resource. Packages only; keys are local to the calling package.\n * @capability session.finalize { key: $key }\n */",
156 );
157 defs
158}
159
160fn entry_type() -> Type {
161 Type::InterfaceRef {
162 mangled: crate::mangle::package_symbol(MODULE_NAME, "Entry"),
163 package: crate::Package(MODULE_NAME.to_string()),
164 name: "Entry".to_string(),
165 args: Vec::new(),
166 }
167}
168
169fn page_type() -> Type {
170 Type::InterfaceRef {
171 mangled: crate::mangle::package_symbol(MODULE_NAME, "Page"),
172 package: crate::Package(MODULE_NAME.to_string()),
173 name: "Page".to_string(),
174 args: Vec::new(),
175 }
176}
177
178fn insert_entry_interface(defs: &mut PackageDeclaration) {
179 let mut properties = BTreeMap::new();
180 insert_property(
181 &mut properties,
182 "key",
183 Type::String,
184 "/** The session key, in the exact code units it was stored under. */",
185 );
186 insert_property(
187 &mut properties,
188 "sizeBytes",
189 Type::Number,
190 "/** Serialized size of the stored value in bytes — the value alone, not the key and not the store's per-entry overhead. */",
191 );
192 insert_interface(
193 defs,
194 "Entry",
195 properties,
196 "/** One key a `list` page discloses. Carries the key and the value's size; the value itself is never part of a listing — call `get(entry.key)` for it. */",
197 );
198}
199
200fn insert_page_interface(defs: &mut PackageDeclaration) {
201 let mut properties = BTreeMap::new();
202 insert_property(
203 &mut properties,
204 "entries",
205 Type::Array(Box::new(entry_type())),
206 "/** The keys this page discloses, in UTF-16 code-unit order. May be shorter than `limit`, or empty, while `nextCursor` is still defined. */",
207 );
208 insert_property(
209 &mut properties,
210 "nextCursor",
211 Type::union(vec![Type::String, Type::Undefined]),
212 "/** Opaque cursor for the next page, or `undefined` when no further matching key remains. Pass it back to `list` unchanged, with the same prefix. */",
213 );
214 insert_interface(
215 defs,
216 "Page",
217 properties,
218 "/** One page of a `list` call: the `entries` it discloses and the `nextCursor` that continues it. Returned by `list`. */",
219 );
220}
221
222fn insert_interface(
223 defs: &mut PackageDeclaration,
224 name: &str,
225 properties: BTreeMap<String, PropertySig>,
226 doc: &str,
227) {
228 defs.types.insert(
229 name.to_string(),
230 TypeSymbol {
231 name: name.to_string(),
232 mangled_name: crate::mangle::package_symbol(MODULE_NAME, name),
233 declaration_span: Span::at(crate::FileId::SESSION),
234 kind: TypeKind::Interface {
235 index: None,
236 generics: Default::default(),
237 methods: BTreeMap::new(),
238 properties,
239 dispatch: Dispatch::VTable,
240 doc: crate::doc(crate::FileId::SESSION, doc),
241 },
242 },
243 );
244}
245
246fn insert_property(
247 properties: &mut BTreeMap<String, PropertySig>,
248 name: &str,
249 ty: Type,
250 doc: &str,
251) {
252 properties.insert(
253 name.to_string(),
254 PropertySig {
255 ty,
256 readonly: true,
257 optional: false,
258 intrinsic: false,
259 doc: crate::doc(crate::FileId::SESSION, doc),
260 },
261 );
262}
263
264fn insert_fn(defs: &mut PackageDeclaration, name: &str, params: Vec<Param>, ret: Type, doc: &str) {
265 insert_generic_fn(defs, name, Vec::new(), params, ret, doc);
266}
267
268fn insert_generic_fn(
269 defs: &mut PackageDeclaration,
270 name: &str,
271 generics: Vec<String>,
272 params: Vec<Param>,
273 ret: Type,
274 doc: &str,
275) {
276 defs.values.insert(
277 name.to_string(),
278 ValueSymbol {
279 name: name.to_string(),
280 mangled_name: crate::mangle::package_symbol(MODULE_NAME, name),
281 declaration_span: Span::at(crate::FileId::SESSION),
282 kind: ValueKind::Function {
283 generics: crate::GenericParameters::unconstrained(generics),
284 params,
285 ret,
286 type_predicate: None,
287 doc: crate::doc(crate::FileId::SESSION, doc),
288 },
289 },
290 );
291}