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,178 @@
1
+ /**
2
+ * Shared structural parsers for special forms.
3
+ *
4
+ * These helpers are pure data transformations — no evaluation, no side effects.
5
+ * Both the sync evaluator (special-forms.ts) and the async evaluator
6
+ * (async-evaluator.ts) import from here so the logic lives in exactly one place.
7
+ *
8
+ * matchesDiscriminator also lives here because it is the same algorithm in both
9
+ * paths — only the EvaluationContext it receives differs (the real ctx in sync,
10
+ * asyncCtx.syncCtx in async, which is correct: discriminator matching is
11
+ * inherently synchronous).
12
+ */
13
+
14
+ import { is } from '../assertions'
15
+ import { EvaluationError } from '../errors'
16
+ import type {
17
+ CljFunction,
18
+ CljList,
19
+ CljNativeFunction,
20
+ CljValue,
21
+ CljVector,
22
+ Env,
23
+ EvaluationContext,
24
+ } from '../types'
25
+
26
+ // ---- let / loop bindings ----
27
+
28
+ /**
29
+ * Validates that a binding vector is well-formed: must be a vector with an
30
+ * even number of forms. Used by let and loop handlers in both the sync and
31
+ * async evaluators so the error messages are consistent.
32
+ *
33
+ * @param formName Display name for the error message, e.g. 'let' or 'loop'.
34
+ */
35
+ export function validateBindingVector(
36
+ vec: CljValue,
37
+ formName: string,
38
+ env: Env
39
+ ): asserts vec is CljVector {
40
+ if (!is.vector(vec)) {
41
+ throw new EvaluationError(`${formName} bindings must be a vector`, {
42
+ bindings: vec,
43
+ env,
44
+ })
45
+ }
46
+ if (vec.value.length % 2 !== 0) {
47
+ throw new EvaluationError(
48
+ `${formName} bindings must have an even number of forms`,
49
+ { bindings: vec, env }
50
+ )
51
+ }
52
+ }
53
+
54
+ // ---- try ----
55
+
56
+ export type CatchClause = {
57
+ discriminator: CljValue
58
+ binding: string
59
+ body: CljValue[]
60
+ }
61
+
62
+ export type TryStructure = {
63
+ bodyForms: CljValue[]
64
+ catchClauses: CatchClause[]
65
+ finallyForms: CljValue[] | null
66
+ }
67
+
68
+ /**
69
+ * Splits a (try ...) list into body forms, catch clauses, and an optional
70
+ * finally clause. Validates that catch has a discriminator + binding symbol and
71
+ * that finally (if present) is the last form.
72
+ */
73
+ export function parseTryStructure(list: CljList, env: Env): TryStructure {
74
+ const forms = list.value.slice(1)
75
+ const bodyForms: CljValue[] = []
76
+ const catchClauses: CatchClause[] = []
77
+ let finallyForms: CljValue[] | null = null
78
+
79
+ for (let i = 0; i < forms.length; i++) {
80
+ const form = forms[i]
81
+ if (is.list(form) && form.value.length > 0 && is.symbol(form.value[0])) {
82
+ const head = form.value[0].name
83
+
84
+ if (head === 'catch') {
85
+ if (form.value.length < 3) {
86
+ throw new EvaluationError(
87
+ 'catch requires a discriminator and a binding symbol',
88
+ { form, env }
89
+ )
90
+ }
91
+ const discriminator = form.value[1]
92
+ const bindingSym = form.value[2]
93
+ if (!is.symbol(bindingSym)) {
94
+ throw new EvaluationError('catch binding must be a symbol', {
95
+ form,
96
+ env,
97
+ })
98
+ }
99
+ catchClauses.push({
100
+ discriminator,
101
+ binding: bindingSym.name,
102
+ body: form.value.slice(3),
103
+ })
104
+ continue
105
+ }
106
+
107
+ if (head === 'finally') {
108
+ if (i !== forms.length - 1) {
109
+ throw new EvaluationError(
110
+ 'finally clause must be the last in try expression',
111
+ { form, env }
112
+ )
113
+ }
114
+ finallyForms = form.value.slice(1)
115
+ continue
116
+ }
117
+ }
118
+ bodyForms.push(form)
119
+ }
120
+
121
+ return { bodyForms, catchClauses, finallyForms }
122
+ }
123
+
124
+ /**
125
+ * Determines whether a catch clause's discriminator matches a thrown value.
126
+ *
127
+ * Rules (same as Clojure-on-JVM, adapted for JS runtime):
128
+ * - Discriminator evaluates to a symbol → catch-all (JVM class names fall here)
129
+ * - `:default` keyword → catch-all
130
+ * - Any other keyword → matches if thrown is a map with a :type entry equal to
131
+ * the discriminator keyword
132
+ * - A callable function → call it with the thrown value; truthy = match
133
+ * - Anything else → error
134
+ *
135
+ * @param ctx Use the real EvaluationContext in sync paths; use asyncCtx.syncCtx
136
+ * in async paths. Discriminator matching is always synchronous.
137
+ */
138
+ export function matchesDiscriminator(
139
+ discriminator: CljValue,
140
+ thrown: CljValue,
141
+ env: Env,
142
+ ctx: EvaluationContext
143
+ ): boolean {
144
+ let disc: CljValue
145
+ try {
146
+ disc = ctx.evaluate(discriminator, env)
147
+ } catch {
148
+ // Discriminator failed to evaluate (e.g. unresolvable Java class name like
149
+ // java.lang.Throwable). Treat as catch-all — we're not on the JVM.
150
+ return true
151
+ }
152
+ // A symbol that evaluated to itself (shouldn't happen, but guard anyway)
153
+ if (disc.kind === 'symbol') return true
154
+
155
+ if (is.keyword(disc)) {
156
+ if (disc.name === ':default') return true
157
+ if (!is.map(thrown)) return false
158
+ const typeEntry = thrown.entries.find(
159
+ ([k]) => is.keyword(k) && k.name === ':type'
160
+ )
161
+ if (!typeEntry) return false
162
+ return is.equal(typeEntry[1], disc)
163
+ }
164
+
165
+ if (is.aFunction(disc)) {
166
+ const result = ctx.applyFunction(
167
+ disc as CljFunction | CljNativeFunction,
168
+ [thrown],
169
+ env
170
+ )
171
+ return is.truthy(result)
172
+ }
173
+
174
+ throw new EvaluationError(
175
+ 'catch discriminator must be a keyword or a predicate function',
176
+ { discriminator: disc, env }
177
+ )
178
+ }
@@ -39,12 +39,19 @@ export function createEvaluationContext(): EvaluationContext {
39
39
  applyMacroWithContext(macro, rawArgs, ctx),
40
40
  expandAll: (form: CljValue, env: Env) =>
41
41
  macroExpandAllWithContext(form, env, ctx),
42
+ resolveNs: (_name: string) => null as null,
43
+ // IO defaults — overwritten by buildSessionFacade with session-specific channels.
44
+ io: {
45
+ stdout: (text: string) => console.log(text),
46
+ stderr: (text: string) => console.error(text),
47
+ },
42
48
  }
