From b5a487fcba0882789381ce3360c347184dcaf8b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20L=C3=B6vqvist?= Date: Tue, 7 Apr 2026 23:21:03 +0200 Subject: [PATCH] Added resolvers, unified package versioning and author section to fit with mono repo style --- documentation/rule-processing/readme.md | 37 +++++++++++ experiments/package.json | 3 +- experiments/res1.mjs | 43 +------------ package-manifest.yaml | 24 +++++--- planning/object-graph-storage.md | 4 ++ source/rule-processing/resolvers.mjs | 43 +++++++++++++ tools/stage-for-pnpn.mjs | 81 +++++++------------------ 7 files changed, 123 insertions(+), 112 deletions(-) create mode 100644 documentation/rule-processing/readme.md create mode 100644 source/rule-processing/resolvers.mjs diff --git a/documentation/rule-processing/readme.md b/documentation/rule-processing/readme.md new file mode 100644 index 0000000..a2b7ef6 --- /dev/null +++ b/documentation/rule-processing/readme.md @@ -0,0 +1,37 @@ +# @efforting.tech/rule-processing + +Rule processing primitives + +- [Source code](https://gitea.efforting.tech/efforting.tech/nodejs.esm-library/src/branch/main/source/rule-processing/resolver.mjs) + +**TODO:** Write proper documentation for this module - currently just a sketch + +## Class overview + + +### Abstract_Resolver + +Abstract base class that currently implements [`resolve()`](#f-Abstract_Resolver-resolve). + + +### Chained_Resolver + +Goes through every link in the chain and uses the first handler that was resolved. + + +### Mapping_Resolver + +Resolves handlers by key, either derived via a key function or simply by the item itself as key. + +## Classes + + +### Abstract_Resolver + + +#### function `resolve` +```js +function resolve(item) +``` +Resolves an item by calling the handler returned by the concrete [`resolve_handler()`](#function-resolve_handler) function. + diff --git a/experiments/package.json b/experiments/package.json index 90ec16a..cfc1bee 100644 --- a/experiments/package.json +++ b/experiments/package.json @@ -2,6 +2,7 @@ "name": "experiments", "type": "module", "dependencies": { - "@efforting.tech/errors": "link:../build/packages/errors" + "@efforting.tech/errors": "link:../build/packages/errors", + "@efforting.tech/rule-processing": "link:../build/packages/rule-processing" } } \ No newline at end of file diff --git a/experiments/res1.mjs b/experiments/res1.mjs index 609fa8d..c99a48a 100644 --- a/experiments/res1.mjs +++ b/experiments/res1.mjs @@ -1,47 +1,6 @@ -import { Item_Unresolvable } from '@efforting.tech/errors'; +import { Mapping_Resolver, Chained_Resolver } from '@efforting.tech/rule-processing/resolvers'; -class Abstract_Resolver { - resolve(item) { - const handler = this.resolve_handler(item); - if (!handler) { - throw new Item_Unresolvable({ resolver: this, item }); - } - return handler({ resolver: this, item }); - } -} - -class Chained_Resolver extends Abstract_Resolver { - constructor(chain_links) { - super(); - Object.assign(this, { chain_links }); - } - - resolve_handler(item) { - const { chain_links } = this; - for (const link of chain_links) { - const handler = link.resolve_handler(item); - if (handler) { - return handler; - } - } - } -} - - -class Mapping_Resolver { - constructor(rules=new Map(), key_function=null) { - Object.assign(this, { rules, key_function }); - } - - resolve_handler(item) { - const { key_function, rules } = this; - const key = key_function ? key_function(item) : item; - return rules.get(key); - } - -} - const vr = new Mapping_Resolver(); diff --git a/package-manifest.yaml b/package-manifest.yaml index a2dce01..931ed24 100644 --- a/package-manifest.yaml +++ b/package-manifest.yaml @@ -1,4 +1,11 @@ scope: '@efforting.tech' +registry: 'https://npm.efforting.tech/' +version: 0.2.1 + +author: + name: 'Mikael Lövqvist' + email: 'mikael@efforting.tech' + url: 'https://gitea.efforting.tech/mikael-lovqvist' packages: @@ -6,18 +13,17 @@ packages: path: source/errors.mjs documentation: documentation/errors description: Library wide error definitions - version: 0.1.2 + + rule-processing: + path: source/rule-processing + documentation: documentation/rule-processing + description: Rule based visitors, transformers, resolvers, processors, operators, aggregators and such. + internal-dependencies: + - errors + wip-packages: object-graph-storage: path: source/object-graph-storage documentation: documentation/object-graph-storage description: Lightweight persistent storage for structured data. - version: 0.1.0 - - rule-processing: - path: source/rule-processing - documentation: documentation/rule-processing - description: Rule based visitors, transformers, resolvers, processors, operators, aggregators and such. - version: 0.1.0 - diff --git a/planning/object-graph-storage.md b/planning/object-graph-storage.md index 18e402b..a7ab053 100644 --- a/planning/object-graph-storage.md +++ b/planning/object-graph-storage.md @@ -3,6 +3,10 @@ > [!NOTE] > This document is written by Claude by Anthropic using Sonnet 4.6 and has yet to be vetted by Mikael Lövqvist +> [!NOTE] +> Insertion order of Set and Map are guaranteed. See https://tc39.es/ecma262/multipage/keyed-collections.html#sec-set.prototype.foreach and https://tc39.es/ecma262/multipage/keyed-collections.html#sec-map.prototype.foreach +> This document should be updated with this information. + ## Overview A reusable, minimum-footprint storage component written in Node.js, intended as a foundation across multiple projects. Backends for other languages are viable as long as they can serialize to JSON. The design aims to address [ACID](https://en.wikipedia.org/wiki/ACID) guarantees while keeping the implementation surface manageable. diff --git a/source/rule-processing/resolvers.mjs b/source/rule-processing/resolvers.mjs new file mode 100644 index 0000000..47fe57a --- /dev/null +++ b/source/rule-processing/resolvers.mjs @@ -0,0 +1,43 @@ +import { Item_Unresolvable } from '@efforting.tech/errors'; + +export class Abstract_Resolver { + resolve(item) { + const handler = this.resolve_handler(item); + if (!handler) { + throw new Item_Unresolvable({ resolver: this, item }); + } + return handler({ resolver: this, item }); + } +} + +export class Chained_Resolver extends Abstract_Resolver { + constructor(chain_links) { + super(); + Object.assign(this, { chain_links }); + } + + resolve_handler(item) { + const { chain_links } = this; + for (const link of chain_links) { + const handler = link.resolve_handler(item); + if (handler) { + return handler; + } + } + } +} + + +export class Mapping_Resolver extends Abstract_Resolver { + constructor(rules=new Map(), key_function=null) { + Object.assign(this, { rules, key_function }); + } + + resolve_handler(item) { + const { key_function, rules } = this; + const key = key_function ? key_function(item) : item; + return rules.get(key); + } + +} + diff --git a/tools/stage-for-pnpn.mjs b/tools/stage-for-pnpn.mjs index c7a33ca..f1ac79c 100644 --- a/tools/stage-for-pnpn.mjs +++ b/tools/stage-for-pnpn.mjs @@ -1,5 +1,5 @@ import { parse as parse_yaml, stringify as format_yaml } from 'yaml'; -import { unlinkSync, linkSync, chmodSync, readFileSync, writeFileSync, readdirSync, mkdirSync, symlinkSync, statSync, readlinkSync } from 'node:fs'; +import { unlinkSync, linkSync, chmodSync, readFileSync, writeFileSync, readdirSync, mkdirSync, statSync, readlinkSync } from 'node:fs'; import path from 'node:path'; const [manifest_file, source, output_directory] = process.argv.slice(2); @@ -7,49 +7,6 @@ const manifest = parse_yaml(readFileSync(manifest_file, 'utf-8')); //TODO: We must test this with a non-single-file type package - -//NOTE: For now we will simply symlink files over from source which is fine as long as we are -// not preprocecessing source, in that case symlinks would be from the processed items -// and rapid iteration becomes trickier, -// though of course we can hook up make to a file event watcher. -function symlink_tree(src, dest) { - mkdirSync(dest, { recursive: true }); - if (statSync(src).isDirectory()) { - for (const entry of readdirSync(src, { withFileTypes: true })) { - - throw new Error('This branch is clanker-suggested and wanted to do absolute symlinks - fix it when needed') - - const src_path = path.resolve(join(src, entry.name)); - const dest_path = path.join(dest, entry.name); - if (entry.isDirectory()) { - symlink_tree(src_path, dest_path); - } else { - console.log('NOT IMPLEMENTED SYMLINK', src_path, dest_path); - //symlinkSync(src_path, dest_path); - } - } - } else { - const symlink_dest = path.join(dest, path.basename(src)); - const symlink_src = path.relative(dest, src); - //console.log('SYMLINK', symlink_src, symlink_dest); - //TODO: make a utility function for this - try { - symlinkSync(symlink_src, symlink_dest); - } catch (e) { - if (e.code === 'EEXIST') { - if (readlinkSync(symlink_dest) !== symlink_src) { - unlinkSync(symlink_dest); - symlinkSync(symlink_src, symlink_dest); - } - } else { - throw e; - } - } - return [symlink_dest]; - } -} - - function link_tree(src, dest) { mkdirSync(dest, { recursive: true }); if (statSync(src).isDirectory()) { @@ -79,7 +36,7 @@ function link_tree(src, dest) { } else { const link_dest = path.join(dest, path.basename(src)); const link_src = src; - //console.log('SYMLINK', link_src, link_dest); + //console.log('LINK', link_src, link_dest); //TODO: make a utility function for this try { linkSync(link_src, link_dest); @@ -99,26 +56,34 @@ const workspace_manifest = { packages: [], }; -const root_package = { - name: path.join(manifest.scope, 'root'), - version: '0.1.0', //TODO: Not hardcode? What does this version even represent? +const { scope, registry, author, version } = manifest; + +const common_package_data = { + author, + version, type: 'module', - dependencies: {}, publishConfig: { - registry: 'https://npm.efforting.tech/', //TODO: Get from manifest + registry }, - author: 'mikael-lovqvist', //TODO: Get from manifest }; +const root_package = { + name: path.join(scope, 'root'), + dependencies: {}, + ...common_package_data, +}; + + for (const [package_name, package_data] of Object.entries(manifest.packages)) { const pkg = { name: package_name, ...package_data }; - const pkg_scope_path = path.join(manifest.scope, pkg.name); + const pkg_scope_path = path.join(scope, pkg.name); workspace_manifest.packages.push(pkg.name); const pkg_dir = path.join(output_directory, pkg.name); const linked_sources = link_tree(pkg.path, pkg_dir).map(p => path.relative(pkg_dir, p)); - const linked_docs = link_tree(pkg.documentation, pkg_dir).map(p => path.relative(pkg_dir, p)); + // Docs are optional for now + const linked_docs = pkg.documentation ? link_tree(pkg.documentation, pkg_dir).map(p => path.relative(pkg_dir, p)) : []; //console.log('DOCS', { linked_docs }); const exports_map = {}; @@ -128,16 +93,12 @@ for (const [package_name, package_data] of Object.entries(manifest.packages)) { exports_map[key] = `./${file}`; } - const { version, description } = pkg; + const { description } = pkg; const pkg_json = JSON.stringify({ name: pkg_scope_path, - version, description, - type: 'module', + description, exports: exports_map, - author: 'mikael-lovqvist', //TODO: Get from manifest - publishConfig: { - registry: 'https://npm.efforting.tech/', //TODO: Get from manifest - }, + ...common_package_data, }, null, ' '); writeFileSync(path.join(pkg_dir, 'package.json'), pkg_json, 'utf-8');