Changeset

12979:fbbf4f0db8f0

teal: Move into prosody namespace
author Kim Alvefur <zash@zash.se>
date Thu, 23 Mar 2023 13:36:52 +0100
parents 12978:088d278c75b5
children 12980:6ebad8e16b3b
files GNUmakefile teal-src/core/storagemanager.d.tl teal-src/core/usermanager.d.tl teal-src/module.d.tl teal-src/net/http.d.tl teal-src/net/http/codes.d.tl teal-src/net/http/errors.d.tl teal-src/net/http/files.d.tl teal-src/net/http/parser.d.tl teal-src/net/http/server.d.tl teal-src/net/server.d.tl teal-src/plugins/mod_cron.tl teal-src/plugins/muc/muc.lib.d.tl teal-src/prosody/core/storagemanager.d.tl teal-src/prosody/core/usermanager.d.tl teal-src/prosody/net/http.d.tl teal-src/prosody/net/http/codes.d.tl teal-src/prosody/net/http/errors.d.tl teal-src/prosody/net/http/files.d.tl teal-src/prosody/net/http/parser.d.tl teal-src/prosody/net/http/server.d.tl teal-src/prosody/net/server.d.tl teal-src/prosody/plugins/mod_cron.tl teal-src/prosody/plugins/muc/muc.lib.d.tl teal-src/prosody/util/array.d.tl teal-src/prosody/util/async.d.tl teal-src/prosody/util/bitcompat.d.tl teal-src/prosody/util/compat.d.tl teal-src/prosody/util/crand.d.tl teal-src/prosody/util/crypto.d.tl teal-src/prosody/util/dataforms.d.tl teal-src/prosody/util/datamapper.tl teal-src/prosody/util/datetime.d.tl teal-src/prosody/util/encodings.d.tl teal-src/prosody/util/error.d.tl teal-src/prosody/util/format.d.tl teal-src/prosody/util/hashes.d.tl teal-src/prosody/util/hex.d.tl teal-src/prosody/util/http.d.tl teal-src/prosody/util/human/io.d.tl teal-src/prosody/util/human/units.d.tl teal-src/prosody/util/id.d.tl teal-src/prosody/util/interpolation.d.tl teal-src/prosody/util/ip.d.tl teal-src/prosody/util/jid.d.tl teal-src/prosody/util/json.d.tl teal-src/prosody/util/jsonpointer.tl teal-src/prosody/util/jsonschema.tl teal-src/prosody/util/jwt.d.tl teal-src/prosody/util/logger.d.tl teal-src/prosody/util/mathcompat.tl teal-src/prosody/util/net.d.tl teal-src/prosody/util/poll.d.tl teal-src/prosody/util/pposix.d.tl teal-src/prosody/util/promise.d.tl teal-src/prosody/util/queue.d.tl teal-src/prosody/util/random.d.tl teal-src/prosody/util/ringbuffer.d.tl teal-src/prosody/util/roles.d.tl teal-src/prosody/util/serialization.d.tl teal-src/prosody/util/set.d.tl teal-src/prosody/util/signal.d.tl teal-src/prosody/util/smqueue.tl teal-src/prosody/util/stanza.d.tl teal-src/prosody/util/strbitop.d.tl teal-src/prosody/util/struct.d.tl teal-src/prosody/util/table.d.tl teal-src/prosody/util/termcolours.d.tl teal-src/prosody/util/time.d.tl teal-src/prosody/util/timer.d.tl teal-src/prosody/util/uuid.d.tl teal-src/prosody/util/xtemplate.tl teal-src/util/array.d.tl teal-src/util/async.d.tl teal-src/util/bitcompat.d.tl teal-src/util/compat.d.tl teal-src/util/crand.d.tl teal-src/util/crypto.d.tl teal-src/util/dataforms.d.tl teal-src/util/datamapper.tl teal-src/util/datetime.d.tl teal-src/util/encodings.d.tl teal-src/util/error.d.tl teal-src/util/format.d.tl teal-src/util/hashes.d.tl teal-src/util/hex.d.tl teal-src/util/http.d.tl teal-src/util/human/io.d.tl teal-src/util/human/units.d.tl teal-src/util/id.d.tl teal-src/util/interpolation.d.tl teal-src/util/ip.d.tl teal-src/util/jid.d.tl teal-src/util/json.d.tl teal-src/util/jsonpointer.tl teal-src/util/jsonschema.tl teal-src/util/jwt.d.tl teal-src/util/logger.d.tl teal-src/util/mathcompat.tl teal-src/util/net.d.tl teal-src/util/poll.d.tl teal-src/util/pposix.d.tl teal-src/util/promise.d.tl teal-src/util/queue.d.tl teal-src/util/random.d.tl teal-src/util/ringbuffer.d.tl teal-src/util/roles.d.tl teal-src/util/serialization.d.tl teal-src/util/set.d.tl teal-src/util/signal.d.tl teal-src/util/smqueue.tl teal-src/util/stanza.d.tl teal-src/util/strbitop.d.tl teal-src/util/struct.d.tl teal-src/util/table.d.tl teal-src/util/termcolours.d.tl teal-src/util/time.d.tl teal-src/util/timer.d.tl teal-src/util/uuid.d.tl teal-src/util/xtemplate.tl
diffstat 120 files changed, 2692 insertions(+), 2692 deletions(-) [+]
line wrap: on
line diff
--- a/GNUmakefile	Fri Mar 17 19:38:39 2023 +0100
+++ b/GNUmakefile	Thu Mar 23 13:36:52 2023 +0100
@@ -135,7 +135,7 @@
 	@echo $$(sed -n '/^\tlocal exclude_files/,/^}/p;' .luacheckrc | sed '1d;$d' | wc -l) files ignored
 	shellcheck configure
 
-vpath %.tl teal-src/
+vpath %.tl teal-src/prosody
 %.lua: %.tl
 	tl -I teal-src/ --gen-compat off --gen-target 5.1 gen $^ -o $@
 	-lua-format -i $@
--- a/teal-src/core/storagemanager.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
--- Storage local record API Description
---
--- This is written as a TypedLua description
-
--- Key-Value stores (the default)
-
-local stanza = require"util.stanza".stanza_t
-
-local record keyval_store
-	get : function ( keyval_store, string ) : any , string
-	set : function ( keyval_store, string, any ) : boolean, string
-end
-
--- Map stores (key-key-value stores)
-
-local record map_store
-	get : function ( map_store, string, any ) : any, string
-	set : function ( map_store, string, any, any ) : boolean, string
-	set_keys : function ( map_store, string, { any : any }) : boolean, string
-	remove : table
-end
-
--- Archive stores
-
-local record archive_query
-	start  : number -- timestamp
-	["end"]: number -- timestamp
-	with   : string
-	after  : string -- archive id
-	before : string -- archive id
-	total  : boolean
-end
-
-local record archive_store
-	-- Optional set of capabilities
-	caps   : {
-		-- Optional total count of matching items returned as second return value from :find()
-		string : any
-	}
-
-	-- Add to the archive
-	append : function ( archive_store, string, string, any, number, string ) : string, string
-
-	-- Iterate over archive
-	type iterator = function () : string, any, number, string
-	find   : function ( archive_store, string, archive_query ) : iterator, integer
-
-	-- Removal of items. API like find. Optional
-	delete : function ( archive_store, string, archive_query ) : boolean | number, string
-
-	-- Array of dates which do have messages (Optional)
-	dates  : function ( archive_store, string ) : { string }, string
-
-	-- Map of counts per "with" field
-	summary : function ( archive_store, string, archive_query ) : { string : integer }, string
-
-	-- Map-store API
-	get    : function ( archive_store, string, string ) : stanza, number, string
-	get    : function ( archive_store, string, string ) : nil, string
-	set    : function ( archive_store, string, string, stanza, number, string ) : boolean, string
-end
-
--- This represents moduleapi
-local record coremodule
-	-- If the first string is omitted then the name of the module is used
-	-- The second string is one of "keyval" (default), "map" or "archive"
-	open_store : function (archive_store, string, string) : keyval_store, string
-	open_store : function (archive_store, string, string) : map_store, string
-	open_store : function (archive_store, string, string) : archive_store, string
-
-	-- Other module methods omitted
-end
-
-return coremodule
--- a/teal-src/core/usermanager.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-local Role = require "util.roles".Role;
-
-local record usermanager
-	record AuthProvider
-		-- TODO
-	end
-	record AccountInfo
-		created : number
-		password_updated : any
-		enabled : boolean
-	end
-
-	-- Users
-	test_password : function (username : string, host : string, password : string) : boolean
-	get_password : function (username : string, host : string) : string, string
-	set_password : function (username : string, host : string, password : string) : boolean, string
-	get_account_info : function (username : string, host : string) : AccountInfo
-	user_exists : function (username : string, host : string) : boolean
-	create_user : function (username : string, password : string, host : string) : boolean, string
-	delete_user : function (username : string, host : string) : boolean, string
-	user_is_enabled : function (username : string, host : string) : boolean, string
-	enable_user : function (username : string, host : string) : boolean, string
-	disable_user : function (username : string, host : string) : boolean, string
-	users : function (host : string) : function () : string
-
-	-- Roles
-	get_user_role : function (username : string, host : string) : Role
-	set_user_role : function (username : string, host : string, role_name : string) : boolean, string
-	user_can_assume_role : function (username : string, host : string, role_name : string) : boolean
-	add_user_secondary_role : function (username : string, host: string, role_name : string) : boolean, string
-	remove_user_secondary_role : function (username : string, host: string, role_name : string) : boolean, string
-	get_user_secondary_roles : function (username : string, host : string) : { string : Role }
-	get_users_with_role : function (role : string, host : string) : { string }
-	get_jid_role : function (jid : string, host : string) : Role
-	set_jid_role : function (jid : string, host : string, role_name : string) : boolean
-	get_jids_with_role : function (role : string, host : string) : { string }
-	get_role_by_name : function (role_name : string) : Role
-
-	-- Etc
-	get_provider : function (host : string) : AuthProvider
-	get_sasl_handler : function (host : string, session : table) : table
-	initialize_host : function (host : string)
-	new_null_provider : function () : AuthProvider
-end
-
-return usermanager
--- a/teal-src/module.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ b/teal-src/module.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -1,4 +1,4 @@
-local st = require"util.stanza"
+local st = require "prosody.util.stanza"
 
 global record moduleapi
 	get_name : function (moduleapi) : string
