conjure-js 0.0.12 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist-cli/conjure-js.mjs +9360 -5298
  2. package/dist-vite-plugin/index.mjs +9463 -5185
  3. package/package.json +3 -1
  4. package/src/bin/cli.ts +2 -2
  5. package/src/bin/nrepl-symbol.ts +150 -0
  6. package/src/bin/nrepl.ts +289 -167
  7. package/src/bin/version.ts +1 -1
  8. package/src/clojure/core.clj +757 -29
  9. package/src/clojure/core.clj.d.ts +75 -131
  10. package/src/clojure/generated/builtin-namespace-registry.ts +4 -0
  11. package/src/clojure/generated/clojure-core-source.ts +758 -29
  12. package/src/clojure/generated/clojure-set-source.ts +136 -0
  13. package/src/clojure/generated/clojure-walk-source.ts +72 -0
  14. package/src/clojure/set.clj +132 -0
  15. package/src/clojure/set.clj.d.ts +20 -0
  16. package/src/clojure/string.clj.d.ts +14 -0
  17. package/src/clojure/walk.clj +68 -0
  18. package/src/clojure/walk.clj.d.ts +7 -0
  19. package/src/core/assertions.ts +114 -6
  20. package/src/core/bootstrap.ts +337 -0
  21. package/src/core/conversions.ts +48 -31
  22. package/src/core/core-module.ts +303 -0
  23. package/src/core/env.ts +20 -6
  24. package/src/core/evaluator/apply.ts +40 -25
  25. package/src/core/evaluator/arity.ts +8 -8
  26. package/src/core/evaluator/async-evaluator.ts +565 -0
  27. package/src/core/evaluator/collections.ts +28 -5
  28. package/src/core/evaluator/destructure.ts +180 -69
  29. package/src/core/evaluator/dispatch.ts +12 -14
  30. package/src/core/evaluator/evaluate.ts +22 -20
  31. package/src/core/evaluator/expand.ts +45 -15
  32. package/src/core/evaluator/form-parsers.ts +178 -0
  33. package/src/core/evaluator/index.ts +7 -9
  34. package/src/core/evaluator/js-interop.ts +189 -0
  35. package/src/core/evaluator/quasiquote.ts +14 -8
  36. package/src/core/evaluator/recur-check.ts +6 -6
  37. package/src/core/evaluator/special-forms.ts +234 -191
  38. package/src/core/factories.ts +182 -3
  39. package/src/core/index.ts +54 -4
  40. package/src/core/module.ts +136 -0
  41. package/src/core/ns-forms.ts +107 -0
  42. package/src/core/printer.ts +371 -11
  43. package/src/core/reader.ts +84 -33
  44. package/src/core/registry.ts +209 -0
  45. package/src/core/runtime.ts +376 -0
  46. package/src/core/session.ts +253 -487
  47. package/src/core/stdlib/arithmetic.ts +528 -194
  48. package/src/core/stdlib/async-fns.ts +132 -0
  49. package/src/core/stdlib/atoms.ts +291 -56
  50. package/src/core/stdlib/errors.ts +54 -50
  51. package/src/core/stdlib/hof.ts +82 -166
  52. package/src/core/stdlib/js-namespace.ts +344 -0
  53. package/src/core/stdlib/lazy.ts +34 -0
  54. package/src/core/stdlib/maps-sets.ts +322 -0
  55. package/src/core/stdlib/meta.ts +61 -30
  56. package/src/core/stdlib/predicates.ts +325 -187
  57. package/src/core/stdlib/regex.ts +126 -98
  58. package/src/core/stdlib/seq.ts +564 -0
  59. package/src/core/stdlib/strings.ts +164 -135
  60. package/src/core/stdlib/transducers.ts +95 -100
  61. package/src/core/stdlib/utils.ts +292 -130
  62. package/src/core/stdlib/vars.ts +27 -27
  63. package/src/core/stdlib/vectors.ts +122 -0
  64. package/src/core/tokenizer.ts +2 -2
  65. package/src/core/transformations.ts +117 -9
  66. package/src/core/types.ts +98 -2
  67. package/src/host/node-host-module.ts +74 -0
  68. package/src/{vite-plugin-clj/nrepl-relay.ts → nrepl/relay.ts} +72 -11
  69. package/src/vite-plugin-clj/codegen.ts +87 -95
  70. package/src/vite-plugin-clj/index.ts +178 -23
  71. package/src/vite-plugin-clj/namespace-utils.ts +39 -0
  72. package/src/vite-plugin-clj/static-analysis.ts +211 -0
  73. package/src/clojure/demo.clj +0 -72
  74. package/src/clojure/demo.clj.d.ts +0 -0
  75. package/src/core/core-env.ts +0 -61
  76. package/src/core/stdlib/collections.ts +0 -739
  77. package/src/host/node.ts +0 -55
