@xframework/x 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.turbo/turbo-build.log +46 -0
  2. package/CHANGELOG.md +7 -0
  3. package/dist/adapter.cjs +50 -0
  4. package/dist/adapter.cjs.map +1 -0
  5. package/dist/adapter.d.cts +2 -0
  6. package/dist/adapter.d.ts +2 -0
  7. package/dist/adapter.js +21 -0
  8. package/dist/adapter.js.map +1 -0
  9. package/dist/core/adapters.cjs +48 -0
  10. package/dist/core/adapters.cjs.map +1 -0
  11. package/dist/core/adapters.d.cts +19 -0
  12. package/dist/core/adapters.d.ts +19 -0
  13. package/dist/core/adapters.js +21 -0
  14. package/dist/core/adapters.js.map +1 -0
  15. package/dist/core/types.cjs +19 -0
  16. package/dist/core/types.cjs.map +1 -0
  17. package/dist/core/types.d.cts +25 -0
  18. package/dist/core/types.d.ts +25 -0
  19. package/dist/core/types.js +1 -0
  20. package/dist/core/types.js.map +1 -0
  21. package/dist/core/x.cjs +134 -0
  22. package/dist/core/x.cjs.map +1 -0
  23. package/dist/core/x.d.cts +17 -0
  24. package/dist/core/x.d.ts +17 -0
  25. package/dist/core/x.js +107 -0
  26. package/dist/core/x.js.map +1 -0
  27. package/dist/index.cjs +134 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +2 -0
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.js +107 -0
  32. package/dist/index.js.map +1 -0
  33. package/package.json +33 -0
  34. package/src/__tests__/async-adapter.test.ts +87 -0
  35. package/src/__tests__/composition.test.ts +45 -0
  36. package/src/__tests__/core.test.ts +23 -0
  37. package/src/__tests__/error-handling.test.ts +71 -0
  38. package/src/__tests__/sync-adapter.test.ts +83 -0
  39. package/src/__tests__/type-system.test.ts +78 -0
  40. package/src/adapter.ts +1 -0
  41. package/src/core/adapters.ts +26 -0
  42. package/src/core/types.ts +47 -0
  43. package/src/core/x.ts +163 -0
  44. package/src/index.ts +1 -0
  45. package/tsconfig.json +9 -0
  46. package/tsup.config.ts +3 -0
