โ† Core
In the workslicense: BSD-3-Clause

tom_core_server

tom_core_server ยท v1.3.0

The server half of a Tom application from one dependency: an HTTP host, an endpoint pipeline, a relational persistence layer with a type-safe query model, datasources and migrations (MySQL/MariaDB), server configuration, caching, transactions, health checks, and authentication/authorization. Re-exports the kernel through its own barrel.

See License
Status
In the works
LOC
28.0k
Tests
4.2k
Test LOC
162.4k

Overview

From the module readme.md file:

What it enables

Enables Backend services, Type-safe persistence, Authenticated, authorized endpoints.

Relationships

Standalone โ€” no declared relationships.

tom_core_server

> 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.

Server-side runtime for the Tom Framework โ€” an HTTP host, an endpoint pipeline, and a relational persistence layer built on the platform-neutral kernel.

Overview

tom_core_server is the backend layer of the Tom Framework. It builds on tom_core_kernel (dependency injection, observable state, security, resources) and adds everything a service needs:

  • a shelf-based HTTP host (TomServer) with a middleware pipeline โ€”

request context, per-request IDs, authentication, logging, and JSON error mapping wrapped around a declarative endpoint router; - an ORM-style persistence layer โ€” repositories with type-safe queries built from expression operators, persistence annotations, and a query builder, over pluggable datasources (MariaDB/MySQL driver included); - versioned schema migrations โ€” with a convergence check that the entities, the migration chain and the live database still agree โ€” and local/XA transaction coordination; - an audit trail that is attributable and tamper-evident, outbound messaging with a durable outbox and per-recipient localized text, tabular export to CSV/XLSX/PDF, streaming blob storage behind one contract, and relational job scheduling for a cluster that shares no filesystem; - server configuration (typed config + YAML resources), an isolate-backed cache, authentication/authorization services, and health endpoints.

Because the barrel re-exports the kernel, a server app depends only on tom_core_server โ€” one import gives both the kernel API and the server stack.

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:

dependencies:
  tom_core_server:
    path: ../tom_core_server

Then import the single barrel โ€” it re-exports tom_core_kernel, tom_basics_console, and the MariaDB client, so you rarely import those directly:

import 'package:tom_core_server/tom_core_server.dart';

SDK: Dart ^3.10.0. Modules that use reflection (persistence entities, endpoint services) require dart run build_runner build to generate *.reflection.dart. The relational layer targets MariaDB/MySQL via mysql_client; the datasource, dialect, migration, and repository contracts are abstract, so other backends can be added.

Features

HTTP host & endpoints (server, endpoints)

TypeResponsibility
TomServerThe shelf-based HTTP host and its middleware pipeline
TomEndpoint / TomEndpointHandler / TomEndpointRouting Endpoint declaration and the request-handling pipeline
TomService / TomApiImplementation / TomTransactional Annotations that turn a class into a routed API, and mark a handler transactional
TomFileEndpoint / TomFileSlots / TomFileSlot Upload/download/probe/delete over a <table>/<id>/<column> slot derived from the entity's own annotations โ€” registered per repository, exposed only where @TomFileReference says so
TomMultipartForm / TomFormFile HTML form bodies (multipart or url-encoded): text parts fold into the request DTO, file parts bind as typed parameters and upload through the same file slot
TomRedirect The directive a handler returns to answer with a 3xx , so the pipeline builds the response rather than the handler
TomWebSocketEndpoint / TomWebSocketConnection Sockets as a kind of endpoint โ€” same scan, access check and parameter binding, run inside the captured request zone, with their own connection lifecycle
TomErrorHandler / TomDefaultErrorHandler Maps a thrown exception onto the response the caller sees

Persistence (object_persistence, datasources)

