Created
May 12, 2025 14:59
-
-
Save kindgracekind/767a5c4da3ce5f346bb909f577ccf7e4 to your computer and use it in GitHub Desktop.
Programming with needles
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { type, entity, literal } from "./needles.js"; | |
// Schema: | |
// Person: | |
// - has_one Name: string | |
// - has_one Age: number | |
// - has_many Post | |
// Post: | |
// - has_one Title: string | |
// - has_one Content: string | |
// Declare types | |
const Person = type(); | |
const Name = type(); | |
const Age = type(); | |
const Post = type(); | |
const Title = type(); | |
const Content = type(); | |
// Create entities | |
const alice = entity(Person); | |
alice.set(Name, literal("Alice")); | |
alice.set(Age, literal(30)); | |
const bob = entity(Person); | |
bob.set(Name, literal("Bob")); | |
bob.set(Age, literal(20)); | |
const post = entity(Post); | |
post.set(Title, literal("My Post")); | |
post.set(Content, literal("This is my first post!")); | |
alice.append(Post, post); | |
const post2 = entity(Post); | |
post2.set(Title, literal("My Post 2")); | |
post2.set(Content, literal("This is my second post!")); | |
alice.append(Post, post2); | |
// Value access | |
console.log(alice.last(Post).get(Content).to); // -> "This is my second post" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Needle { | |
constructor({ | |
from = null, | |
to = null, | |
child = null, | |
next = null, | |
label = null, | |
} = {}) { | |
this.label = label; | |
this.from = from; | |
this.to = to; | |
this.child = child; | |
this.next = next; | |
} | |
get lastChild() { | |
if (!this.child) { | |
return null; | |
} | |
let curr = this.child; | |
while (curr.next) { | |
curr = curr.next; | |
} | |
return curr; | |
} | |
addChild(needle) { | |
if (this.child) { | |
this.lastChild.next = needle; | |
} else { | |
this.child = needle; | |
} | |
needle.from = this; | |
} | |
findChildBy(fn) { | |
if (!this.child) { | |
return null; | |
} | |
let curr = this.child; | |
while (curr) { | |
if (fn(curr)) { | |
return curr; | |
} | |
curr = curr.next; | |
} | |
return null; | |
} | |
getOrCreatePeg(type) { | |
let peg = this.getPeg(type); | |
if (!peg) { | |
peg = new Needle({ from: this, to: type }); | |
this.addChild(peg); | |
} | |
return peg; | |
} | |
getPeg(type) { | |
return this.findChildBy((child) => child.to === type); | |
} | |
get(type) { | |
return this.getPeg(type)?.child?.to; | |
} | |
set(type, val) { | |
const peg = this.getOrCreatePeg(type); | |
const link = new Needle({ from: peg, to: val }); | |
peg.child = link; | |
} | |
append(type, value) { | |
const peg = this.getOrCreatePeg(type); | |
const link = new Needle({ from: peg, to: value }); | |
peg.addChild(link); | |
} | |
first(type) { | |
return this.getPeg(type)?.child?.to; | |
} | |
last(type) { | |
return this.getPeg(type)?.lastChild?.to; | |
} | |
find(type, fn) { | |
const peg = this.getPeg(type); | |
if (!peg) { | |
return null; | |
} | |
return peg.findChildBy((child) => fn(child.to))?.to; | |
} | |
} | |
class Domain { | |
constructor() { | |
this.root = new Needle({ label: "Root" }); | |
this.literalType = this.type("Literal"); | |
} | |
type(label) { | |
const needle = new Needle({ label }); | |
needle.to = needle; // types point to themselves | |
return needle; | |
} | |
entity(type, label) { | |
const needle = new Needle({ from: type, label }); | |
this.root.append(type, needle); // allow global access | |
return needle; | |
} | |
literal(value) { | |
let inst = this.root.find(this.literalType, (l) => l.to === value); // re-use existing instances | |
if (!inst) { | |
inst = this.entity(this.literalType, value); | |
inst.to = value; // literals point to their value | |
} | |
return inst; | |
} | |
} | |
// Direct functions for convenience | |
const domain = new Domain(); | |
export const type = domain.type.bind(domain); | |
export const entity = domain.entity.bind(domain); | |
export const literal = domain.literal.bind(domain); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment