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