TypeResponsibility
TomRepository / TomSqlDatasourceRepository CRUD + type-safe query repository
TomDbTable / TomDbColumn / TomEntityId / TomReference / TomFileReference / TomDbScope Entity-mapping annotations
TomQueryBuilder + Eq / And / Like / In / Gte โ€ฆ Type-safe query expressions
TomAggregate + countAll / sum / avg / min / max , TomGroupedSelect , TomGroupedRow Grouping and aggregation โ€” results come back as group keys plus aggregate values, not entities
TomQuerySentenceCompiler Compiles a readable query sentence ( find first 25 skip 50 by name = :name order by age desc name asc ) into the same query objects the builder produces
TomPageRequest / TomSortField / TomSortDirection The row window and the per-field sort a caller passes to findAll , findAllById , findByQuery and findByQuerySentence โ€” cut out of the scoped rows, so count() reports the scoped total beside it
TomDataScope / TomDataScopeRegistry / TomColumnAuthorization Row scoping ANDed into every query โ€” owner , group and the grant-driven resource kind, the last applied automatically to any entity with a resource_key column โ€” and per-column read/write rights enforced by the repository
TomDataSource / MariaDbDatasource / MariaDbXADatasource Connection drivers (MariaDB/MySQL)
TomDbType family / TomStandardSqlDialect Typed columns and SQL rendering
TomSelect / TomCount / TomDelete / TomGroupedSelect / TomLiteralSelect Generated query objects, plus the raw-SQL escape hatch

Schema & transactions (db_migration, transactions)

TypeResponsibility
TomDbMigrator / TomDbMigrations / TomDbVersion Versioned schema migrations (MariaDB adapter)
TomSchemaConvergenceCheck / TomSchemaModel / TomEntitySchemaReader / TomSchemaDivergence Checks that the entities, the migrations and the live database still describe the same schema, and names each divergence
TomTransactionManager / TomTransaction / TomTransactionParticipant Local/XA transaction coordination

Platform services (configuration, cache, authentication, authorization, audit, messaging, resources, export, file_storage, scheduling, preferences, healthcheck, cmdline, little_things)

ModuleKey typesWhat it does
configuration TomBaseServerConfiguration , TomServerConfigResourceProvider , TomConfig<T> , TomYaml Typed config + YAML resources
cache TomCache, TomCacheManager Isolate-backed server-side cache
authentication TomAuthenticationService , TomAuthenticationData , TomAuthenticationAdaptor , Tom2FAAdaptor , Tom2FAService , Tom2FARegistry , TomTotp2FAService , TomServerSelfLogin , TomOAuthAuthenticationService , TomOAuthFlow , TomOAuthProviders , TomExternalIdentityStore Authentication service and adaptor annotations; second-factor challenges behind one service contract, with a TOTP implementation and a registry that picks the factor per principal; a server's own login when it calls another service as itself; and external identity โ€” an OAuth 2.0 / OIDC authorization-code + PKCE login written as another implementation of the same service contract , so it reaches the unchanged token and 2FA path instead of a second one
authorization TomAuthorizationCache , TomResourceGrant , TomGradedAccessControlInformation , TomPrincipalResourceGrant , TomPrincipalDelegation , TomDelegationResolver , TomResourceDataScope Isolate-backed authorization cache; graded per-key grants that add the middle states the kernel's granted/denied list cannot express; per-principal-per-resource roles; delegation of those roles by explicit share, out-of-office window, or revocable hand-over; and the row scope that filters an entity's rows by those grants โ€” read to list, full to write โ€” with no application-side resolver
audit TomAuditTrail , TomAudited , TomAuditRecord , TomAuditSink , TomSqlAuditSink , TomAuditAnchor , TomAuditAnchorStore , TomAuditAnchorPublisher Attributable, tamper-evident trail written at the endpoint and repository chokepoints, stored in memory, in append-only files, or in a table whose write-once guarantee is the grant it is served with โ€” and anchors, published outside the server, that turn a wholesale rewrite or a truncation from invisible into provable
messaging TomMessage , TomMessageTransport , TomSmtpTransport , TomMessageOutbox Outbound messages: a channel-neutral shape, SMTP, and a durable outbox on the kernel's job scheduler
resources TomTextLocale , TomLocalizedTextResources , TomResourceLocaleException Per-recipient text for a server that renders many languages at once: the locale is an argument rather than ambient state, bundles resolve along a fallback chain per key so a half-translated language serves what it has, and a missing key answers null instead of the key โ€” right on a screen, unacceptable in a subject line
export TomTabularResult , TomTabularRenderer , TomCsvRenderer , TomXlsxRenderer , TomPdfRenderer , TomExportService , TomExportStore CSV/XLSX/PDF rendering from one streaming tabular shape, streamed back to a caller or into storage
file_storage TomBlobStore , TomDatabaseBlobStore , TomDirectoryBlobStore , TomS3BlobStore , TomMemoryBlobStore , TomBlobStoreInfo , TomFileReference Streaming blob storage behind one four-method contract, backends chosen in configuration, and entity columns that hold a storage key instead of the bytes
scheduling TomSqlJobStore, TomSqlLeaseLock The kernel's job-store and lease-lock contracts over a shared database, so a cluster whose nodes share no filesystem still runs each window exactly once
preferences TomUserPreference , TomUserPreferenceRepository , TomUserPreferences , TomUserPreferencesServer Per-principal preferences that follow the user across devices: one scoped table, a typed service that reads its caller from the request zone, and the /preferences/* endpoints behind the kernel's contract
healthcheck TomHealthService, TomHealthServer, TomHealthAnalyzer Health endpoints and analyzers
cmdline TomArgs, TomEnvFile Argument parsing and .env loading
little_things TomServerException, TomLoginException, TomRequestId Server exception base (extends kernel TomException ), the login failure it raises, and the per-request correlation id it reads from the zone

Quick start

Construct a server from a configuration and start it:

import 'package:tom_core_server/tom_core_server.dart';

void main(List<String> argv) {
  // `configuration` is your subclass of TomBaseServerConfiguration.
  final server = TomServer(configuration);
  server.start(); // HTTP host listening; endpoints routed
}

Parse command-line arguments and a .env file into typed values:

final config = TomArgs.fillFromArgs(argv, MyServerArgs()); // reflected key=value
final env = TomEnvFile.read('.env');                       // typed map

Example projects

Every module ships a runnable sample under example/. All of them run without a database โ€” including the datasource, persistence and migration samples, which demonstrate SQL rendering, repository wiring and migration planning against in-memory stand-ins. The sole exception is example/scheduling/cluster/, whose three programs are nodes of one demonstration and expect a live MariaDB and each other.

<a id="a-green-example-run-is-a-verification"></a>

A green run is a verification, not a transcript. Each example states its expectations through the shared example/_support/check.dart, which throws when one does not hold. Deliberately not assert: dart run leaves assertions disabled โ€” that is what its --enable-asserts flag is for โ€” so an assert would compile out under the very command each example's header documents, leaving a sample that prints a plausible transcript and exits 0 with every expectation in it violated. That is not a hypothetical and it is not left as prose: test/fixtures/ holds two programs identical but for that one line, and test/examples_test.dart runs both โ€” the check exits non-zero and stops before its next step, the assert exits 0 and runs to the end, and only --enable-asserts makes it fail.

test/examples_test.dart holds the property for the set rather than per example, which is what makes it cover samples added later: it requires that no file under example/ contains assert(, that none re-declares a local check, that none makes a check inside a handler that would catch it โ€” the same silence in a different spelling โ€” that the shared helper throws rather than narrating, and that every example program is either spawned by its module suite, spawned there, or excluded with a stated reason.

SampleDemonstrates
example/server/ Building and starting TomServer
example/endpoints/ Declaring routed endpoints
example/object_persistence/ Repositories, annotations, query builder
example/datasources/ SQL-typed values and dialect rendering
example/db_migration/ Versioned schema migrations
example/transactions/ Local/XA transaction coordination
example/configuration/ Typed config + YAML resources
example/cache/ Isolate-backed cache
example/authentication/ ยท example/authorization/ Login and second factors; the authorization cache, graded grants, resource roles and delegation, and the grant-driven row scope those grants filter the database with
example/audit/ Audit records, the declared/automatic split, the hash chain, the relational trail's schema and its write-once check, and signed anchors published outside the server
example/messaging/ Message composition, the MIME document, stand-in transports, the durable outbox
example/export/ The streaming tabular shape, the same shape with its columns derived from a reflected entity โ€” rendered against the hand-written one byte for byte โ€” CSV/XLSX/PDF renderers, the format registry, one export through all three storage backends, and the two delivery channels
example/resources/ Localized text as an argument rather than ambient state: bundle sets from maps, the three-rung per-key fallback chain, the locale a principal carries, and the isTranslated completeness check
example/preferences/ The five-piece bootstrap a user preference needs โ€” reflection, the datasource name, the bean, a principal-carrying zone and the migration โ€” with a scalar and a @tomReflector object written and read back through the shipped service
example/file_storage/ The one streaming blob contract, the key grammar, the infrastructure-free backends, configuration-driven selection, generated file-reference keys and the blob-backed export store
example/scheduling/ The relational job queue and lease lock: the schema and why each constraint is load-bearing, the shipped migrations, the UTC instant encoding, and the cluster wiring. example/scheduling/cluster/ adds the part that needs a database โ€” two real nodes arbitrating over one MariaDB, asserted rather than narrated
example/healthcheck/ ยท example/cmdline/ ยท example/little_things/ Health, CLI, and exceptions

Run a single example, or all of them:

dart run example/datasources/datasources_example.dart
dart run example/tom_core_server_example.dart   # every sample, in reading order

example/tom_core_server_example.dart is the runnable form of the table above: one line per program saying what it shows, and a runner that spawns each in turn and fails if any of them does not exit 0. The four programs it declines are named in it with their reasons, and test/examples_test.dart requires the two lists together to be exactly the example programs in the package โ€” so a sample added later appears there rather than quietly not being on the tour.

Usage

Connecting a datasource (MariaDB/MySQL)

The MariaDB driver is configured by its connection fields, then connect() opens the live connection:

final ds = MariaDbDatasource()
  ..host = 'localhost'
  ..port = 3306
  ..username = 'tom'
  ..password = 'secret'
  ..databaseName = 'tomdb';
await ds.connect(); // throws MariadbDatasourceException on failure

> MySQL-in-Docker. The runnable end-to-end story โ€” a Docker Compose MySQL > plus bootstrap and migrations โ€” ships with the > tom_core_samples/core_server_sample > project (see the samples map). This package > provides the abstract contracts and the MariaDB driver it builds on.

Repositories and type-safe queries

Repositories expose CRUD plus queries built from the expression operators (Eq, And, Like, In, Gte, โ€ฆ):

final all  = await repository.findAll();
final one  = await repository.findById(42);
final some = await repository.findByQuery(query, params);

A grouped query is the one whose rows are not entities โ€” its shape is decided by the query, so it comes back as group keys plus aggregate values:

final rows = await repository.findGroupedByQuery(groupedQuery, params);
for (final row in rows) {
  print('${row.stringValue('status')}: ${row.intValue('n')}');
}

Rendering SQL through the dialect

The dialect renders literals and bound-parameter placeholders, keeping untrusted input out of the SQL string:

final dialect = TomStandardSqlDialect();
print(dialect.text('abc'));     // 'abc'
print(dialect.objectOp([1, 2, 3])); // 1,2,3
print(dialect.p('userId'));     // :userId

Authentication, authorization, and health

TomAuthenticationService (with TomAuthenticationAdaptor / Tom2FAAdaptor annotations) drives login, and Tom2FARegistry picks the second factor for a principal โ€” TomTotp2FAService ships as one. TomAuthorizationCache caches authorization decisions on an isolate, over grants that are graded per key (TomResourceGrant), held per resource (TomPrincipalResourceGrant), and lendable to another principal (TomPrincipalDelegation). TomHealthService / TomHealthServer expose health endpoints backed by TomHealthAnalyzers.

Those same grants filter the database. Install TomResourceDataScope once and every entity carrying a resource_key column is scoped to the keys the current principal holds a grant on, on reads and on writes โ€” a read grade lists the row, only full may change it, and resolution is live, so a revoked share stops granting on the next request rather than at token expiry:

TomResourceDataScope.install(delegationResolver);

"Sign in with โ€ฆ" is wired in as one line, because it is not a second login path:

TomAuthenticationServer.globalSettingUserAuthenticationService =
    TomOAuthAuthenticationService(
      directory: MyDirectory(),  // your account lookup + provisioning policy
      delegate: TomAuthenticationServer.globalSettingUserAuthenticationService,
    );

TomOAuthAuthenticationService implements TomAuthenticationService. It receives a TomOAuthCredential where a password login receives a password, exchanges the authorization code, resolves the local account through TomExternalIdentityStore's (issuer, subject) linking table โ€” and then stops. It issues no token, consults no 2FA policy and makes no session decision, because the result is handed to the same continuation a password login reaches. An externally authenticated principal is therefore challenged for its second factor and issued exactly the claims a password login would produce, not because this is reimplemented but because it is never touched. Any credential that is not a TomOAuthCredential passes through to the delegate, so an account may hold a password and several external identities at once.

Providers are configuration data (oauth.providers), never code, and what a first external login with no local account does โ€” refuse or autoProvision โ€” is per-provider configuration too. See doc/authentication.md.

Architecture

                       TomServer (shelf host)
                            โ”‚
         middleware: context ยท request-id ยท auth ยท logging ยท JSON errors
                            โ”‚
                    TomEndpointRouting
                            โ”‚
         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   object_persistence   transactions        services
   (TomRepository)   (TomTransactionMgr)  (auth ยท cache ยท health)
         โ”‚
     datasources  โ†’  MariaDbDatasource / MariaDbXADatasource (MySQL)
         โ”‚
     db_migration  โ†’  TomDbMigrator (versioned schema)
TypeResponsibility
TomServerHTTP host + middleware pipeline
TomEndpointRoutingMaps requests to endpoint handlers
TomRepositoryCRUD + type-safe query repository
TomDataSource / MariaDbDatasource Database connection driver
TomQueryBuilderBuilds type-safe queries from expression operators
TomDbMigratorApplies versioned schema migrations
TomTransactionManagerCoordinates local/XA transactions
TomBaseServerConfigurationServer configuration root

Ecosystem

tom_basics ยท tom_crypto ยท tom_reflection
                 โ”‚
          tom_core_kernel
                 โ”‚
          tom_core_server        โ† you are here
          (re-exports the kernel; HTTP, persistence, migrations)

tom_core_server sits directly on tom_core_kernel and re-exports it, so server apps get the full kernel API through one dependency. See the repository map for the full picture.

Further documentation

  • Per-module guides under doc/ โ€” one markdown file per module

(doc/object_persistence.md, doc/datasources.md, doc/endpoints.md, โ€ฆ). - Per-module API reference under doc/api_reference/ โ€” start at doc/api_reference/api_summary_index.md for the type index and one api_summary_{module}.md per module. (doc/api/ is reserved for generated dart doc HTML and is gitignored.) - doc/reflection_generation.md โ€” how the committed *.reflection.dart files are produced, why regenerating rewrites thousands of lines nobody edited, and how to tell a stale one from a current one. - tom_core_kernel โ€” the platform-neutral building blocks this layer is composed from.

Status

  • Version: 1.3.0
  • Tests: run with testkit :test or dart test. Part of the suite is

tagged db and skips unless a live MySQL is reachable โ€” bring one up with tool/run_db_tests.sh, and the tagged tests then run too.

No count is given, and that is deliberate. A count here is a claim nothing checks, and this package's whole position is that an unchecked claim about itself breaks โ€” test/api_reference_coverage_test.dart is a hundred-odd assertions built on exactly that premise. A count breaks faster than most, because every added test is a README edit nobody makes. The db tag above is what a reader can actually act on, and the version is checked, because a wrong version is the claim that actively misleads. - Conformance guards: some of those suites check the package rather than the code โ€” test/formatting_test.dart fails when any Dart file is not dart format clean (the fix is always dart format ., never a code change), test/api_reference_coverage_test.dart fails when doc/api_reference/ and lib/ disagree, test/reflection_currency_test.dart fails when a class or column was added to a reflection root without regenerating, and test/examples_test.dart fails when an example stops being verified. They need no separate invocation: a normal test run is the gate, which is the point โ€” dart analyze cannot see any of these. The one check too slow to run that way is tool/check_reflection_current.sh, which regenerates and compares exactly (two to four minutes). - Mutation sweep: python3 tool/mutation_sweep.py breaks one recorded behaviour at a time in lib/ and reports whether the suite noticed. A green test that names a behaviour is not the same as one that pins it, and reading the test cannot tell them apart. The catalogue is tool/mutations.yaml and is the committed half: a module absent from it has not been swept. A mutant that does not compile is reported as INVALID rather than killed, because it turns every test red without asking anything of them; one that makes the suite hang is stopped after ten minutes and counted as killed, since the suite did notice. Not part of a normal test run โ€” it edits lib/, restores in a finally, and takes minutes. - 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.