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