createActiveApp() now accepts an `options.services: TSchema` object
and exposes each declared service as a lowercase property on the
returned App. Schema services coexist with the legacy uppercase
core (App.Cache, App.Bus, App.createActiveSession() etc.) — neither
collides with the other and apps can adopt the schema gradually.
Type changes:
- ActiveApp<S, TSchema = AppServiceSchema> intersects
ActiveAppLegacy<S> (the uppercase core + factory methods),
ResolveServiceInstances<TSchema> (schema instances), and
ActiveAppServicesIntrospection ({ services: { … } status map }).
- ActiveAppOptions<S, TSchema> adds the optional `services?` slot.
Runtime changes:
- createActiveApp builds a CoreServices snapshot (logger, bus,
timers, orca) after Bus + Timers + Orca are constructed.
- When `services` is provided, buildServiceBuilders() is invoked
and every key becomes a getter on the App; reading triggers
lazy construction. Immediate services build during
createActiveApp().
- dispose() runs schema services first (in reverse construction
order) and then the legacy core in its existing order.
- When `services` is absent, App.services returns an empty frozen
object so introspection still works.
Tests:
- active-app.test.ts: API surface includes the new `services` key.
- schema-declarative.test.ts (new, 6 cases): lowercase access,
lazy construction, coexistence with legacy uppercase, dispose,
introspection map, empty schema fallback.
Total suite: 1408 pass (1402 + 6 new). The legacy translators,
APP_EVENT_* publishers and App.createActiveX() factories are still
present; their removal lands in subsequent commits of step 3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
master
parent
fb2e3ac507
commit
7148a5d2ec
@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Verifies the new declarative service schema integrates with
|
||||
* createActiveApp(): services are exposed as lowercase properties on
|
||||
* the App, build lazily on first access, and are disposed in reverse
|
||||
* order alongside the legacy core.
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { LogLevel } from '$logger';
|
||||
import { createActiveApp } from '../active-app.svelte.ts';
|
||||
import {
|
||||
defineActiveCache,
|
||||
defineActiveDom,
|
||||
defineActiveFormat,
|
||||
defineActiveStorage,
|
||||
defineEngineHttp
|
||||
} from '../service-factories/index.ts';
|
||||
|
||||
const SILENT_LOGGER = { level: LogLevel.NONE, transports: [] };
|
||||
|
||||
describe('createActiveApp — declarative service schema', () => {
|
||||
it('exposes declared services as lowercase properties', () => {
|
||||
const App = createActiveApp({
|
||||
logger: SILENT_LOGGER,
|
||||
services: {
|
||||
cache: defineActiveCache(),
|
||||
http: defineEngineHttp({ baseUrl: '/api' })
|
||||
}
|
||||
});
|
||||
|
||||
expect(App.cache).toBeDefined();
|
||||
expect(App.http).toBeDefined();
|
||||
expect(typeof App.http.get).toBe('function');
|
||||
|
||||
App.dispose();
|
||||
});
|
||||
|
||||
it('builds services lazily by default', () => {
|
||||
const App = createActiveApp({
|
||||
logger: SILENT_LOGGER,
|
||||
services: {
|
||||
cache: defineActiveCache()
|
||||
}
|
||||
});
|
||||
|
||||
expect(App.services.cache).toBe('absent');
|
||||
// touching it triggers construction
|
||||
void App.cache;
|
||||
expect(App.services.cache).toBe('present');
|
||||
|
||||
App.dispose();
|
||||
});
|
||||
|
||||
it('coexists with the legacy uppercase core (App.Cache, App.Bus, etc.)', () => {
|
||||
const App = createActiveApp({
|
||||
logger: SILENT_LOGGER,
|
||||
services: {
|
||||
cache: defineActiveCache()
|
||||
}
|
||||
});
|
||||
|
||||
// legacy: App.Cache always present
|
||||
expect(App.Cache).toBeDefined();
|
||||
// declarative: App.cache is the schema-declared instance
|
||||
expect(App.cache).toBeDefined();
|
||||
// they are independent ActiveCache instances
|
||||
expect(App.cache).not.toBe(App.Cache);
|
||||
|
||||
App.dispose();
|
||||
});
|
||||
|
||||
it('disposes schema services on App.dispose()', () => {
|
||||
let disposed = false;
|
||||
const App = createActiveApp({
|
||||
logger: SILENT_LOGGER,
|
||||
services: {
|
||||
probe: {
|
||||
name: 'probe' as const,
|
||||
coreDependencies: [] as const,
|
||||
initMode: 'immediate' as const,
|
||||
create: () => ({ value: 1 }),
|
||||
dispose: () => {
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
App.dispose();
|
||||
expect(disposed).toBe(true);
|
||||
});
|
||||
|
||||
it('exposes services introspection map via App.services', () => {
|
||||
const App = createActiveApp({
|
||||
logger: SILENT_LOGGER,
|
||||
services: {
|
||||
cache: defineActiveCache(),
|
||||
dom: defineActiveDom(),
|
||||
format: defineActiveFormat(),
|
||||
storage: defineActiveStorage()
|
||||
}
|
||||
});
|
||||
|
||||
expect(Object.keys(App.services).sort()).toEqual(['cache', 'dom', 'format', 'storage']);
|
||||
// All lazy: nothing built yet.
|
||||
for (const status of Object.values(App.services)) {
|
||||
expect(status).toBe('absent');
|
||||
}
|
||||
|
||||
App.dispose();
|
||||
});
|
||||
|
||||
it('returns an empty services map when no schema is declared', () => {
|
||||
const App = createActiveApp({ logger: SILENT_LOGGER });
|
||||
expect(App.services).toEqual({});
|
||||
App.dispose();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in new issue