Commit 9d5ee363 authored by nanahira's avatar nanahira

fix context

parent ad4c365d
......@@ -41,6 +41,52 @@ export class AppContextCore<Cur = Empty, Req = Empty> {
private registry = new Map<string | AnyClass, LoadEntry>();
private objectSteps: ObjectStep[] = [];
started = false;
private starting = false;
private startingEntries: LoadEntry[] | null = null;
private createdRecords: Set<ProvideRecord> | null = null;
private createdEntries: Map<ProvideRecord, LoadEntry> | null = null;
private loadingRecords = new Set<ProvideRecord>();
private findProvideRecord(key: AnyClass): ProvideRecord | undefined {
for (let i = 0; i < this.provideRecords.length; i += 1) {
const record = this.provideRecords[i];
if (
record.classRef === key &&
(!this.createdRecords || !this.createdRecords.has(record))
) {
return record;
}
}
return undefined;
}
private createEntryFromRecord(record: ProvideRecord): LoadEntry {
const existing = this.createdEntries?.get(record);
if (existing) {
return existing;
}
if (this.loadingRecords.has(record)) {
throw new Error(
`Circular dependency detected while providing: ${record.classRef?.name ?? 'UnknownService'}`,
);
}
this.loadingRecords.add(record);
try {
const entry: LoadEntry = {
classRef: record.classRef,
inst: record.factory(this),
};
this.createdRecords?.add(record);
this.createdEntries?.set(record, entry);
this.registry.set(record.classRef, entry);
this.startingEntries?.push(entry);
return entry;
} finally {
this.loadingRecords.delete(record);
}
}
provide<
C extends AppServiceClass<Cur, Req>,
......@@ -156,6 +202,13 @@ export class AppContextCore<Cur = Empty, Req = Empty> {
key = (cls as () => AppServiceClass<Cur, Req, any, R>)() as AnyClass;
}
if (!this.registry.has(key) && this.starting) {
const record = this.findProvideRecord(key);
if (record) {
this.createEntryFromRecord(record);
}
}
if (!this.registry.has(key)) {
throw new Error(`Service not provided: ${key?.name ?? cls.name}`);
}
......@@ -225,39 +278,42 @@ export class AppContextCore<Cur = Empty, Req = Empty> {
const startedEntries: LoadEntry[] = [];
const preloadedKeys = new Set(this.registry.keys());
// Create all instances
for (const record of this.provideRecords) {
if (preloadedKeys.has(record.classRef)) {
continue;
this.starting = true;
this.startingEntries = startedEntries;
this.createdRecords = new Set();
this.createdEntries = new Map();
try {
// Create all instances. Dependencies requested via get() during construction
// can be created on-demand from remaining provide records.
for (const record of this.provideRecords) {
if (preloadedKeys.has(record.classRef)) {
continue;
}
this.createEntryFromRecord(record);
}
const inst = record.factory(this);
const entry: LoadEntry = {
classRef: record.classRef,
inst,
};
this.registry.set(record.classRef, entry);
startedEntries.push(entry);
}
// Resolve all promises created in this start().
for (const entry of startedEntries) {
if (entry.inst && typeof entry.inst.then === 'function') {
entry.inst = await entry.inst;
// Resolve all promises created in this start().
for (const entry of startedEntries) {
if (entry.inst && typeof entry.inst.then === 'function') {
entry.inst = await entry.inst;
}
}
}
// Init only instances created in this start().
for (const entry of startedEntries) {
const inst = entry.inst;
if (inst && typeof inst.init === 'function') {
await inst.init();
// Init only instances created in this start().
for (const entry of startedEntries) {
const inst = entry.inst;
if (inst && typeof inst.init === 'function') {
await inst.init();
}
}
this.started = true;
return this as any;
} finally {
this.createdEntries = null;
this.createdRecords = null;
this.startingEntries = null;
this.starting = false;
}
this.started = true;
return this as any;
}
}
......
......@@ -63,6 +63,14 @@ class NeedsMergedMethodService {
}
}
class NeedsCounterService {
counter: CounterService;
constructor(public ctx: AppContext) {
this.counter = ctx.get(CounterService);
}
}
describe('app-context runtime', () => {
test('provide + merge(method) binds this correctly', async () => {
const ctx = await createAppContext()
......@@ -192,6 +200,32 @@ describe('app-context runtime', () => {
const root = await createAppContext().use(parent).use(child).define().start();
expect(root.needsMerged.value).toBe(11);
});
test('provider in earlier used context can get provider from later used context', async () => {
const a = createAppContext()
.provide(NeedsCounterService, { provide: 'needsCounter' })
.define();
const b = createAppContext()
.provide(CounterService, 21, { provide: 'counter' })
.define();
const root = await createAppContext().use(a).use(b).define().start();
expect(root.needsCounter.counter).toBe(root.counter);
expect(root.needsCounter.counter.value).toBe(21);
});
test('provider in later used context can get provider from earlier used context', async () => {
const a = createAppContext()
.provide(CounterService, 34, { provide: 'counter' })
.define();
const b = createAppContext()
.provide(NeedsCounterService, { provide: 'needsCounter' })
.define();
const root = await createAppContext().use(a).use(b).define().start();
expect(root.needsCounter.counter).toBe(root.counter);
expect(root.needsCounter.counter.value).toBe(34);
});
});
describe('app-context type checks', () => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment