@raubjo/architect-core 0.1.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.
- package/bun.lock +20 -0
- package/coverage/lcov.info +1078 -0
- package/package.json +43 -0
- package/src/cache/cache.ts +3 -0
- package/src/cache/manager.ts +115 -0
- package/src/config/app.ts +5 -0
- package/src/config/clone.ts +9 -0
- package/src/config/env.global.d.ts +5 -0
- package/src/config/env.ts +79 -0
- package/src/config/repository.ts +204 -0
- package/src/filesystem/adapters/local.ts +104 -0
- package/src/filesystem/filesystem.ts +21 -0
- package/src/foundation/application.ts +207 -0
- package/src/index.ts +33 -0
- package/src/rendering/adapters/react.tsx +27 -0
- package/src/rendering/renderer.ts +13 -0
- package/src/runtimes/react.tsx +22 -0
- package/src/storage/adapters/indexed-db.ts +180 -0
- package/src/storage/adapters/local-storage.ts +46 -0
- package/src/storage/adapters/memory.ts +35 -0
- package/src/storage/manager.ts +78 -0
- package/src/storage/storage.ts +8 -0
- package/src/support/facades/cache.ts +46 -0
- package/src/support/facades/config.ts +67 -0
- package/src/support/facades/facade.ts +42 -0
- package/src/support/facades/storage.ts +46 -0
- package/src/support/providers/config-service-provider.ts +19 -0
- package/src/support/service-provider.ts +25 -0
- package/src/support/str.ts +126 -0
- package/tests/application.test.ts +236 -0
- package/tests/cache-facade.test.ts +45 -0
- package/tests/cache.test.ts +68 -0
- package/tests/config-clone.test.ts +31 -0
- package/tests/config-env.test.ts +88 -0
- package/tests/config-facade.test.ts +96 -0
- package/tests/config-repository.test.ts +124 -0
- package/tests/facade-base.test.ts +80 -0
- package/tests/filesystem.test.ts +81 -0
- package/tests/runtime-react.test.tsx +37 -0
- package/tests/service-provider.test.ts +23 -0
- package/tests/storage-facade.test.ts +46 -0
- package/tests/storage.test.ts +264 -0
- package/tests/str.test.ts +73 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import ConfigRepository from "../src/config/repository";
|
|
3
|
+
import IndexedDbAdapter from "../src/storage/adapters/indexed-db";
|
|
4
|
+
import LocalStorageAdapter from "../src/storage/adapters/local-storage";
|
|
5
|
+
import MemoryStorageAdapter from "../src/storage/adapters/memory";
|
|
6
|
+
import StorageManager from "../src/storage/manager";
|
|
7
|
+
|
|
8
|
+
class FakeWebStorage implements Storage {
|
|
9
|
+
protected data = new Map<string, string>();
|
|
10
|
+
|
|
11
|
+
get length(): number {
|
|
12
|
+
return this.data.size;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
clear(): void {
|
|
16
|
+
this.data.clear();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getItem(key: string): string | null {
|
|
20
|
+
return this.data.get(key) ?? null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
key(index: number): string | null {
|
|
24
|
+
const keys = Array.from(this.data.keys());
|
|
25
|
+
return keys[index] ?? null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
removeItem(key: string): void {
|
|
29
|
+
this.data.delete(key);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setItem(key: string, value: string): void {
|
|
33
|
+
this.data.set(key, value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type RequestLike<T> = Partial<IDBRequest<T>> & {
|
|
38
|
+
onsuccess: ((this: IDBRequest<T>, ev: Event) => unknown) | null;
|
|
39
|
+
onerror: ((this: IDBRequest<T>, ev: Event) => unknown) | null;
|
|
40
|
+
onupgradeneeded?: ((this: IDBOpenDBRequest, ev: IDBVersionChangeEvent) => unknown) | null;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function makeRequest<T>(resolveValue: () => T, shouldFail = false): IDBRequest<T> {
|
|
44
|
+
const request: RequestLike<T> = {
|
|
45
|
+
onsuccess: null,
|
|
46
|
+
onerror: null,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
queueMicrotask(() => {
|
|
50
|
+
if (shouldFail) {
|
|
51
|
+
request.error = new Error("fail") as unknown as DOMException;
|
|
52
|
+
request.onerror?.call(request as IDBRequest<T>, new Event("error"));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
request.result = resolveValue();
|
|
57
|
+
request.onsuccess?.call(request as IDBRequest<T>, new Event("success"));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return request as IDBRequest<T>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createIndexedDbFactory(options: { failOpen?: boolean; failGet?: boolean } = {}) {
|
|
64
|
+
const items = new Map<string, unknown>();
|
|
65
|
+
let hasStore = false;
|
|
66
|
+
|
|
67
|
+
const store: Partial<IDBObjectStore> = {
|
|
68
|
+
get: (key: IDBValidKey) => makeRequest(() => items.get(String(key)), options.failGet),
|
|
69
|
+
put: (value: unknown, key?: IDBValidKey) =>
|
|
70
|
+
makeRequest(() => {
|
|
71
|
+
items.set(String(key), value);
|
|
72
|
+
return key as IDBValidKey;
|
|
73
|
+
}),
|
|
74
|
+
count: (key?: IDBValidKey | IDBKeyRange) =>
|
|
75
|
+
makeRequest(() => (items.has(String(key)) ? 1 : 0)),
|
|
76
|
+
delete: (key: IDBValidKey | IDBKeyRange) =>
|
|
77
|
+
makeRequest(() => {
|
|
78
|
+
items.delete(String(key));
|
|
79
|
+
return undefined;
|
|
80
|
+
}),
|
|
81
|
+
clear: () =>
|
|
82
|
+
makeRequest(() => {
|
|
83
|
+
items.clear();
|
|
84
|
+
return undefined;
|
|
85
|
+
}),
|
|
86
|
+
getAllKeys: () => makeRequest(() => Array.from(items.keys()) as Array<IDBValidKey>),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const db: Partial<IDBDatabase> = {
|
|
90
|
+
objectStoreNames: {
|
|
91
|
+
contains: (name: string) => hasStore && name === "kv",
|
|
92
|
+
item: () => null,
|
|
93
|
+
length: 0,
|
|
94
|
+
[Symbol.iterator]: function* iterator() {},
|
|
95
|
+
} as DOMStringList,
|
|
96
|
+
createObjectStore: () => {
|
|
97
|
+
hasStore = true;
|
|
98
|
+
return store as IDBObjectStore;
|
|
99
|
+
},
|
|
100
|
+
transaction: () =>
|
|
101
|
+
({
|
|
102
|
+
objectStore: () => store as IDBObjectStore,
|
|
103
|
+
}) as IDBTransaction,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const factory: Pick<IDBFactory, "open"> = {
|
|
107
|
+
open: () => {
|
|
108
|
+
const request: RequestLike<IDBDatabase> = {
|
|
109
|
+
onsuccess: null,
|
|
110
|
+
onerror: null,
|
|
111
|
+
onupgradeneeded: null,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
queueMicrotask(() => {
|
|
115
|
+
if (options.failOpen) {
|
|
116
|
+
request.error = new Error("open failed") as unknown as DOMException;
|
|
117
|
+
request.onerror?.call(request as IDBOpenDBRequest, new Event("error"));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
request.result = db as IDBDatabase;
|
|
122
|
+
request.onupgradeneeded?.call(
|
|
123
|
+
request as IDBOpenDBRequest,
|
|
124
|
+
new Event("upgradeneeded") as IDBVersionChangeEvent,
|
|
125
|
+
);
|
|
126
|
+
request.onsuccess?.call(request as IDBOpenDBRequest, new Event("success"));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return request as IDBOpenDBRequest;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return { factory, items };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
describe("Storage adapters and manager", () => {
|
|
137
|
+
test("memory adapter reads/writes/deletes/clears", async () => {
|
|
138
|
+
const adapter = new MemoryStorageAdapter();
|
|
139
|
+
|
|
140
|
+
await adapter.set("a", 1);
|
|
141
|
+
expect(await adapter.get<number>("a")).toBe(1);
|
|
142
|
+
expect(await adapter.has("a")).toBe(true);
|
|
143
|
+
expect(await adapter.keys()).toEqual(["a"]);
|
|
144
|
+
await adapter.delete("a");
|
|
145
|
+
expect(await adapter.get("a")).toBeNull();
|
|
146
|
+
await adapter.set("b", 2);
|
|
147
|
+
await adapter.clear();
|
|
148
|
+
expect(await adapter.keys()).toEqual([]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("local storage adapter serializes values", async () => {
|
|
152
|
+
const storage = new FakeWebStorage();
|
|
153
|
+
const adapter = new LocalStorageAdapter(storage);
|
|
154
|
+
|
|
155
|
+
await adapter.set("name", { v: "ioc" });
|
|
156
|
+
expect(await adapter.get<{ v: string }>("name")).toEqual({ v: "ioc" });
|
|
157
|
+
expect(await adapter.has("name")).toBe(true);
|
|
158
|
+
expect(await adapter.keys()).toEqual(["name"]);
|
|
159
|
+
await adapter.delete("name");
|
|
160
|
+
expect(await adapter.get("name")).toBeNull();
|
|
161
|
+
await adapter.set("x", 1);
|
|
162
|
+
await adapter.clear();
|
|
163
|
+
expect(await adapter.keys()).toEqual([]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("indexed db adapter uses indexeddb when available", async () => {
|
|
167
|
+
const { factory, items } = createIndexedDbFactory();
|
|
168
|
+
const adapter = new IndexedDbAdapter({ factory, name: "ioc-test" });
|
|
169
|
+
|
|
170
|
+
await adapter.set("k", { n: 1 });
|
|
171
|
+
expect(items.get("k")).toEqual({ n: 1 });
|
|
172
|
+
expect(await adapter.get<{ n: number }>("k")).toEqual({ n: 1 });
|
|
173
|
+
expect(await adapter.has("k")).toBe(true);
|
|
174
|
+
expect(await adapter.keys()).toEqual(["k"]);
|
|
175
|
+
await adapter.delete("k");
|
|
176
|
+
expect(await adapter.get("k")).toBeNull();
|
|
177
|
+
await adapter.set("z", true);
|
|
178
|
+
await adapter.clear();
|
|
179
|
+
expect(await adapter.keys()).toEqual([]);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("indexed db adapter falls back to memory when indexeddb is unavailable or fails", async () => {
|
|
183
|
+
const fallback = new MemoryStorageAdapter();
|
|
184
|
+
const unavailable = new IndexedDbAdapter({ factory: null, fallback });
|
|
185
|
+
|
|
186
|
+
await unavailable.set("a", 1);
|
|
187
|
+
expect(await unavailable.get<number>("a")).toBe(1);
|
|
188
|
+
expect(await unavailable.has("a")).toBe(true);
|
|
189
|
+
expect(await unavailable.keys()).toEqual(["a"]);
|
|
190
|
+
await unavailable.delete("a");
|
|
191
|
+
expect(await unavailable.has("a")).toBe(false);
|
|
192
|
+
await unavailable.set("b", 2);
|
|
193
|
+
await unavailable.clear();
|
|
194
|
+
expect(await unavailable.keys()).toEqual([]);
|
|
195
|
+
|
|
196
|
+
const failedOpenFactory: Pick<IDBFactory, "open"> = {
|
|
197
|
+
open: () => null as unknown as IDBOpenDBRequest,
|
|
198
|
+
};
|
|
199
|
+
const failedOpen = new IndexedDbAdapter({ factory: failedOpenFactory, fallback });
|
|
200
|
+
await failedOpen.set("x", 42);
|
|
201
|
+
expect(await failedOpen.get<number>("x")).toBe(42);
|
|
202
|
+
|
|
203
|
+
const { factory: failOpenFactory } = createIndexedDbFactory({ failOpen: true });
|
|
204
|
+
const failedOpenEvent = new IndexedDbAdapter({ factory: failOpenFactory, fallback });
|
|
205
|
+
expect(await failedOpenEvent.get("none")).toBeNull();
|
|
206
|
+
|
|
207
|
+
const { factory: failGetFactory } = createIndexedDbFactory({ failGet: true });
|
|
208
|
+
const failedGet = new IndexedDbAdapter({ factory: failGetFactory, fallback });
|
|
209
|
+
await failedGet.set("y", 7);
|
|
210
|
+
expect(await failedGet.get<number>("y")).toBeNull();
|
|
211
|
+
|
|
212
|
+
const rejectingFallback = {
|
|
213
|
+
get: async () => null,
|
|
214
|
+
set: async () => {
|
|
215
|
+
throw new Error("set failed");
|
|
216
|
+
},
|
|
217
|
+
has: async () => false,
|
|
218
|
+
delete: async () => {},
|
|
219
|
+
clear: async () => {},
|
|
220
|
+
keys: async () => [],
|
|
221
|
+
};
|
|
222
|
+
const rejected = new IndexedDbAdapter({ factory: null, fallback: rejectingFallback });
|
|
223
|
+
await expect(rejected.set("bad", 1)).rejects.toThrow("set failed");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("storage manager chooses and switches drivers", async () => {
|
|
227
|
+
const memory = new MemoryStorageAdapter();
|
|
228
|
+
const alt = new MemoryStorageAdapter();
|
|
229
|
+
const manager = new StorageManager({ memory, alt }, "memory");
|
|
230
|
+
|
|
231
|
+
await manager.set("k", 1);
|
|
232
|
+
expect(await manager.get("k")).toBe(1);
|
|
233
|
+
manager.use("alt");
|
|
234
|
+
expect(manager.drv()).toBe(alt);
|
|
235
|
+
expect(() => manager.drv("missing")).toThrow("Storage driver [missing] is not defined.");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("storage manager builds defaults from config", () => {
|
|
239
|
+
const originalWindow = (globalThis as { window?: unknown }).window;
|
|
240
|
+
const originalIndexedDb = (globalThis as { indexedDB?: unknown }).indexedDB;
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
(globalThis as { window?: unknown }).window = {
|
|
244
|
+
localStorage: new FakeWebStorage(),
|
|
245
|
+
};
|
|
246
|
+
(globalThis as { indexedDB?: unknown }).indexedDB = createIndexedDbFactory().factory;
|
|
247
|
+
|
|
248
|
+
const manager = StorageManager.fromConfig(
|
|
249
|
+
new ConfigRepository({
|
|
250
|
+
storage: {
|
|
251
|
+
driver: "local",
|
|
252
|
+
},
|
|
253
|
+
}),
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
expect(manager.drv()).toBeTruthy();
|
|
257
|
+
expect(manager.drv("indexed")).toBeTruthy();
|
|
258
|
+
expect(manager.drv("memory")).toBeTruthy();
|
|
259
|
+
} finally {
|
|
260
|
+
(globalThis as { window?: unknown }).window = originalWindow;
|
|
261
|
+
(globalThis as { indexedDB?: unknown }).indexedDB = originalIndexedDb;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import Str, { __strTesting, registerGlobalStr } from "../src/support/str";
|
|
3
|
+
|
|
4
|
+
describe("Str helper", () => {
|
|
5
|
+
const originalGlobalStr = (globalThis as { Str?: unknown }).Str;
|
|
6
|
+
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
(globalThis as { Str?: unknown }).Str = originalGlobalStr;
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("changes string case and length", () => {
|
|
12
|
+
expect(new (Str as unknown as { new (): object })()).toBeTruthy();
|
|
13
|
+
expect(Str.lower("TeSt")).toBe("test");
|
|
14
|
+
expect(Str.upper("TeSt")).toBe("TEST");
|
|
15
|
+
expect(Str.length("abc")).toBe(3);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("checks contains and boundaries", () => {
|
|
19
|
+
expect(Str.contains("Hello World", "World")).toBe(true);
|
|
20
|
+
expect(Str.contains("Hello World", "world")).toBe(false);
|
|
21
|
+
expect(Str.contains("Hello World", "world", true)).toBe(true);
|
|
22
|
+
expect(Str.contains("Hello World", ["x", "y"])).toBe(false);
|
|
23
|
+
|
|
24
|
+
expect(Str.startsWith("framework", "frame")).toBe(true);
|
|
25
|
+
expect(Str.startsWith("framework", ["zzz", "fra"])).toBe(true);
|
|
26
|
+
expect(Str.startsWith("framework", "zzz")).toBe(false);
|
|
27
|
+
|
|
28
|
+
expect(Str.endsWith("framework", "work")).toBe(true);
|
|
29
|
+
expect(Str.endsWith("framework", ["abc", "work"])).toBe(true);
|
|
30
|
+
expect(Str.endsWith("framework", "abc")).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("replaces content", () => {
|
|
34
|
+
expect(Str.replace("laravel", "ioc", "laravel-app")).toBe("ioc-app");
|
|
35
|
+
expect(Str.replace(/-app$/, "", "laravel-app")).toBe("laravel");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("converts string casing styles", () => {
|
|
39
|
+
expect(Str.snake("HelloWorld")).toBe("hello_world");
|
|
40
|
+
expect(Str.snake("hello-world", ".")).toBe("hello.world");
|
|
41
|
+
expect(Str.kebab("HelloWorld")).toBe("hello-world");
|
|
42
|
+
expect(Str.studly("hello_world-test")).toBe("HelloWorldTest");
|
|
43
|
+
expect(Str.camel("hello_world-test")).toBe("helloWorldTest");
|
|
44
|
+
expect(Str.camel("")).toBe("");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("creates slugs", () => {
|
|
48
|
+
expect(Str.slug("Héllo, Wörld!")).toBe("hello-world");
|
|
49
|
+
expect(Str.slug("Héllo, Wörld!", "_")).toBe("hello_world");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("registers global Str without overriding existing value", () => {
|
|
53
|
+
(globalThis as { Str?: unknown }).Str = undefined;
|
|
54
|
+
registerGlobalStr();
|
|
55
|
+
expect((globalThis as { Str?: unknown }).Str).toBe(Str);
|
|
56
|
+
|
|
57
|
+
const custom = class Custom {};
|
|
58
|
+
(globalThis as { Str?: unknown }).Str = custom;
|
|
59
|
+
registerGlobalStr();
|
|
60
|
+
expect((globalThis as { Str?: unknown }).Str).toBe(custom);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("exposes helper internals", () => {
|
|
64
|
+
expect(__strTesting.splitWords("HelloWorld_test-value")).toEqual([
|
|
65
|
+
"Hello",
|
|
66
|
+
"World",
|
|
67
|
+
"test",
|
|
68
|
+
"value",
|
|
69
|
+
]);
|
|
70
|
+
expect(__strTesting.splitWords("")).toEqual([]);
|
|
71
|
+
expect(__strTesting.normalizeForSlug("Héllo Wörld")).toBe("hello-world");
|
|
72
|
+
});
|
|
73
|
+
});
|