tom_core_kernel
The foundation every other Tom layer is composed from: dependency injection (bean locator), observable state, security and the four-state authorization model, resource resolution, logging, JSON, isolate pooling, settings and runtime utilities. Reflection-aware, with no UI or server-runtime dependency, so it runs unchanged on CLI, server and Flutter targets.
Overview
From the module readme.md file:
What it enables
Enables Shared state, security and resources, Runtime auto-assembly via reflection, Any Dart target.
Relationships
Standalone โ no declared relationships.
tom_core_kernel
> Attribution. The Tom Framework is developed by Peter Nicolai Alexis Kyaw. > These packages are BSD-3-Clause licensed and internal (publish_to: none).
> โ ๏ธ Work in progress โ unstable API. The Tom Core packages are under active > development and not yet stable. Their version numbers carry no promise about > API maturity: they are publishing counters, not semver. The patch number is > bumped on every change โ including breaking changes โ so a patch bump may > break you. Expect the public surface to shift without notice. Not recommended > for production use yet.
Core kernel library for the Tom Framework โ the platform-neutral building blocks every other Tom layer is composed from.
Overview
tom_core_kernel is the foundation of the Tom Framework. It carries no UI and no server runtime, so it runs unchanged on any Dart target โ CLI, server, or Flutter. Everything layered above it (the
server stack, the Flutter
/ UI layers) depends on the contracts defined here.
The kernel is reflection-aware: it builds on tom_reflection
so observable objects, security principals, and resource trees can be introspected and serialized without hand-written boilerplate. Conceptually it provides five things:
- Observable state โ values and objects that notify observers on change, with
a terse operator syntax (| set, ~ read, >> observe). - Ambient context
โ zone-propagated execution context and hierarchical scope identifiers, so a deeply nested call can resolve "where am I" without threading parameters through every frame. -
Dependency injection โ a reflection-driven bean container. - Security
โ authentication, a layered access-control model, bearer-token auth, and a four-state authorization model (TomAuthState). -
Runtime utilities โ logging (including isolate/remote), an isolate worker pool, a durable job scheduler, the canonical success-or-error result envelope, JSON tree helpers, resource providers, settings, timezone-aware timestamps, and ordered shutdown.
Installation
This package is internal to the Tom workspace and consumed by path (it is publish_to: none), so there is no
dart pub add line. Add it as a path dependency:
dependencies:
tom_core_kernel:
path: ../tom_core_kernel
Then import the single barrel:
import 'package:tom_core_kernel/tom_core_kernel.dart';
The barrel re-exports tom_basics,
tom_crypto and tom_reflection, so one import covers the common surface.
SDK: Dart ^3.10.0. Modules that use reflection require a dart run build_runner build
to generate *.reflection.dart.
Features
Observable state (observable)
| Type | Responsibility |
|---|---|
TomObject<T> | Base observable holding a single typed value |
TomString / TomInt / TomDouble / TomBool |
Typed scalar observables |
TomClass / TomList / TomMap |
Observable composites |
Context & scope (context, scope_ids)
| Type | Responsibility |
|---|---|
TomExecutionContext | Ambient, zone-propagated execution context |
TomContextEntry<T> | A typed value carried on the context |
TomScope / ScopeCapture |
Hierarchical, zone-propagated scope identifiers |
Dependency injection (beanlocator)
| Type | Responsibility |
|---|---|
TomComponent |
The discovery annotation, carrying the interface
Type
the annotated class registers under
|
TomBean<T> |
Typed handle that resolves its bean lazily, by type or by name |
TomNoInterface |
The Type @tomComponent carries, for a bean registered under its own type |
initializeBeanContext() runs the discovery pass: it finds the @TomComponent-annotated classes, selects one implementation per interface for the active environment and platform, instantiates it, and then resolves every handle declared so far so a missing binding fails at start-up.
Security (security)
| Type | Responsibility |
|---|---|
TomAuthorization / TomAuthState |
Authorization decisions and the four-state model |
TomAccessControl
(+
TomNoAccess
,
TomPublicAccess
,
TomAuthenticatedAccess
,
TomGuestAccess
)
|
Layered access-control rules |
TomBearerAuthentication | Bearer-token authentication |
TomAccessControlInformation
,
TomGroup
,
TomRole
,
TomEntitlement
|
Principal / ACI model |
TomResourceRoleRegistry, TomResourceRoleAdapter |
Per-principal-per-resource roles โ the same user as
owner
of one resource and
viewer
of another. The kernel's only shipped
TomAuthorizationAdapter
|
tomOAuthApi
,
TomOAuthProviderDto
,
TomOAuthAuthorizationRequest
,
TomOAuthCallbackRequest
,
TomOAuthApiService
|
The wire contract for external-identity login ("Sign in with โฆ"). The
/oauth/complete
endpoint โ which the
application
calls, forwarding what the provider's redirect left on its own page โ answers with
TomAuthenticationResult
โ the
same
type
/login
answers with, because an OAuth provider resolves a principal and everything after that is the one login path every credential reaches, second factor included.
|
Runtime utilities
| Module | Key types | What it does |
|---|---|---|
logging |
isolate & remote log output/server | Structured logging across isolates and the wire |
isolate_pooling |
TomWorker
,
TomWorkerPool
,
TomExecutor
,
TomCommand
|
Worker-pool execution across isolates |
scheduling |
TomScheduler
,
TomJobDefinition
,
TomSchedule
(cron / calendar / interval / once / event),
TomJobStore
,
TomLeaseLock
|
Durable, leased job scheduling with retry and catch-up policies |
json |
map-merge, pretty-print helpers | JSON tree merge/serialization utilities |
resources |
TomTextResourceProvider, TomConfigResourceProvider |
Text and configuration resource resolution |
settings | client settings/authorization | Settings management |
preferences |
TomUserPreferenceKeys
,
TomUserPreferenceDto
,
TomUserPreferenceCodec
,
tomUserPreferencesApi
|
The contract both preference stores bind to: well-known keys, wire DTOs, the value codec, and the
/preferences
API
|
shutdown_cleanup |
TomShutdownCleanup, TomDisposable, TomClosable |
Ordered shutdown / cleanup hooks |
timezoned |
DateTimestamp, timezone tables |
Timezone-aware date/time |
http_connection |
server connection, endpoints, headers | HTTP connection helpers |
little_things |
zone helpers, formatting, TomException |
Small cross-cutting utilities |
reflection |
reflection bootstrap | Reflection support built on tom_reflection |
Quick start
Observable state with automatic observer propagation:
import 'package:tom_core_kernel/tom_core_kernel.dart';
void main() {
final count = TomInt(0);
final observer = count >> (obs) => print('changed to ${~count}');
count | 5; // prints: changed to 5
count.removeObserver(observer);
}
| sets the value, ~ reads it, and >> registers an observer that fires on every change. Observers are held by weak reference, so keep the one
>> returns โ an observer nothing references is collected and silently stops firing. See
doc/observable/observable.md.
Example projects
Every module ships a runnable sample folder under example/samples/, and the top-level
run_all_samples.dart
runs the whole set in sequence. That is the only example tree โ each folder's 00_run_all_examples.dart
both stands alone and is called by the master runner, so anything listed below is executed on every run.
| Sample | Demonstrates |
|---|---|
example/samples/observable/
|
Observable scalars, composites, and observers |
example/samples/beanlocator/
|
Registering and locating beans |
example/samples/context/
|
Execution context & context entries |
example/samples/scope_ids/
|
Hierarchical scope paths |
example/samples/security/
|
Access controls, authorization, bearer auth |
example/samples/crypto/
|
JWT / hashing helpers (via tom_crypto) |
example/samples/runtime/
|
Environment / platform detection (via tom_basics) |
example/samples/isolate_pooling/
|
Worker-pool execution |
example/samples/logging/
|
Structured / isolate / remote logging |
example/samples/resources/
|
Text & config resource providers |
example/samples/preferences/
|
The preference codec's scalar,
DateTime
and reflected round-trips, and the
/preferences
contract
|
example/samples/scheduling/
|
The in-memory substrate,
@TomScheduledJob
discovery, every schedule kind against the missed-window policies, and what at-least-once asks of a job body
|
example/samples/json/
ยท
example/samples/timezoned/
ยท
example/samples/settings/
ยท
example/samples/shutdown_cleanup/
ยท
example/samples/http_connection/
ยท
example/samples/little_things/
ยท
example/samples/reflection/
|
The remaining utility modules |
Run the whole set:
dart run build_runner build # generate reflection (once / on signature change)
dart run --enable-asserts example/run_all_samples.dart
--enable-asserts is not optional. The samples check themselves with assert, and Dart compiles asserts away unless the flag is passed โ without it the examples would still print their output and still exit 0, while verifying nothing they claim. Rather than leave that trap open, every sample's
main() opens with prepareSampleProcess() (example/sample_preamble.dart), which prints the correct command and exits
2 instead of running hollow.
That function is also where the process gets its platform identity. The framework builds its log header from
TomPlatformUtils.current.getIsolateName(), and until something installs an implementation the fallback answers with the empty string โ so every framework log line reads
<timestamp> - INFO โฆ rather than <timestamp> main-main INFO โฆ. Installing
TomStandalonePlatformUtils in the shared preamble rather than in the master runner is what makes the prefix uniform across all three ways of starting a sample: the full run, a group runner, and a single file. No sample configures it, and none has to.
Every sample also runs on its own, which is usually what you want while reading one:
dart run --enable-asserts example/samples/observable/03_tom_class.dart
Samples that build reflected types call bootstrapSamples() (example/sample_bootstrap.dart) as the first statement of their
main() instead of prepareSampleProcess() โ it does the preamble and then installs the generated reflection registry, which is the one place that happens. The rest reflect over nothing and skip it, because importing the registry costs roughly three seconds of compile time per entry point. That cost is also why the preamble lives in its own registry-free library: it lets the non-reflecting samples be prepared without paying for a registry they never touch.
The master runner cannot police the standalone path โ it drives samples through their group runners and ends in
exit(0), so a sample that has no main(), never registers reflection, or never terminates is invisible to it.
tool/run_standalone_examples.sh
is the guard for that. It runs every sample by itself under a watchdog, failing on a hang as well as on a non-zero exit, and before the sweep it checks the commands this package
hands out โ anywhere in it, not just in a sample header:
- every documented
dart runcommand pointing intoexample/**names a file
that exists, which the sweep itself cannot check, since it finds samples with find rather than by reading the documentation; - every one of them
passes --enable-asserts, so a reader who copies the command gets a run that verifies something; - every sample's main()
calls prepareSampleProcess(), so a reader who types the command from memory and omits the flag is refused rather than shown a hollow green; - every sample
contains at least one assert โ one that asserts nothing exits 0 whatever the API does, and has no verification for a missing flag to strip, so it slips past the check above as well; - every sample
opens run() with print('=== NN: Title ===\n') carrying its own file number**, so that a reader watching a group runner's single continuous stream can tell which file each block came from. A sample that prints nothing contributes no block at all; one whose banner carries somebody else's number sends a reader to the wrong file, which is worse.
And after the sweep, one check that reads its output rather than the source: every sample appears in its own group's stream. The check above sees a banner anywhere in the file, which is not the same thing โ a banner in
main() names the sample on the standalone path and leaves it anonymous inside the group runner, which calls
run(). Reading the group runners' logs is what tells the two apart, and the sweep has just produced them.
./tool/run_standalone_examples.sh # all of them
./tool/run_standalone_examples.sh observable # one group
The group argument narrows the sweep only โ the five static checks always cover the whole package, because a wrong command is wrong for every reader, not only for the group under test. The stream check necessarily follows the sweep, so it covers whichever groups ran.
> Cross-package sample. Beyond these per-module examples, the shared > tom_core_samples/todo_domain
> project models one todo-management domain on this kernel's reflection โ the > single entity set every Tom Core sample reuses. See the >
samples map for the full learning path.
Usage
Namespacing with zone-propagated scopes
TomScope.withScope('checkout', () {
TomScope.withScope('orderForm', () {
print(TomScope.current.getScopePath()); // checkout.orderForm
});
});
Resolving text resources
TomTextResourceProvider.setAppResourceProvider(
TomTextResourceProvider.from({'greeting': 'Hello, {name}!'}),
);
Locating a bean
initializeBeanContext() discovers the classes annotated @TomComponent, selects one implementation per interface for the active environment and platform, and instantiates it. A consumer then holds a
TomBean<T> handle, which resolves itself on first use:
@tomReflector
@TomComponent(UserService)
class UserServiceImpl implements UserService { /* โฆ */ }
class OrderService {
final TomBean<UserService> users = TomBean();
Order create(int userId) => Order(users().getUser(userId));
}
Nothing is injected into a bean: it is built through its unnamed no-argument constructor, and each handle it declares resolves on its own. What discovery does provide is the choice โ which implementation a deployment gets โ and a start-up check that every declared handle can be resolved.
Ordered shutdown
TomShutdownCleanup registers TomDisposable / TomClosable resources and tears them down in a deterministic order on process exit.
Architecture
tom_core_kernel
โ
โโโโโโโโโโโโฌโโโโโโโโผโโโโโโโโโฌโโโโโโโโโโโโ
observable context security resources runtime utils
(TomObject) (scope) (TomAuth*) (providers) (logging, isolates,
json, settings,
shutdown, timezoned)
โ
tom_reflection โ introspection / serialization
tom_basics ยท tom_crypto
The kernel is a single package with one barrel; modules live under lib/src/tombase/<module>/
and are mutually low-coupled โ context/scope and observable underpin the rest, security builds on both, and the runtime utilities are largely independent.
| Type | Responsibility |
|---|---|
TomObject<T> | Observable value, the unit of reactive state |
TomScope | Zone-propagated hierarchical scope identifier |
TomExecutionContext | Ambient context carried through a call tree |
TomAuthorization / TomAuthState |
Authorization decision + four-state model |
TomAccessControl | Base access-control rule |
TomBean<T> / TomComponent |
Lazily resolved bean handle / the discovery annotation |
TomWorker / TomWorkerPool | Isolate worker-pool execution |
TomTextResourceProvider | Text resource resolution |
TomShutdownCleanup | Ordered teardown registry |
Ecosystem
tom_basics ยท tom_crypto ยท tom_reflection
โ
tom_core_kernel โ you are here
โโโโโโโโโโดโโโโโโโโโโ
tom_core_server tom_core_flutter
โ
tom_flutter_ui
tom_core_kernel sits directly on the basics/crypto/reflection foundation and is the common dependency of the server and Flutter layers. See the
repository map for the full picture.
Further documentation
-
Per-module guides under
doc/โ one folder per module
(doc/observable/, doc/security/, doc/scope_ids/, โฆ). - doc/scheduling/scheduling.md
โ the job scheduler: schedule kinds, retry and missed-window policies, the job store, and the lease lock that keeps one owner per job across processes. - Per-module API reference under
doc/api_reference/
โ one summary per barrel-exported module plus a cross-module type index; start at api_summary_index.md. -
tom_basics/doc/runtime/runtime.md
โ the runtime guide (TomEnvironment, TomPlatform, TomRuntime). The code lives in
tom_basics; the kernel barrel re-exports it. - tom_core_server
โ the server runtime built on this kernel. - tom_core_flutter
/ tom_flutter_ui โ the client/UI layers.
Status
- Version: 1.1.16
-
Tests: run with
testkit :testordart test. No count is given: a
count is a claim nothing checks, and every added test would be a README edit nobody makes. The version above is checked, because a wrong one is the claim that actively misleads. - License: BSD-3-Clause โ see LICENSE.
License
BSD 3-Clause License Copyright (c) 2024-2026, Peter Nicolai Alexis Kyaw All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.