--- a/teal-src/net/http.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-local Promise = require "util.promise".Promise;
-
-local record sslctx -- from LuaSec
-end
-
-local record lib
-
-	enum http_method
-		"GET"
-		"HEAD"
-		"POST"
-		"PUT"
-		"OPTIONS"
-		"DELETE"
-		-- etc?
-	end
-
-	record http_client_options
-		sslctx : sslctx
-	end
-
-	record http_options
-		id : string
-		onlystatus : boolean
-		body : string
-		method : http_method
-		headers : { string : string }
-		insecure : boolean
-		suppress_errors : boolean
-		streaming_handler : function
-		suppress_url : boolean
-		sslctx : sslctx
-	end
-
-	record http_request
-		host : string
-		port : string
-		enum scheme
-			"http"
-			"https"
-		end
-		scheme : scheme
-		url : string
-		userinfo : string
-		path : string
-
-		method : http_method
-		headers : { string : string }
-
-		insecure : boolean
-		suppress_errors : boolean
-		streaming_handler : function
-		http : http_client
-		time : integer
-		id : string
-		callback : http_callback
-	end
-
-	record http_response
-	end
-
-	type http_callback = function (string, number, http_response, http_request)
-
-	record http_client
-		options : http_client_options
-		request : function (http_client, string, http_options, http_callback)
-	end
-
-	request : function (string, http_options, http_callback) : Promise, string
-	default : http_client
-	new : function (http_client_options) : http_client
-	events : table
-	-- COMPAT
-	urlencode : function (string) : string
-	urldecode : function (string) : string
-	formencode : function ({ string : string }) : string
-	formdecode : function (string) : { string : string }
-	destroy_request : function (http_request)
-
-	enum available_features
-		"sni"
-	end
-	features : { available_features : boolean }
-end
-
-return lib
--- a/teal-src/net/http/codes.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-local type response_codes = { integer : string }
-return response_codes
--- a/teal-src/net/http/errors.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-local record http_errors
-	enum known_conditions
-		"cancelled"
-		"connection-closed"
-		"certificate-chain-invalid"
-		"certificate-verify-failed"
-		"connection failed"
-		"invalid-url"
-		"unable to resolve service"
-	end
-	type registry_keys = known_conditions | integer
-	record error
-		type : string
-		condition : string
-		code : integer
-		text : string
-	end
-	registry : { registry_keys : error }
-	new : function (integer, known_conditions, table)
-	new : function (integer, string, table)
-end
-return http_errors
--- a/teal-src/net/http/files.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-local record serve_options
-	path : string
-	mime_map : { string : string }
-	cache_size : integer
-	cache_max_file_size : integer
-	index_files : { string }
-	directory_index : boolean
-end
-
-local record http_files
-	serve : function(serve_options|string) : function
-end
-
-return http_files
--- a/teal-src/net/http/parser.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-local record httpstream
-	feed : function(httpstream, string)
-end
-
-local type sink_cb = function ()
-
-local record httppacket
-	enum http_method
-		"HEAD"
-		"GET"
-		"POST"
-		"PUT"
-		"DELETE"
-		"OPTIONS"
-		-- etc
-	end
-	method : http_method
-	record url_details
-		path : string
-		query : string
-	end
-	url : url_details
-	path : string
-	enum http_version
-		"1.0"
-		"1.1"
-	end
-	httpversion : http_version
-	headers : { string : string }
-	body : string | boolean
-	body_sink : sink_cb
-	chunked : boolean
-	partial : boolean
-end
-
-local enum error_conditions
-	"cancelled"
-	"connection-closed"
-	"certificate-chain-invalid"
-	"certificate-verify-failed"
-	"connection failed"
-	"invalid-url"
-	"unable to resolve service"
-end
-
-local type success_cb = function (httppacket)
-local type error_cb = function (error_conditions)
-
-local enum stream_mode
-	"client"
-	"server"
-end
-
-local record lib
-	new : function (success_cb, error_cb, stream_mode) : httpstream
-end
-
-return lib
--- a/teal-src/net/http/server.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-
-local record http_server
-	-- TODO
-end
-
-return http_server
--- a/teal-src/net/server.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-local record server
-	record LuaSocketTCP
-	end
-	record LuaSecCTX
-	end
-
-	record extra_settings
-	end
-
-	record interface
-	end
-	enum socket_type
-		"tcp"
-		"tcp6"
-		"tcp4"
-	end
-
-	record listeners
-		onconnect : function (interface)
-		ondetach : function (interface)
-		onattach : function (interface, string)
-		onincoming : function (interface, string, string)
-		ondrain : function (interface)
-		onreadtimeout : function (interface)
-		onstarttls : function (interface)
-		onstatus : function (interface, string)
-		ondisconnect : function (interface, string)
-	end
-
-	get_backend : function () : string
-
-	type port = string | integer
-	enum read_mode
-		"*a"
-		"*l"
-	end
-	type read_size = read_mode | integer
-	addserver : function (string, port, listeners, read_size, LuaSecCTX) : interface
-	addclient : function (string, port, listeners, read_size, LuaSecCTX, socket_type, extra_settings) : interface
-	record listen_config
-		read_size : read_size
-		tls_ctx : LuaSecCTX
-		tls_direct : boolean
-		sni_hosts : { string : LuaSecCTX }
-	end
-	listen : function (string, port, listeners, listen_config) : interface
-	enum quitting
-		"quitting"
-	end
-	loop : function () : quitting
-	closeall : function ()
-	setquitting : function (boolean | quitting)
-
-	wrapclient : function (LuaSocketTCP, string, port, listeners, read_size, LuaSecCTX, extra_settings) : interface
-	wrapserver : function (LuaSocketTCP, string, port, listeners, listen_config) : interface
-	watchfd : function (integer | LuaSocketTCP, function (interface), function (interface)) : interface
-	link : function ()
-
-	record config
-	end
-	set_config : function (config)
-
-end
-
-return server
--- a/teal-src/plugins/mod_cron.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-module:set_global();
-
-local async = require "util.async";
-local datetime = require "util.datetime";
-
-local record map_store<K,V>
-	-- TODO move to somewhere sensible
-	get : function (map_store<K,V>, string, K) : V
-	set : function (map_store<K,V>, string, K, V)
-end
-
-local enum frequency
-	"hourly"
-	"daily"
-	"weekly"
-end
-
-local record task_spec
-	id : string -- unique id
-	name : string -- name or short description
-	when : frequency
-	last : integer
-	run : function (task_spec, integer)
-	save : function (task_spec, integer)
-end
-
-local record task_event
-	source : module
-	item : task_spec
-end
-
-local periods : { frequency : integer } = { hourly = 3600, daily = 86400, weekly = 7*86400 }
-
-local active_hosts : { string : boolean } = {  }
-
-function module.add_host(host_module : moduleapi)
-
-	local last_run_times = host_module:open_store("cron", "map") as map_store<string,integer>;
-	active_hosts[host_module.host] = true;
-
-	local function save_task(task : task_spec, started_at : integer)
-		last_run_times:set(nil, task.id, started_at);
-	end
-
-	local function task_added(event : task_event) : boolean
-		local task = event.item;
-		if task.name == nil then
-			task.name = task.when;
-		end
-		if task.id == nil then
-			task.id = event.source.name .. "/" .. task.name:gsub("%W", "_"):lower();
-		end
-		if task.last == nil then
-			task.last = last_run_times:get(nil, task.id);
-		end
-		task.save = save_task;
-		module:log("debug", "%s task %s added, last run %s", task.when, task.id,
-			task.last and datetime.datetime(task.last) or "never");
-		if task.last == nil then
-			-- initialize new tasks so e.g. daily tasks run at ~midnight UTC for now
-			local now = os.time();
-			task.last = now - now % periods[task.when];
-		end
-		return true;
-	end
-
-	local function task_removed(event : task_event) : boolean
-		local task = event.item;
-		host_module:log("debug", "Task %s removed", task.id);
-		return true;
-	end
-
-	host_module:handle_items("task", task_added, task_removed, true);
-
-	function host_module.unload()
-		active_hosts[host_module.host]=nil;
-	end
-end
-
-local function should_run(when : frequency, last : integer) : boolean
-	return not last or last + periods[when]*0.995 <= os.time();
-end
-
-local function run_task(task : task_spec)
-	local started_at = os.time();
-	task:run(started_at);
-	task.last = started_at;
-	task:save(started_at);
-end
-
-local task_runner : async.runner_t<task_spec> = async.runner(run_task);
-scheduled = module:add_timer(1, function() : integer
-	module:log("info", "Running periodic tasks");
-	local delay = 3600;
-	for host in pairs(active_hosts) do
-		module:log("debug", "Running periodic tasks for host %s", host);
-		for _, task in ipairs(module:context(host):get_host_items("task") as { task_spec } ) do
-			module:log("debug", "Considering %s task %s (%s)", task.when, task.id, task.run);
-			if should_run(task.when, task.last) then task_runner:run(task); end
-		end
-	end
-	module:log("debug", "Wait %ds", delay);
-	return delay;
-end);
-
--- TODO measure load, pick a good time to do stuff
--- a/teal-src/plugins/muc/muc.lib.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,178 +0,0 @@
-local Stanza = require "util.stanza".stanza_t
-
-local record Room
-	jid : string
-
-	enum Affiliation
-		"outcast"
-		"none"
-		"member"
-		"admin"
-		"owner"
-	end
-
-	enum Role
-		"none"
-		"visitor"
-		"participant"
-		"moderator"
-	end
-
-	record Occupant
-		bare_jid : string
-		nick : string
-		sessions : { string : Stanza }
-		role : Role
-		jid : string
-
-		choose_new_primary : function (Occupant) : string
-		set_session : function (Occupant, string, Stanza, boolean)
-		remove_session : function (Occupant, string)
-		each_session : function (Occupant) -- TODO Iterator
-
-	end
-
-	-- Private properties
-	_jid_nick : { string : string }
-	_occupants : { string : Occupant }
-	_data : { string : any }
-	_affiliations : { string : Affiliation }
-	_affiliation_data : { string : { string : any } }
-
-	-- Occupant methods
-	get_occupant_jid : function (Room, real_jid : string) : string
-	new_occupant : function (Room, bare_real_jid : string, nick : string) : Occupant
-	get_occupant_by_nick : function (Room, nick : string) : Occupant
-	type OccupantIterator = function ({string:Occupant}, occupant_jid : string) : string, Occupant
-	each_occupant : function (Room, read_only : boolean) : OccupantIterator, {string:Occupant}, nil
-	has_occupant : function (Room) : boolean
-	get_occupant_by_real_jid : function (Room, real_jid : string) : Occupant
-	save_occupant :function (Room, Occupant) : Occupant
-
-	-- Affiliation methods
-	type AffiliationIterator = function (any, jid : string) : string, Affiliation
-	get_affiliation : function (Room, jid : string) : Affiliation
-	each_affiliation : function (Room, Affiliation) : AffiliationIterator, nil, nil
-	set_affiliation : function (Room, jid : string, Affiliation, reason : string, data : { string : any }) : boolean, string, string, string -- ok + error tripplet
-	get_affiliation_data : function (Room, jid : string, key : string) : any
-	set_affiliation_data : function (Room, jid : string, key : string, value : any) : boolean
-	get_registered_nick : function (Room, jid : string) : string
-	get_registered_jid : function (Room, nick : string) : string
-
-	-- Role methods
-	get_default_role : function (Room, Affiliation) : Role, integer
-	get_role : function (Room, nick : string) : Role
-	may_set_role : function (Room, actor : string, Occupant, Role) : boolean
-	set_role : function (Room, actor : string, occupant_jid : string, Role, reason : string) : boolean, string, string, string
-
-	-- Routing input, generally handled by mod_muc and hooked up to Prosody routing events
-	handle_first_presence : function (Room, table, Stanza) : boolean
-	handle_normal_presence : function (Room, table, Stanza) : boolean
-	handle_presence_to_room : function (Room, table, Stanza) : boolean
-	handle_presence_to_occupant : function (Room, table, Stanza) : boolean
-	handle_message_to_room : function (Room, table, Stanza) : boolean
-	handle_message_to_occupant : function (Room, table, Stanza) : boolean
-	handle_groupchat_to_room : function (Room, table, Stanza) : boolean
-	handle_iq_to_occupant : function (Room, table, Stanza) : boolean
-	handle_disco_info_get_query : function (Room, table, Stanza) : boolean
-	handle_disco_items_get_query : function (Room, table, Stanza) : boolean
-	handle_admin_query_set_command : function (Room, table, Stanza) : boolean
-	handle_admin_query_get_command : function (Room, table, Stanza) : boolean
-	handle_owner_query_get_to_room : function (Room, table, Stanza) : boolean
-	handle_owner_query_set_to_room : function (Room, table, Stanza) : boolean
-	handle_mediated_invite : function (Room, table, Stanza) : boolean
-	handle_mediated_decline : function (Room, table, Stanza) : boolean
-	handle_role_request : function (Room, table, Stanza) : boolean
-	handle_register_iq : function (Room, table, Stanza) : boolean
-	handle_kickable : function (Room, table, Stanza) : boolean
-
-	-- Routing output
-	broadcast : function (Room, Stanza, function (nick : string, Occupant) : boolean)
-	broadcast_message : function (Room, Stanza) : boolean
-	route_stanza : function (Room, Stanza)
-	route_to_occupant : function (Room, Occupant, Stanza)
-
-	-- Sending things to someone joining
-	publicise_occupant_status :  function (Room, Occupant, x : Stanza, nick : string, actor : string, reason : string, prev_role : Role, force_unavailable : boolean, recipient : Occupant)
-	send_occupant_list : function (Room, to : string, filter : function (occupant_jid : string, Occupant) : boolean)
-	send_history : function (Room, Stanza)
-	send_subject : function (Room, to : string, time : number)
-
-	respond_to_probe : function (Room, table, Stanza, Occupant)
-
-	-- Constructors for various answer stanzas
-	get_disco_info : function (Room, Stanza) : Stanza
-	get_disco_items : function (Room, Stanza) : Stanza
-
-	build_item_list : function (Room, Occupant, Stanza, is_anonymous : boolean, nick : string, actor_nick : string, actor_jid : string, reason : string) : Stanza
-	build_unavailable_presence : function (Room, from_muc_jid : string, to_jid : string) : Stanza
-
-	-- Form handling
-	send_form : function (Room, table, Stanza)
-	get_form_layout : function (Room, actor : string) : table
-	process_form : function (Room, table, Stanza) : boolean
-
-	-- Properties and configuration
-	get_name : function (Room) : string
-	set_name : function (Room, string) : boolean
-	get_description : function (Room) : string
-	set_description : function (Room, string) : boolean
-	get_language : function (Room) : string
-	set_language : function (Room, string) : boolean
-	get_hidden : function (Room) : boolean
-	set_hidden : function (Room, boolean)
-	get_public : function (Room) : boolean
-	set_public : function (Room, boolean)
-	get_password : function (Room) : string
-	set_password : function (Room, string) : boolean
-	get_members_only : function (Room) : boolean
-	set_members_only : function (Room, boolean) : boolean
-	get_allow_member_invites : function (Room) : boolean
-	set_allow_member_invites : function (Room, boolean) : boolean
-	get_moderated : function (Room) : boolean
-	set_moderated : function (Room, boolean) : boolean
-	get_persistent : function (Room) : boolean
-	set_persistent : function (Room, boolean) : boolean
-	get_changesubject : function (Room) : boolean
-	set_changesubject : function (Room, boolean) : boolean
-	get_subject : function (Room) : string
-	set_subject : function (Room, string) : boolean
-	get_historylength : function (Room) : integer
-	set_historylength : function (Room, integer) : boolean
-	get_presence_broadcast : function (Room) : { Role : boolean }
-	set_presence_broadcast : function (Room, { Role : boolean }) : boolean
-
-	is_anonymous_for : function (Room, jid : string) : boolean
-	get_salt : function (Room) : string
-	get_occupant_id : function (Room, Occupant)
-
-	-- Room teardown
-	clear : function (Room, x : Stanza)
-	destroy : function (Room, newjid : string, reason : string, password : string) : boolean
-
-	-- Room state persistence
-	record FrozenRoom
-		_jid : string
-		_data : { string : any }
-		_affiliation_data : { string : { string : any } }
-		-- { string : Affiliation }
-	end
-
-	record StateEntry
-		bare_jid : string
-		role : Role
-		jid : string
-	end
-
-	save : function (Room, forced : boolean, savestate : boolean) : boolean
-	freeze : function (Room, live : boolean) : FrozenRoom, { string : StateEntry }
-end
-
-local record lib
-	new_room : function (jid : string, config : { string : any }) : Room
-	restore_room : function (Room.FrozenRoom, { string : Room.StateEntry }) : Room
-
-	room_mt : metatable
-end
-
-return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/core/storagemanager.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,74 @@
+-- Storage local record API Description
+--
+-- This is written as a TypedLua description
+
+-- Key-Value stores (the default)
+
+local stanza = require"prosody.util.stanza".stanza_t
+
+local record keyval_store
+	get : function ( keyval_store, string ) : any , string
+	set : function ( keyval_store, string, any ) : boolean, string
+end
+
+-- Map stores (key-key-value stores)
+
+local record map_store
+	get : function ( map_store, string, any ) : any, string
+	set : function ( map_store, string, any, any ) : boolean, string
+	set_keys : function ( map_store, string, { any : any }) : boolean, string
+	remove : table
+end
+
+-- Archive stores
+
+local record archive_query
+	start  : number -- timestamp
+	["end"]: number -- timestamp
+	with   : string
+	after  : string -- archive id
+	before : string -- archive id
+	total  : boolean
+end
+
+local record archive_store
+	-- Optional set of capabilities
+	caps   : {
+		-- Optional total count of matching items returned as second return value from :find()
+		string : any
+	}
+
+	-- Add to the archive
+	append : function ( archive_store, string, string, any, number, string ) : string, string
+
+	-- Iterate over archive
+	type iterator = function () : string, any, number, string
+	find   : function ( archive_store, string, archive_query ) : iterator, integer
+
+	-- Removal of items. API like find. Optional
+	delete : function ( archive_store, string, archive_query ) : boolean | number, string
+
+	-- Array of dates which do have messages (Optional)
+	dates  : function ( archive_store, string ) : { string }, string
+
+	-- Map of counts per "with" field
+	summary : function ( archive_store, string, archive_query ) : { string : integer }, string
+
+	-- Map-store API
+	get    : function ( archive_store, string, string ) : stanza, number, string
+	get    : function ( archive_store, string, string ) : nil, string
+	set    : function ( archive_store, string, string, stanza, number, string ) : boolean, string
+end
+
+-- This represents moduleapi
+local record coremodule
+	-- If the first string is omitted then the name of the module is used
+	-- The second string is one of "keyval" (default), "map" or "archive"
+	open_store : function (archive_store, string, string) : keyval_store, string
+	open_store : function (archive_store, string, string) : map_store, string
+	open_store : function (archive_store, string, string) : archive_store, string
+
+	-- Other module methods omitted
+end
+
+return coremodule
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/core/usermanager.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,46 @@
+local Role = require "prosody.util.roles".Role;
+
+local record usermanager
+	record AuthProvider
+		-- TODO
+	end
+	record AccountInfo
+		created : number
+		password_updated : any
+		enabled : boolean
+	end
+
+	-- Users
+	test_password : function (username : string, host : string, password : string) : boolean
+	get_password : function (username : string, host : string) : string, string
+	set_password : function (username : string, host : string, password : string) : boolean, string
+	get_account_info : function (username : string, host : string) : AccountInfo
+	user_exists : function (username : string, host : string) : boolean
+	create_user : function (username : string, password : string, host : string) : boolean, string
+	delete_user : function (username : string, host : string) : boolean, string
+	user_is_enabled : function (username : string, host : string) : boolean, string
+	enable_user : function (username : string, host : string) : boolean, string
+	disable_user : function (username : string, host : string) : boolean, string
+	users : function (host : string) : function () : string
+
+	-- Roles
+	get_user_role : function (username : string, host : string) : Role
+	set_user_role : function (username : string, host : string, role_name : string) : boolean, string
+	user_can_assume_role : function (username : string, host : string, role_name : string) : boolean
+	add_user_secondary_role : function (username : string, host: string, role_name : string) : boolean, string
+	remove_user_secondary_role : function (username : string, host: string, role_name : string) : boolean, string
+	get_user_secondary_roles : function (username : string, host : string) : { string : Role }
+	get_users_with_role : function (role : string, host : string) : { string }
+	get_jid_role : function (jid : string, host : string) : Role
+	set_jid_role : function (jid : string, host : string, role_name : string) : boolean
+	get_jids_with_role : function (role : string, host : string) : { string }
+	get_role_by_name : function (role_name : string) : Role
+
+	-- Etc
+	get_provider : function (host : string) : AuthProvider
+	get_sasl_handler : function (host : string, session : table) : table
+	initialize_host : function (host : string)
+	new_null_provider : function () : AuthProvider
+end
+
+return usermanager
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/net/http.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,86 @@
+local Promise = require "prosody.util.promise".Promise;
+
+local record sslctx -- from LuaSec
+end
+
+local record lib
+
+	enum http_method
+		"GET"
+		"HEAD"
+		"POST"
+		"PUT"
+		"OPTIONS"
+		"DELETE"
+		-- etc?
+	end
+
+	record http_client_options
+		sslctx : sslctx
+	end
+
+	record http_options
+		id : string
+		onlystatus : boolean
+		body : string
+		method : http_method
+		headers : { string : string }
+		insecure : boolean
+		suppress_errors : boolean
+		streaming_handler : function
+		suppress_url : boolean
+		sslctx : sslctx
+	end
+
+	record http_request
+		host : string
+		port : string
+		enum scheme
+			"http"
+			"https"
+		end
+		scheme : scheme
+		url : string
+		userinfo : string
+		path : string
+
+		method : http_method
+		headers : { string : string }
+
+		insecure : boolean
+		suppress_errors : boolean
+		streaming_handler : function
+		http : http_client
+		time : integer
+		id : string
+		callback : http_callback
+	end
+
+	record http_response
+	end
+
+	type http_callback = function (string, number, http_response, http_request)
+
+	record http_client
+		options : http_client_options
+		request : function (http_client, string, http_options, http_callback)
+	end
+
+	request : function (string, http_options, http_callback) : Promise, string
+	default : http_client
+	new : function (http_client_options) : http_client
+	events : table
+	-- COMPAT
+	urlencode : function (string) : string
+	urldecode : function (string) : string
+	formencode : function ({ string : string }) : string
+	formdecode : function (string) : { string : string }
+	destroy_request : function (http_request)
+
+	enum available_features
+		"sni"
+	end
+	features : { available_features : boolean }
+end
+
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/net/http/codes.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,2 @@
+local type response_codes = { integer : string }
+return response_codes
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/net/http/errors.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,22 @@
+local record http_errors
+	enum known_conditions
+		"cancelled"
+		"connection-closed"
+		"certificate-chain-invalid"
+		"certificate-verify-failed"
+		"connection failed"
+		"invalid-url"
+		"unable to resolve service"
+	end
+	type registry_keys = known_conditions | integer
+	record error
+		type : string
+		condition : string
+		code : integer
+		text : string
+	end
+	registry : { registry_keys : error }
+	new : function (integer, known_conditions, table)
+	new : function (integer, string, table)
+end
+return http_errors
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/net/http/files.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,14 @@
+local record serve_options
+	path : string
+	mime_map : { string : string }
+	cache_size : integer
+	cache_max_file_size : integer
+	index_files : { string }
+	directory_index : boolean
+end
+
+local record http_files
+	serve : function(serve_options|string) : function
+end
+
+return http_files
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/net/http/parser.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,58 @@
+local record httpstream
+	feed : function(httpstream, string)
+end
+
+local type sink_cb = function ()
+
+local record httppacket
+	enum http_method
+		"HEAD"
+		"GET"
+		"POST"
+		"PUT"
+		"DELETE"
+		"OPTIONS"
+		-- etc
+	end
+	method : http_method
+	record url_details
+		path : string
+		query : string
+	end
+	url : url_details
+	path : string
+	enum http_version
+		"1.0"
+		"1.1"
+	end
+	httpversion : http_version
+	headers : { string : string }
+	body : string | boolean
+	body_sink : sink_cb
+	chunked : boolean
+	partial : boolean
+end
+
+local enum error_conditions
+	"cancelled"
+	"connection-closed"
+	"certificate-chain-invalid"
+	"certificate-verify-failed"
+	"connection failed"
+	"invalid-url"
+	"unable to resolve service"
+end
+
+local type success_cb = function (httppacket)
+local type error_cb = function (error_conditions)
+
+local enum stream_mode
+	"client"
+	"server"
+end
+
+local record lib
+	new : function (success_cb, error_cb, stream_mode) : httpstream
+end
+
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/net/http/server.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,6 @@
+
+local record http_server
+	-- TODO
+end
+
+return http_server
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/net/server.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,65 @@
+local record server
+	record LuaSocketTCP
+	end
+	record LuaSecCTX
+	end
+
+	record extra_settings
+	end
+
+	record interface
+	end
+	enum socket_type
+		"tcp"
+		"tcp6"
+		"tcp4"
+	end
+
+	record listeners
+		onconnect : function (interface)
+		ondetach : function (interface)
+		onattach : function (interface, string)
+		onincoming : function (interface, string, string)
+		ondrain : function (interface)
+		onreadtimeout : function (interface)
+		onstarttls : function (interface)
+		onstatus : function (interface, string)
+		ondisconnect : function (interface, string)
+	end
+
+	get_backend : function () : string
+
+	type port = string | integer
+	enum read_mode
+		"*a"
+		"*l"
+	end
+	type read_size = read_mode | integer
+	addserver : function (string, port, listeners, read_size, LuaSecCTX) : interface
+	addclient : function (string, port, listeners, read_size, LuaSecCTX, socket_type, extra_settings) : interface
+	record listen_config
+		read_size : read_size
+		tls_ctx : LuaSecCTX
+		tls_direct : boolean
+		sni_hosts : { string : LuaSecCTX }
+	end
+	listen : function (string, port, listeners, listen_config) : interface
+	enum quitting
+		"quitting"
+	end
+	loop : function () : quitting
+	closeall : function ()
+	setquitting : function (boolean | quitting)
+
+	wrapclient : function (LuaSocketTCP, string, port, listeners, read_size, LuaSecCTX, extra_settings) : interface
+	wrapserver : function (LuaSocketTCP, string, port, listeners, listen_config) : interface
+	watchfd : function (integer | LuaSocketTCP, function (interface), function (interface)) : interface
+	link : function ()
+
+	record config
+	end
+	set_config : function (config)
+
+end
+
+return server
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/plugins/mod_cron.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,106 @@
+module:set_global();
+
+local async = require "prosody.util.async";
+local datetime = require "prosody.util.datetime";
+
+local record map_store<K,V>
+	-- TODO move to somewhere sensible
+	get : function (map_store<K,V>, string, K) : V
+	set : function (map_store<K,V>, string, K, V)
+end
+
+local enum frequency
+	"hourly"
+	"daily"
+	"weekly"
+end
+
+local record task_spec
+	id : string -- unique id
+	name : string -- name or short description
+	when : frequency
+	last : integer
+	run : function (task_spec, integer)
+	save : function (task_spec, integer)
+end
+
+local record task_event
+	source : module
+	item : task_spec
+end
+
+local periods : { frequency : integer } = { hourly = 3600, daily = 86400, weekly = 7*86400 }
+
+local active_hosts : { string : boolean } = {  }
+
+function module.add_host(host_module : moduleapi)
+
+	local last_run_times = host_module:open_store("cron", "map") as map_store<string,integer>;
+	active_hosts[host_module.host] = true;
+
+	local function save_task(task : task_spec, started_at : integer)
+		last_run_times:set(nil, task.id, started_at);
+	end
+
+	local function task_added(event : task_event) : boolean
+		local task = event.item;
+		if task.name == nil then
+			task.name = task.when;
+		end
+		if task.id == nil then
+			task.id = event.source.name .. "/" .. task.name:gsub("%W", "_"):lower();
+		end
+		if task.last == nil then
+			task.last = last_run_times:get(nil, task.id);
+		end
+		task.save = save_task;
+		module:log("debug", "%s task %s added, last run %s", task.when, task.id,
+			task.last and datetime.datetime(task.last) or "never");
+		if task.last == nil then
+			-- initialize new tasks so e.g. daily tasks run at ~midnight UTC for now
+			local now = os.time();
+			task.last = now - now % periods[task.when];
+		end
+		return true;
+	end
+
+	local function task_removed(event : task_event) : boolean
+		local task = event.item;
+		host_module:log("debug", "Task %s removed", task.id);
+		return true;
+	end
+
+	host_module:handle_items("task", task_added, task_removed, true);
+
+	function host_module.unload()
+		active_hosts[host_module.host]=nil;
+	end
+end
+
+local function should_run(when : frequency, last : integer) : boolean
+	return not last or last + periods[when]*0.995 <= os.time();
+end
+
+local function run_task(task : task_spec)
+	local started_at = os.time();
+	task:run(started_at);
+	task.last = started_at;
+	task:save(started_at);
+end
+
+local task_runner : async.runner_t<task_spec> = async.runner(run_task);
+scheduled = module:add_timer(1, function() : integer
+	module:log("info", "Running periodic tasks");
+	local delay = 3600;
+	for host in pairs(active_hosts) do
+		module:log("debug", "Running periodic tasks for host %s", host);
+		for _, task in ipairs(module:context(host):get_host_items("task") as { task_spec } ) do
+			module:log("debug", "Considering %s task %s (%s)", task.when, task.id, task.run);
+			if should_run(task.when, task.last) then task_runner:run(task); end
+		end
+	end
+	module:log("debug", "Wait %ds", delay);
+	return delay;
+end);
+
+-- TODO measure load, pick a good time to do stuff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/plugins/muc/muc.lib.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,178 @@
+local Stanza = require "prosody.util.stanza".stanza_t
+
+local record Room
+	jid : string
+
+	enum Affiliation
+		"outcast"
+		"none"
+		"member"
+		"admin"
+		"owner"
+	end
+
+	enum Role
+		"none"
+		"visitor"
+		"participant"
+		"moderator"
+	end
+
+	record Occupant
+		bare_jid : string
+		nick : string
+		sessions : { string : Stanza }
+		role : Role
+		jid : string
+
+		choose_new_primary : function (Occupant) : string
+		set_session : function (Occupant, string, Stanza, boolean)
+		remove_session : function (Occupant, string)
+		each_session : function (Occupant) -- TODO Iterator
+
+	end
+
+	-- Private properties
+	_jid_nick : { string : string }
+	_occupants : { string : Occupant }
+	_data : { string : any }
+	_affiliations : { string : Affiliation }
+	_affiliation_data : { string : { string : any } }
+
+	-- Occupant methods
+	get_occupant_jid : function (Room, real_jid : string) : string
+	new_occupant : function (Room, bare_real_jid : string, nick : string) : Occupant
+	get_occupant_by_nick : function (Room, nick : string) : Occupant
+	type OccupantIterator = function ({string:Occupant}, occupant_jid : string) : string, Occupant
+	each_occupant : function (Room, read_only : boolean) : OccupantIterator, {string:Occupant}, nil
+	has_occupant : function (Room) : boolean
+	get_occupant_by_real_jid : function (Room, real_jid : string) : Occupant
+	save_occupant :function (Room, Occupant) : Occupant
+
+	-- Affiliation methods
+	type AffiliationIterator = function (any, jid : string) : string, Affiliation
+	get_affiliation : function (Room, jid : string) : Affiliation
+	each_affiliation : function (Room, Affiliation) : AffiliationIterator, nil, nil
+	set_affiliation : function (Room, jid : string, Affiliation, reason : string, data : { string : any }) : boolean, string, string, string -- ok + error tripplet
+	get_affiliation_data : function (Room, jid : string, key : string) : any
+	set_affiliation_data : function (Room, jid : string, key : string, value : any) : boolean
+	get_registered_nick : function (Room, jid : string) : string
+	get_registered_jid : function (Room, nick : string) : string
+
+	-- Role methods
+	get_default_role : function (Room, Affiliation) : Role, integer
+	get_role : function (Room, nick : string) : Role
+	may_set_role : function (Room, actor : string, Occupant, Role) : boolean
+	set_role : function (Room, actor : string, occupant_jid : string, Role, reason : string) : boolean, string, string, string
+
+	-- Routing input, generally handled by mod_muc and hooked up to Prosody routing events
+	handle_first_presence : function (Room, table, Stanza) : boolean
+	handle_normal_presence : function (Room, table, Stanza) : boolean
+	handle_presence_to_room : function (Room, table, Stanza) : boolean
+	handle_presence_to_occupant : function (Room, table, Stanza) : boolean
+	handle_message_to_room : function (Room, table, Stanza) : boolean
+	handle_message_to_occupant : function (Room, table, Stanza) : boolean
+	handle_groupchat_to_room : function (Room, table, Stanza) : boolean
+	handle_iq_to_occupant : function (Room, table, Stanza) : boolean
+	handle_disco_info_get_query : function (Room, table, Stanza) : boolean
+	handle_disco_items_get_query : function (Room, table, Stanza) : boolean
+	handle_admin_query_set_command : function (Room, table, Stanza) : boolean
+	handle_admin_query_get_command : function (Room, table, Stanza) : boolean
+	handle_owner_query_get_to_room : function (Room, table, Stanza) : boolean
+	handle_owner_query_set_to_room : function (Room, table, Stanza) : boolean
+	handle_mediated_invite : function (Room, table, Stanza) : boolean
+	handle_mediated_decline : function (Room, table, Stanza) : boolean
+	handle_role_request : function (Room, table, Stanza) : boolean
+	handle_register_iq : function (Room, table, Stanza) : boolean
+	handle_kickable : function (Room, table, Stanza) : boolean
+
+	-- Routing output
+	broadcast : function (Room, Stanza, function (nick : string, Occupant) : boolean)
+	broadcast_message : function (Room, Stanza) : boolean
+	route_stanza : function (Room, Stanza)
+	route_to_occupant : function (Room, Occupant, Stanza)
+
+	-- Sending things to someone joining
+	publicise_occupant_status :  function (Room, Occupant, x : Stanza, nick : string, actor : string, reason : string, prev_role : Role, force_unavailable : boolean, recipient : Occupant)
+	send_occupant_list : function (Room, to : string, filter : function (occupant_jid : string, Occupant) : boolean)
+	send_history : function (Room, Stanza)
+	send_subject : function (Room, to : string, time : number)
+
+	respond_to_probe : function (Room, table, Stanza, Occupant)
+
+	-- Constructors for various answer stanzas
+	get_disco_info : function (Room, Stanza) : Stanza
+	get_disco_items : function (Room, Stanza) : Stanza
+
+	build_item_list : function (Room, Occupant, Stanza, is_anonymous : boolean, nick : string, actor_nick : string, actor_jid : string, reason : string) : Stanza
+	build_unavailable_presence : function (Room, from_muc_jid : string, to_jid : string) : Stanza
+
+	-- Form handling
+	send_form : function (Room, table, Stanza)
+	get_form_layout : function (Room, actor : string) : table
+	process_form : function (Room, table, Stanza) : boolean
+
+	-- Properties and configuration
+	get_name : function (Room) : string
+	set_name : function (Room, string) : boolean
+	get_description : function (Room) : string
+	set_description : function (Room, string) : boolean
+	get_language : function (Room) : string
+	set_language : function (Room, string) : boolean
+	get_hidden : function (Room) : boolean
+	set_hidden : function (Room, boolean)
+	get_public : function (Room) : boolean
+	set_public : function (Room, boolean)
+	get_password : function (Room) : string
+	set_password : function (Room, string) : boolean
+	get_members_only : function (Room) : boolean
+	set_members_only : function (Room, boolean) : boolean
+	get_allow_member_invites : function (Room) : boolean
+	set_allow_member_invites : function (Room, boolean) : boolean
+	get_moderated : function (Room) : boolean
+	set_moderated : function (Room, boolean) : boolean
+	get_persistent : function (Room) : boolean
+	set_persistent : function (Room, boolean) : boolean
+	get_changesubject : function (Room) : boolean
+	set_changesubject : function (Room, boolean) : boolean
+	get_subject : function (Room) : string
+	set_subject : function (Room, string) : boolean
+	get_historylength : function (Room) : integer
+	set_historylength : function (Room, integer) : boolean
+	get_presence_broadcast : function (Room) : { Role : boolean }
+	set_presence_broadcast : function (Room, { Role : boolean }) : boolean
+
+	is_anonymous_for : function (Room, jid : string) : boolean
+	get_salt : function (Room) : string
+	get_occupant_id : function (Room, Occupant)
+
+	-- Room teardown
+	clear : function (Room, x : Stanza)
+	destroy : function (Room, newjid : string, reason : string, password : string) : boolean
+
+	-- Room state persistence
+	record FrozenRoom
+		_jid : string
+		_data : { string : any }
+		_affiliation_data : { string : { string : any } }
+		-- { string : Affiliation }
+	end
+
+	record StateEntry
+		bare_jid : string
+		role : Role
+		jid : string
+	end
+
+	save : function (Room, forced : boolean, savestate : boolean) : boolean
+	freeze : function (Room, live : boolean) : FrozenRoom, { string : StateEntry }
+end
+
+local record lib
+	new_room : function (jid : string, config : { string : any }) : Room
+	restore_room : function (Room.FrozenRoom, { string : Room.StateEntry }) : Room
+
+	room_mt : metatable
+end
+
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/array.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,9 @@
+local record array_t<T>
+	{ T }
+end
+
+local record lib
+	metamethod __call : function () : array_t
+end
+
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/async.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,42 @@
+local record lib
+	ready : function () : boolean
+	waiter : function (num : integer, allow_many : boolean) : function (), function ()
+	guarder : function () : function (id : function ()) : function () | nil
+	record runner_t<T>
+		func : function (T)
+		thread : thread
+		enum state_e
+			-- from Lua manual
+			"running"
+			"suspended"
+			"normal"
+			"dead"
+
+			-- from util.async
+			"ready"
+			"error"
+		end
+		state : state_e
+		notified_state : state_e
+		queue : { T }
+		type watcher_t = function (runner_t<T>, ... : any)
+		type watchers_t = { state_e : watcher_t }
+		data : any
+		id : string
+
+		run : function (runner_t<T>, T) : boolean, state_e, integer
+		enqueue : function (runner_t<T>, T) : runner_t<T>
+		log : function (runner_t<T>, string, string, ... : any)
+		onready : function (runner_t<T>, function) : runner_t<T>
+		onready : function (runner_t<T>, function) : runner_t<T>
+		onwaiting : function (runner_t<T>, function) : runner_t<T>
+		onerror : function (runner_t<T>, function) : runner_t<T>
+	end
+	runner : function <T>(function (T), runner_t.watchers_t, any) : runner_t<T>
+	wait_for : function (any) : any, any
+	sleep : function (t:number)
+
+	-- set_nexttick = function(new_next_tick) next_tick = new_next_tick; end;
+	-- set_schedule_function = function (new_schedule_function) schedule_task = new_schedule_function; end;
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/bitcompat.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,8 @@
+local record lib
+	band : function (integer, integer, ... : integer) : integer
+	bor : function (integer, integer, ... : integer) : integer
+	bxor : function (integer, integer, ... : integer) : integer
+	lshift : function (integer, integer) : integer
+	rshift : function (integer, integer) : integer
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/compat.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,4 @@
+local record lib
+	xpcall : function (function, function, ...:any):boolean, any
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/crand.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,6 @@
+local record lib
+	bytes : function (n : integer) : string
+	enum sourceid "OpenSSL" "arc4random()" "Linux" end
+	_source : sourceid
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/crypto.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,29 @@
+local record lib
+	record key
+		private_pem : function (key) : string
+		public_pem : function (key) : string
+		get_type : function (key) : string
+	end
+
+	generate_ed25519_keypair : function () : key
+	ed25519_sign : function (key, string) : string
+	ed25519_verify : function (key, string, string) : boolean
+
+	ecdsa_sha256_sign : function (key, string) : string
+	ecdsa_sha256_verify : function (key, string, string) : boolean
+	parse_ecdsa_signature : function (string) : string, string
+	build_ecdsa_signature : function (string, string) : string
+
+	import_private_pem : function (string) : key
+	import_public_pem : function (string) : key
+
+	aes_128_gcm_encrypt : function (key, string, string) : string
+	aes_128_gcm_decrypt : function (key, string, string) : string
+	aes_256_gcm_encrypt : function (key, string, string) : string
+	aes_256_gcm_decrypt : function (key, string, string) : string
+
+
+	version : string
+	_LIBCRYPTO_VERSION : string
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/dataforms.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,54 @@
+local stanza_t = require "prosody.util.stanza".stanza_t
+
+local record lib
+	record dataform
+		title : string
+		instructions : string
+
+		record form_field
+
+			enum field_type
+				"boolean"
+				"fixed"
+				"hidden"
+				"jid-multi"
+				"jid-single"
+				"list-multi"
+				"list-single"
+				"text-multi"
+				"text-private"
+				"text-single"
+			end
+
+			type : field_type
+			var : string -- protocol name
+			name :  string -- internal name
+
+			label : string
+			desc : string
+
+			datatype : string
+			range_min : number
+			range_max : number
+
+			value : any -- depends on field_type
+			options : table
+		end
+
+		{ form_field }
+
+		enum form_type
+			"form"
+			"submit"
+			"cancel"
+			"result"
+		end
+
+		form : function ( dataform, { string : any }, form_type ) : stanza_t
+		data : function ( dataform, stanza_t ) : { string : any }
+	end
+
+	new : function ( dataform ) : dataform
+end
+
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/datamapper.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,379 @@
+-- Copyright (C) 2021 Kim Alvefur
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+-- Based on
+-- https://json-schema.org/draft/2020-12/json-schema-core.html
+-- https://json-schema.org/draft/2020-12/json-schema-validation.html
+-- http://spec.openapis.org/oas/v3.0.1#xmlObject
+-- https://github.com/OAI/OpenAPI-Specification/issues/630 (text:true)
+--
+-- XML Object Extensions:
+-- text to refer to the text content at the same time as attributes
+-- x_name_is_value for enum fields where the <tag-name/> is the value
+-- x_single_attribute for <tag attr="this"/>
+--
+-- TODO pointers
+-- TODO cleanup / refactor
+-- TODO s/number/integer/ once we have appropriate math.type() compat
+--
+
+if not math.type then require "prosody.util.mathcompat" end
+
+local st = require "prosody.util.stanza";
+local json = require "prosody.util.json"
+local pointer = require "prosody.util.jsonpointer";
+
+local json_type_name = json.json_type_name;
+local json_schema_object = require "prosody.util.jsonschema"
+local type schema_t = boolean | json_schema_object
+
+local function toboolean ( s : string ) : boolean
+	if s == "true" or s == "1" then
+		return true
+	elseif s == "false" or s == "0" then
+		return false
+	elseif s then
+		return true
+	end
+end
+
+local function totype(t : json_type_name, s : string) : any
+	if not s then return nil end
+	if t == "string" then
+		return s;
+	elseif t == "boolean" then
+		return toboolean(s)
+	elseif t == "number" or t == "integer" then
+		return tonumber(s)
+	end
+end
+
+local enum value_goes
+	"in_tag_name"
+	"in_text"
+	"in_text_tag"
+	"in_attribute"
+	"in_single_attribute"
+	"in_children"
+	"in_wrapper"
+end
+
+local function resolve_schema(schema  : schema_t, root : json_schema_object) : schema_t
+	if schema is json_schema_object then
+		if schema["$ref"] and schema["$ref"]:sub(1, 1) == "#" then
+			return pointer.resolve(root as table, schema["$ref"]:sub(2)) as schema_t;
+		end
+	end
+	return schema;
+end
+
+local function guess_schema_type(schema : json_schema_object) : json_type_name
+	local schema_types = schema.type
+	if schema_types is json_type_name then
+		return schema_types
+	elseif schema_types ~= nil then
+		error "schema has unsupported 'type' property"
+	elseif schema.properties then
+		return "object"
+	elseif schema.items then
+		return "array"
+	end
+	return "string" -- default assumption
+end
+
+local function unpack_propschema( propschema : schema_t, propname : string, current_ns : string )
+		: json_type_name, value_goes, string, string, string, string, { any }
+	local proptype : json_type_name = "string"
+	local value_where : value_goes = propname and "in_text_tag" or "in_text"
+	local name = propname
+	local namespace : string
+	local prefix : string
+	local single_attribute : string
+	local enums : { any }
+
+	if propschema is json_schema_object then
+		proptype = guess_schema_type(propschema);
+	elseif propschema is string then -- Teal says this can never be a string, but it could before so best be sure
+		error("schema as string is not supported: "..propschema.." {"..current_ns.."}"..propname)
+	end
+
+	if proptype == "object" or proptype == "array" then
+		value_where = "in_children"
+	end
+
+	if propschema is json_schema_object then
+		local xml = propschema.xml
+		if xml then
+			if xml.name then
+				name = xml.name
+			end
+			if xml.namespace and xml.namespace ~= current_ns then
+				namespace = xml.namespace
+			end
+			if xml.prefix then
+				prefix = xml.prefix
+			end
+			if proptype == "array" and xml.wrapped then
+				value_where = "in_wrapper"
+			elseif xml.attribute then
+				value_where = "in_attribute"
+			elseif xml.text then
+				value_where = "in_text"
+			elseif xml.x_name_is_value then
+				value_where = "in_tag_name"
+			elseif xml.x_single_attribute then
+				single_attribute = xml.x_single_attribute
+				value_where = "in_single_attribute"
+			end
+		end
+		if propschema["const"] then
+			enums = { propschema["const"] }
+		elseif propschema["enum"] then
+			enums = propschema["enum"]
+		end
+	end
+
+	return proptype, value_where, name, namespace, prefix, single_attribute, enums
+end
+
+local parse_object : function (schema : schema_t, s : st.stanza_t, root : json_schema_object) : { string : any }
+local parse_array : function (schema : schema_t, s : st.stanza_t, root : json_schema_object) : { any }
+
+local function extract_value (s : st.stanza_t, value_where : value_goes, proptype : json.json_type_name, name : string, namespace : string, prefix : string, single_attribute : string, enums : { any }) : string
+	if value_where == "in_tag_name" then
+		local c : st.stanza_t
+		if proptype == "boolean" then
+			c = s:get_child(name, namespace);
+		elseif enums and proptype == "string" then
+			-- XXX O(n²) ?
+			-- Probably better to flip the table and loop over :childtags(nil, ns), should be 2xO(n)
+			-- BUT works first, optimize later
+			for i = 1, #enums do
+				c = s:get_child(enums[i] as string, namespace);
+				if c then break end
+			end
+		else
+			c = s:get_child(nil, namespace);
+		end
+		if c then
+			return c.name;
+		end
+	elseif value_where == "in_attribute" then
+		local attr = name
+		if prefix then
+			attr = prefix .. ':' .. name
+		elseif namespace and namespace ~= s.attr.xmlns then
+			attr = namespace .. "\1" .. name
+		end
+		return s.attr[attr]
+
+	elseif value_where == "in_text" then
+		return s:get_text()
+
+	elseif value_where == "in_single_attribute" then
+		local c = s:get_child(name, namespace)
+		return c and c.attr[single_attribute]
+	elseif value_where == "in_text_tag" then
+		return s:get_child_text(name, namespace)
+	end
+end
+
+function parse_object (schema : schema_t, s : st.stanza_t, root : json_schema_object) : { string : any }
+	local out : { string : any } = {}
+	schema = resolve_schema(schema, root)
+	if schema is json_schema_object and schema.properties then
+		for prop, propschema in pairs(schema.properties) do
+			propschema = resolve_schema(propschema, root)
+
+			local proptype, value_where, name, namespace, prefix, single_attribute, enums = unpack_propschema(propschema, prop, s.attr.xmlns)
+
+			if value_where == "in_children" and propschema is json_schema_object then
+				if proptype == "object" then
+					local c = s:get_child(name, namespace)
+					if c then
+						out[prop] = parse_object(propschema, c, root);
+					end
+				elseif proptype == "array" then
+					local a = parse_array(propschema, s, root);
+					if a and a[1] ~= nil then
+						out[prop] = a;
+					end
+				else
+					error "unreachable"
+				end
+			elseif value_where == "in_wrapper" and propschema is json_schema_object and proptype == "array" then
+				local wrapper = s:get_child(name, namespace);
+				if wrapper then
+					out[prop] = parse_array(propschema, wrapper, root);
+				end
+			else
+				local value : string = extract_value (s, value_where, proptype, name, namespace, prefix, single_attribute, enums)
+
+				out[prop] = totype(proptype, value)
+			end
+		end
+	end
+
+	return out
+end
+
+function parse_array (schema : json_schema_object, s : st.stanza_t, root : json_schema_object) : { any }
+	local itemschema : schema_t = resolve_schema(schema.items, root);
+	local proptype, value_where, child_name, namespace, prefix, single_attribute, enums = unpack_propschema(itemschema, nil, s.attr.xmlns)
+	local attr_name : string
+	if value_where == "in_single_attribute" then -- FIXME this shouldn't be needed
+		value_where = "in_attribute";
+		attr_name = single_attribute;
+	end
+	local out : { any } = {}
+
+	if proptype == "object" then
+		if itemschema is json_schema_object then
+			for c in s:childtags(child_name, namespace) do
+				table.insert(out, parse_object(itemschema, c, root));
+			end
+		else
+			error "array items must be schema object"
+		end
+	elseif proptype == "array" then
+		if itemschema is json_schema_object then
+			for c in s:childtags(child_name, namespace) do
+				table.insert(out, parse_array(itemschema, c, root));
+			end
+		end
+	else
+		for c in s:childtags(child_name, namespace) do
+			local value : string = extract_value (c, value_where, proptype, attr_name or child_name, namespace, prefix, single_attribute, enums)
+
+			table.insert(out, totype(proptype, value));
+		end
+	end
+	return out;
+end
+
+local function parse (schema : json_schema_object, s : st.stanza_t) : table
+	local s_type = guess_schema_type(schema)
+	if s_type == "object" then
+		return parse_object(schema, s, schema)
+	elseif s_type == "array" then
+		return parse_array(schema, s, schema)
+	else
+		error "top-level scalars unsupported"
+	end
+end
+
+local function toxmlstring(proptype : json_type_name, v : any) : string
+	if proptype == "string" and v is string then
+		return  v
+	elseif proptype == "number" and v is number then
+		return  string.format("%g", v)
+	elseif proptype == "integer" and v is number then -- TODO is integer
+		return  string.format("%d", v)
+	elseif proptype == "boolean" then
+		return  v and "1" or "0"
+	end
+end
+
+local unparse : function (json_schema_object, table, string, string, st.stanza_t, json_schema_object) : st.stanza_t
+
+local function unparse_property(out : st.stanza_t, v : any, proptype : json_type_name, propschema : schema_t, value_where : value_goes, name : string, namespace : string, current_ns : string, prefix : string, single_attribute : string, root : json_schema_object)
+
+	if value_where == "in_attribute" then
+		local attr = name
+		if prefix then
+			attr = prefix .. ':' .. name
+		elseif namespace and namespace ~= current_ns then
+			attr = namespace .. "\1" .. name
+		end
+
+		out.attr[attr] = toxmlstring(proptype, v)
+	elseif value_where == "in_text" then
+		out:text(toxmlstring(proptype, v))
+	elseif value_where == "in_single_attribute" then
+		assert(single_attribute)
+		local propattr : { string : string } = {}
+
+		if namespace and namespace ~= current_ns then
+			propattr.xmlns = namespace
+		end
+
+		propattr[single_attribute] = toxmlstring(proptype, v)
+		out:tag(name, propattr):up();
+
+	else
+		local propattr : { string : string }
+		if namespace ~= current_ns then
+			propattr = { xmlns = namespace }
+		end
+		if value_where == "in_tag_name" then
+			if proptype == "string" and v is string then
+				out:tag(v, propattr):up();
+			elseif proptype == "boolean" and v == true then
+				out:tag(name, propattr):up();
+			end
+		elseif proptype == "object" and propschema is json_schema_object and v is table then
+			local c = unparse(propschema, v, name, namespace, nil, root);
+			if c then
+				out:add_direct_child(c);
+			end
+		elseif proptype == "array" and propschema is json_schema_object and v is table then
+			if value_where == "in_wrapper" then
+				local c = unparse(propschema, v, name, namespace, nil, root);
+				if c then
+					out:add_direct_child(c);
+				end
+			else
+				unparse(propschema, v, name, namespace, out, root);
+			end
+		else
+			out:text_tag(name, toxmlstring(proptype, v), propattr)
+		end
+	end
+end
+
+function unparse ( schema : json_schema_object, t : table, current_name : string, current_ns : string, ctx : st.stanza_t, root : json_schema_object ) : st.stanza_t
+
+	if root == nil then root = schema end
+
+	if schema.xml then
+		if schema.xml.name then
+			current_name = schema.xml.name
+		end
+		if schema.xml.namespace then
+			current_ns = schema.xml.namespace
+		end
+		-- TODO prefix?
+	end
+
+	local out = ctx or st.stanza(current_name, { xmlns = current_ns })
+
+	local s_type = guess_schema_type(schema)
+	if s_type == "object" then
+
+		for prop, propschema in pairs(schema.properties) do
+			propschema = resolve_schema(propschema, root)
+			local v = t[prop]
+
+			if v ~= nil then
+				local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(propschema, prop, current_ns)
+				unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute, root)
+			end
+		end
+		return out;
+
+	elseif s_type == "array" then
+		local itemschema = resolve_schema(schema.items, root)
+		local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(itemschema, current_name, current_ns)
+		for _, item in ipairs(t as { string }) do
+			unparse_property(out, item, proptype, itemschema, value_where, name, namespace, current_ns, prefix, single_attribute, root)
+		end
+		return out;
+	end
+end
+
+return {
+	parse = parse,
+	unparse = unparse,
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/datetime.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,9 @@
+local record lib
+	date     : function (t : number) : string
+	datetime : function (t : number) : string
+	time     : function (t : number) : string
+	legacy   : function (t : number) : string
+	parse    : function (t : string) : number
+end
+
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/encodings.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,27 @@
+-- TODO many actually return Maybe(String)
+local record lib
+	record base64
+		encode : function (s : string) : string
+		decode : function (s : string) : string
+	end
+	record stringprep
+		nameprep : function (s : string, strict : boolean) : string
+		nodeprep : function (s : string, strict : boolean) : string
+		resourceprep : function (s : string, strict : boolean) : string
+		saslprep : function (s : string, strict : boolean) : string
+	end
+	record idna
+		to_ascii : function (s : string) : string
+		to_unicode : function (s : string) : string
+	end
+	record utf8
+		valid : function (s : string) : boolean
+		length : function (s : string) : integer
+	end
+	record confusable
+		skeleton : function (s : string) : string
+	end
+	version : string
+end
+return lib
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/error.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,78 @@
+local enum error_type
+	"auth"
+	"cancel"
+	"continue"
+	"modify"
+	"wait"
+end
+
+local enum error_condition
+	"bad-request"
+	"conflict"
+	"feature-not-implemented"
+	"forbidden"
+	"gone"
+	"internal-server-error"
+	"item-not-found"
+	"jid-malformed"
+	"not-acceptable"
+	"not-allowed"
+	"not-authorized"
+	"policy-violation"
+	"recipient-unavailable"
+	"redirect"
+	"registration-required"
+	"remote-server-not-found"
+	"remote-server-timeout"
+	"resource-constraint"
+	"service-unavailable"
+	"subscription-required"
+	"undefined-condition"
+	"unexpected-request"
+end
+
+local record protoerror
+	type : error_type
+	condition : error_condition
+	text : string
+	code : integer
+end
+
+local record Error
+	type : error_type
+	condition : error_condition
+	text : string
+	code : integer
+	context : { any : any }
+	source : string
+end
+
+local type compact_registry_item = { string, string, string, string }
+local type compact_registry = { compact_registry_item }
+local type registry = { string : protoerror }
+local type context = { string : any }
+
+local record error_registry_wrapper
+	source : string
+	registry : registry
+	new : function (string, context) : Error
+	coerce : function (any, string) : any, Error
+	wrap : function (Error) : Error
+	wrap : function (string, context) : Error
+	is_error : function (any) : boolean
+end
+
+local record lib
+	record configure_opt
+		auto_inject_traceback : boolean
+	end
+	new : function (protoerror, context, { string : protoerror }, string) : Error
+	init : function (string, string, registry | compact_registry) : error_registry_wrapper
+	init : function (string, registry | compact_registry) : error_registry_wrapper
+	is_error : function (any) : boolean
+	coerce : function (any, string) : any, Error
+	from_stanza : function (table, context, string) : Error
+	configure : function
+end
+
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/format.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,4 @@
+local record lib
+	format : function (string, ... : any) : string
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/hashes.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,31 @@
+local type hash = function (msg : string, hex : boolean) : string
+local type hmac = function (key : string, msg : string, hex : boolean) : string
+local type kdf = function (pass : string, salt : string, i : integer) : string
+
+local record lib
+	sha1 : hash
+	sha256 : hash
+	sha224 : hash
+	sha384 : hash
+	sha512 : hash
+	md5 : hash
+	sha3_256 : hash
+	sha3_512 : hash
+	blake2s256 : hash
+	blake2b512 : hash
+	hmac_sha1 : hmac
+	hmac_sha256 : hmac
+	hmac_sha224 : hmac
+	hmac_sha384  :hmac
+	hmac_sha512 : hmac
+	hmac_md5 : hmac
+	hmac_sha3_256 : hmac
+	hmac_sha3_512 : hmac
+	scram_Hi_sha1 : kdf
+	pbkdf2_hmac_sha1 : kdf
+	pbkdf2_hmac_sha256 : kdf
+	equals : function (string, string) : boolean
+	version : string
+	_LIBCRYPTO_VERSION : string
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/hex.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,8 @@
+local type s2s = function (s : string) : string
+local record lib
+	to : s2s
+	from : s2s
+	encode : s2s
+	decode : s2s
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/http.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,9 @@
+local record lib
+	urlencode : function (s : string) : string 
+	urldecode : function (s : string) : string 
+	formencode : function (f : { string : string }) : string 
+	formdecode : function (s : string) : { string : string } 
+	contains_token : function (field : string, token : string) : boolean 
+	normalize_path : function (path : string) : string 
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/human/io.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,28 @@
+local record lib
+	getchar : function (n : integer) : string
+	getline : function () : string
+	getpass : function () : string
+	show_yesno : function (prompt : string) : boolean
+	read_password : function () : string
+	show_prompt : function (prompt : string) : boolean
+	printf : function (fmt : string, ... : any)
+	padleft : function (s : string, width : integer) : string
+	padright : function (s : string, width : integer) : string
+
+	-- {K:V} vs T ?
+	record tablerow<K,V>
+		width : integer | string -- generate an 1..100 % enum?
+		title : string
+		mapper : function (V, {K:V}) : string
+		key : K
+		enum alignments
+			"left"
+			"right"
+		end
+		align : alignments
+	end
+	type getrow = function<K,V> ({ K : V }) : string
+	table : function<K,V> ({ tablerow<K,V> }, width : integer) : getrow<K,V>
+end
+
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/human/units.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,8 @@
+local lib = record
+	enum logbase
+		"b" -- 1024
+	end
+	adjust : function (number, string) : number, string
+	format : function (number, string, logbase) : string
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/id.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,9 @@
+local record lib
+	short : function () : string
+	medium : function () : string
+	long : function () : string
+	custom : function (integer) : function () : string
+
+end
+return lib
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/interpolation.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,6 @@
+local type renderer = function (string, { string : any }) : string
+local type filter = function (string, any) : string
+local record lib
+	new : function (string, string, funcs : { string : filter }) : renderer
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/ip.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,20 @@
+local record iplib
+	enum protocol
+		"IPv6"
+		"IPv4"
+	end
+	record ip_t
+		addr : string
+		packed : string
+		proto : protocol
+		zone : string
+	end
+
+	new_ip : function (string, protocol) : ip_t
+	commonPrefixLength : function (ip_t, ip_t) : integer
+	parse_cidr : function (string) : ip_t, integer
+	match : function (ip_t, ip_t, integer) : boolean
+	is_ip : function (any) : boolean
+	truncate : function (ip_t, integer) : ip_t
+end
+return iplib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/jid.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,15 @@
+local record lib
+	split : function (string) : string, string, string
+	bare : function (string) : string
+	prepped_split : function (string, boolean) : string, string, string
+	join : function (string, string, string) : string
+	prep : function (string, boolean) : string
+	compare : function (string, string) : boolean
+	node : function (string) : string
+	host : function (string) : string
+	resource : function (string) : string
+	escape : function (string) : string
+	unescape : function (string) : string
+end
+
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/json.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,18 @@
+local record lib
+	encode : function (any) : string
+	decode : function (string) : any, string
+
+	enum json_type_name
+		"null"
+		"boolean"
+		"object"
+		"array"
+		"number"
+		"string"
+		"integer"
+	end
+
+	type null_type = (nil)
+	null : null_type
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/jsonpointer.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,46 @@
+
+local enum ptr_error
+	"invalid-table"
+	"invalid-path"
+end
+
+local function unescape_token(escaped_token : string) : string
+	local unescaped = escaped_token:gsub("~1", "/"):gsub("~0", "~")
+	return unescaped
+end
+
+local function resolve_json_pointer(ref : table, path : string) : any, ptr_error
+	local ptr_len = #path+1
+	for part, pos in path:gmatch("/([^/]*)()") do
+		local token = unescape_token(part)
+		if not ref is table then
+			return nil
+		end
+		local idx = next(ref)
+		local new_ref : any
+
+		if idx is string then
+			new_ref = ref[token]
+		elseif idx is integer then
+			local i = tonumber(token)
+			if token == "-" then i = #ref + 1 end
+			new_ref = ref[i+1]
+		else
+			return nil, "invalid-table"
+		end
+
+		if pos as integer == ptr_len then
+			return new_ref
+		elseif new_ref is table then
+			ref = new_ref
+		elseif not ref is table then
+			return nil, "invalid-path"
+		end
+
+	end
+	return ref
+end
+
+return {
+	resolve = resolve_json_pointer,
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/jsonschema.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,376 @@
+-- Copyright (C) 2021 Kim Alvefur
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+-- Based on
+-- https://json-schema.org/draft/2020-12/json-schema-core.html
+-- https://json-schema.org/draft/2020-12/json-schema-validation.html
+--
+
+if not math.type then require "prosody.util.mathcompat" end
+
+local json = require "prosody.util.json"
+local null = json.null;
+
+local pointer = require "prosody.util.jsonpointer"
+
+local type json_type_name = json.json_type_name
+
+-- json_type_name here is non-standard
+local type schema_t = boolean | json_schema_object
+
+local record json_schema_object
+	type json_type_name = json.json_type_name
+	type schema_object = json_schema_object
+
+	type : json_type_name | { json_type_name }
+	enum : { any }
+	const : any
+
+	allOf : { schema_t }
+	anyOf : { schema_t }
+	oneOf : { schema_t }
+
+	["not"] : schema_t
+	["if"] : schema_t
+	["then"] : schema_t
+	["else"] : schema_t
+
+	["$ref"] : string
+
+	-- numbers
+	multipleOf : number
+	maximum : number
+	exclusiveMaximum : number
+	minimum : number
+	exclusiveMinimum : number
+
+	-- strings
+	maxLength : integer
+	minLength : integer
+	pattern : string -- NYI
+	format : string
+
+	-- arrays
+	prefixItems : { schema_t }
+	items : schema_t
+	contains : schema_t
+	maxItems : integer
+	minItems : integer
+	uniqueItems : boolean
+	maxContains : integer -- NYI
+	minContains : integer -- NYI
+
+	-- objects
+	properties : { string : schema_t }
+	maxProperties : integer -- NYI
+	minProperties : integer -- NYI
+	required : { string }
+	dependentRequired : { string : { string } }
+	additionalProperties: schema_t
+	patternProperties: schema_t -- NYI
+	propertyNames : schema_t
+
+	-- xml
+	record xml_t
+		name : string
+		namespace : string
+		prefix : string
+		attribute : boolean
+		wrapped : boolean
+
+		-- nonstantard, maybe in the future
+		text : boolean
+		x_name_is_value : boolean
+		x_single_attribute : string
+	end
+
+	xml : xml_t
+
+	-- descriptive
+	title : string
+	description : string
+	deprecated : boolean
+	readOnly : boolean
+	writeOnly : boolean
+
+	-- methods
+	validate : function ( schema_t, any, json_schema_object ) : boolean
+end
+
+-- TODO validator function per schema property
+
+local function simple_validate(schema : json_type_name | { json_type_name }, data : any) : boolean
+	if schema == nil then
+		return true
+	elseif schema == "object" and data is table then
+		return type(data) == "table" and (next(data)==nil or type((next(data, nil))) == "string")
+	elseif schema == "array" and data is table then
+		return type(data) == "table" and (next(data)==nil or type((next(data, nil))) == "number")
+	elseif schema == "integer" then
+		return math.type(data) == schema
+	elseif schema == "null" then
+		return data == null
+	elseif schema is { json_type_name } then
+		for _, one in ipairs(schema as { json_type_name }) do
+			if simple_validate(one, data) then
+				return true
+			end
+		end
+		return false
+	else
+		return type(data) == schema
+	end
+end
+
+local complex_validate : function ( json_schema_object, any, json_schema_object ) : boolean
+
+local function validate (schema : schema_t, data : any, root : json_schema_object) : boolean
+	if schema is boolean then
+		return schema
+	else
+		return complex_validate(schema, data, root)
+	end
+end
+
+function complex_validate (schema : json_schema_object, data : any, root : json_schema_object) : boolean
+
+	if root == nil then
+		root = schema
+	end
+
+	if schema["$ref"] and schema["$ref"]:sub(1,1) == "#" then
+		local referenced = pointer.resolve(root as table, schema["$ref"]:sub(2)) as schema_t
+		if referenced ~= nil and referenced ~= root and referenced ~= schema then
+			if not validate(referenced, data, root) then
+				return false;
+			end
+		end
+	end
+
+	if not simple_validate(schema.type, data) then
+		return false;
+	end
+
+	if schema.type == "object" then
+		if data is table then
+			-- just check that there the keys are all strings
+			for k in pairs(data) do
+				if not k is string then
+					return false
+				end
+			end
+		end
+	end
+
+	if schema.type == "array" then
+		if data is table then
+			-- just check that there the keys are all numbers
+			for i in pairs(data) do
+				if not i is integer then
+					return false
+				end
+			end
+		end
+	end
+
+	if schema["enum"] ~= nil then
+		local match = false
+		for _, v in ipairs(schema["enum"]) do
+			if v == data then
+				-- FIXME supposed to do deep-compare
+				match = true
+				break
+			end
+		end
+		if not match then
+			return false
+		end
+	end
+
+	-- XXX this is measured in byte, while JSON measures in ... bork
+	-- TODO use utf8.len?
+	if data is string then
+		if schema.maxLength and #data > schema.maxLength then
+			return false
+		end
+		if schema.minLength and #data < schema.minLength then
+			return false
+		end
+	end
+
+	if data is number then
+		if schema.multipleOf and (data == 0 or data % schema.multipleOf ~= 0) then
+			return false
+		end
+
+		if schema.maximum and not ( data <= schema.maximum ) then
+			return false
+		end
+
+		if schema.exclusiveMaximum and not ( data < schema.exclusiveMaximum ) then
+			return false
+		end
+
+		if schema.minimum and not ( data >= schema.minimum ) then
+			return false
+		end
+
+		if schema.exclusiveMinimum and not ( data > schema.exclusiveMinimum ) then
+			return false
+		end
+	end
+
+	if schema.allOf then
+		for _, sub in ipairs(schema.allOf) do
+			if not validate(sub, data, root) then
+				return false
+			end
+		end
+	end
+
+	if schema.oneOf then
+		local valid = 0
+		for _, sub in ipairs(schema.oneOf) do
+			if validate(sub, data, root) then
+				valid = valid + 1
+			end
+		end
+		if valid ~= 1 then
+			return false
+		end
+	end
+
+	if schema.anyOf then
+		local match = false
+		for _, sub in ipairs(schema.anyOf) do
+			if validate(sub, data, root) then
+				match = true
+				break
+			end
+		end
+		if not match then
+			return false
+		end
+	end
+
+	if schema["not"] then
+		if validate(schema["not"], data, root) then
+			return false
+		end
+	end
+
+	if schema["if"] ~= nil then
+		if validate(schema["if"], data, root) then
+			if schema["then"] then
+				return validate(schema["then"], data, root)
+			end
+		else
+			if schema["else"] then
+				return validate(schema["else"], data, root)
+			end
+		end
+	end
+
+	if schema.const ~= nil and schema.const ~= data then
+		return false
+	end
+
+	if data is table then
+
+		if schema.maxItems and #data > schema.maxItems then
+			return false
+		end
+
+		if schema.minItems and #data < schema.minItems then
+			return false
+		end
+
+		if schema.required then
+			for _, k in ipairs(schema.required) do
+				if data[k] == nil then
+					return false
+				end
+			end
+		end
+
+		if schema.propertyNames ~= nil then
+			for k in pairs(data) do
+				if not validate(schema.propertyNames, k, root) then
+					return false
+				end
+			end
+		end
+
+		if schema.properties then
+			for k, sub in pairs(schema.properties) do
+				if data[k] ~= nil and not validate(sub, data[k], root) then
+					return false
+				end
+			end
+		end
+
+		if schema.additionalProperties ~= nil then
+			for k, v in pairs(data) do
+				if schema.properties == nil or schema.properties[k as string] == nil then
+					if not validate(schema.additionalProperties, v, root) then
+						return false
+					end
+				end
+			end
+		end
+
+		if schema.uniqueItems then
+			-- only works for scalars, would need to deep-compare for objects/arrays/tables
+			local values : { any : boolean } = {}
+			for _, v in pairs(data) do
+				if values[v] then
+					return false
+				end
+				values[v] = true
+			end
+		end
+
+		local p = 0
+		if schema.prefixItems ~= nil then
+			for i, s in ipairs(schema.prefixItems) do
+				if data[i] == nil then
+					break
+				elseif validate(s, data[i], root) then
+					p = i
+				else
+					return false
+				end
+			end
+		end
+
+		if schema.items ~= nil then
+			for i = p+1, #data do
+				if not validate(schema.items, data[i], root) then
+					return false
+				end
+			end
+		end
+
+		if schema.contains ~= nil then
+			local found = false
+			for i = 1, #data do
+				if validate(schema.contains, data[i], root) then
+					found = true
+					break
+				end
+			end
+			if not found then
+				return false
+			end
+		end
+	end
+
+	return true;
+end
+
+
+json_schema_object.validate = validate;
+
+return json_schema_object;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/jwt.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,38 @@
+local crypto = require "prosody.util.crypto"
+local record jwtlib
+	enum algorithm
+		"HS256"
+		"HS384"
+		"HS512"
+		"ES256"
+		"ES512"
+		"RS256"
+		"RS384"
+		"RS512"
+		"PS256"
+		"PS384"
+		"PS512"
+	end
+	type payload = { string : any }
+	type signer_t = function (payload : payload) : string
+	type verifier_t = function (token : string) : payload
+	enum key_type
+		"rsaEncryption"
+		"id-ecPublicKey"
+	end
+	record algorithm_t
+		sign : signer_t
+		verify : verifier_t
+		load_key : function (key : string) : crypto.key
+	end
+	init : function (algorithm, private_key : string, public_key : string, table) : signer_t, verifier_t
+	new_signer : function (algorithm, string, table) : signer_t
+	new_verifier : function (algorithm, string, table) : verifier_t
+	_algorithms : {
+		algorithm : algorithm_t
+	}
+	-- Deprecated
+	sign : function (private_key : string, payload) : string
+	verify : function (string) : payload
+end
+return jwtlib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/logger.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,18 @@
+local record util
+	enum loglevel
+		"debug"
+		"info"
+		"warn"
+		"error"
+	end
+	type logger = function ( loglevel, string, ...:any )
+	type sink = function ( string, loglevel, string, ...:any )
+	type simple_sink = function ( string, loglevel, string )
+	init : function ( string ) : logger
+	make_logger : function ( string, loglevel ) : function ( string, ...:any )
+	reset : function ()
+	add_level_sink : function ( loglevel, sink )
+	add_simple_sink : function ( simple_sink, { loglevel } )
+end
+
+return util
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/mathcompat.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,15 @@
+if not math.type then
+	local enum number_subtype
+		"float" "integer"
+	end
+	local function math_type(t:any) : number_subtype
+		if t is number then
+			if t % 1 == 0 and t ~= t+1 and t ~= t-1 then
+				return "integer"
+			else
+				return "float"
+			end
+		end
+	end
+	_G.math.type = math_type
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/net.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,13 @@
+
+local enum type_strings
+	"both"
+	"ipv4"
+	"ipv6"
+end
+
+local record lib
+	local_addresses : function (type_strings, boolean) : { string }
+	pton : function (string):string
+	ntop : function (string):string
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/poll.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,31 @@
+local record state
+	enum waiterr
+		"timeout"
+		"signal"
+	end
+	add : function (state, integer, boolean, boolean) : boolean
+	add : function (state, integer, boolean, boolean) : nil, string, integer
+	set : function (state, integer, boolean, boolean) : boolean
+	set : function (state, integer, boolean, boolean) : nil, string, integer
+	del : function (state, integer) : boolean
+	del : function (state, integer) : nil, string, integer
+	wait : function (state, integer) : integer, boolean, boolean
+	wait : function (state, integer) : nil, string, integer
+	wait : function (state, integer) : nil, waiterr
+	getfd : function (state) : integer
+end
+
+local record lib
+	new : function () : state
+	EEXIST : integer
+	EMFILE : integer
+	ENOENT : integer
+	enum api_backend
+		"epoll"
+		"poll"
+		"select"
+	end
+	api : api_backend
+end
+
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/pposix.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,108 @@
+local record pposix
+	enum syslog_facility
+		"auth"
+		"authpriv"
+		"cron"
+		"daemon"
+		"ftp"
+		"kern"
+		"local0"
+		"local1"
+		"local2"
+		"local3"
+		"local4"
+		"local5"
+		"local6"
+		"local7"
+		"lpr"
+		"mail"
+		"syslog"
+		"user"
+		"uucp"
+	end
+
+	enum syslog_level
+		"debug"
+		"info"
+		"notice"
+		"warn"
+		"error"
+	end
+
+	enum ulimit_resource
+		"CORE"
+		"CPU"
+		"DATA"
+		"FSIZE"
+		"NOFILE"
+		"STACK"
+		"MEMLOCK"
+		"NPROC"
+		"RSS"
+		"NICE"
+	end
+
+	enum ulimit_unlimited
+		"unlimited"
+	end
+
+	type ulimit_limit = integer | ulimit_unlimited
+
+	record utsname
+		sysname         :  string
+		nodename        :  string
+		release         :  string
+		version         :  string
+		machine         :  string
+		domainname      :  string
+	end
+
+	record memoryinfo
+		allocated       :  integer
+		allocated_mmap  :  integer
+		used            :  integer
+		unused          :  integer
+		returnable      :  integer
+	end
+
+	abort : function ()
+
+	daemonize : function () : boolean, string
+
+	syslog_open : function (ident : string, facility : syslog_facility)
+	syslog_close : function ()
+	syslog_log : function (level : syslog_level, src : string, msg : string)
+	syslog_setminlevel : function (level : syslog_level)
+
+	getpid : function () : integer
+	getuid : function () : integer
+	getgid : function () : integer
+
+	setuid : function (uid : integer | string) : boolean, string -- string|integer
+	setgid : function (uid : integer | string) : boolean, string
+	initgroups : function (user : string, gid : integer) : boolean, string
+
+	umask : function (umask : string) : string
+
+	mkdir : function (dir : string) : boolean, string
+
+	setrlimit : function (resource : ulimit_resource, soft : ulimit_limit, hard : ulimit_limit) : boolean, string
+	getrlimit : function (resource : ulimit_resource) : boolean, ulimit_limit, ulimit_limit
+	getrlimit : function (resource : ulimit_resource) : boolean, string
+
+	uname : function () : utsname
+
+	setenv : function (key : string, value : string) : boolean
+
+	meminfo : function () : memoryinfo
+
+	atomic_append : function (f : FILE, s : string) : boolean, string, integer
+
+	isatty : function(FILE) : boolean
+
+	ENOENT : integer
+	_NAME : string
+	_VESRION : string
+end
+
+return pposix
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/promise.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,22 @@
+
+local record lib
+	type resolve_func = function (any)
+	type promise_body = function (resolve_func, resolve_func)
+
+	record Promise<A, B>
+		type on_resolved = function (A) : any
+		type on_rejected = function (B) : any
+		next : function (Promise, on_resolved, on_rejected) : Promise<any, any>
+	end
+
+	new : function (promise_body) : Promise
+	resolve : function (any) : Promise
+	reject : function (any) : Promise
+	all : function ({ Promise }) : Promise
+	all_settled : function ({ Promise }) : Promise
+	race : function ({ Promise }) : Promise
+	try : function
+	is_promise : function(any) : boolean
+end
+
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/queue.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,21 @@
+local record lib
+	record queue<T>
+		size : integer
+		count : function (queue<T>) : integer
+		enum push_errors
+			"queue full"
+		end
+
+		push : function (queue<T>, T) : boolean, push_errors
+		pop : function (queue<T>) : T
+		peek : function (queue<T>) : T
+		replace : function (queue<T>, T) : boolean, push_errors
+		type iterator = function (T, integer) : integer, T
+		items : function (queue<T>) : iterator, T, integer
+		type consume_iter = function (queue<T>) : T
+		consume : function (queue<T>) : consume_iter
+	end
+
+	new : function<T> (size:integer, allow_wrapping:boolean) : queue<T>
+end
+return lib;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/random.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,4 @@
+local record lib
+	bytes : function (n:integer):string
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/ringbuffer.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,20 @@
+local record lib
+	record ringbuffer
+		find : function (ringbuffer, string) : integer
+		discard : function (ringbuffer, integer) : boolean
+		read : function (ringbuffer, integer, boolean) : string
+		readuntil : function (ringbuffer, string) : string
+		write : function (ringbuffer, string) : integer
+		size : function (ringbuffer) : integer
+		length : function (ringbuffer) : integer
+		sub : function (ringbuffer, integer, integer) : string
+		byte : function (ringbuffer, integer, integer) : integer...
+		free : function (ringbuffer) : integer
+	end
+
+	new : function (integer) : ringbuffer
+end
+
+return lib
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/roles.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,32 @@
+local record util_roles
+
+	type context = any
+
+	record Role
+		id : string
+		name : string
+		description : string
+		default : boolean
+		priority : number -- or integer?
+		permissions : { string : boolean }
+
+		may : function (Role, string, context)
+		clone : function (Role, role_config)
+		set_permission : function (Role, string, boolean, boolean)
+	end
+
+	is_role : function (any) : boolean
+
+	record role_config
+		name : string
+		description : string
+		default : boolean
+		priority : number -- or integer?
+		inherits : { Role }
+		permissions : { string : boolean }
+	end
+
+	new : function (role_config, Role) : Role
+end
+
+return util_roles
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/serialization.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,33 @@
+local record _M
+	enum preset
+		"debug"
+		"oneline"
+		"compact"
+	end
+	type fallback = function (any, string) : string
+	record config
+		preset : preset
+		fallback :  fallback
+		fatal : boolean
+		keywords : { string : boolean }
+		indentwith : string
+		itemstart : string
+		itemsep : string
+		itemlast : string
+		tstart : string
+		tend : string
+		kstart : string
+		kend : string
+		equals : string
+		unquoted : boolean | string
+		hex : string
+		freeze : boolean
+		maxdepth : integer
+		multirefs : boolean
+		table_pairs : function
+	end
+	type serializer = function (any) : string
+	new : function (config|preset) : serializer
+	serialize : function (any, config|preset) : string
+end
+return _M
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/set.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,21 @@
+local record lib
+	record Set<T>
+		add : function<T> (Set<T>, T)
+		contains : function<T> (Set<T>, T) : boolean
+		contains_set : function<T> (Set<T>, Set<T>) : boolean
+		items :  function<T> (Set<T>) : function<T> (Set<T>, T) : T
+		add_list : function<T> (Set<T>, { T })
+		include : function<T> (Set<T>, Set<T>)
+		exclude : function<T> (Set<T>, Set<T>)
+		empty : function<T> (Set<T>) : boolean
+	end
+
+	new : function<T> ({ T }) : Set<T>
+	is_set : function (any) : boolean
+	union : function<T> (Set<T>, Set<T>) : Set <T>
+	difference : function<T> (Set<T>, Set<T>) : Set <T>
+	intersection : function<T> (Set<T>, Set<T>) : Set <T>
+	xor : function<T> (Set<T>, Set<T>) : Set <T>
+end
+
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/signal.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,41 @@
+local record lib
+	enum Signal
+		"SIGABRT"
+		"SIGALRM"
+		"SIGBUS"
+		"SIGCHLD"
+		"SIGCLD"
+		"SIGCONT"
+		"SIGFPE"
+		"SIGHUP"
+		"SIGILL"
+		"SIGINT"
+		"SIGIO"
+		"SIGIOT"
+		"SIGKILL"
+		"SIGPIPE"
+		"SIGPOLL"
+		"SIGPROF"
+		"SIGQUIT"
+		"SIGSEGV"
+		"SIGSTKFLT"
+		"SIGSTOP"
+		"SIGSYS"
+		"SIGTERM"
+		"SIGTRAP"
+		"SIGTTIN"
+		"SIGTTOU"
+		"SIGURG"
+		"SIGUSR1"
+		"SIGUSR2"
+		"SIGVTALRM"
+		"SIGWINCH"
+		"SIGXCPU"
+		"SIGXFSZ"
+	end
+	signal : function (integer | Signal, function, boolean) : boolean
+	raise : function (integer | Signal)
+	kill : function (integer, integer | Signal)
+	-- enum : integer
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/smqueue.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,99 @@
+local queue = require "prosody.util.queue";
+
+local record lib
+	-- T would typically be util.stanza
+	record smqueue<T>
+		_queue : queue.queue<T>
+		_head : integer
+		_tail : integer
+
+		enum ack_errors
+			"tail"
+			"head"
+			"pop"
+		end
+		push : function (smqueue, T)
+		ack : function (smqueue, integer) : { T }, ack_errors
+		resumable : function (smqueue<T>) : boolean
+		resume : function (smqueue<T>)  : queue.queue.iterator, any, integer
+		type consume_iter = function (smqueue<T>) : T
+		consume : function (smqueue<T>) : consume_iter
+
+		table : function (smqueue<T>) : { T }
+	end
+	new : function <T>(integer) : smqueue<T>
+end
+
+local type smqueue = lib.smqueue;
+
+function smqueue:push(v)
+	self._head = self._head + 1;
+	-- Wraps instead of errors
+	assert(self._queue:push(v));
+end
+
+function smqueue:ack(h : integer) : { any }, smqueue.ack_errors
+	if h < self._tail then
+		return nil, "tail";
+	elseif h > self._head then
+		return nil, "head";
+	end
+	-- TODO optimize? cache table fields
+	local acked = {};
+	self._tail = h;
+	local expect = self._head - self._tail;
+	while expect < self._queue:count() do
+		local v = self._queue:pop();
+		if not v then return nil, "pop"; end
+		table.insert(acked, v);
+	end
+	return acked;
+end
+
+function smqueue:count_unacked() : integer
+	return self._head - self._tail;
+end
+
+function smqueue:count_acked() : integer
+	return self._tail;
+end
+
+function smqueue:resumable() : boolean
+	return self._queue:count() >= (self._head - self._tail);
+end
+
+function smqueue:resume() : queue.queue.iterator, any, integer
+	return self._queue:items();
+end
+
+function smqueue:consume() : queue.queue.consume_iter
+	return self._queue:consume()
+end
+
+-- Compatibility layer, plain ol' table
+function smqueue:table() : { any }
+	local t : { any } = {};
+	for i, v in self:resume() do
+		t[i] = v;
+	end
+	return t;
+end
+
+local function freeze(q : smqueue<any>) : { string:integer }
+	return { head = q._head, tail = q._tail }
+end
+
+local queue_mt = {
+	--
+	__name = "smqueue";
+	__index = smqueue;
+	__len = smqueue.count_unacked;
+	__freeze = freeze;
+}
+
+function lib.new<T>(size : integer) : queue.queue<T>
+	assert(size>0);
+	return setmetatable({ _head = 0; _tail = 0; _queue = queue.new(size, true) }, queue_mt);
+end
+
+return lib;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/stanza.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,145 @@
+local record lib
+
+	type children_iter = function ( stanza_t ) : stanza_t
+	type childtags_iter = function () : stanza_t
+	type maptags_cb = function ( stanza_t ) : stanza_t
+
+
+	enum stanza_error_type
+		"auth"
+		"cancel"
+		"continue"
+		"modify"
+		"wait"
+	end
+	enum stanza_error_condition
+		"bad-request"
+		"conflict"
+		"feature-not-implemented"
+		"forbidden"
+		"gone"
+		"internal-server-error"
+		"item-not-found"
+		"jid-malformed"
+		"not-acceptable"
+		"not-allowed"
+		"not-authorized"
+		"policy-violation"
+		"recipient-unavailable"
+		"redirect"
+		"registration-required"
+		"remote-server-not-found"
+		"remote-server-timeout"
+		"resource-constraint"
+		"service-unavailable"
+		"subscription-required"
+		"undefined-condition"
+		"unexpected-request"
+	end
+
+	record stanza_t
+		name : string
+		attr : { string : string }
+		{ stanza_t | string }
+		tags : { stanza_t }
+
+		query : function ( stanza_t, string ) : stanza_t
+		body : function ( stanza_t, string, { string : string } ) : stanza_t
+		text_tag : function ( stanza_t, string, string, { string : string } ) : stanza_t
+		tag : function ( stanza_t, string, { string : string } ) : stanza_t
+		text : function ( stanza_t, string ) : stanza_t
+		up : function ( stanza_t ) : stanza_t
+		at_top : function ( stanza_t ) : boolean
+		reset : function ( stanza_t ) : stanza_t
+		add_direct_child : function ( stanza_t, stanza_t )
+		add_child : function ( stanza_t, stanza_t )
+		remove_children : function ( stanza_t, string, string ) : stanza_t
+
+		get_child : function ( stanza_t, string, string ) : stanza_t
+		get_text : function ( stanza_t ) : string
+		get_child_text : function ( stanza_t, string, string ) : string
+		get_child_attr : function ( stanza_t, string, string ) : string
+		get_child_with_attr : function ( stanza_t, string, string, string, function (string) : boolean ) : string
+		child_with_name : function ( stanza_t, string, string ) : stanza_t
+		child_with_ns : function ( stanza_t, string, string ) : stanza_t
+		children : function ( stanza_t ) : children_iter, stanza_t, integer
+		childtags : function ( stanza_t, string, string ) : childtags_iter
+		maptags : function ( stanza_t, maptags_cb ) : stanza_t
+		find : function ( stanza_t, string ) : stanza_t | string
+
+		top_tag : function ( stanza_t ) : string
+		pretty_print : function ( stanza_t ) : string
+		pretty_top_tag : function ( stanza_t ) : string
+
+		-- FIXME Represent util.error support
+		get_error : function ( stanza_t ) : stanza_error_type, stanza_error_condition, string, stanza_t
+		add_error : function ( stanza_t, stanza_error_type, stanza_error_condition, string, string )
+		indent : function ( stanza_t, integer, string ) : stanza_t
+	end
+
+	record serialized_stanza_t
+		name : string
+		attr : { string : string }
+		{ serialized_stanza_t | string }
+	end
+
+	record message_attr
+		["xml:lang"] : string
+		from : string
+		id : string
+		to : string
+		type : message_type
+		enum message_type
+			"chat"
+			"error"
+			"groupchat"
+			"headline"
+			"normal"
+		end
+	end
+
+	record presence_attr
+		["xml:lang"] : string
+		from : string
+		id : string
+		to : string
+		type : presence_type
+		enum presence_type
+			"error"
+			"probe"
+			"subscribe"
+			"subscribed"
+			"unsubscribe"
+			"unsubscribed"
+		end
+	end
+
+	record iq_attr
+		["xml:lang"] : string
+		from : string
+		id : string
+		to : string
+		type : iq_type
+		enum iq_type
+			"error"
+			"get"
+			"result"
+			"set"
+		end
+	end
+
+	stanza : function ( string, { string : string } ) : stanza_t
+	is_stanza : function ( any ) : boolean
+	preserialize : function ( stanza_t ) : serialized_stanza_t
+	deserialize : function ( serialized_stanza_t ) : stanza_t
+	clone : function ( stanza_t, boolean ) : stanza_t
+	message : function ( message_attr, string ) : stanza_t
+	iq : function ( iq_attr ) : stanza_t
+	reply : function ( stanza_t ) : stanza_t
+	error_reply : function ( stanza_t, stanza_error_type, stanza_error_condition, string, string ) : stanza_t
+	presence : function ( presence_attr ) : stanza_t
+	xml_escape : function ( string ) : string
+	pretty_print : function ( string ) : string
+end
+
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/strbitop.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,6 @@
+local record mod
+	sand : function (string, string) : string
+	sor : function (string, string) : string
+	sxor : function (string, string) : string
+end
+return mod
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/struct.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,6 @@
+local record lib
+	pack : function (string, ...:any) : string
+	unpack : function(string, string, integer) : any...
+	size : function(string) : integer
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/table.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,7 @@
+local record lib
+	create : function (narr:integer, nrec:integer):table
+	pack : function (...:any):{any}
+	move : function (table, integer, integer, integer, table) : table
+end
+return lib
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/termcolours.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,7 @@
+local record lib
+	getstring : function (string, string) : string
+	getstyle : function (...:string) : string
+	setstyle : function (string) : string
+	tohtml :  function (string) : string
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/time.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,6 @@
+
+local record lib
+	now : function () : number
+	monotonic : function () : number
+end
+return lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/timer.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,8 @@
+local record util_timer
+	record task end
+	type timer_callback = function (number) : number
+	add_task : function ( number, timer_callback, any ) : task
+	stop : function ( task )
+	reschedule : function ( task, number ) : task
+end
+return util_timer
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/uuid.d.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,8 @@
+local record lib
+	get_nibbles : function (number) : string
+	generate : function () : string
+
+	seed : function (string)
+end
+return lib
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/teal-src/prosody/util/xtemplate.tl	Thu Mar 23 13:36:52 2023 +0100
@@ -0,0 +1,101 @@
+-- render(template, stanza) --> string
+-- {path} --> stanza:find(path)
+-- {{ns}name/child|each({ns}name){sub-template}}
+
+--[[
+template ::= "{" path ("|" name ("(" args ")")? (template)? )* "}"
+path ::= defined by util.stanza
+name ::= %w+
+args ::= anything with balanced ( ) pairs
+]]
+
+local s_gsub = string.gsub;
+local s_match = string.match;
+local s_sub = string.sub;
+local t_concat = table.concat;
+
+local st = require "prosody.util.stanza";
+
+local type escape_t = function (string) : string
+local type filter_t = function (string, string | st.stanza_t, string) : string | st.stanza_t, boolean
+local type filter_coll = { string : filter_t }
+
+local function render(template : string, root : st.stanza_t, escape : escape_t, filters : filter_coll) : string
+	escape = escape or st.xml_escape;
+
+	return (s_gsub(template, "%b{}", function(block : string) : string
+		local inner = s_sub(block, 2, -2);
+		local path, pipe, pos = s_match(inner, "^([^|]+)(|?)()");
+		if not path is string then return end
+		local value : string | st.stanza_t
+		if path == "." then
+			value = root;
+		elseif path == "#" then
+			value = root:get_text();
+		else
+			value = root:find(path);
+		end
+		local is_escaped = false;
+
+		while pipe == "|" do
+			local func, args, tmpl, p = s_match(inner, "^(%w+)(%b())(%b{})()", pos as integer);
+			if not func then func, args, p = s_match(inner, "^(%w+)(%b())()", pos as integer); end
+			if not func then func, tmpl, p = s_match(inner, "^(%w+)(%b{})()", pos as integer); end
+			if not func then func, p = s_match(inner, "^(%w+)()", pos as integer); end
+			if not func then break end
+			if tmpl then tmpl = s_sub(tmpl, 2, -2); end
+			if args then args = s_sub(args, 2, -2); end
+
+			if func == "each" and tmpl and st.is_stanza(value) then
+				if not args then value, args = root, path; end
+				local ns, name = s_match(args, "^(%b{})(.*)$");
+				if ns then ns = s_sub(ns, 2, -2); else name, ns = args, nil; end
+				if ns == "" then ns = nil; end
+				if name == "" then name = nil; end
+				local out, i = {}, 1;
+				for c in (value as st.stanza_t):childtags(name, ns) do
+					out[i], i = render(tmpl, c, escape, filters), i + 1;
+				end
+				value = t_concat(out);
+				is_escaped = true;
+			elseif func == "and" and tmpl then
+				local condition = value;
+				if args then condition = root:find(args); end
+				if condition then
+					value = render(tmpl, root, escape, filters);
+					is_escaped = true;
+				end
+			elseif func == "or" and tmpl then
+				local condition = value;
+				if args then condition = root:find(args); end
+				if not condition then
+					value = render(tmpl, root, escape, filters);
+					is_escaped = true;
+				end
+			elseif filters and filters[func] then
+				local f = filters[func];
+				if args == nil then
+					value, is_escaped = f(value, tmpl);
+				else
+					value, is_escaped = f(args, value, tmpl);
+				end
+			else
+				error("No such filter function: " .. func);
+			end
+			pipe, pos = s_match(inner, "^(|?)()", p as integer);
+		end
+
+		if value is string then
+			if not is_escaped then value = escape(value); end
+			return value;
+		elseif st.is_stanza(value) then
+			value = value:get_text();
+			if value then
+				return escape(value);
+			end
+		end
+		return "";
+	end));
+end
+
+return { render = render };
--- a/teal-src/util/array.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-local record array_t<T>
-	{ T }
-end
-
-local record lib
-	metamethod __call : function () : array_t
-end
-
-return lib
--- a/teal-src/util/async.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-local record lib
-	ready : function () : boolean
-	waiter : function (num : integer, allow_many : boolean) : function (), function ()
-	guarder : function () : function (id : function ()) : function () | nil
-	record runner_t<T>
-		func : function (T)
-		thread : thread
-		enum state_e
-			-- from Lua manual
-			"running"
-			"suspended"
-			"normal"
-			"dead"
-
-			-- from util.async
-			"ready"
-			"error"
-		end
-		state : state_e
-		notified_state : state_e
-		queue : { T }
-		type watcher_t = function (runner_t<T>, ... : any)
-		type watchers_t = { state_e : watcher_t }
-		data : any
-		id : string
-
-		run : function (runner_t<T>, T) : boolean, state_e, integer
-		enqueue : function (runner_t<T>, T) : runner_t<T>
-		log : function (runner_t<T>, string, string, ... : any)
-		onready : function (runner_t<T>, function) : runner_t<T>
-		onready : function (runner_t<T>, function) : runner_t<T>
-		onwaiting : function (runner_t<T>, function) : runner_t<T>
-		onerror : function (runner_t<T>, function) : runner_t<T>
-	end
-	runner : function <T>(function (T), runner_t.watchers_t, any) : runner_t<T>
-	wait_for : function (any) : any, any
-	sleep : function (t:number)
-
-	-- set_nexttick = function(new_next_tick) next_tick = new_next_tick; end;
-	-- set_schedule_function = function (new_schedule_function) schedule_task = new_schedule_function; end;
-end
-return lib
--- a/teal-src/util/bitcompat.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-local record lib
-	band : function (integer, integer, ... : integer) : integer
-	bor : function (integer, integer, ... : integer) : integer
-	bxor : function (integer, integer, ... : integer) : integer
-	lshift : function (integer, integer) : integer
-	rshift : function (integer, integer) : integer
-end
-return lib
--- a/teal-src/util/compat.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-local record lib
-	xpcall : function (function, function, ...:any):boolean, any
-end
-return lib
--- a/teal-src/util/crand.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-local record lib
-	bytes : function (n : integer) : string
-	enum sourceid "OpenSSL" "arc4random()" "Linux" end
-	_source : sourceid
-end
-return lib
--- a/teal-src/util/crypto.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-local record lib
-	record key
-		private_pem : function (key) : string
-		public_pem : function (key) : string
-		get_type : function (key) : string
-	end
-
-	generate_ed25519_keypair : function () : key
-	ed25519_sign : function (key, string) : string
-	ed25519_verify : function (key, string, string) : boolean
-
-	ecdsa_sha256_sign : function (key, string) : string
-	ecdsa_sha256_verify : function (key, string, string) : boolean
-	parse_ecdsa_signature : function (string) : string, string
-	build_ecdsa_signature : function (string, string) : string
-
-	import_private_pem : function (string) : key
-	import_public_pem : function (string) : key
-
-	aes_128_gcm_encrypt : function (key, string, string) : string
-	aes_128_gcm_decrypt : function (key, string, string) : string
-	aes_256_gcm_encrypt : function (key, string, string) : string
-	aes_256_gcm_decrypt : function (key, string, string) : string
-
-
-	version : string
-	_LIBCRYPTO_VERSION : string
-end
-return lib
--- a/teal-src/util/dataforms.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-local stanza_t = require "util.stanza".stanza_t
-
-local record lib
-	record dataform
-		title : string
-		instructions : string
-
-		record form_field
-
-			enum field_type
-				"boolean"
-				"fixed"
-				"hidden"
-				"jid-multi"
-				"jid-single"
-				"list-multi"
-				"list-single"
-				"text-multi"
-				"text-private"
-				"text-single"
-			end
-
-			type : field_type
-			var : string -- protocol name
-			name :  string -- internal name
-
-			label : string
-			desc : string
-
-			datatype : string
-			range_min : number
-			range_max : number
-
-			value : any -- depends on field_type
-			options : table
-		end
-
-		{ form_field }
-
-		enum form_type
-			"form"
-			"submit"
-			"cancel"
-			"result"
-		end
-
-		form : function ( dataform, { string : any }, form_type ) : stanza_t
-		data : function ( dataform, stanza_t ) : { string : any }
-	end
-
-	new : function ( dataform ) : dataform
-end
-
-return lib
--- a/teal-src/util/datamapper.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,379 +0,0 @@
--- Copyright (C) 2021 Kim Alvefur
---
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
--- Based on
--- https://json-schema.org/draft/2020-12/json-schema-core.html
--- https://json-schema.org/draft/2020-12/json-schema-validation.html
--- http://spec.openapis.org/oas/v3.0.1#xmlObject
--- https://github.com/OAI/OpenAPI-Specification/issues/630 (text:true)
---
--- XML Object Extensions:
--- text to refer to the text content at the same time as attributes
--- x_name_is_value for enum fields where the <tag-name/> is the value
--- x_single_attribute for <tag attr="this"/>
---
--- TODO pointers
--- TODO cleanup / refactor
--- TODO s/number/integer/ once we have appropriate math.type() compat
---
-
-if not math.type then require "util.mathcompat" end
-
-local st = require "util.stanza";
-local json = require"util.json"
-local pointer = require"util.jsonpointer";
-
-local json_type_name = json.json_type_name;
-local json_schema_object = require "util.jsonschema"
-local type schema_t = boolean | json_schema_object
-
-local function toboolean ( s : string ) : boolean
-	if s == "true" or s == "1" then
-		return true
-	elseif s == "false" or s == "0" then
-		return false
-	elseif s then
-		return true
-	end
-end
-
-local function totype(t : json_type_name, s : string) : any
-	if not s then return nil end
-	if t == "string" then
-		return s;
-	elseif t == "boolean" then
-		return toboolean(s)
-	elseif t == "number" or t == "integer" then
-		return tonumber(s)
-	end
-end
-
-local enum value_goes
-	"in_tag_name"
-	"in_text"
-	"in_text_tag"
-	"in_attribute"
-	"in_single_attribute"
-	"in_children"
-	"in_wrapper"
-end
-
-local function resolve_schema(schema  : schema_t, root : json_schema_object) : schema_t
-	if schema is json_schema_object then
-		if schema["$ref"] and schema["$ref"]:sub(1, 1) == "#" then
-			return pointer.resolve(root as table, schema["$ref"]:sub(2)) as schema_t;
-		end
-	end
-	return schema;
-end
-
-local function guess_schema_type(schema : json_schema_object) : json_type_name
-	local schema_types = schema.type
-	if schema_types is json_type_name then
-		return schema_types
-	elseif schema_types ~= nil then
-		error "schema has unsupported 'type' property"
-	elseif schema.properties then
-		return "object"
-	elseif schema.items then
-		return "array"
-	end
-	return "string" -- default assumption
-end
-
-local function unpack_propschema( propschema : schema_t, propname : string, current_ns : string )
-		: json_type_name, value_goes, string, string, string, string, { any }
-	local proptype : json_type_name = "string"
-	local value_where : value_goes = propname and "in_text_tag" or "in_text"
-	local name = propname
-	local namespace : string
-	local prefix : string
-	local single_attribute : string
-	local enums : { any }
-
-	if propschema is json_schema_object then
-		proptype = guess_schema_type(propschema);
-	elseif propschema is string then -- Teal says this can never be a string, but it could before so best be sure
-		error("schema as string is not supported: "..propschema.." {"..current_ns.."}"..propname)
-	end
-
-	if proptype == "object" or proptype == "array" then
-		value_where = "in_children"
-	end
-
-	if propschema is json_schema_object then
-		local xml = propschema.xml
-		if xml then
-			if xml.name then
-				name = xml.name
-			end
-			if xml.namespace and xml.namespace ~= current_ns then
-				namespace = xml.namespace
-			end
-			if xml.prefix then
-				prefix = xml.prefix
-			end
-			if proptype == "array" and xml.wrapped then
-				value_where = "in_wrapper"
-			elseif xml.attribute then
-				value_where = "in_attribute"
-			elseif xml.text then
-				value_where = "in_text"
-			elseif xml.x_name_is_value then
-				value_where = "in_tag_name"
-			elseif xml.x_single_attribute then
-				single_attribute = xml.x_single_attribute
-				value_where = "in_single_attribute"
-			end
-		end
-		if propschema["const"] then
-			enums = { propschema["const"] }
-		elseif propschema["enum"] then
-			enums = propschema["enum"]
-		end
-	end
-
-	return proptype, value_where, name, namespace, prefix, single_attribute, enums
-end
-
-local parse_object : function (schema : schema_t, s : st.stanza_t, root : json_schema_object) : { string : any }
-local parse_array : function (schema : schema_t, s : st.stanza_t, root : json_schema_object) : { any }
-
-local function extract_value (s : st.stanza_t, value_where : value_goes, proptype : json.json_type_name, name : string, namespace : string, prefix : string, single_attribute : string, enums : { any }) : string
-	if value_where == "in_tag_name" then
-		local c : st.stanza_t
-		if proptype == "boolean" then
-			c = s:get_child(name, namespace);
-		elseif enums and proptype == "string" then
-			-- XXX O(n²) ?
-			-- Probably better to flip the table and loop over :childtags(nil, ns), should be 2xO(n)
-			-- BUT works first, optimize later
-			for i = 1, #enums do
-				c = s:get_child(enums[i] as string, namespace);
-				if c then break end
-			end
-		else
-			c = s:get_child(nil, namespace);
-		end
-		if c then
-			return c.name;
-		end
-	elseif value_where == "in_attribute" then
-		local attr = name
-		if prefix then
-			attr = prefix .. ':' .. name
-		elseif namespace and namespace ~= s.attr.xmlns then
-			attr = namespace .. "\1" .. name
-		end
-		return s.attr[attr]
-
-	elseif value_where == "in_text" then
-		return s:get_text()
-
-	elseif value_where == "in_single_attribute" then
-		local c = s:get_child(name, namespace)
-		return c and c.attr[single_attribute]
-	elseif value_where == "in_text_tag" then
-		return s:get_child_text(name, namespace)
-	end
-end
-
-function parse_object (schema : schema_t, s : st.stanza_t, root : json_schema_object) : { string : any }
-	local out : { string : any } = {}
-	schema = resolve_schema(schema, root)
-	if schema is json_schema_object and schema.properties then
-		for prop, propschema in pairs(schema.properties) do
-			propschema = resolve_schema(propschema, root)
-
-			local proptype, value_where, name, namespace, prefix, single_attribute, enums = unpack_propschema(propschema, prop, s.attr.xmlns)
-
-			if value_where == "in_children" and propschema is json_schema_object then
-				if proptype == "object" then
-					local c = s:get_child(name, namespace)
-					if c then
-						out[prop] = parse_object(propschema, c, root);
-					end
-				elseif proptype == "array" then
-					local a = parse_array(propschema, s, root);
-					if a and a[1] ~= nil then
-						out[prop] = a;
-					end
-				else
-					error "unreachable"
-				end
-			elseif value_where == "in_wrapper" and propschema is json_schema_object and proptype == "array" then
-				local wrapper = s:get_child(name, namespace);
-				if wrapper then
-					out[prop] = parse_array(propschema, wrapper, root);
-				end
-			else
-				local value : string = extract_value (s, value_where, proptype, name, namespace, prefix, single_attribute, enums)
-
-				out[prop] = totype(proptype, value)
-			end
-		end
-	end
-
-	return out
-end
-
-function parse_array (schema : json_schema_object, s : st.stanza_t, root : json_schema_object) : { any }
-	local itemschema : schema_t = resolve_schema(schema.items, root);
-	local proptype, value_where, child_name, namespace, prefix, single_attribute, enums = unpack_propschema(itemschema, nil, s.attr.xmlns)
-	local attr_name : string
-	if value_where == "in_single_attribute" then -- FIXME this shouldn't be needed
-		value_where = "in_attribute";
-		attr_name = single_attribute;
-	end
-	local out : { any } = {}
-
-	if proptype == "object" then
-		if itemschema is json_schema_object then
-			for c in s:childtags(child_name, namespace) do
-				table.insert(out, parse_object(itemschema, c, root));
-			end
-		else
-			error "array items must be schema object"
-		end
-	elseif proptype == "array" then
-		if itemschema is json_schema_object then
-			for c in s:childtags(child_name, namespace) do
-				table.insert(out, parse_array(itemschema, c, root));
-			end
-		end
-	else
-		for c in s:childtags(child_name, namespace) do
-			local value : string = extract_value (c, value_where, proptype, attr_name or child_name, namespace, prefix, single_attribute, enums)
-
-			table.insert(out, totype(proptype, value));
-		end
-	end
-	return out;
-end
-
-local function parse (schema : json_schema_object, s : st.stanza_t) : table
-	local s_type = guess_schema_type(schema)
-	if s_type == "object" then
-		return parse_object(schema, s, schema)
-	elseif s_type == "array" then
-		return parse_array(schema, s, schema)
-	else
-		error "top-level scalars unsupported"
-	end
-end
-
-local function toxmlstring(proptype : json_type_name, v : any) : string
-	if proptype == "string" and v is string then
-		return  v
-	elseif proptype == "number" and v is number then
-		return  string.format("%g", v)
-	elseif proptype == "integer" and v is number then -- TODO is integer
-		return  string.format("%d", v)
-	elseif proptype == "boolean" then
-		return  v and "1" or "0"
-	end
-end
-
-local unparse : function (json_schema_object, table, string, string, st.stanza_t, json_schema_object) : st.stanza_t
-
-local function unparse_property(out : st.stanza_t, v : any, proptype : json_type_name, propschema : schema_t, value_where : value_goes, name : string, namespace : string, current_ns : string, prefix : string, single_attribute : string, root : json_schema_object)
-
-	if value_where == "in_attribute" then
-		local attr = name
-		if prefix then
-			attr = prefix .. ':' .. name
-		elseif namespace and namespace ~= current_ns then
-			attr = namespace .. "\1" .. name
-		end
-
-		out.attr[attr] = toxmlstring(proptype, v)
-	elseif value_where == "in_text" then
-		out:text(toxmlstring(proptype, v))
-	elseif value_where == "in_single_attribute" then
-		assert(single_attribute)
-		local propattr : { string : string } = {}
-
-		if namespace and namespace ~= current_ns then
-			propattr.xmlns = namespace
-		end
-
-		propattr[single_attribute] = toxmlstring(proptype, v)
-		out:tag(name, propattr):up();
-
-	else
-		local propattr : { string : string }
-		if namespace ~= current_ns then
-			propattr = { xmlns = namespace }
-		end
-		if value_where == "in_tag_name" then
-			if proptype == "string" and v is string then
-				out:tag(v, propattr):up();
-			elseif proptype == "boolean" and v == true then
-				out:tag(name, propattr):up();
-			end
-		elseif proptype == "object" and propschema is json_schema_object and v is table then
-			local c = unparse(propschema, v, name, namespace, nil, root);
-			if c then
-				out:add_direct_child(c);
-			end
-		elseif proptype == "array" and propschema is json_schema_object and v is table then
-			if value_where == "in_wrapper" then
-				local c = unparse(propschema, v, name, namespace, nil, root);
-				if c then
-					out:add_direct_child(c);
-				end
-			else
-				unparse(propschema, v, name, namespace, out, root);
-			end
-		else
-			out:text_tag(name, toxmlstring(proptype, v), propattr)
-		end
-	end
-end
-
-function unparse ( schema : json_schema_object, t : table, current_name : string, current_ns : string, ctx : st.stanza_t, root : json_schema_object ) : st.stanza_t
-
-	if root == nil then root = schema end
-
-	if schema.xml then
-		if schema.xml.name then
-			current_name = schema.xml.name
-		end
-		if schema.xml.namespace then
-			current_ns = schema.xml.namespace
-		end
-		-- TODO prefix?
-	end
-
-	local out = ctx or st.stanza(current_name, { xmlns = current_ns })
-
-	local s_type = guess_schema_type(schema)
-	if s_type == "object" then
-
-		for prop, propschema in pairs(schema.properties) do
-			propschema = resolve_schema(propschema, root)
-			local v = t[prop]
-
-			if v ~= nil then
-				local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(propschema, prop, current_ns)
-				unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute, root)
-			end
-		end
-		return out;
-
-	elseif s_type == "array" then
-		local itemschema = resolve_schema(schema.items, root)
-		local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(itemschema, current_name, current_ns)
-		for _, item in ipairs(t as { string }) do
-			unparse_property(out, item, proptype, itemschema, value_where, name, namespace, current_ns, prefix, single_attribute, root)
-		end
-		return out;
-	end
-end
-
-return {
-	parse = parse,
-	unparse = unparse,
-}
--- a/teal-src/util/datetime.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-local record lib
-	date     : function (t : number) : string
-	datetime : function (t : number) : string
-	time     : function (t : number) : string
-	legacy   : function (t : number) : string
-	parse    : function (t : string) : number
-end
-
-return lib
--- a/teal-src/util/encodings.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
--- TODO many actually return Maybe(String)
-local record lib
-	record base64
-		encode : function (s : string) : string
-		decode : function (s : string) : string
-	end
-	record stringprep
-		nameprep : function (s : string, strict : boolean) : string
-		nodeprep : function (s : string, strict : boolean) : string
-		resourceprep : function (s : string, strict : boolean) : string
-		saslprep : function (s : string, strict : boolean) : string
-	end
-	record idna
-		to_ascii : function (s : string) : string
-		to_unicode : function (s : string) : string
-	end
-	record utf8
-		valid : function (s : string) : boolean
-		length : function (s : string) : integer
-	end
-	record confusable
-		skeleton : function (s : string) : string
-	end
-	version : string
-end
-return lib
-
--- a/teal-src/util/error.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-local enum error_type
-	"auth"
-	"cancel"
-	"continue"
-	"modify"
-	"wait"
-end
-
-local enum error_condition
-	"bad-request"
-	"conflict"
-	"feature-not-implemented"
-	"forbidden"
-	"gone"
-	"internal-server-error"
-	"item-not-found"
-	"jid-malformed"
-	"not-acceptable"
-	"not-allowed"
-	"not-authorized"
-	"policy-violation"
-	"recipient-unavailable"
-	"redirect"
-	"registration-required"
-	"remote-server-not-found"
-	"remote-server-timeout"
-	"resource-constraint"
-	"service-unavailable"
-	"subscription-required"
-	"undefined-condition"
-	"unexpected-request"
-end
-
-local record protoerror
-	type : error_type
-	condition : error_condition
-	text : string
-	code : integer
-end
-
-local record Error
-	type : error_type
-	condition : error_condition
-	text : string
-	code : integer
-	context : { any : any }
-	source : string
-end
-
-local type compact_registry_item = { string, string, string, string }
-local type compact_registry = { compact_registry_item }
-local type registry = { string : protoerror }
-local type context = { string : any }
-
-local record error_registry_wrapper
-	source : string
-	registry : registry
-	new : function (string, context) : Error
-	coerce : function (any, string) : any, Error
-	wrap : function (Error) : Error
-	wrap : function (string, context) : Error
-	is_error : function (any) : boolean
-end
-
-local record lib
-	record configure_opt
-		auto_inject_traceback : boolean
-	end
-	new : function (protoerror, context, { string : protoerror }, string) : Error
-	init : function (string, string, registry | compact_registry) : error_registry_wrapper
-	init : function (string, registry | compact_registry) : error_registry_wrapper
-	is_error : function (any) : boolean
-	coerce : function (any, string) : any, Error
-	from_stanza : function (table, context, string) : Error
-	configure : function
-end
-
-return lib
--- a/teal-src/util/format.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-local record lib
-	format : function (string, ... : any) : string
-end
-return lib
--- a/teal-src/util/hashes.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-local type hash = function (msg : string, hex : boolean) : string
-local type hmac = function (key : string, msg : string, hex : boolean) : string
-local type kdf = function (pass : string, salt : string, i : integer) : string
-
-local record lib
-	sha1 : hash
-	sha256 : hash
-	sha224 : hash
-	sha384 : hash
-	sha512 : hash
-	md5 : hash
-	sha3_256 : hash
-	sha3_512 : hash
-	blake2s256 : hash
-	blake2b512 : hash
-	hmac_sha1 : hmac
-	hmac_sha256 : hmac
-	hmac_sha224 : hmac
-	hmac_sha384  :hmac
-	hmac_sha512 : hmac
-	hmac_md5 : hmac
-	hmac_sha3_256 : hmac
-	hmac_sha3_512 : hmac
-	scram_Hi_sha1 : kdf
-	pbkdf2_hmac_sha1 : kdf
-	pbkdf2_hmac_sha256 : kdf
-	equals : function (string, string) : boolean
-	version : string
-	_LIBCRYPTO_VERSION : string
-end
-return lib
--- a/teal-src/util/hex.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-local type s2s = function (s : string) : string
-local record lib
-	to : s2s
-	from : s2s
-	encode : s2s
-	decode : s2s
-end
-return lib
--- a/teal-src/util/http.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-local record lib
-	urlencode : function (s : string) : string 
-	urldecode : function (s : string) : string 
-	formencode : function (f : { string : string }) : string 
-	formdecode : function (s : string) : { string : string } 
-	contains_token : function (field : string, token : string) : boolean 
-	normalize_path : function (path : string) : string 
-end
-return lib
--- a/teal-src/util/human/io.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-local record lib
-	getchar : function (n : integer) : string
-	getline : function () : string
-	getpass : function () : string
-	show_yesno : function (prompt : string) : boolean
-	read_password : function () : string
-	show_prompt : function (prompt : string) : boolean
-	printf : function (fmt : string, ... : any)
-	padleft : function (s : string, width : integer) : string
-	padright : function (s : string, width : integer) : string
-
-	-- {K:V} vs T ?
-	record tablerow<K,V>
-		width : integer | string -- generate an 1..100 % enum?
-		title : string
-		mapper : function (V, {K:V}) : string
-		key : K
-		enum alignments
-			"left"
-			"right"
-		end
-		align : alignments
-	end
-	type getrow = function<K,V> ({ K : V }) : string
-	table : function<K,V> ({ tablerow<K,V> }, width : integer) : getrow<K,V>
-end
-
-return lib
--- a/teal-src/util/human/units.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-local lib = record
-	enum logbase
-		"b" -- 1024
-	end
-	adjust : function (number, string) : number, string
-	format : function (number, string, logbase) : string
-end
-return lib
--- a/teal-src/util/id.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-local record lib
-	short : function () : string
-	medium : function () : string
-	long : function () : string
-	custom : function (integer) : function () : string
-
-end
-return lib
-
--- a/teal-src/util/interpolation.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-local type renderer = function (string, { string : any }) : string
-local type filter = function (string, any) : string
-local record lib
-	new : function (string, string, funcs : { string : filter }) : renderer
-end
-return lib
--- a/teal-src/util/ip.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-local record iplib
-	enum protocol
-		"IPv6"
-		"IPv4"
-	end
-	record ip_t
-		addr : string
-		packed : string
-		proto : protocol
-		zone : string
-	end
-
-	new_ip : function (string, protocol) : ip_t
-	commonPrefixLength : function (ip_t, ip_t) : integer
-	parse_cidr : function (string) : ip_t, integer
-	match : function (ip_t, ip_t, integer) : boolean
-	is_ip : function (any) : boolean
-	truncate : function (ip_t, integer) : ip_t
-end
-return iplib
--- a/teal-src/util/jid.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-local record lib
-	split : function (string) : string, string, string
-	bare : function (string) : string
-	prepped_split : function (string, boolean) : string, string, string
-	join : function (string, string, string) : string
-	prep : function (string, boolean) : string
-	compare : function (string, string) : boolean
-	node : function (string) : string
-	host : function (string) : string
-	resource : function (string) : string
-	escape : function (string) : string
-	unescape : function (string) : string
-end
-
-return lib
--- a/teal-src/util/json.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-local record lib
-	encode : function (any) : string
-	decode : function (string) : any, string
-
-	enum json_type_name
-		"null"
-		"boolean"
-		"object"
-		"array"
-		"number"
-		"string"
-		"integer"
-	end
-
-	type null_type = (nil)
-	null : null_type
-end
-return lib
--- a/teal-src/util/jsonpointer.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-
-local enum ptr_error
-	"invalid-table"
-	"invalid-path"
-end
-
-local function unescape_token(escaped_token : string) : string
-	local unescaped = escaped_token:gsub("~1", "/"):gsub("~0", "~")
-	return unescaped
-end
-
-local function resolve_json_pointer(ref : table, path : string) : any, ptr_error
-	local ptr_len = #path+1
-	for part, pos in path:gmatch("/([^/]*)()") do
-		local token = unescape_token(part)
-		if not ref is table then
-			return nil
-		end
-		local idx = next(ref)
-		local new_ref : any
-
-		if idx is string then
-			new_ref = ref[token]
-		elseif idx is integer then
-			local i = tonumber(token)
-			if token == "-" then i = #ref + 1 end
-			new_ref = ref[i+1]
-		else
-			return nil, "invalid-table"
-		end
-
-		if pos as integer == ptr_len then
-			return new_ref
-		elseif new_ref is table then
-			ref = new_ref
-		elseif not ref is table then
-			return nil, "invalid-path"
-		end
-
-	end
-	return ref
-end
-
-return {
-	resolve = resolve_json_pointer,
-}
--- a/teal-src/util/jsonschema.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,376 +0,0 @@
--- Copyright (C) 2021 Kim Alvefur
---
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
--- Based on
--- https://json-schema.org/draft/2020-12/json-schema-core.html
--- https://json-schema.org/draft/2020-12/json-schema-validation.html
---
-
-if not math.type then require "util.mathcompat" end
-
-local json = require"util.json"
-local null = json.null;
-
-local pointer = require "util.jsonpointer"
-
-local type json_type_name = json.json_type_name
-
--- json_type_name here is non-standard
-local type schema_t = boolean | json_schema_object
-
-local record json_schema_object
-	type json_type_name = json.json_type_name
-	type schema_object = json_schema_object
-
-	type : json_type_name | { json_type_name }
-	enum : { any }
-	const : any
-
-	allOf : { schema_t }
-	anyOf : { schema_t }
-	oneOf : { schema_t }
-
-	["not"] : schema_t
-	["if"] : schema_t
-	["then"] : schema_t
-	["else"] : schema_t
-
-	["$ref"] : string
-
-	-- numbers
-	multipleOf : number
-	maximum : number
-	exclusiveMaximum : number
-	minimum : number
-	exclusiveMinimum : number
-
-	-- strings
-	maxLength : integer
-	minLength : integer
-	pattern : string -- NYI
-	format : string
-
-	-- arrays
-	prefixItems : { schema_t }
-	items : schema_t
-	contains : schema_t
-	maxItems : integer
-	minItems : integer
-	uniqueItems : boolean
-	maxContains : integer -- NYI
-	minContains : integer -- NYI
-
-	-- objects
-	properties : { string : schema_t }
-	maxProperties : integer -- NYI
-	minProperties : integer -- NYI
-	required : { string }
-	dependentRequired : { string : { string } }
-	additionalProperties: schema_t
-	patternProperties: schema_t -- NYI
-	propertyNames : schema_t
-
-	-- xml
-	record xml_t
-		name : string
-		namespace : string
-		prefix : string
-		attribute : boolean
-		wrapped : boolean
-
-		-- nonstantard, maybe in the future
-		text : boolean
-		x_name_is_value : boolean
-		x_single_attribute : string
-	end
-
-	xml : xml_t
-
-	-- descriptive
-	title : string
-	description : string
-	deprecated : boolean
-	readOnly : boolean
-	writeOnly : boolean
-
-	-- methods
-	validate : function ( schema_t, any, json_schema_object ) : boolean
-end
-
--- TODO validator function per schema property
-
-local function simple_validate(schema : json_type_name | { json_type_name }, data : any) : boolean
-	if schema == nil then
-		return true
-	elseif schema == "object" and data is table then
-		return type(data) == "table" and (next(data)==nil or type((next(data, nil))) == "string")
-	elseif schema == "array" and data is table then
-		return type(data) == "table" and (next(data)==nil or type((next(data, nil))) == "number")
-	elseif schema == "integer" then
-		return math.type(data) == schema
-	elseif schema == "null" then
-		return data == null
-	elseif schema is { json_type_name } then
-		for _, one in ipairs(schema as { json_type_name }) do
-			if simple_validate(one, data) then
-				return true
-			end
-		end
-		return false
-	else
-		return type(data) == schema
-	end
-end
-
-local complex_validate : function ( json_schema_object, any, json_schema_object ) : boolean
-
-local function validate (schema : schema_t, data : any, root : json_schema_object) : boolean
-	if schema is boolean then
-		return schema
-	else
-		return complex_validate(schema, data, root)
-	end
-end
-
-function complex_validate (schema : json_schema_object, data : any, root : json_schema_object) : boolean
-
-	if root == nil then
-		root = schema
-	end
-
-	if schema["$ref"] and schema["$ref"]:sub(1,1) == "#" then
-		local referenced = pointer.resolve(root as table, schema["$ref"]:sub(2)) as schema_t
-		if referenced ~= nil and referenced ~= root and referenced ~= schema then
-			if not validate(referenced, data, root) then
-				return false;
-			end
-		end
-	end
-
-	if not simple_validate(schema.type, data) then
-		return false;
-	end
-
-	if schema.type == "object" then
-		if data is table then
-			-- just check that there the keys are all strings
-			for k in pairs(data) do
-				if not k is string then
-					return false
-				end
-			end
-		end
-	end
-
-	if schema.type == "array" then
-		if data is table then
-			-- just check that there the keys are all numbers
-			for i in pairs(data) do
-				if not i is integer then
-					return false
-				end
-			end
-		end
-	end
-
-	if schema["enum"] ~= nil then
-		local match = false
-		for _, v in ipairs(schema["enum"]) do
-			if v == data then
-				-- FIXME supposed to do deep-compare
-				match = true
-				break
-			end
-		end
-		if not match then
-			return false
-		end
-	end
-
-	-- XXX this is measured in byte, while JSON measures in ... bork
-	-- TODO use utf8.len?
-	if data is string then
-		if schema.maxLength and #data > schema.maxLength then
-			return false
-		end
-		if schema.minLength and #data < schema.minLength then
-			return false
-		end
-	end
-
-	if data is number then
-		if schema.multipleOf and (data == 0 or data % schema.multipleOf ~= 0) then
-			return false
-		end
-
-		if schema.maximum and not ( data <= schema.maximum ) then
-			return false
-		end
-
-		if schema.exclusiveMaximum and not ( data < schema.exclusiveMaximum ) then
-			return false
-		end
-
-		if schema.minimum and not ( data >= schema.minimum ) then
-			return false
-		end
-
-		if schema.exclusiveMinimum and not ( data > schema.exclusiveMinimum ) then
-			return false
-		end
-	end
-
-	if schema.allOf then
-		for _, sub in ipairs(schema.allOf) do
-			if not validate(sub, data, root) then
-				return false
-			end
-		end
-	end
-
-	if schema.oneOf then
-		local valid = 0
-		for _, sub in ipairs(schema.oneOf) do
-			if validate(sub, data, root) then
-				valid = valid + 1
-			end
-		end
-		if valid ~= 1 then
-			return false
-		end
-	end
-
-	if schema.anyOf then
-		local match = false
-		for _, sub in ipairs(schema.anyOf) do
-			if validate(sub, data, root) then
-				match = true
-				break
-			end
-		end
-		if not match then
-			return false
-		end
-	end
-
-	if schema["not"] then
-		if validate(schema["not"], data, root) then
-			return false
-		end
-	end
-
-	if schema["if"] ~= nil then
-		if validate(schema["if"], data, root) then
-			if schema["then"] then
-				return validate(schema["then"], data, root)
-			end
-		else
-			if schema["else"] then
-				return validate(schema["else"], data, root)
-			end
-		end
-	end
-
-	if schema.const ~= nil and schema.const ~= data then
-		return false
-	end
-
-	if data is table then
-
-		if schema.maxItems and #data > schema.maxItems then
-			return false
-		end
-
-		if schema.minItems and #data < schema.minItems then
-			return false
-		end
-
-		if schema.required then
-			for _, k in ipairs(schema.required) do
-				if data[k] == nil then
-					return false
-				end
-			end
-		end
-
-		if schema.propertyNames ~= nil then
-			for k in pairs(data) do
-				if not validate(schema.propertyNames, k, root) then
-					return false
-				end
-			end
-		end
-
-		if schema.properties then
-			for k, sub in pairs(schema.properties) do
-				if data[k] ~= nil and not validate(sub, data[k], root) then
-					return false
-				end
-			end
-		end
-
-		if schema.additionalProperties ~= nil then
-			for k, v in pairs(data) do
-				if schema.properties == nil or schema.properties[k as string] == nil then
-					if not validate(schema.additionalProperties, v, root) then
-						return false
-					end
-				end
-			end
-		end
-
-		if schema.uniqueItems then
-			-- only works for scalars, would need to deep-compare for objects/arrays/tables
-			local values : { any : boolean } = {}
-			for _, v in pairs(data) do
-				if values[v] then
-					return false
-				end
-				values[v] = true
-			end
-		end
-
-		local p = 0
-		if schema.prefixItems ~= nil then
-			for i, s in ipairs(schema.prefixItems) do
-				if data[i] == nil then
-					break
-				elseif validate(s, data[i], root) then
-					p = i
-				else
-					return false
-				end
-			end
-		end
-
-		if schema.items ~= nil then
-			for i = p+1, #data do
-				if not validate(schema.items, data[i], root) then
-					return false
-				end
-			end
-		end
-
-		if schema.contains ~= nil then
-			local found = false
-			for i = 1, #data do
-				if validate(schema.contains, data[i], root) then
-					found = true
-					break
-				end
-			end
-			if not found then
-				return false
-			end
-		end
-	end
-
-	return true;
-end
-
-
-json_schema_object.validate = validate;
-
-return json_schema_object;
--- a/teal-src/util/jwt.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-local crypto = require "util.crypto"
-local record jwtlib
-	enum algorithm
-		"HS256"
-		"HS384"
-		"HS512"
-		"ES256"
-		"ES512"
-		"RS256"
-		"RS384"
-		"RS512"
-		"PS256"
-		"PS384"
-		"PS512"
-	end
-	type payload = { string : any }
-	type signer_t = function (payload : payload) : string
-	type verifier_t = function (token : string) : payload
-	enum key_type
-		"rsaEncryption"
-		"id-ecPublicKey"
-	end
-	record algorithm_t
-		sign : signer_t
-		verify : verifier_t
-		load_key : function (key : string) : crypto.key
-	end
-	init : function (algorithm, private_key : string, public_key : string, table) : signer_t, verifier_t
-	new_signer : function (algorithm, string, table) : signer_t
-	new_verifier : function (algorithm, string, table) : verifier_t
-	_algorithms : {
-		algorithm : algorithm_t
-	}
-	-- Deprecated
-	sign : function (private_key : string, payload) : string
-	verify : function (string) : payload
-end
-return jwtlib
--- a/teal-src/util/logger.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-local record util
-	enum loglevel
-		"debug"
-		"info"
-		"warn"
-		"error"
-	end
-	type logger = function ( loglevel, string, ...:any )
-	type sink = function ( string, loglevel, string, ...:any )
-	type simple_sink = function ( string, loglevel, string )
-	init : function ( string ) : logger
-	make_logger : function ( string, loglevel ) : function ( string, ...:any )
-	reset : function ()
-	add_level_sink : function ( loglevel, sink )
-	add_simple_sink : function ( simple_sink, { loglevel } )
-end
-
-return util
--- a/teal-src/util/mathcompat.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-if not math.type then
-	local enum number_subtype
-		"float" "integer"
-	end
-	local function math_type(t:any) : number_subtype
-		if t is number then
-			if t % 1 == 0 and t ~= t+1 and t ~= t-1 then
-				return "integer"
-			else
-				return "float"
-			end
-		end
-	end
-	_G.math.type = math_type
-end
--- a/teal-src/util/net.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-
-local enum type_strings
-	"both"
-	"ipv4"
-	"ipv6"
-end
-
-local record lib
-	local_addresses : function (type_strings, boolean) : { string }
-	pton : function (string):string
-	ntop : function (string):string
-end
-return lib
--- a/teal-src/util/poll.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-local record state
-	enum waiterr
-		"timeout"
-		"signal"
-	end
-	add : function (state, integer, boolean, boolean) : boolean
-	add : function (state, integer, boolean, boolean) : nil, string, integer
-	set : function (state, integer, boolean, boolean) : boolean
-	set : function (state, integer, boolean, boolean) : nil, string, integer
-	del : function (state, integer) : boolean
-	del : function (state, integer) : nil, string, integer
-	wait : function (state, integer) : integer, boolean, boolean
-	wait : function (state, integer) : nil, string, integer
-	wait : function (state, integer) : nil, waiterr
-	getfd : function (state) : integer
-end
-
-local record lib
-	new : function () : state
-	EEXIST : integer
-	EMFILE : integer
-	ENOENT : integer
-	enum api_backend
-		"epoll"
-		"poll"
-		"select"
-	end
-	api : api_backend
-end
-
-return lib
--- a/teal-src/util/pposix.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-local record pposix
-	enum syslog_facility
-		"auth"
-		"authpriv"
-		"cron"
-		"daemon"
-		"ftp"
-		"kern"
-		"local0"
-		"local1"
-		"local2"
-		"local3"
-		"local4"
-		"local5"
-		"local6"
-		"local7"
-		"lpr"
-		"mail"
-		"syslog"
-		"user"
-		"uucp"
-	end
-
-	enum syslog_level
-		"debug"
-		"info"
-		"notice"
-		"warn"
-		"error"
-	end
-
-	enum ulimit_resource
-		"CORE"
-		"CPU"
-		"DATA"
-		"FSIZE"
-		"NOFILE"
-		"STACK"
-		"MEMLOCK"
-		"NPROC"
-		"RSS"
-		"NICE"
-	end
-
-	enum ulimit_unlimited
-		"unlimited"
-	end
-
-	type ulimit_limit = integer | ulimit_unlimited
-
-	record utsname
-		sysname         :  string
-		nodename        :  string
-		release         :  string
-		version         :  string
-		machine         :  string
-		domainname      :  string
-	end
-
-	record memoryinfo
-		allocated       :  integer
-		allocated_mmap  :  integer
-		used            :  integer
-		unused          :  integer
-		returnable      :  integer
-	end
-
-	abort : function ()
-
-	daemonize : function () : boolean, string
-
-	syslog_open : function (ident : string, facility : syslog_facility)
-	syslog_close : function ()
-	syslog_log : function (level : syslog_level, src : string, msg : string)
-	syslog_setminlevel : function (level : syslog_level)
-
-	getpid : function () : integer
-	getuid : function () : integer
-	getgid : function () : integer
-
-	setuid : function (uid : integer | string) : boolean, string -- string|integer
-	setgid : function (uid : integer | string) : boolean, string
-	initgroups : function (user : string, gid : integer) : boolean, string
-
-	umask : function (umask : string) : string
-
-	mkdir : function (dir : string) : boolean, string
-
-	setrlimit : function (resource : ulimit_resource, soft : ulimit_limit, hard : ulimit_limit) : boolean, string
-	getrlimit : function (resource : ulimit_resource) : boolean, ulimit_limit, ulimit_limit
-	getrlimit : function (resource : ulimit_resource) : boolean, string
-
-	uname : function () : utsname
-
-	setenv : function (key : string, value : string) : boolean
-
-	meminfo : function () : memoryinfo
-
-	atomic_append : function (f : FILE, s : string) : boolean, string, integer
-
-	isatty : function(FILE) : boolean
-
-	ENOENT : integer
-	_NAME : string
-	_VESRION : string
-end
-
-return pposix
--- a/teal-src/util/promise.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-
-local record lib
-	type resolve_func = function (any)
-	type promise_body = function (resolve_func, resolve_func)
-
-	record Promise<A, B>
-		type on_resolved = function (A) : any
-		type on_rejected = function (B) : any
-		next : function (Promise, on_resolved, on_rejected) : Promise<any, any>
-	end
-
-	new : function (promise_body) : Promise
-	resolve : function (any) : Promise
-	reject : function (any) : Promise
-	all : function ({ Promise }) : Promise
-	all_settled : function ({ Promise }) : Promise
-	race : function ({ Promise }) : Promise
-	try : function
-	is_promise : function(any) : boolean
-end
-
-return lib
--- a/teal-src/util/queue.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-local record lib
-	record queue<T>
-		size : integer
-		count : function (queue<T>) : integer
-		enum push_errors
-			"queue full"
-		end
-
-		push : function (queue<T>, T) : boolean, push_errors
-		pop : function (queue<T>) : T
-		peek : function (queue<T>) : T
-		replace : function (queue<T>, T) : boolean, push_errors
-		type iterator = function (T, integer) : integer, T
-		items : function (queue<T>) : iterator, T, integer
-		type consume_iter = function (queue<T>) : T
-		consume : function (queue<T>) : consume_iter
-	end
-
-	new : function<T> (size:integer, allow_wrapping:boolean) : queue<T>
-end
-return lib;
--- a/teal-src/util/random.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-local record lib
-	bytes : function (n:integer):string
-end
-return lib
--- a/teal-src/util/ringbuffer.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-local record lib
-	record ringbuffer
-		find : function (ringbuffer, string) : integer
-		discard : function (ringbuffer, integer) : boolean
-		read : function (ringbuffer, integer, boolean) : string
-		readuntil : function (ringbuffer, string) : string
-		write : function (ringbuffer, string) : integer
-		size : function (ringbuffer) : integer
-		length : function (ringbuffer) : integer
-		sub : function (ringbuffer, integer, integer) : string
-		byte : function (ringbuffer, integer, integer) : integer...
-		free : function (ringbuffer) : integer
-	end
-
-	new : function (integer) : ringbuffer
-end
-
-return lib
-
-
--- a/teal-src/util/roles.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-local record util_roles
-
-	type context = any
-
-	record Role
-		id : string
-		name : string
-		description : string
-		default : boolean
-		priority : number -- or integer?
-		permissions : { string : boolean }
-
-		may : function (Role, string, context)
-		clone : function (Role, role_config)
-		set_permission : function (Role, string, boolean, boolean)
-	end
-
-	is_role : function (any) : boolean
-
-	record role_config
-		name : string
-		description : string
-		default : boolean
-		priority : number -- or integer?
-		inherits : { Role }
-		permissions : { string : boolean }
-	end
-
-	new : function (role_config, Role) : Role
-end
-
-return util_roles
--- a/teal-src/util/serialization.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-local record _M
-	enum preset
-		"debug"
-		"oneline"
-		"compact"
-	end
-	type fallback = function (any, string) : string
-	record config
-		preset : preset
-		fallback :  fallback
-		fatal : boolean
-		keywords : { string : boolean }
-		indentwith : string
-		itemstart : string
-		itemsep : string
-		itemlast : string
-		tstart : string
-		tend : string
-		kstart : string
-		kend : string
-		equals : string
-		unquoted : boolean | string
-		hex : string
-		freeze : boolean
-		maxdepth : integer
-		multirefs : boolean
-		table_pairs : function
-	end
-	type serializer = function (any) : string
-	new : function (config|preset) : serializer
-	serialize : function (any, config|preset) : string
-end
-return _M
--- a/teal-src/util/set.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-local record lib
-	record Set<T>
-		add : function<T> (Set<T>, T)
-		contains : function<T> (Set<T>, T) : boolean
-		contains_set : function<T> (Set<T>, Set<T>) : boolean
-		items :  function<T> (Set<T>) : function<T> (Set<T>, T) : T
-		add_list : function<T> (Set<T>, { T })
-		include : function<T> (Set<T>, Set<T>)
-		exclude : function<T> (Set<T>, Set<T>)
-		empty : function<T> (Set<T>) : boolean
-	end
-
-	new : function<T> ({ T }) : Set<T>
-	is_set : function (any) : boolean
-	union : function<T> (Set<T>, Set<T>) : Set <T>
-	difference : function<T> (Set<T>, Set<T>) : Set <T>
-	intersection : function<T> (Set<T>, Set<T>) : Set <T>
-	xor : function<T> (Set<T>, Set<T>) : Set <T>
-end
-
-return lib
--- a/teal-src/util/signal.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-local record lib
-	enum Signal
-		"SIGABRT"
-		"SIGALRM"
-		"SIGBUS"
-		"SIGCHLD"
-		"SIGCLD"
-		"SIGCONT"
-		"SIGFPE"
-		"SIGHUP"
-		"SIGILL"
-		"SIGINT"
-		"SIGIO"
-		"SIGIOT"
-		"SIGKILL"
-		"SIGPIPE"
-		"SIGPOLL"
-		"SIGPROF"
-		"SIGQUIT"
-		"SIGSEGV"
-		"SIGSTKFLT"
-		"SIGSTOP"
-		"SIGSYS"
-		"SIGTERM"
-		"SIGTRAP"
-		"SIGTTIN"
-		"SIGTTOU"
-		"SIGURG"
-		"SIGUSR1"
-		"SIGUSR2"
-		"SIGVTALRM"
-		"SIGWINCH"
-		"SIGXCPU"
-		"SIGXFSZ"
-	end
-	signal : function (integer | Signal, function, boolean) : boolean
-	raise : function (integer | Signal)
-	kill : function (integer, integer | Signal)
-	-- enum : integer
-end
-return lib
--- a/teal-src/util/smqueue.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-local queue = require "util.queue";
-
-local record lib
-	-- T would typically be util.stanza
-	record smqueue<T>
-		_queue : queue.queue<T>
-		_head : integer
-		_tail : integer
-
-		enum ack_errors
-			"tail"
-			"head"
-			"pop"
-		end
-		push : function (smqueue, T)
-		ack : function (smqueue, integer) : { T }, ack_errors
-		resumable : function (smqueue<T>) : boolean
-		resume : function (smqueue<T>)  : queue.queue.iterator, any, integer
-		type consume_iter = function (smqueue<T>) : T
-		consume : function (smqueue<T>) : consume_iter
-
-		table : function (smqueue<T>) : { T }
-	end
-	new : function <T>(integer) : smqueue<T>
-end
-
-local type smqueue = lib.smqueue;
-
-function smqueue:push(v)
-	self._head = self._head + 1;
-	-- Wraps instead of errors
-	assert(self._queue:push(v));
-end
-
-function smqueue:ack(h : integer) : { any }, smqueue.ack_errors
-	if h < self._tail then
-		return nil, "tail";
-	elseif h > self._head then
-		return nil, "head";
-	end
-	-- TODO optimize? cache table fields
-	local acked = {};
-	self._tail = h;
-	local expect = self._head - self._tail;
-	while expect < self._queue:count() do
-		local v = self._queue:pop();
-		if not v then return nil, "pop"; end
-		table.insert(acked, v);
-	end
-	return acked;
-end
-
-function smqueue:count_unacked() : integer
-	return self._head - self._tail;
-end
-
-function smqueue:count_acked() : integer
-	return self._tail;
-end
-
-function smqueue:resumable() : boolean
-	return self._queue:count() >= (self._head - self._tail);
-end
-
-function smqueue:resume() : queue.queue.iterator, any, integer
-	return self._queue:items();
-end
-
-function smqueue:consume() : queue.queue.consume_iter
-	return self._queue:consume()
-end
-
--- Compatibility layer, plain ol' table
-function smqueue:table() : { any }
-	local t : { any } = {};
-	for i, v in self:resume() do
-		t[i] = v;
-	end
-	return t;
-end
-
-local function freeze(q : smqueue<any>) : { string:integer }
-	return { head = q._head, tail = q._tail }
-end
-
-local queue_mt = {
-	--
-	__name = "smqueue";
-	__index = smqueue;
-	__len = smqueue.count_unacked;
-	__freeze = freeze;
-}
-
-function lib.new<T>(size : integer) : queue.queue<T>
-	assert(size>0);
-	return setmetatable({ _head = 0; _tail = 0; _queue = queue.new(size, true) }, queue_mt);
-end
-
-return lib;
--- a/teal-src/util/stanza.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,145 +0,0 @@
-local record lib
-
-	type children_iter = function ( stanza_t ) : stanza_t
-	type childtags_iter = function () : stanza_t
-	type maptags_cb = function ( stanza_t ) : stanza_t
-
-
-	enum stanza_error_type
-		"auth"
-		"cancel"
-		"continue"
-		"modify"
-		"wait"
-	end
-	enum stanza_error_condition
-		"bad-request"
-		"conflict"
-		"feature-not-implemented"
-		"forbidden"
-		"gone"
-		"internal-server-error"
-		"item-not-found"
-		"jid-malformed"
-		"not-acceptable"
-		"not-allowed"
-		"not-authorized"
-		"policy-violation"
-		"recipient-unavailable"
-		"redirect"
-		"registration-required"
-		"remote-server-not-found"
-		"remote-server-timeout"
-		"resource-constraint"
-		"service-unavailable"
-		"subscription-required"
-		"undefined-condition"
-		"unexpected-request"
-	end
-
-	record stanza_t
-		name : string
-		attr : { string : string }
-		{ stanza_t | string }
-		tags : { stanza_t }
-
-		query : function ( stanza_t, string ) : stanza_t
-		body : function ( stanza_t, string, { string : string } ) : stanza_t
-		text_tag : function ( stanza_t, string, string, { string : string } ) : stanza_t
-		tag : function ( stanza_t, string, { string : string } ) : stanza_t
-		text : function ( stanza_t, string ) : stanza_t
-		up : function ( stanza_t ) : stanza_t
-		at_top : function ( stanza_t ) : boolean
-		reset : function ( stanza_t ) : stanza_t
-		add_direct_child : function ( stanza_t, stanza_t )
-		add_child : function ( stanza_t, stanza_t )
-		remove_children : function ( stanza_t, string, string ) : stanza_t
-
-		get_child : function ( stanza_t, string, string ) : stanza_t
-		get_text : function ( stanza_t ) : string
-		get_child_text : function ( stanza_t, string, string ) : string
-		get_child_attr : function ( stanza_t, string, string ) : string
-		get_child_with_attr : function ( stanza_t, string, string, string, function (string) : boolean ) : string
-		child_with_name : function ( stanza_t, string, string ) : stanza_t
-		child_with_ns : function ( stanza_t, string, string ) : stanza_t
-		children : function ( stanza_t ) : children_iter, stanza_t, integer
-		childtags : function ( stanza_t, string, string ) : childtags_iter
-		maptags : function ( stanza_t, maptags_cb ) : stanza_t
-		find : function ( stanza_t, string ) : stanza_t | string
-
-		top_tag : function ( stanza_t ) : string
-		pretty_print : function ( stanza_t ) : string
-		pretty_top_tag : function ( stanza_t ) : string
-
-		-- FIXME Represent util.error support
-		get_error : function ( stanza_t ) : stanza_error_type, stanza_error_condition, string, stanza_t
-		add_error : function ( stanza_t, stanza_error_type, stanza_error_condition, string, string )
-		indent : function ( stanza_t, integer, string ) : stanza_t
-	end
-
-	record serialized_stanza_t
-		name : string
-		attr : { string : string }
-		{ serialized_stanza_t | string }
-	end
-
-	record message_attr
-		["xml:lang"] : string
-		from : string
-		id : string
-		to : string
-		type : message_type
-		enum message_type
-			"chat"
-			"error"
-			"groupchat"
-			"headline"
-			"normal"
-		end
-	end
-
-	record presence_attr
-		["xml:lang"] : string
-		from : string
-		id : string
-		to : string
-		type : presence_type
-		enum presence_type
-			"error"
-			"probe"
-			"subscribe"
-			"subscribed"
-			"unsubscribe"
-			"unsubscribed"
-		end
-	end
-
-	record iq_attr
-		["xml:lang"] : string
-		from : string
-		id : string
-		to : string
-		type : iq_type
-		enum iq_type
-			"error"
-			"get"
-			"result"
-			"set"
-		end
-	end
-
-	stanza : function ( string, { string : string } ) : stanza_t
-	is_stanza : function ( any ) : boolean
-	preserialize : function ( stanza_t ) : serialized_stanza_t
-	deserialize : function ( serialized_stanza_t ) : stanza_t
-	clone : function ( stanza_t, boolean ) : stanza_t
-	message : function ( message_attr, string ) : stanza_t
-	iq : function ( iq_attr ) : stanza_t
-	reply : function ( stanza_t ) : stanza_t
-	error_reply : function ( stanza_t, stanza_error_type, stanza_error_condition, string, string ) : stanza_t
-	presence : function ( presence_attr ) : stanza_t
-	xml_escape : function ( string ) : string
-	pretty_print : function ( string ) : string
-end
-
-return lib
--- a/teal-src/util/strbitop.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-local record mod
-	sand : function (string, string) : string
-	sor : function (string, string) : string
-	sxor : function (string, string) : string
-end
-return mod
--- a/teal-src/util/struct.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-local record lib
-	pack : function (string, ...:any) : string
-	unpack : function(string, string, integer) : any...
-	size : function(string) : integer
-end
-return lib
--- a/teal-src/util/table.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-local record lib
-	create : function (narr:integer, nrec:integer):table
-	pack : function (...:any):{any}
-	move : function (table, integer, integer, integer, table) : table
-end
-return lib
-
--- a/teal-src/util/termcolours.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-local record lib
-	getstring : function (string, string) : string
-	getstyle : function (...:string) : string
-	setstyle : function (string) : string
-	tohtml :  function (string) : string
-end
-return lib
--- a/teal-src/util/time.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-
-local record lib
-	now : function () : number
-	monotonic : function () : number
-end
-return lib
--- a/teal-src/util/timer.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-local record util_timer
-	record task end
-	type timer_callback = function (number) : number
-	add_task : function ( number, timer_callback, any ) : task
-	stop : function ( task )
-	reschedule : function ( task, number ) : task
-end
-return util_timer
--- a/teal-src/util/uuid.d.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-local record lib
-	get_nibbles : function (number) : string
-	generate : function () : string
-
-	seed : function (string)
-end
-return lib
-
--- a/teal-src/util/xtemplate.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
--- render(template, stanza) --> string
--- {path} --> stanza:find(path)
--- {{ns}name/child|each({ns}name){sub-template}}
-
---[[
-template ::= "{" path ("|" name ("(" args ")")? (template)? )* "}"
-path ::= defined by util.stanza
-name ::= %w+
-args ::= anything with balanced ( ) pairs
-]]
-
-local s_gsub = string.gsub;
-local s_match = string.match;
-local s_sub = string.sub;
-local t_concat = table.concat;
-
-local st = require "util.stanza";
-
-local type escape_t = function (string) : string
-local type filter_t = function (string, string | st.stanza_t, string) : string | st.stanza_t, boolean
-local type filter_coll = { string : filter_t }
-
-local function render(template : string, root : st.stanza_t, escape : escape_t, filters : filter_coll) : string
-	escape = escape or st.xml_escape;
-
-	return (s_gsub(template, "%b{}", function(block : string) : string
-		local inner = s_sub(block, 2, -2);
-		local path, pipe, pos = s_match(inner, "^([^|]+)(|?)()");
-		if not path is string then return end
-		local value : string | st.stanza_t
-		if path == "." then
-			value = root;
-		elseif path == "#" then
-			value = root:get_text();
-		else
-			value = root:find(path);
-		end
-		local is_escaped = false;
-
-		while pipe == "|" do
-			local func, args, tmpl, p = s_match(inner, "^(%w+)(%b())(%b{})()", pos as integer);
-			if not func then func, args, p = s_match(inner, "^(%w+)(%b())()", pos as integer); end
-			if not func then func, tmpl, p = s_match(inner, "^(%w+)(%b{})()", pos as integer); end
-			if not func then func, p = s_match(inner, "^(%w+)()", pos as integer); end
-			if not func then break end
-			if tmpl then tmpl = s_sub(tmpl, 2, -2); end
-			if args then args = s_sub(args, 2, -2); end
-
-			if func == "each" and tmpl and st.is_stanza(value) then
-				if not args then value, args = root, path; end
-				local ns, name = s_match(args, "^(%b{})(.*)$");
-				if ns then ns = s_sub(ns, 2, -2); else name, ns = args, nil; end
-				if ns == "" then ns = nil; end
-				if name == "" then name = nil; end
-				local out, i = {}, 1;
-				for c in (value as st.stanza_t):childtags(name, ns) do
-					out[i], i = render(tmpl, c, escape, filters), i + 1;
-				end
-				value = t_concat(out);
-				is_escaped = true;
-			elseif func == "and" and tmpl then
-				local condition = value;
-				if args then condition = root:find(args); end
-				if condition then
-					value = render(tmpl, root, escape, filters);
-					is_escaped = true;
-				end
-			elseif func == "or" and tmpl then
-				local condition = value;
-				if args then condition = root:find(args); end
-				if not condition then
-					value = render(tmpl, root, escape, filters);
-					is_escaped = true;
-				end
-			elseif filters and filters[func] then
-				local f = filters[func];
-				if args == nil then
-					value, is_escaped = f(value, tmpl);
-				else
-					value, is_escaped = f(args, value, tmpl);
-				end
-			else
-				error("No such filter function: " .. func);
-			end
-			pipe, pos = s_match(inner, "^(|?)()", p as integer);
-		end
-
-		if value is string then
-			if not is_escaped then value = escape(value); end
-			return value;
-		elseif st.is_stanza(value) then
-			value = value:get_text();
-			if value then
-				return escape(value);
-			end
-		end
-		return "";
-	end));
-end
-
-return { render = render };