Skip to content

Replace essential module system with isFatal errors and bootstrap libraries#112

Open
taylortom wants to merge 50 commits intomasterfrom
feature/isfatal-and-absorb-libraries
Open

Replace essential module system with isFatal errors and bootstrap libraries#112
taylortom wants to merge 50 commits intomasterfrom
feature/isfatal-and-absorb-libraries

Conversation

@taylortom
Copy link
Copy Markdown
Collaborator

@taylortom taylortom commented Mar 26, 2026

Breaking

  • Removes two-phase essential/non-essential module loading (essentialApis, essentialType no longer used)
  • Removes force option from DependencyLoader.loadModules()
  • app.config, app.logger, app.errors, app.lang are now bootstrap library instances, not AbstractModule subclasses
  • Modules that previously called waitForModule('config'), waitForModule('errors'), or waitForModule('logger') no longer need to — these are available immediately
  • Lang.translate() signature changed from (defaultLang, logWarn, lang, key, data) to (lang, key, data) — callers no longer pass logWarn or defaultLang
  • Lang.translateError() removed — use Lang.translate(lang, error) instead (Error keys handled automatically)
  • Lang.storeStrings() static method removed
  • Static Lang.translate() and Lang.translateError() removed — use instance methods

New

  • AdaptError class with isFatal property, set from error JSON definitions
  • Config library — loads user settings, env vars, and validates module schemas at bootstrap
  • Logger library — configurable console logging with levels, colours, and module overrides
  • Errors library — loads and registers all error definitions from module errors/*.json files
  • Lang library — loads and merges language phrases from module lang/*.json files, with translate utilities
  • All four libraries are instantiated during App.loadLibraries() before any modules initialise

Update

  • DependencyLoader uses single-pass module loading — waitForModule handles all ordering
  • DependencyLoader.loadModules checks error.isFatal / error.cause.isFatal to decide whether to halt or continue
  • DependencyLoader.waitForModule only rejects when the specific waited-for module fails, not on any unrelated failure — prevents misleading error cascading
  • DependencyLoader passes failed module name through moduleLoadedHook for targeted error reporting
  • AbstractModule.log() calls logger directly (no more deferred hook-based logging)
  • AbstractModule.getConfig() simplified — no try/catch needed since config is always available
  • DependencyLoader.log() calls logger directly (no more console.log fallback)
  • DependencyLoader.getConfig() simplified — no _isReady check needed
  • Lang.translate() simplified — removed logWarn callback parameter, uses stored log function internally
  • Lang.translate() now handles Error keys directly (merged translateError functionality)
  • Lang uses defaultLang from core config instead of requiring it per-call
  • Lang.substituteData() extracted for readability, uses [v].flat() to normalise array/scalar handling
  • Lang.storeStrings() inlined into loadPhrases — no longer a separate static method
  • App passes defaultLang config value to Lang constructor
  • Config, Lang, and Errors constructors now default their options argument so instances can be constructed with no arguments (e.g. in unit tests)
  • Logger.log() guards against unhandled promise rejections from logHook.invoke() with a .catch() handler
  • Merged docs/configure-environment.md, docs/plugins/configuration.js, docs/plugins/configuration.md from adapt-authoring-config
  • Merged docs/error-handling.md, docs/plugins/errors.js, docs/plugins/errorsref.md from adapt-authoring-errors
  • Fixed docs/plugins/errors.js dataToMd() recursive accumulator duplication — each recursive call now starts from an empty string
  • Fixed docs/error-handling.md — removed duplicate heading, corrected ## User errors typo, updated outdated ErrorsModule/waitForModule references to use app.errors

Testing

  • All existing + new tests pass
  • New test suites: AdaptError.spec.js, Config.spec.js, Errors.spec.js, Lang.spec.js, Logger.spec.js
  • Updated DependencyLoader.spec.js — replaced force option tests with isFatal tests
  • Updated AbstractModule.spec.js — simplified log/getConfig tests
  • Updated Errors.spec.js — uses constructor (new Errors(...)) instead of non-existent Errors.load()
  • Lang.spec.js updated to use instance methods with createLang helper
  • Standard.js lint passes

Migration notes for other modules

  • adapt-authoring-config: Can be deprecated — functionality absorbed into core Config library. Route serving public config should move to adapt-authoring-server
  • adapt-authoring-errors: Can be deprecated — functionality absorbed into core Errors/AdaptError. Modules should mark critical errors as "isFatal": true in their errors/*.json
  • adapt-authoring-logger: Can be deprecated — functionality absorbed into core Logger. mongodblogger should update to use app.logger.logHook
  • adapt-authoring-lang: Can be deprecated — phrase loading absorbed into core Lang. req.translate middleware should move to adapt-authoring-middleware
  • adapt-authoring-middleware: Add translationUtils() middleware to bind req.translate on API requests (previously in adapt-authoring-lang)
  • adapt-authoring-mongodb, adapt-authoring-jsonschema: Remove essentialType from adapt-authoring.json, mark connection/init errors as "isFatal": true
  • adapt-authoring (root): Remove essentialApis from adapt-authoring.json, remove deprecated module dependencies

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

…p libraries

Absorbs config, logger, errors, and lang into core as bootstrap libraries
loaded before any modules initialise. Replaces the two-phase essential/
non-essential loading with single-pass loading where any error can be
marked isFatal in its JSON definition to halt the app. waitForModule
handles all load ordering.
Merges all error JSON and config schema properties from the absorbed
modules (errors, config, lang, logger) into core. Logger config keys
use loggerX prefix (e.g. loggerLevels, loggerMute). Lang defaultLang
included as-is.
Empty loggerLevels array now implies mute. Removes loggerMute and
loggerDateFormat config options. Timestamps always use ISO format.
Modules can define conf/deprecated.json to map old config keys to new
ones. Supports _module (check a different module section) and _fatal
(throw instead of warn). Core includes deprecation files for absorbed
logger and lang modules.
@taylortom
Copy link
Copy Markdown
Collaborator Author

The docs and doc plugins from the absorbed modules still need to be merged into core:

adapt-authoring-config:

  • docs/configure-environment.md
  • docs/plugins/configuration.js
  • docs/plugins/configuration.md

adapt-authoring-errors:

  • docs/error-handling.md
  • docs/plugins/errors.js
  • docs/plugins/errorsref.md

(adapt-authoring-logger and adapt-authoring-lang don't have docs directories.)

taylortom and others added 18 commits March 26, 2026 13:28
Config deprecation warnings are superseded by the migrations module's
new conf migration type, which can actually rewrite config files
rather than just warning about stale keys.
Migrates config keys from their old module sections to adapt-authoring-core:
- adapt-authoring-logger.levels → adapt-authoring-core.logLevels
- adapt-authoring-logger.showTimestamp → adapt-authoring-core.showLogTimestamp
- adapt-authoring-logger.mute → removed
- adapt-authoring-logger.dateFormat → removed
- adapt-authoring-lang.defaultLang → adapt-authoring-core.defaultLang
Adds resolveDirectory() to Config which handles $ROOT, $DATA, $TEMP
variable substitution. This was previously done by jsonschema's
isDirectory keyword, but config now loads before jsonschema.
JsonSchemaModule's isDirectory keyword now delegates to this method.
- Call runMigrations() in App.start() between loadConfigs() and
  loadLibraries(), so config migrations run before schema validation
- Fix Schemas().init() not being called in Config.storeSchemaSettings(),
  which caused all schema defaults to be silently skipped
- Improve config validation error formatting with chalk colours

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Create Logger with defaults at the start of App.start(), before
loadConfigs/migrations/loadLibraries. Config and migrations now
use the logger instead of raw console.log + chalk. Logger is
reconfigured with user settings once Config is loaded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Simplify getModuleOverrides with filter/startsWith
- Inline colourise and getDateStamp into log method
- Merge early returns in log method

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merge load() into loadModules() by defaulting the modules parameter
to all configured dependencies. App.start() now calls loadModules()
directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The _isReady check is sufficient to guard against double-start.
The setReady override was only resetting _isStarting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
rootDir is determined by cwd/ROOT_DIR env var at construction time.
Allowing config to override it after the fact is circular — config,
dependencies, and metadata have already been loaded using the
original rootDir by this point.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move app, args, dependencyloader setup to constructor
- Inline loadLibraries into start()
- Move verbose DIR/GIT logs to end of start()
- Simplify init() to just error handling and failed module reporting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
taylortom and others added 18 commits March 27, 2026 10:16
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use instanceof Error instead of constructor name string check
- Use fs.readFile with utf8 encoding directly
- Remove getPhrasesForLang (callers can use phrases[lang] directly)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace lodash _.isFunction with typeof checks
- Replace fs-extra with core readJson util
- Combine two sorts in loadConfigs into one
- Use Object.fromEntries for init times
- Lazy-create DependencyError in waitForModule
- Remove eslint directive and cosmetic cleanup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace plain Error throws in DependencyLoader with AdaptError
definitions: DEP_ALREADY_LOADED, DEP_FAILED, DEP_INVALID_EXPORT,
DEP_MISSING, DEP_NO_ONREADY, DEP_TIMEOUT.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace lodash _.isFunction with typeof check
- Replace lodash _.cloneDeep with native structuredClone
- Replace minimist with native util.parseArgs
- fs-extra was already unused after readJson util was added

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add adapt-authoring-migrations as dependency, static import
- Remove tests for removed methods (logError, getConfig,
  getPhrasesForLang, colourise, getDateStamp, setReady/_isStarting)
- Update DependencyLoader tests to use AdaptError codes
- Update Config storeUserSettings test for new signature
- Update getArgs tests for util.parseArgs shape
- Fix lint: remove padded block in DependencyLoader

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All App tests rely on App.instance which triggers a real boot,
making them integration tests rather than unit tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move instance variable definitions into init() to fix race condition where
super() triggers init() before constructor body runs. Make Config, Errors,
and Lang use consistent constructor patterns with shared options object.
Convert Errors and Lang to synchronous. Use log function instead of logger
instance across all libraries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Config schemas lack $id, causing AJV "already exists" collisions
when multiple schemas compile into the same validator instance.
Adds a temporary workaround to set $id from the module name until
config schemas define their own. Also logs non-file-not-found schema
errors instead of swallowing them silently.

See adapt-security/adapt-authoring-jsonschema#66

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove logWarn param from instance translate/translateError signatures
  to match how callers actually use them (lang, key, data)
- Store defaultLang from config rather than requiring it per-call
- Merge translateError into translate (handles Error keys inline)
- Extract substituteData for readability
- Remove static translate/translateError (instance methods are the API)
- Pass defaultLang config value from App

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only used in one place, no need for a separate static method.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use [v].flat() to handle both scalar and array values in a single
code path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously, when any module failed to load, the moduleLoadedHook
fired with (error, undefined), causing every pending waitForModule
promise to reject — even for unrelated modules. This led to
misleading errors (e.g. reporting mongodb as failed when ui was
the actual failure).

Now the failed module name is passed through the hook, and
waitForModule only rejects if the specific module being waited
for is the one that failed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Load lang phrases from lang/<language>/<namespace>.json instead of
lang/<language>.<namespace>.json. Plain lang/<language>.json files
still work for simple cases.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the “essential vs non-essential” module loading mechanism with isFatal error-based halting, and moves foundational utilities (config, logger, errors, lang) into core bootstrap libraries available before modules initialise.

Changes:

  • Introduces new core bootstrap libraries: Config, Logger, Errors, Lang, plus AdaptError with isFatal.
  • Refactors DependencyLoader to single-pass loading and fatal/non-fatal failure handling, and simplifies AbstractModule config/logging access.
  • Adds migrations + documentation pages/plugins and updates test suites accordingly.

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
tests/utils-getArgs.spec.js Adjusts CLI arg parsing expectations for new getArgs() implementation.
tests/Logger.spec.js Adds unit tests for new Logger library helpers + basic logging.
tests/Lang.spec.js Adds unit tests for new Lang library phrase loading/translation.
tests/Errors.spec.js Adds unit tests for error registry behavior (currently mismatched with implementation API).
tests/DependencyLoader.spec.js Updates loader tests for isFatal behavior and new error codes.
tests/Config.spec.js Adds unit tests for new Config library (currently calls new Config() without required opts).
tests/App.spec.js Removes the App test suite.
tests/AdaptError.spec.js Adds unit tests for new AdaptError class.
tests/AbstractModule.spec.js Updates module logging/config tests to reflect bootstrap libraries.
package.json Replaces dependencies (adds chalk/adapt-schemas/migrations) and removes others (currently breaks fs-extra usage).
migrations/3.0.0-conf-migrate-logger-config.js Migration for legacy logger config keys.
migrations/3.0.0-conf-migrate-lang-config.js Migration for legacy lang config keys.
lib/utils/getArgs.js Switches from minimist to node:util.parseArgs.
lib/Logger.js Adds new configurable console logger with levels/overrides + hook.
lib/Lang.js Adds phrase loading and translation utilities.
lib/Hook.js Drops lodash usage and switches deep clone to structuredClone.
lib/Errors.js Adds error registry that loads module errors/*.json into AdaptError getters.
lib/DependencyLoader.js Single-pass loading, fatal checks, updated wait semantics (currently has a resolve-on-failure bug).
lib/Config.js Adds config loading/env/schema validation (currently doesn’t tolerate missing opts/log).
lib/App.js Bootstraps logger/config/errors/lang before loading modules; runs migrations.
lib/AdaptError.js Adds AdaptError class with isFatal and metadata/data support.
lib/AbstractModule.js Simplifies config access and logs directly via bootstrap app.logger.
index.js Exports new libraries from the package entrypoint.
errors/node-core.json Adds Node core error definitions.
errors/errors.json Adds dependency/config/lang-related core error definitions with isFatal flags.
docs/plugins/errorsref.md Adds template page for generated error reference.
docs/plugins/errors.js Adds docs generator for errors reference (currently has recursive formatting bug).
docs/plugins/configuration.md Adds template page for generated configuration reference.
docs/plugins/configuration.js Adds docs generator for configuration reference from schemas.
docs/error-handling.md Adds error-handling guide (currently contains duplicate headings/typos/outdated example).
docs/configure-environment.md Adds environment configuration documentation.
conf/deprecated-logger.json Maps deprecated logger config keys to new core keys.
conf/deprecated-lang.json Maps deprecated lang config key to new core key.
conf/config.schema.json Extends core schema with logging + default language config.
Comments suppressed due to low confidence (1)

package.json:22

  • package.json no longer lists fs-extra, but it's imported by multiple test files (and at least one docs plugin). A clean install will fail at runtime when running npm test. Either add fs-extra back (likely as a devDependency) or migrate the tests/tools to node:fs / node:fs/promises.
  "dependencies": {
    "adapt-authoring-migrations": "github:adapt-security/adapt-authoring-migrations#feature/boot-phase-migrations",
    "adapt-schemas": "^3.1.0",
    "chalk": "^5.4.1",
    "glob": "^13.0.0"
  },
  "devDependencies": {
    "@adaptlearning/semantic-release-config": "^1.0.0",
    "standard": "^17.1.0"
  },

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@taylortom
Copy link
Copy Markdown
Collaborator Author

@copilot Thanks for the review — here's the response to each comment:

1. waitForModule resolve-on-failure bug (DependencyLoader.js) — Fixed. loadModule now passes { name: modName } on failure so the hook can identify which module failed. waitForModule tap now checks instance?.name !== modName first, then decides resolve vs reject. This means unrelated module failures no longer cause spurious rejections.

2. dataToMd recursive accumulator duplication (docs/plugins/errors.js) — Fixed. Removed the s parameter from recursive calls — each level now starts from an empty string.

3. Config constructor missing default (Config.js) — Fixed. Constructor now uses ({ rootDir, configFilePath, dependencies = {}, appName = '', log = () => {} } = {}) so new Config() works in tests.

4. Lang.loadPhrases undefined guard (Lang.js) — Fixed. dependencies defaults to {} and appRootDir is only included in the dirs array when truthy.

5. Errors constructor guard for undefined dependencies (Errors.js) — Fixed. Added dependencies = {} default in the constructor destructuring.

6. Logger.log unhandled promise rejection (Logger.js) — Fixed. Added .catch() on the fire-and-forget logHook.invoke() call.

7. docs/error-handling.md issues — Fixed. Removed duplicate ## Initialisation errors heading, fixed ## User errors errors typo, updated outdated waitForModule('errors') references to app.errors.

8. tests/Errors.spec.js calls non-existent Errors.load() — Fixed. Updated tests to use new Errors({ dependencies, log }) instead.

9. fs-extra missing from dependencies (suppressed comment) — Fixed. Added fs-extra as a devDependency.

Also fixed lint issues in App.js (trailing whitespace + padded block) and cleaned up unused imports in Config.spec.js.

All 271 tests pass and standard lint is clean.

taylortom and others added 3 commits March 27, 2026 20:56
- Fix waitForModule to only reject for the specific waited-for module
- Fix dataToMd recursive accumulator duplication in docs plugin
- Add defaults to Config/Lang/Errors constructors for test compatibility
- Add .catch() on Logger logHook to prevent unhandled promise rejections
- Fix error-handling.md duplicate heading, typo, and outdated references
- Update Errors tests to use constructor instead of non-existent .load()
- Clean up unused imports in Config tests
- Add fs-extra as devDependency
- Fix lint issues in App.js

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ries' into feature/isfatal-and-absorb-libraries

Co-authored-by: taylortom <1059083+taylortom@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 27, 2026

The docs and doc plugins from the absorbed modules still need to be merged into core:

adapt-authoring-config:...

All six doc files were already merged in commit a7b4c59. I've also applied the corrections flagged in the PR review (commit 9548d74): removed the duplicate ## Initialisation errors heading, fixed the ## User errors errors typo, updated the outdated ErrorsModule/waitForModule('errors) references to use app.errors, and fixed the dataToMd recursive accumulator duplication in docs/plugins/errors.js.

taylortom and others added 2 commits March 28, 2026 17:26
Moves the NODE_ENV default from bin/start.js into core so all consumers get it automatically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Calls setReady with the error before process.exit(1) so that onReady
listeners (e.g. tests) receive the rejection before the process dies.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants