feat: define clean architecture layers and ports

This commit is contained in:
donghyeon-ka
2026-07-25 20:39:59 +09:00
parent 24d7072d66
commit 49b20bd557
11 changed files with 227 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
/**
* Application facade factory. Concrete dependencies are supplied by bootstrap.
*
* @param {{
* resources: {
* query: import("./ports/resource-ports.js").ResourceQueryPort<unknown, unknown>,
* command: import("./ports/resource-ports.js").ResourceCommandPort<unknown, unknown>
* },
* cache: import("./ports/query-cache-port.js").QueryCachePort,
* storage: import("./ports/storage-port.js").StoragePort,
* telemetry: import("./ports/telemetry-port.js").TelemetryPort
* }} ports
*/
export function createApplication(ports) {
return Object.freeze({
resources: Object.freeze({
query: (query, context) => ports.resources.query.execute(query, context),
command: (command, context) => ports.resources.command.execute(command, context),
}),
cache: ports.cache,
storage: ports.storage,
telemetry: ports.telemetry,
});
}
@@ -0,0 +1,16 @@
/**
* @typedef {"authenticated" | "unauthenticated" | "recovery-pending" | "integration-failed"} SessionState
*/
/**
* The session is opaque: credentials are attached without exposing tokens.
*
* @typedef {{
* getState(): SessionState,
* attach(request: Request): Promise<Request>,
* recover(): Promise<"restored" | "no-session">,
* onUnauthenticated(): void
* }} AuthSessionPort
*/
export {};
+28
View File
@@ -0,0 +1,28 @@
/**
* @typedef {{
* now(): number,
* sleep(milliseconds: number, signal?: AbortSignal): Promise<void>
* }} ClockPort
*/
export const systemClock = Object.freeze({
now: () => Date.now(),
sleep(milliseconds, signal) {
return new Promise((resolve, reject) => {
if (signal?.aborted) {
reject(signal.reason);
return;
}
const timer = setTimeout(resolve, milliseconds);
signal?.addEventListener(
"abort",
() => {
clearTimeout(timer);
reject(signal.reason);
},
{ once: true },
);
});
},
});
@@ -0,0 +1,9 @@
/**
* @typedef {{
* read(key: readonly unknown[]): unknown,
* write(key: readonly unknown[], value: unknown): void,
* invalidate(namespace: readonly unknown[]): Promise<void>
* }} QueryCachePort
*/
export {};
@@ -0,0 +1,13 @@
/**
* @typedef {{
* getCurrent(): Promise<{
* buildId: string,
* configSchemaVersion: string,
* apiContractVersion: string,
* assetManifestHash: string,
* releaseId: string
* }>
* }} ReleaseInfoPort
*/
export {};
+28
View File
@@ -0,0 +1,28 @@
/**
* @template Query
* @template Model
* @typedef {{ execute(query: Query, context?: RequestContext): Promise<Result<Model>> }} ResourceQueryPort
*/
/**
* @template Command
* @template Model
* @typedef {{ execute(command: Command, context?: RequestContext): Promise<Result<Model>> }} ResourceCommandPort
*/
/**
* @typedef {{
* operationId: string,
* routeId: string,
* signal?: AbortSignal,
* idempotencyKey?: string
* }} RequestContext
*/
/**
* @template Value
* @typedef {{ ok: true, value: Value, meta?: Record<string, unknown> } |
* { ok: false, error: import("../../contracts/errors.js").ApiFailure }} Result
*/
export {};
+9
View File
@@ -0,0 +1,9 @@
/**
* @typedef {{
* read(logicalName: string): { ok: true, value: unknown } | { ok: false, error: unknown },
* write(logicalName: string, value: unknown): { ok: true } | { ok: false, error: unknown },
* remove(logicalName: string): { ok: true } | { ok: false, error: unknown }
* }} StoragePort
*/
export {};
+5
View File
@@ -0,0 +1,5 @@
/**
* @typedef {{ emit(eventName: string, attributes: Record<string, unknown>): void }} TelemetryPort
*/
export {};