- Runtime: Node.js v22.21.1
- Module System: CommonJS (
require()) - Package Manager: npm (though no dependencies currently)
- Language: Plain JavaScript (no TypeScript)
Zero external dependencies - Only uses Node.js standard library:
fs- File system operationspath- Path manipulationcrypto- UUID generation (crypto.randomUUID())readline- Interactive CLI input
ubicity/
├── capture.js (CLI tool - CommonJS)
├── mapper.js (Analysis engine - CommonJS)
├── visualize.js (HTML generator - CommonJS)
├── examples/
│ └── populate-examples.js
├── schema/
│ └── learning-experience.json
└── ubicity-data/ (Generated data)
- Used legacy
require()instead of modernimport - Node.js is moving towards ESM as default
- Harder to use in modern toolchains
- No tree-shaking or static analysis benefits
Impact: Technical debt, harder to integrate with modern tools
- You likely prefer Deno based on your question
- Deno offers:
- TypeScript by default
- Secure by default (explicit permissions)
- No node_modules
- Built-in formatter/linter/test runner
- Better dependency management (URLs not npm)
- Modern standard library
Impact: Missing better DX and security model
- Schema exists but no runtime validation
- Relies on try/catch rather than proper validation
- Could accept malformed data
Impact: Data quality issues, harder debugging
- Zero test coverage
- Examples exist but no automated tests
- Hard to refactor safely
Impact: Fragile codebase, harder to maintain
- Plain JavaScript means no type safety
- Schema exists as JSON but not as types
- Runtime errors instead of compile-time checks
Impact: More bugs, worse IDE support
- Uses
fs.writeFileSync,fs.readFileSync - Blocks the event loop
- Fine for CLI but not scalable
Impact: Performance ceiling if data grows
- Hand-rolled argument parsing
- No help text generation
- No command composition
Impact: Poor UX, harder to extend
- JSON Schema defined but never used for validation
- Should use Zod, AJV, or similar
Impact: Runtime errors instead of helpful validation messages
./ubicity-datais hard-coded- No configuration file support
- Can't easily point to different data dirs
Impact: Inflexible for different use cases
- Schema will evolve but no migration plan
- Breaking changes will orphan old data
Impact: Future compatibility issues
- Uses console.log/console.error
- No log levels, no structured logging
- Hard to debug in production
- Schema has privacy_level field
- But no code enforces it or filters exports
- Mentions Voyant export but not implemented
- No CSV, no GeoJSON, no standardized formats
- No interactive map
- No real-time updates
- Can't filter/search in UI
- Uses native Date which is notoriously problematic
- No timezone handling
// Modern Node.js with TypeScript
import { readFile, writeFile } from 'node:fs/promises';
import { z } from 'zod'; // Runtime validation
import { Command } from 'commander'; // CLI framework
// Schema as TypeScript type + Zod validator
const LearningExperienceSchema = z.object({
learner: z.object({ id: z.string() }),
context: z.object({
location: z.object({ name: z.string() })
}),
experience: z.object({
type: z.enum(['observation', 'experiment', ...]),
description: z.string()
})
});
type LearningExperience = z.infer<typeof LearningExperienceSchema>;Pros: Incremental migration path, familiar ecosystem Cons: Still Node.js quirks, still need npm
// Deno with TypeScript
import { z } from "https://deno.land/x/zod/mod.ts";
import { parse } from "https://deno.land/std/flags/mod.ts";
// Built-in permissions
await Deno.writeTextFile("./data.json", json, {
create: true
}); // Fails without --allow-write
// Built-in formatter, linter, test runner
// deno fmt, deno lint, deno test
// No node_modules, dependencies from URLsPros:
- TypeScript native
- Better security model
- Modern standard library
- No build step needed
- Built-in tooling
Cons:
- Different from existing zotero-voyant-export code
- Smaller ecosystem than npm
- Learning curve if unfamiliar
// For maximum performance and type safety
use serde::{Deserialize, Serialize};
use clap::Parser;
#[derive(Serialize, Deserialize)]
struct LearningExperience {
learner: Learner,
context: Context,
experience: Experience,
}Pros:
- Blazingly fast
- Compile-time guarantees
- Single binary distribution
- Memory safe
Cons:
- Much higher complexity
- Slower development
- Overkill for this use case
- Add input validation with Zod
- Add tests with built-in test runner
- Convert to ESM (change require → import)
- Add CLI help text
Result: Better but still Node.js
- Create
deno.jsonconfig - Convert all files to Deno imports
- Add TypeScript types
- Use Deno standard library
- Add permissions flags
Result: Modern, secure, TypeScript
- Keep core mapper in Node.js (matches existing repo)
- Add Deno wrapper scripts for new features
- Use FFI or JSON pipe for communication
Result: Best of both worlds, incremental
Given your concerns and the project goals:
- Convert to ESM - Use
importnotrequire - Add Zod validation - Enforce schema at runtime
- Add basic tests - At least smoke tests
- Port to Deno - Better DX, matches your style
- Add TypeScript - Type safety for the schema
- Add CLI framework - Better UX
- Add data migrations - Handle schema evolution
- Add proper export formats - GeoJSON, CSV, etc.
- Build interactive viz - Use Observable or similar
Do you want me to:
A) Migrate everything to Deno + TypeScript now?
B) Quick fixes to the Node.js version (ESM + validation)?
C) Create a parallel Deno implementation you can compare?
D) Document the issues but leave as-is (working prototype)?
The current version works but has technical debt. Given that UbiCity is about starting quickly and iterating, option D might align best with the Minimal Viable Protocol philosophy - use it, see if it works for you, then invest in better infrastructure only if needed.
But if you have strong preferences (especially Deno), I should migrate now before you build habits around the Node.js version.
What's your preference?