@@ -0,0 +1,78 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createX } from "..";
3
+ import { SyncAdapter } from "../adapter";
4
+
5
+ interface Config {
6
+ key: string;
7
+ value: number;
8
+ }
9
+
10
+ class ConfigAdapter extends SyncAdapter<Config> {
11
+ export(): Config {
12
+ return { key: "test", value: 42 };
13
+ }
14
+ }
15
+
16
+ class NumberAdapter extends SyncAdapter<number> {
17
+ constructor(private value: number) {
18
+ super();
19
+ }
20
+ export() {
21
+ return this.value;
22
+ }
23
+ }
24
+
25
+ class StringAdapter extends SyncAdapter<string> {
26
+ constructor(private deps: { number: number }) {
27
+ super();
28
+ }
29
+ export() {
30
+ return `value: ${this.deps.number}`;
31
+ }
32
+ }
33
+
34
+ // type-system.test.ts
35
+ describe("Type System Tests", () => {
36
+ it("should properly type exports", () => {
37
+ const result = createX()
38
+ .syncAdapter("config", () => new ConfigAdapter())
39
+ .build();
40
+
41
+ // TypeScript should recognize these properties
42
+ expect(result.config.key).toBe("test");
43
+ expect(result.config.value).toBe(42);
44
+ });
45
+
46
+ it("should properly type dependencies", () => {
47
+ class DependentAdapter extends SyncAdapter<string> {
48
+ constructor(private deps: { config: { value: number } }) {
49
+ super();
50
+ }
51
+ export() {
52
+ return `value: ${this.deps.config.value}`;
53
+ }
54
+ }
55
+
56
+ const result = createX()
57
+ .syncAdapter("config", () => new ConfigAdapter())
58
+ .syncAdapter("dependent", (deps) => new DependentAdapter(deps))
59
+ .build();
60
+
61
+ expect(result.dependent).toBe("value: 42");
62
+ });
63
+
64
+ it("should maintain type safety with use()", () => {
65
+ const first = createX().syncAdapter("num", () => new NumberAdapter(1));
66
+ const second = createX()
67
+ .use(first)
68
+ .syncAdapter("str", ({ num }) => new StringAdapter({ number: num }));
69
+
70
+ // TypeScript should recognize these types
71
+ type SecondType = typeof second;
72
+ type BuildType = Awaited<ReturnType<SecondType["build"]>>;
73
+
74
+ const result = second.build() as BuildType;
75
+ expect(result.num).toBeDefined();
76
+ expect(result.str).toBeDefined();
77
+ });
78
+ });
package/src/adapter.ts ADDED
@@ -0,0 +1 @@
1
+ export { AsyncAdapter, SyncAdapter, AdapterError } from "./core/adapters";
@@ -0,0 +1,26 @@
1
+ import type { SyncAdapterType, AsyncAdapterType } from "./types";
2
+
3
+ export abstract class SyncAdapter<TExport> implements SyncAdapterType<TExport> {
4
+ readonly __type = "sync" as const;
5
+ init?(): void;
6
+ abstract export(): TExport;
7
+ }
8
+
9
+ export abstract class AsyncAdapter<TExport>
10
+ implements AsyncAdapterType<TExport>
11
+ {
12
+ readonly __type = "async" as const;
13
+ init?(): Promise<void>;
14
+ abstract export(): Promise<TExport>;
15
+ }
16
+
17
+ export class AdapterError extends Error {
18
+ constructor(
19
+ message: string,
20
+ public readonly adapterName: string,
21
+ public readonly cause?: unknown,
22
+ ) {
23
+ super(message);
24
+ this.name = "AdapterError";
25
+ }
26
+ }
@@ -0,0 +1,47 @@
1
+ // Core adapter type definitions
2
+ export type SyncAdapterType<TExport = unknown> = {
3
+ __type: "sync";
4
+ init?(): void;
5
+ export(): TExport;
6
+ };
7
+
8
+ export type AsyncAdapterType<TExport = unknown> = {
9
+ __type: "async";
10
+ init?(): Promise<void>;
11
+ export(): Promise<TExport>;
12
+ };
13
+
14
+ export type AnyAdapter = SyncAdapterType | AsyncAdapterType;
15
+ export type AdapterRegistry = Record<string, AnyAdapter>;
16
+
17
+ // Helper type to extract the exported type from an adapter
18
+ export type ExportType<TAdapter> = TAdapter extends
19
+ | SyncAdapterType<infer TExport>
20
+ | AsyncAdapterType<infer TExport>
21
+ ? TExport
22
+ : never;
23
+
24
+ // Result type with metadata container
25
+ export type AdapterResult<TAdapters extends AdapterRegistry> = {
26
+ [Key in keyof TAdapters]: ExportType<TAdapters[Key]>;
27
+ } & {
28
+ _: {
29
+ adapters: TAdapters;
30
+ };
31
+ };
32
+
33
+ export type BuildOutput<
34
+ TAdapters extends AdapterRegistry,
35
+ TIsAsync extends boolean,
36
+ > = TIsAsync extends true
37
+ ? Promise<AdapterResult<TAdapters>>
38
+ : AdapterResult<TAdapters>;
39
+
40
+ // Factory types
41
+ export type SyncAdapterCreator<TExport, TDeps extends AdapterRegistry> = (
42
+ dependencies: AdapterResult<TDeps>,
43
+ ) => SyncAdapterType<TExport>;
44
+
45
+ export type AsyncAdapterCreator<TExport, TDeps extends AdapterRegistry> = (
46
+ dependencies: AdapterResult<TDeps>,
47
+ ) => Promise<AsyncAdapterType<TExport>> | AsyncAdapterType<TExport>;
package/src/core/x.ts ADDED
@@ -0,0 +1,163 @@
1
+ import { AdapterError } from "./adapters";
2
+ import type {
3
+ SyncAdapterCreator,
4
+ SyncAdapterType,
5
+ AsyncAdapterCreator,
6
+ AsyncAdapterType,
7
+ AdapterRegistry,
8
+ AdapterResult,
9
+ BuildOutput,
10
+ } from "./types";
11
+
12
+ type AdapterFactory<T extends AdapterRegistry> =
13
+ | [(deps: AdapterResult<T>) => SyncAdapterType<unknown>, false]
14
+ | [(deps: AdapterResult<T>) => Promise<AsyncAdapterType<unknown>>, true];
15
+
16
+ class X<
17
+ TAdapters extends AdapterRegistry = Record<string, never>,
18
+ TIsAsync extends boolean = false,
19
+ > {
20
+ constructor(
21
+ private readonly adapterFactories: Record<
22
+ string,
23
+ AdapterFactory<TAdapters>
24
+ > = {} as Record<string, AdapterFactory<TAdapters>>,
25
+ private readonly isAsync: TIsAsync = false as TIsAsync,
26
+ ) {}
27
+
28
+ syncAdapter<TKey extends string, TExport>(
29
+ name: TKey,
30
+ factory: SyncAdapterCreator<TExport, TAdapters>,
31
+ ): X<TAdapters & Record<TKey, SyncAdapterType<TExport>>, TIsAsync> {
32
+ type NewAdapters = TAdapters & Record<TKey, SyncAdapterType<TExport>>;
33
+ const newFactories = { ...this.adapterFactories };
34
+ // We know this is safe because factory is a SyncAdapterCreator
35
+ (newFactories as Record<string, unknown>)[name] = [
36
+ factory,
37
+ false,
38
+ ] as AdapterFactory<NewAdapters>;
39
+ return new X(
40
+ newFactories as Record<string, AdapterFactory<NewAdapters>>,
41
+ this.isAsync,
42
+ );
43
+ }
44
+
45
+ asyncAdapter<TKey extends string, TExport>(
46
+ name: TKey,
47
+ factory: AsyncAdapterCreator<TExport, TAdapters>,
48
+ ): X<TAdapters & Record<TKey, AsyncAdapterType<TExport>>, true> {
49
+ type NewAdapters = TAdapters & Record<TKey, AsyncAdapterType<TExport>>;
50
+ const wrappedFactory = async (deps: AdapterResult<TAdapters>) => {
51
+ const result = factory(deps);
52
+ return result instanceof Promise ? await result : result;
53
+ };
54
+
55
+ const newFactories = { ...this.adapterFactories };
56
+ // We know this is safe because wrappedFactory returns a Promise<AsyncAdapterType>
57
+ (newFactories as Record<string, unknown>)[name] = [
58
+ wrappedFactory,
59
+ true,
60
+ ] as AdapterFactory<NewAdapters>;
61
+ return new X(
62
+ newFactories as Record<string, AdapterFactory<NewAdapters>>,
63
+ true,
64
+ );
65
+ }
66
+
67
+ use<TOtherAdapters extends AdapterRegistry, TOtherAsync extends boolean>(
68
+ other: X<TOtherAdapters, TOtherAsync>,
69
+ ): X<TAdapters & TOtherAdapters, TIsAsync | TOtherAsync> {
70
+ type CombinedAdapters = TAdapters & TOtherAdapters;
71
+ const newFactories = {
72
+ ...this.adapterFactories,
73
+ ...other["adapterFactories"],
74
+ } as Record<string, AdapterFactory<CombinedAdapters>>;
75
+ return new X(newFactories, this.isAsync || other["isAsync"]);
76
+ }
77
+
78
+ build(): BuildOutput<TAdapters, TIsAsync> {
79
+ return this.isAsync
80
+ ? (this.buildAsync() as BuildOutput<TAdapters, TIsAsync>)
81
+ : (this.buildSync() as BuildOutput<TAdapters, TIsAsync>);
82
+ }
83
+
84
+ private buildSync(): AdapterResult<TAdapters> {
85
+ const result = {
86
+ _: { adapters: {} as TAdapters },
87
+ } as AdapterResult<TAdapters>;
88
+
89
+ for (const [key, [factory, isAsync]] of Object.entries(
90
+ this.adapterFactories,
91
+ )) {
92
+ if (isAsync) {
93
+ throw new AdapterError(
94
+ `Cannot use async adapter "${key}" in sync build`,
95
+ key,
96
+ );
97
+ }
98
+
99
+ const adapter = factory(result) as SyncAdapterType<unknown>;
100
+
101
+ if (adapter.__type !== "sync") {
102
+ throw new AdapterError(
103
+ `Factory returned async adapter "${key}" in sync build`,
104
+ key,
105
+ );
106
+ }
107
+
108
+ (result._.adapters as Record<string, SyncAdapterType<unknown>>)[key] =
109
+ adapter;
110
+ adapter.init?.();
111
+ (result as Record<string, unknown>)[key] = adapter.export();
112
+ }
113
+
114
+ return result;
115
+ }
116
+
117
+ private async buildAsync(): Promise<AdapterResult<TAdapters>> {
118
+ const result = {
119
+ _: { adapters: {} as TAdapters },
120
+ } as AdapterResult<TAdapters>;
121
+
122
+ for (const [key, [factory, isAsync]] of Object.entries(
123
+ this.adapterFactories,
124
+ )) {
125
+ const currentResult = { ...result };
126
+ const adapter = isAsync
127
+ ? await (
128
+ factory as (
129
+ deps: AdapterResult<TAdapters>,
130
+ ) => Promise<AsyncAdapterType<unknown>>
131
+ )(currentResult)
132
+ : (
133
+ factory as (
134
+ deps: AdapterResult<TAdapters>,
135
+ ) => SyncAdapterType<unknown>
136
+ )(currentResult);
137
+
138
+ (
139
+ result._.adapters as Record<
140
+ string,
141
+ SyncAdapterType<unknown> | AsyncAdapterType<unknown>
142
+ >
143
+ )[key] = adapter;
144
+
145
+ if (adapter.init) {
146
+ if (adapter.__type === "async") {
147
+ await adapter.init();
148
+ } else {
149
+ adapter.init();
150
+ }
151
+ }
152
+
153
+ (result as Record<string, unknown>)[key] =
154
+ adapter.__type === "async" ? await adapter.export() : adapter.export();
155
+ }
156
+
157
+ return result;
158
+ }
159
+ }
160
+
161
+ export function createX() {
162
+ return new X();
163
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { createX } from "./core/x";
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "lib": ["ESNext", "DOM"]
6
+ },
7
+ "include": ["src", "tsup.config.ts"],
8
+ "exclude": ["node_modules", "dist"]
9
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { tsupConfig } from "../../../utils/tsup.config";
2
+
3
+ export default tsupConfig;