@@ -0,0 +1,209 @@
1
+ import { isKeyword, isSymbol, isVector } from './assertions'
2
+ import { makeEnv, makeNamespace } from './env'
3
+ import { EvaluationError } from './errors'
4
+ import type { CljValue, Env } from './types'
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Core types
8
+ // ---------------------------------------------------------------------------
9
+
10
+ export type NamespaceRegistry = Map<string, Env>
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Clone helpers — used by snapshot / restoreRuntime
14
+ // ---------------------------------------------------------------------------
15
+
16
+ function cloneBindings(bindings: Map<string, CljValue>): Map<string, CljValue> {
17
+ const out = new Map<string, CljValue>()
18
+ for (const [k, v] of bindings) {
19
+ out.set(k, v.kind === 'var' ? { ...v } : v)
20
+ }
21
+ return out
22
+ }
23
+
24
+ function cloneEnv(env: Env, memo: Map<Env, Env>): Env {
25
+ if (memo.has(env)) return memo.get(env)!
26
+ const cloned: Env = {
27
+ bindings: cloneBindings(env.bindings),
28
+ outer: null,
29
+ }
30
+ if (env.ns) {
31
+ cloned.ns = {
32
+ kind: 'namespace',
33
+ name: env.ns.name,
34
+ vars: new Map([...env.ns.vars].map(([k, v]) => [k, { ...v }])),
35
+ aliases: new Map(), // wired in cloneRegistry pass 2
36
+ readerAliases: new Map(env.ns.readerAliases),
37
+ }
38
+ }
39
+ memo.set(env, cloned)
40
+ if (env.outer) cloned.outer = cloneEnv(env.outer, memo)
41
+ return cloned
42
+ }
43
+
44
+ export function cloneRegistry(registry: NamespaceRegistry): NamespaceRegistry {
45
+ const memo = new Map<Env, Env>()
46
+ const next = new Map<string, Env>()
47
+ // Pass 1: clone all envs (ns.aliases left empty)
48
+ for (const [name, env] of registry) {
49
+ next.set(name, cloneEnv(env, memo))
50
+ }
51
+ // Pass 2: wire ns.aliases to the cloned CljNamespace objects
52
+ for (const [name, env] of registry) {
53
+ const clonedEnv = next.get(name)!
54
+ if (env.ns && clonedEnv.ns) {
55
+ for (const [alias, origNs] of env.ns.aliases) {
56
+ const targetCloned = next.get(origNs.name)
57
+ if (targetCloned?.ns) clonedEnv.ns.aliases.set(alias, targetCloned.ns)
58
+ }
59
+ }
60
+ }
61
+ return next
62
+ }
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // ensureNamespaceInRegistry — creates namespace env if it doesn't exist yet
66
+ // ---------------------------------------------------------------------------
67
+
68
+ export function ensureNamespaceInRegistry(
69
+ registry: NamespaceRegistry,
70
+ coreEnv: Env,
71
+ name: string
72
+ ): Env {
73
+ if (!registry.has(name)) {
74
+ const nsEnv = makeEnv(coreEnv)
75
+ nsEnv.ns = makeNamespace(name)
76
+ registry.set(name, nsEnv)
77
+ }
78
+ return registry.get(name)!
79
+ }
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // processRequireSpec — processes a single [ns.name :as alias :refer [...]] spec.
83
+ // resolveNs is called when the target namespace isn't yet loaded.
84
+ // ---------------------------------------------------------------------------
85
+
86
+ export function processRequireSpec(
87
+ spec: CljValue,
88
+ currentEnv: Env,
89
+ registry: NamespaceRegistry,
90
+ resolveNs?: (nsName: string) => boolean
91
+ ): void {
92
+ if (!isVector(spec)) {
93
+ throw new EvaluationError(
94
+ 'require spec must be a vector, e.g. [my.ns :as alias]',
95
+ { spec }
96
+ )
97
+ }
98
+
99
+ const elements = spec.value
100
+ if (elements.length === 0 || !isSymbol(elements[0])) {
101
+ throw new EvaluationError(
102
+ 'First element of require spec must be a namespace symbol',
103
+ { spec }
104
+ )
105
+ }
106
+
107
+ const nsName = elements[0].name
108
+
109
+ const hasAsAlias = elements.some(
110
+ (el) => isKeyword(el) && el.name === ':as-alias'
111
+ )
112
+ if (hasAsAlias) {
113
+ let i = 1
114
+ while (i < elements.length) {
115
+ const kw = elements[i]
116
+ if (!isKeyword(kw)) {
117
+ throw new EvaluationError(
118
+ `Expected keyword in require spec, got ${kw.kind}`,
119
+ { spec, position: i }
120
+ )
121
+ }
122
+ if (kw.name === ':as-alias') {
123
+ i++
124
+ const alias = elements[i]
125
+ if (!alias || !isSymbol(alias)) {
126
+ throw new EvaluationError(':as-alias expects a symbol alias', {
127
+ spec,
128
+ position: i,
129
+ })
130
+ }
131
+ currentEnv.ns!.readerAliases.set(alias.name, nsName)
132
+ i++
133
+ } else {
134
+ throw new EvaluationError(
135
+ `:as-alias specs only support :as-alias, got ${kw.name}`,
136
+ { spec }
137
+ )
138
+ }
139
+ }
140
+ return
141
+ }
142
+
143
+ let targetEnv = registry.get(nsName)
144
+ if (!targetEnv && resolveNs) {
145
+ resolveNs(nsName)
146
+ targetEnv = registry.get(nsName)
147
+ }
148
+ if (!targetEnv) {
149
+ throw new EvaluationError(
150
+ `Namespace ${nsName} not found. Only already-loaded namespaces can be required.`,
151
+ { nsName }
152
+ )
153
+ }
154
+
155
+ let i = 1
156
+ while (i < elements.length) {
157
+ const kw = elements[i]
158
+ if (!isKeyword(kw)) {
159
+ throw new EvaluationError(
160
+ `Expected keyword in require spec, got ${kw.kind}`,
161
+ { spec, position: i }
162
+ )
163
+ }
164
+
165
+ if (kw.name === ':as') {
166
+ i++
167
+ const alias = elements[i]
168
+ if (!alias || !isSymbol(alias)) {
169
+ throw new EvaluationError(':as expects a symbol alias', {
170
+ spec,
171
+ position: i,
172
+ })
173
+ }
174
+ currentEnv.ns!.aliases.set(alias.name, targetEnv.ns!)
175
+ i++
176
+ } else if (kw.name === ':refer') {
177
+ i++
178
+ const symsVec = elements[i]
179
+ if (!symsVec || !isVector(symsVec)) {
180
+ throw new EvaluationError(':refer expects a vector of symbols', {
181
+ spec,
182
+ position: i,
183
+ })
184
+ }
185
+ for (const sym of symsVec.value) {
186
+ if (!isSymbol(sym)) {
187
+ throw new EvaluationError(':refer vector must contain only symbols', {
188
+ spec,
189
+ sym,
190
+ })
191
+ }
192
+ const v = targetEnv.ns!.vars.get(sym.name)
193
+ if (v === undefined) {
194
+ throw new EvaluationError(
195
+ `Symbol ${sym.name} not found in namespace ${nsName}`,
196
+ { nsName, symbol: sym.name }
197
+ )
198
+ }
199
+ currentEnv.ns!.vars.set(sym.name, v)
200
+ }
201
+ i++
202
+ } else {
203
+ throw new EvaluationError(
204
+ `Unknown require option ${kw.name}. Supported: :as, :refer`,
205
+ { spec, keyword: kw.name }
206
+ )
207
+ }
208
+ }
209
+ }
@@ -0,0 +1,376 @@
1
+ import { isKeyword, isString, isSymbol, isVector } from './assertions'
2
+ import {
3
+ resolveModuleOrder,
4
+ type RuntimeModule,
5
+ type ModuleContext,
6
+ } from './module'
7
+ import { internVar, makeEnv, makeNamespace } from './env'
8
+ import { EvaluationError } from './errors'
9
+ import { v } from './factories'
10
+ import { readForms } from './reader'
11
+ import { tokenize } from './tokenizer'
12
+ import type { CljNamespace, CljValue, Env, EvaluationContext } from './types'
13
+ import { builtInNamespaceSources } from '../clojure/generated/builtin-namespace-registry'
14
+ import { makeCoreModule } from './core-module'
15
+ import { makeJsModule } from './stdlib/js-namespace'
16
+ import {
17
+ cloneRegistry,
18
+ ensureNamespaceInRegistry,
19
+ processRequireSpec,
20
+ } from './registry'
21
+ import type { NamespaceRegistry } from './registry'
22
+ import {
23
+ extractAliasMapFromTokens,
24
+ extractNsNameFromTokens,
25
+ extractRequireClauses,
26
+ } from './ns-forms'
27
+ import { wireIdeStubs, wireNsCore } from './bootstrap'
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Core types
31
+ // ---------------------------------------------------------------------------
32
+
33
+ export type { NamespaceRegistry }
34
+
35
+ export type RuntimeSnapshot = {
36
+ registry: NamespaceRegistry
37
+ }
38
+
39
+ export type RuntimeOptions = {
40
+ sourceRoots?: string[]
41
+ readFile?: (filePath: string) => string
42
+ }
43
+
44
+ export type Runtime = {
45
+ readonly registry: NamespaceRegistry
46
+
47
+ // Namespace management
48
+ ensureNamespace(name: string): Env
49
+ getNamespaceEnv(name: string): Env | null
50
+ getNs(name: string): CljNamespace | null
51
+ /** Updates the *ns* var root to reflect the named namespace. Called by session.setNs. */
52
+ syncNsVar(name: string): void
53
+ addSourceRoot(path: string): void
54
+
55
+ // Require mechanics — ctx is threaded through so lazy namespace loading works
56
+ processRequireSpec(spec: CljValue, fromEnv: Env, ctx: EvaluationContext): void
57
+ processNsRequires(
58
+ forms: CljValue[],
59
+ fromEnv: Env,
60
+ ctx: EvaluationContext
61
+ ): void
62
+ /**
63
+ * Async variant of processNsRequires.
64
+ * Handles both symbol specs (sync) and string specs (async via ctx.importModule).
65
+ * Must be used when the ns form contains string (:require ["module" :as Alias]) entries.
66
+ */
67
+ processNsRequiresAsync(
68
+ forms: CljValue[],
69
+ fromEnv: Env,
70
+ ctx: EvaluationContext
71
+ ): Promise<void>
72
+
73
+ // File loading — ctx comes from the owning session
74
+ loadFile(
75
+ source: string,
76
+ nsName: string | undefined,
77
+ filePath: string | undefined,
78
+ ctx: EvaluationContext
79
+ ): string
80
+
81
+ // Snapshot
82
+ snapshot(): RuntimeSnapshot
83
+
84
+ // Module installation — declarative capability extension
85
+ installModules(modules: RuntimeModule[]): void
86
+ }
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // buildRuntime — shared factory used by createRuntime and restoreRuntime.
90
+ // Orchestrates registry wiring and native fn installation via bootstrap.ts.
91
+ // Does NOT load clojure.core source — that's the session's bootstrap job.
92
+ // ---------------------------------------------------------------------------
93
+
94
+ function buildRuntime(
95
+ registry: NamespaceRegistry,
96
+ coreEnv: Env,
97
+ options: RuntimeOptions | undefined
98
+ ): Runtime {
99
+ const sourceRoots = new Set<string>(options?.sourceRoots ?? [])
100
+
101
+ // varOwners tracks which module installed each var, keyed by "ns/varName".
102
+ // Prevents two modules from declaring the same var in the same namespace.
103
+ const varOwners = new Map<string, string>()
104
+
105
+ // currentNsRef mirrors the owning session's currentNs for require/resolve.
106
+ // Updated via runtime.syncNsVar() which session.setNs calls.
107
+ let currentNsRef = 'user'
108
+
109
+ // resolveNamespace: loads a namespace by name if not already in the registry.
110
+ // ctx is passed explicitly — this is the active EvaluationContext at the
111
+ // call site. For loadFile calls, it's the file's ctx. For runtime require
112
+ // native fn calls, it comes from the evaluator via cljNativeFunctionWithContext.
113
+ function resolveNamespace(nsName: string, ctx: EvaluationContext): boolean {
114
+ const builtInLoader = builtInNamespaceSources[nsName]
115
+ if (builtInLoader) {
116
+ runtime.loadFile(builtInLoader(), nsName, undefined, ctx)
117
+ return true
118
+ }
119
+ if (!options?.readFile || sourceRoots.size === 0) return false
120
+ for (const root of sourceRoots) {
121
+ const filePath = `${root.replace(/\/$/, '')}/${nsName.replace(/\./g, '/')}.clj`
122
+ try {
123
+ const source = options.readFile(filePath)
124
+ if (source) {
125
+ runtime.loadFile(source, undefined, undefined, ctx)
126
+ return true
127
+ }
128
+ } catch {
129
+ continue
130
+ }
131
+ }
132
+ return false
133
+ }
134
+
135
+ wireNsCore(registry, coreEnv, () => currentNsRef, resolveNamespace)
136
+ wireIdeStubs(registry, coreEnv)
137
+
138
+ const runtime: Runtime = {
139
+ get registry() {
140
+ return registry
141
+ },
142
+
143
+ ensureNamespace(name: string): Env {
144
+ return ensureNamespaceInRegistry(registry, coreEnv, name)
145
+ },
146
+
147
+ getNamespaceEnv(name: string): Env | null {
148
+ return registry.get(name) ?? null
149
+ },
150
+
151
+ getNs(name: string): CljNamespace | null {
152
+ return registry.get(name)?.ns ?? null
153
+ },
154
+
155
+ syncNsVar(name: string): void {
156
+ currentNsRef = name
157
+ const nsVarInner = coreEnv.ns?.vars.get('*ns*')
158
+ if (nsVarInner) {
159
+ const nsObj = registry.get(name)?.ns
160
+ if (nsObj) nsVarInner.value = nsObj
161
+ }
162
+ },
163
+
164
+ addSourceRoot(path: string): void {
165
+ sourceRoots.add(path)
166
+ },
167
+
168
+ processRequireSpec(
169
+ spec: CljValue,
170
+ fromEnv: Env,
171
+ ctx: EvaluationContext
172
+ ): void {
173
+ processRequireSpec(spec, fromEnv, registry, (nsName) =>
174
+ resolveNamespace(nsName, ctx)
175
+ )
176
+ },
177
+
178
+ processNsRequires(
179
+ forms: CljValue[],
180
+ fromEnv: Env,
181
+ ctx: EvaluationContext
182
+ ): void {
183
+ const requireClauses = extractRequireClauses(forms)
184
+ for (const specs of requireClauses) {
185
+ for (const spec of specs) {
186
+ if (
187
+ isVector(spec) &&
188
+ spec.value.length > 0 &&
189
+ isString(spec.value[0])
190
+ ) {
191
+ const specifier = spec.value[0].value
192
+ throw new EvaluationError(
193
+ `String module require ["${specifier}" :as ...] is async — use evaluateAsync() instead of evaluate()`,
194
+ { specifier }
195
+ )
196
+ }
197
+ processRequireSpec(spec, fromEnv, registry, (nsName) =>
198
+ resolveNamespace(nsName, ctx)
199
+ )
200
+ }
201
+ }
202
+ },
203
+
204
+ async processNsRequiresAsync(
205
+ forms: CljValue[],
206
+ fromEnv: Env,
207
+ ctx: EvaluationContext
208
+ ): Promise<void> {
209
+ const requireClauses = extractRequireClauses(forms)
210
+ for (const specs of requireClauses) {
211
+ for (const spec of specs) {
212
+ if (
213
+ isVector(spec) &&
214
+ spec.value.length > 0 &&
215
+ isString(spec.value[0])
216
+ ) {
217
+ // String module require — calls importModule and interns result as a var
218
+ const specifier = spec.value[0].value
219
+ if (!ctx.importModule) {
220
+ throw new EvaluationError(
221
+ `importModule is not configured; cannot require "${specifier}". Pass importModule to createSession().`,
222
+ { specifier }
223
+ )
224
+ }
225
+ const elements = spec.value
226
+ let aliasName: string | null = null
227
+ for (let i = 1; i < elements.length; i++) {
228
+ if (
229
+ isKeyword(elements[i]) &&
230
+ (elements[i] as { name: string }).name === ':as'
231
+ ) {
232
+ i++
233
+ const aliasSym = elements[i]
234
+ if (!aliasSym || !isSymbol(aliasSym)) {
235
+ throw new EvaluationError(':as expects a symbol alias', {
236
+ spec,
237
+ })
238
+ }
239
+ aliasName = aliasSym.name
240
+ break
241
+ }
242
+ }
243
+ if (aliasName === null) {
244
+ throw new EvaluationError(
245
+ `String require spec must have an :as alias: ["${specifier}" :as Alias]`,
246
+ { spec }
247
+ )
248
+ }
249
+ const rawModule = await ctx.importModule(specifier)
250
+ internVar(aliasName, v.jsValue(rawModule), fromEnv)
251
+ } else {
252
+ // Symbol require spec — sync path
253
+ processRequireSpec(spec, fromEnv, registry, (nsName) =>
254
+ resolveNamespace(nsName, ctx)
255
+ )
256
+ }
257
+ }
258
+ }
259
+ },
260
+
261
+ loadFile(
262
+ source: string,
263
+ nsName: string | undefined,
264
+ filePath: string | undefined,
265
+ ctx: EvaluationContext
266
+ ): string {
267
+ const tokens = tokenize(source)
268
+ const targetNs = extractNsNameFromTokens(tokens) ?? nsName ?? 'user'
269
+ const aliasMap = extractAliasMapFromTokens(tokens)
270
+ const forms = readForms(tokens, targetNs, aliasMap)
271
+ const env = this.ensureNamespace(targetNs)
272
+ ctx.currentSource = source
273
+ ctx.currentFile = filePath
274
+ ctx.currentLineOffset = 0
275
+ ctx.currentColOffset = 0
276
+ this.processNsRequires(forms, env, ctx)
277
+ try {
278
+ for (const form of forms) {
279
+ const expanded = ctx.expandAll(form, env)
280
+ ctx.evaluate(expanded, env)
281
+ }
282
+ } finally {
283
+ ctx.currentSource = undefined
284
+ ctx.currentFile = undefined
285
+ }
286
+ return targetNs
287
+ },
288
+
289
+ installModules(modules: RuntimeModule[]): void {
290
+ const ordered = resolveModuleOrder(modules, new Set(registry.keys()))
291
+
292
+ for (const mod of ordered) {
293
+ for (const decl of mod.declareNs) {
294
+ const nsEnv = ensureNamespaceInRegistry(registry, coreEnv, decl.name)
295
+
296
+ const ctx: ModuleContext = {
297
+ getVar(ns, name) {
298
+ const nsEnv = registry.get(ns)
299
+ const v = nsEnv?.ns?.vars.get(name)
300
+ return v ?? null
301
+ },
302
+ getNamespace(name) {
303
+ return registry.get(name)?.ns ?? null
304
+ },
305
+ }
306
+
307
+ const varMap = decl.vars(ctx)
308
+
309
+ for (const [varName, decl] of varMap) {
310
+ const key = `${nsEnv.ns!.name}/${varName}`
311
+ const existing = varOwners.get(key)
312
+ if (existing !== undefined) {
313
+ throw new Error(
314
+ `var '${varName}' in '${nsEnv.ns!.name}' already declared by module '${existing}'`
315
+ )
316
+ }
317
+ internVar(varName, decl.value, nsEnv, decl.meta)
318
+ if (decl.dynamic) {
319
+ const v = nsEnv.ns!.vars.get(varName)!
320
+ v.dynamic = true
321
+ }
322
+ varOwners.set(key, mod.id)
323
+ }
324
+ }
325
+ }
326
+ },
327
+
328
+ snapshot(): RuntimeSnapshot {
329
+ return { registry: cloneRegistry(registry) }
330
+ },
331
+ }
332
+
333
+ return runtime
334
+ }
335
+
336
+ // ---------------------------------------------------------------------------
337
+ // createRuntime — builds a fresh runtime with bootstrapped core + user envs.
338
+ // Does NOT load clojure.core source — that's the session's bootstrap job.
339
+ // ---------------------------------------------------------------------------
340
+
341
+ export function createRuntime(options?: RuntimeOptions): Runtime {
342
+ const registry: NamespaceRegistry = new Map()
343
+
344
+ // Bootstrap: clojure.core env
345
+ const coreEnv = makeEnv()
346
+ coreEnv.ns = makeNamespace('clojure.core')
347
+ registry.set('clojure.core', coreEnv)
348
+
349
+ // Bootstrap: user env (outer = coreEnv for implicit core access)
350
+ const userEnv = makeEnv(coreEnv)
351
+ userEnv.ns = makeNamespace('user')
352
+ registry.set('user', userEnv)
353
+
354
+ const runtime = buildRuntime(registry, coreEnv, options)
355
+ runtime.installModules([makeCoreModule(), makeJsModule()])
356
+ return runtime
357
+ }
358
+
359
+ // ---------------------------------------------------------------------------
360
+ // restoreRuntime — re-wires a runtime from a snapshot.
361
+ // The cloned registry already contains fully-evaluated namespaces (including
362
+ // clojure.core), so no source bootstrap is needed. Only wiring is re-applied.
363
+ // ---------------------------------------------------------------------------
364
+
365
+ export function restoreRuntime(
366
+ snapshot: RuntimeSnapshot,
367
+ options?: RuntimeOptions
368
+ ): Runtime {
369
+ const registry = cloneRegistry(snapshot.registry)
370
+ const coreEnv = registry.get('clojure.core')!
371
+ const runtime = buildRuntime(registry, coreEnv, options)
372
+ // No module reinstallation needed — IO functions (println, print, etc.) read
373
+ // ctx.io.stdout at call time, so the snapshot's native functions automatically
374
+ // use the session's output channel without any rewiring.
375
+ return runtime
376
+ }