                (let [dirtied-rules (aget kind->name->rules kind name)]
                  (dotimes [j (alength dirtied-rules)]
                    (aset rule->dirty? (aget dirtied-rules j) true)))))))))
-
-(deftype AggregateFlow [prefix-len input-index output-kind output-name output-fields aggregate-function]
-  Object
-  (run [this kn rule->dirty? kind->name->rules]
-       (let [current-key nil
-             inputs #js []
-             output-facts&vals #js []]
-         (.foreach input-index
-                   (fn [key]
-                     (when (nil? current-key)
-                       (set!! current-key key))
-                     (when (btree/prefix-not= key current-key prefix-len)
-                       (let [outputs (aggregate-function inputs)]
-                         (dotimes [i (alength outputs)]
-                           (let [output-key (.slice current-key 0 prefix-len)]
-                             (aset output-key prefix-len (aget outputs i))
-                             (apush output-facts&vals output-key)
-                             (apush output-facts&vals 1)))
-                         (set!! current-key key)
-                         (aclear inputs)))
-                     (apush inputs (.slice key prefix-len))))
-         (when (> (alength inputs) 0)
-           (let [outputs (aggregate-function inputs)]
-             (dotimes [i (alength outputs)]
-               (let [output-key (.slice current-key 0 prefix-len)]
-                 (aset output-key prefix-len (aget outputs i))
-                 (apush output-facts&vals output-key)
-                 (apush output-facts&vals 1)))
-             (set!! current-key key)
-             (aclear inputs)))
-         (when (true? (.update-facts kn output-kind output-name output-fields output-facts&vals))
-           (let [dirtied-rules (aget kind->name->rules output-kind output-name)]
-             (dotimes [j (alength dirtied-rules)]
-               (aset rule->dirty? (aget dirtied-rules j) true)))))))
 
 (deftype Flows [rules rule->flow rule->dirty? kind->name->rules name->transient?]
   Object


(.get-or-create-index kn "know" "clauses" #js ["rule-id" "when|know|remember|forget" "clause-id" "name"])

(.get-or-create-index kn "know" "clause-fields" #js ["clause-id" "constant|variable|aggregate" "key" "val"])

(.get-or-create-index kn "know" "clause-aggregate-vars" #js ["aggregate-id" "ix" "var"])

(.get-or-create-index kn "know" "clause-aggregate-funs" #js ["aggregate-id" "js"])

(.add-facts kn "know" "clauses" #js ["rule-id" "when|know|remember|forget" "clause-id" "name"] #js [#js ["single-edge" "when" "get-edges" "edge"]
                                                                                                    #js ["single-edge" "know" "output-connected" "connected"]])

(.add-facts kn "know" "clause-fields" #js ["clause-id" "constant|variable|aggregate" "key" "val"] #js [#js ["get-edges" "variable" "x" "xx"]
                                                                                             #js ["get-edges" "variable" "y" "yy"]
                                                                                             #js ["output-connected" "variable" "x" "xx"]
                                                                                             #js ["output-connected" "variable" "y" "yy"]])

              solver (btree/solver num-vars (into-array constraints))

              ;; make output specs
              output-kinds (into-array
                            (for [[_ clause-type clause-id name] clauses
                                  :when (not= clause-type "when")]
                              clause-type))
              output-names (into-array
                            (for [[_ clause-type clause-id name] clauses
                                  :when (not= clause-type "when")]
                              name))
              output-fields (into-array
                             (for [[_ clause-type clause-id name] clauses
                                   :when (not= clause-type "when")]
                               (let [clause-fields (get @clause-id->fields clause-id)
                                     output-fields (make-array num-vars)]
                                 (doseq [[_ field-type key val] clause-fields]
                                   (assert (= field-type "variable") [rule-id clause-id clause-fields])
                                   (aset output-fields (var->ix val) key))
                                 output-fields)))]

          ;; set up state for outputs
          (dotimes [i (alength output-kinds)]
            (let [kind (aget output-kinds i)
                  name (aget output-names i)
                  fields (aget output-fields i)
                  filtered-fields (into-array (filter #(not (nil? %)) fields))
                  transient? (identical? kind "know")]
              ;; TODO get transient? from schema instead
              (assert (not= (not transient?) (get @name->transient? name)) name)
              (swap! name->transient? assoc name transient?)
              (swap! kind->name->rules update-in [kind name] #(or % #{}))
              (.ensure-index kn "know" name filtered-fields)
              (when (false? transient?)
                (.ensure-index kn "forget" name filtered-fields)
                (.ensure-index kn "remember" name filtered-fields))))

          (swap! rule->flow assoc rule-id (Flow. solver output-kinds output-names output-fields)))))

    ;; TODO stratify
    (Flows. (clj->js (map first @rule->flow)) (clj->js @rule->flow) #js {} (clj->js @kind->name->rules) (clj->js @name->transient?))))
