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');