43
49
  return ctx
44
50
  }
45
51
 
46
52
  /** Public API, this is the only place where we create a new evaluation context
47
53
  * All inner evaluations will use the same context
54
+ * @deprecated use session.applyFunction instead
48
55
  */
49
56
  export function applyFunction(
50
57
  fn: CljFunction | CljNativeFunction,
@@ -53,15 +60,6 @@ export function applyFunction(
53
60
  ): CljValue {
54
61
  return createEvaluationContext().applyFunction(fn, args, callEnv)
55
62
  }
56
- export function applyMacro(macro: CljMacro, rawArgs: CljValue[]): CljValue {
57
- return createEvaluationContext().applyMacro(macro, rawArgs)
58
- }
59
- export function evaluate(expr: CljValue, env: Env): CljValue {
60
- return createEvaluationContext().evaluate(expr, env)
61
- }
62
- export function evaluateForms(forms: CljValue[], env: Env): CljValue {
63
- return createEvaluationContext().evaluateForms(forms, env)
64
- }
65
63
 
66
64
  export function evaluateWithMeasurements(
67
65
  expr: CljValue,
@@ -0,0 +1,189 @@
1
+ import { is } from '../assertions'
2
+ import { EvaluationError } from '../errors'
3
+ import { cljBoolean, cljJsValue, cljNil, cljNumber, cljString } from '../factories'
4
+ import type { CljList, CljValue, Env, EvaluationContext } from '../types'
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // JS ↔ Clojure conversion
8
+ // ---------------------------------------------------------------------------
9
+
10
+ /**
11
+ * Convert a raw JS value to a CljValue.
12
+ * - null → CljNil (intentional absence)
13
+ * - undefined → CljJsValue(undefined) (property does not exist / unset — distinct from null)
14
+ * - primitives convert; everything else boxes.
15
+ */
16
+ export function jsToClj(raw: unknown): CljValue {
17
+ if (raw === null) return cljNil()
18
+ if (raw === undefined) return cljJsValue(undefined)
19
+ if (typeof raw === 'number') return cljNumber(raw)
20
+ if (typeof raw === 'string') return cljString(raw)
21
+ if (typeof raw === 'boolean') return cljBoolean(raw)
22
+ return cljJsValue(raw)
23
+ }
24
+
25
+ /**
26
+ * Convert a CljValue map key to a JS object key string.
27
+ * Only primitive keys are allowed. Rich keys (vectors, maps, sets, etc.)
28
+ * have no meaningful JS representation and must be reduced to a primitive first.
29
+ */
30
+ function mapKeyToString(key: CljValue): string {
31
+ if (key.kind === 'string') return key.value
32
+ if (key.kind === 'keyword') return key.name.slice(1) // strip leading ':'
33
+ if (key.kind === 'number') return String(key.value)
34
+ if (key.kind === 'boolean') return String(key.value)
35
+ throw new EvaluationError(
36
+ `cljToJs: map key must be a string, keyword, number, or boolean — ` +
37
+ `got ${key.kind} (rich keys are not allowed as JS object keys; reduce to a primitive first)`,
38
+ { key }
39
+ )
40
+ }
41
+
42
+ /**
43
+ * Convert a CljValue to a raw JS value for crossing the interop boundary.
44
+ * Called on each argument passed to `.` and `js/new`.
45
+ */
46
+ export function cljToJs(val: CljValue, ctx: EvaluationContext, callEnv: Env): unknown {
47
+ switch (val.kind) {
48
+ case 'js-value': return val.value
49
+ case 'number': return val.value
50
+ case 'string': return val.value
51
+ case 'boolean': return val.value
52
+ case 'nil': return null
53
+ case 'keyword': return val.name.slice(1) // strip leading ':'
54
+ case 'function':
55
+ case 'native-function': {
56
+ const fn = val
57
+ // Wrap so JS can call it: converts args JS→Clj on entry, result Clj→JS on exit.
58
+ return (...jsArgs: unknown[]) => {
59
+ const cljArgs = jsArgs.map(jsToClj)
60
+ const result = ctx.applyCallable(fn, cljArgs, callEnv)
61
+ return cljToJs(result, ctx, callEnv)
62
+ }
63
+ }
64
+ case 'list':
65
+ case 'vector':
66
+ return val.value.map((v) => cljToJs(v, ctx, callEnv))
67
+ case 'map': {
68
+ const obj: Record<string, unknown> = {}
69
+ for (const [key, value] of val.entries) {
70
+ obj[mapKeyToString(key)] = cljToJs(value, ctx, callEnv)
71
+ }
72
+ return obj
73
+ }
74
+ default:
75
+ throw new EvaluationError(
76
+ `cannot convert ${val.kind} to JS value — no coercion defined`,
77
+ { val }
78
+ )
79
+ }
80
+ }
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // (. obj prop) / (. obj method arg1 arg2 ...)
84
+ // ---------------------------------------------------------------------------
85
+
86
+ /**
87
+ * Extract the raw JS value from a target CljValue for use in `.`.
88
+ * Strings, numbers, and booleans are auto-boxed (JS auto-promotes them for
89
+ * property/method access). Nil and all other Clojure types are rejected.
90
+ */
91
+ function extractRawTarget(target: CljValue): unknown {
92
+ switch (target.kind) {
93
+ case 'js-value': return target.value
94
+ case 'string':
95
+ case 'number':
96
+ case 'boolean': return target.value
97
+ default:
98
+ throw new EvaluationError(
99
+ `cannot use . on ${target.kind}`,
100
+ { target }
101
+ )
102
+ }
103
+ }
104
+
105
+ export function evaluateDot(
106
+ list: CljList,
107
+ env: Env,
108
+ ctx: EvaluationContext
109
+ ): CljValue {
110
+ if (list.value.length < 3) {
111
+ throw new EvaluationError(
112
+ '. requires at least 2 arguments: (. obj prop)',
113
+ { list }
114
+ )
115
+ }
116
+
117
+ const target = ctx.evaluate(list.value[1], env)
118
+ const rawTarget = extractRawTarget(target)
119
+
120
+ if (rawTarget === null || rawTarget === undefined) {
121
+ const label = rawTarget === null ? 'null' : 'undefined'
122
+ throw new EvaluationError(
123
+ `cannot use . on ${label} js value — check for nil/undefined before accessing properties`,
124
+ { target }
125
+ )
126
+ }
127
+
128
+ const propForm = list.value[2]
129
+ if (!is.symbol(propForm)) {
130
+ throw new EvaluationError(
131
+ `. expects a symbol for property name, got: ${propForm.kind}`,
132
+ { propForm }
133
+ )
134
+ }
135
+
136
+ const propName = propForm.name
137
+ const rawObj = rawTarget as Record<string, unknown>
138
+
139
+ if (list.value.length === 3) {
140
+ // Property access — zero extra args.
141
+ // Functions are bound to their object so that ((. obj method)) works correctly.
142
+ const rawProp = rawObj[propName]
143
+ if (typeof rawProp === 'function') {
144
+ return cljJsValue((rawProp as (...a: unknown[]) => unknown).bind(rawObj))
145
+ }
146
+ return jsToClj(rawProp)
147
+ }
148
+
149
+ // Method call — one or more extra args
150
+ const method = rawObj[propName]
151
+ if (typeof method !== 'function') {
152
+ throw new EvaluationError(
153
+ `method '${propName}' is not callable on ${String(rawObj)}`,
154
+ { propName, rawObj }
155
+ )
156
+ }
157
+
158
+ const cljArgs = list.value.slice(3).map((a) => ctx.evaluate(a, env))
159
+ const jsArgs = cljArgs.map((a) => cljToJs(a, ctx, env))
160
+ const rawResult = (method as (...args: unknown[]) => unknown).apply(rawObj, jsArgs)
161
+ return jsToClj(rawResult)
162
+ }
163
+
164
+ // ---------------------------------------------------------------------------
165
+ // (js/new ClassName arg1 arg2 ...)
166
+ // ---------------------------------------------------------------------------
167
+
168
+ export function evaluateNew(
169
+ list: CljList,
170
+ env: Env,
171
+ ctx: EvaluationContext
172
+ ): CljValue {
173
+ if (list.value.length < 2) {
174
+ throw new EvaluationError('js/new requires a constructor argument', { list })
175
+ }
176
+
177
+ const cls = ctx.evaluate(list.value[1], env)
178
+ if (!is.jsValue(cls) || typeof cls.value !== 'function') {
179
+ throw new EvaluationError(
180
+ `js/new: expected js-value constructor, got ${cls.kind}`,
181
+ { cls }
182
+ )
183
+ }
184
+
185
+ const cljArgs = list.value.slice(2).map((a) => ctx.evaluate(a, env))
186
+ const jsArgs = cljArgs.map((a) => cljToJs(a, ctx, env))
187
+ const ctor = cls.value as new (...args: unknown[]) => unknown
188
+ return cljJsValue(new ctor(...jsArgs))
189
+ }
@@ -1,6 +1,7 @@
1
- import { isList, isSymbol, isVector } from '../assertions'
1
+ import { is } from '../assertions'
2
2
  import { EvaluationError } from '../errors'
3
3
  import { cljList, cljMap, cljVector } from '../factories'
4
+ import { toSeq } from '../transformations'
4
5
  import { makeGensym } from '../gensym'
5
6
  import {
6
7
  type CljValue,
@@ -19,11 +20,11 @@ export function evaluateQuasiquote(
19
20
  case valueKeywords.vector:
20
21
  case valueKeywords.list: {
21
22
  // Handle unquote
22
- const isAList = isList(form)
23
+ const isAList = is.list(form)
23
24
  if (
24
25
  isAList &&
25
26
  form.value.length === 2 &&
26
- isSymbol(form.value[0]) &&
27
+ is.symbol(form.value[0]) &&
27
28
  form.value[0].name === 'unquote'
28
29
  ) {
29
30
  return ctx.evaluate(form.value[1], env)
@@ -34,19 +35,24 @@ export function evaluateQuasiquote(
34
35
  for (const elem of form.value) {
35
36
  // Handle unquote splicing
36
37
  if (
37
- isList(elem) &&
38
+ is.list(elem) &&
38
39
  elem.value.length === 2 &&
39
- isSymbol(elem.value[0]) &&
40
+ is.symbol(elem.value[0]) &&
40
41
  elem.value[0].name === 'unquote-splicing'
41
42
  ) {
42
43
  const toSplice = ctx.evaluate(elem.value[1], env)
43
- if (!isList(toSplice) && !isVector(toSplice)) {
44
+ if (is.list(toSplice) || is.vector(toSplice)) {
45
+ elements.push(...toSplice.value)
46
+ } else if (is.lazySeq(toSplice) || is.cons(toSplice)) {
47
+ elements.push(...toSeq(toSplice))
48
+ } else if (is.nil(toSplice)) {
49
+ // nil splices as empty — nothing to push
50
+ } else {
44
51
  throw new EvaluationError(
45
- 'Unquote-splicing must evaluate to a list or vector',
52
+ 'Unquote-splicing must evaluate to a seqable',
46
53
  { elem, env }
47
54
  )
48
55
  }
49
- elements.push(...toSplice.value)
50
56
  continue
51
57
  }
52
58
  // Otherwise, recursively evaluate the quasiquote
@@ -1,4 +1,4 @@
1
- import { isList, isSymbol, isVector } from '../assertions'
1
+ import { is } from '../assertions'
2
2
  import { EvaluationError } from '../errors'
3
3
  import type { CljValue } from '../types'
4
4
  import { specialFormKeywords } from './special-forms'
@@ -28,9 +28,9 @@ export function assertRecurInTailPosition(body: CljValue[]): void {
28
28
 
29
29
  function isRecurForm(form: CljValue): boolean {
30
30
  return (
31
- isList(form) &&
31
+ is.list(form) &&
32
32
  form.value.length >= 1 &&
33
- isSymbol(form.value[0]) &&
33
+ is.symbol(form.value[0]) &&
34
34
  form.value[0].name === specialFormKeywords.recur
35
35
  )
36
36
  }
@@ -44,7 +44,7 @@ function validateForms(forms: CljValue[], inTail: boolean): void {
44
44
 
45
45
  /** Walk a single form; `inTail` = whether a direct recur here is valid. */
46
46
  function validateForm(form: CljValue, inTail: boolean): void {
47
- if (!isList(form)) return
47
+ if (!is.list(form)) return
48
48
 
49
49
  if (isRecurForm(form)) {
50
50
  if (!inTail) {
@@ -57,7 +57,7 @@ function validateForm(form: CljValue, inTail: boolean): void {
57
57
 
58
58
  const first = form.value[0]
59
59
 
60
- if (!isSymbol(first)) {
60
+ if (!is.symbol(first)) {
61
61
  // Anonymous fn call etc. — all sub-forms are non-tail
62
62
  for (const sub of form.value) validateForm(sub, false)
63
63
  return
@@ -92,7 +92,7 @@ function validateForm(form: CljValue, inTail: boolean): void {
92
92
  // `let`: binding values are non-tail; last body form inherits inTail
93
93
  if (name === specialFormKeywords.let) {
94
94
  const bindings = form.value[1]
95
- if (isVector(bindings)) {
95
+ if (is.vector(bindings)) {
96
96
  for (let i = 1; i < bindings.value.length; i += 2) {
97
97
  validateForm(bindings.value[i], false)
98
98
  }