@orkify/cli 1.0.0-beta.5 → 1.0.0-beta.6

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 (90) hide show
  1. package/README.md +10 -5
  2. package/package.json +8 -31
  3. package/packages/cache/README.md +0 -114
  4. package/packages/cache/dist/CacheClient.d.ts +0 -26
  5. package/packages/cache/dist/CacheClient.d.ts.map +0 -1
  6. package/packages/cache/dist/CacheClient.js +0 -174
  7. package/packages/cache/dist/CacheClient.js.map +0 -1
  8. package/packages/cache/dist/CacheFileStore.d.ts +0 -45
  9. package/packages/cache/dist/CacheFileStore.d.ts.map +0 -1
  10. package/packages/cache/dist/CacheFileStore.js +0 -446
  11. package/packages/cache/dist/CacheFileStore.js.map +0 -1
  12. package/packages/cache/dist/CachePersistence.d.ts +0 -9
  13. package/packages/cache/dist/CachePersistence.d.ts.map +0 -1
  14. package/packages/cache/dist/CachePersistence.js +0 -67
  15. package/packages/cache/dist/CachePersistence.js.map +0 -1
  16. package/packages/cache/dist/CachePrimary.d.ts +0 -25
  17. package/packages/cache/dist/CachePrimary.d.ts.map +0 -1
  18. package/packages/cache/dist/CachePrimary.js +0 -155
  19. package/packages/cache/dist/CachePrimary.js.map +0 -1
  20. package/packages/cache/dist/CacheStore.d.ts +0 -50
  21. package/packages/cache/dist/CacheStore.d.ts.map +0 -1
  22. package/packages/cache/dist/CacheStore.js +0 -271
  23. package/packages/cache/dist/CacheStore.js.map +0 -1
  24. package/packages/cache/dist/constants.d.ts +0 -6
  25. package/packages/cache/dist/constants.d.ts.map +0 -1
  26. package/packages/cache/dist/constants.js +0 -9
  27. package/packages/cache/dist/constants.js.map +0 -1
  28. package/packages/cache/dist/index.d.ts +0 -16
  29. package/packages/cache/dist/index.d.ts.map +0 -1
  30. package/packages/cache/dist/index.js +0 -86
  31. package/packages/cache/dist/index.js.map +0 -1
  32. package/packages/cache/dist/serialize.d.ts +0 -9
  33. package/packages/cache/dist/serialize.d.ts.map +0 -1
  34. package/packages/cache/dist/serialize.js +0 -40
  35. package/packages/cache/dist/serialize.js.map +0 -1
  36. package/packages/cache/dist/types.d.ts +0 -123
  37. package/packages/cache/dist/types.d.ts.map +0 -1
  38. package/packages/cache/dist/types.js +0 -2
  39. package/packages/cache/dist/types.js.map +0 -1
  40. package/packages/cache/package.json +0 -27
  41. package/packages/cache/src/CacheClient.ts +0 -227
  42. package/packages/cache/src/CacheFileStore.ts +0 -528
  43. package/packages/cache/src/CachePersistence.ts +0 -89
  44. package/packages/cache/src/CachePrimary.ts +0 -172
  45. package/packages/cache/src/CacheStore.ts +0 -308
  46. package/packages/cache/src/constants.ts +0 -10
  47. package/packages/cache/src/index.ts +0 -100
  48. package/packages/cache/src/serialize.ts +0 -49
  49. package/packages/cache/src/types.ts +0 -156
  50. package/packages/cache/tsconfig.json +0 -18
  51. package/packages/cache/tsconfig.tsbuildinfo +0 -1
  52. package/packages/next/README.md +0 -166
  53. package/packages/next/dist/error-capture.d.ts +0 -34
  54. package/packages/next/dist/error-capture.d.ts.map +0 -1
  55. package/packages/next/dist/error-capture.js +0 -130
  56. package/packages/next/dist/error-capture.js.map +0 -1
  57. package/packages/next/dist/error-handler.d.ts +0 -10
  58. package/packages/next/dist/error-handler.d.ts.map +0 -1
  59. package/packages/next/dist/error-handler.js +0 -186
  60. package/packages/next/dist/error-handler.js.map +0 -1
  61. package/packages/next/dist/isr-cache.d.ts +0 -9
  62. package/packages/next/dist/isr-cache.d.ts.map +0 -1
  63. package/packages/next/dist/isr-cache.js +0 -86
  64. package/packages/next/dist/isr-cache.js.map +0 -1
  65. package/packages/next/dist/stream.d.ts +0 -5
  66. package/packages/next/dist/stream.d.ts.map +0 -1
  67. package/packages/next/dist/stream.js +0 -22
  68. package/packages/next/dist/stream.js.map +0 -1
  69. package/packages/next/dist/types.d.ts +0 -33
  70. package/packages/next/dist/types.d.ts.map +0 -1
  71. package/packages/next/dist/types.js +0 -6
  72. package/packages/next/dist/types.js.map +0 -1
  73. package/packages/next/dist/use-cache.d.ts +0 -4
  74. package/packages/next/dist/use-cache.d.ts.map +0 -1
  75. package/packages/next/dist/use-cache.js +0 -86
  76. package/packages/next/dist/use-cache.js.map +0 -1
  77. package/packages/next/dist/utils.d.ts +0 -32
  78. package/packages/next/dist/utils.d.ts.map +0 -1
  79. package/packages/next/dist/utils.js +0 -88
  80. package/packages/next/dist/utils.js.map +0 -1
  81. package/packages/next/package.json +0 -52
  82. package/packages/next/src/error-capture.ts +0 -177
  83. package/packages/next/src/error-handler.ts +0 -221
  84. package/packages/next/src/isr-cache.ts +0 -100
  85. package/packages/next/src/stream.ts +0 -23
  86. package/packages/next/src/types.ts +0 -33
  87. package/packages/next/src/use-cache.ts +0 -99
  88. package/packages/next/src/utils.ts +0 -102
  89. package/packages/next/tsconfig.json +0 -19
  90. package/packages/next/tsconfig.tsbuildinfo +0 -1
@@ -1,172 +0,0 @@
1
- import type { Worker } from 'node:cluster';
2
- import type { CacheConfig, CacheWorkerMessage, ICacheStore } from './types.js';
3
- import { CacheFileStore } from './CacheFileStore.js';
4
- import { CachePersistence } from './CachePersistence.js';
5
- import { CacheStore } from './CacheStore.js';
6
-
7
- interface WorkerState {
8
- worker: Worker;
9
- }
10
-
11
- export class CachePrimary {
12
- private fileBacked: boolean;
13
- private persistence: CachePersistence;
14
- private processName: string;
15
- private store: ICacheStore;
16
-
17
- constructor(processName: string, config?: CacheConfig) {
18
- this.processName = processName;
19
- this.fileBacked = config?.fileBacked === true;
20
- this.store = this.fileBacked ? new CacheFileStore(processName, config) : new CacheStore(config);
21
- this.persistence = new CachePersistence(processName);
22
- }
23
-
24
- handleMessage(
25
- _worker: Worker,
26
- msg: CacheWorkerMessage,
27
- allWorkers: Map<number, WorkerState>
28
- ): void {
29
- switch (msg.type) {
30
- case 'cache:set': {
31
- const expiresAt = msg.ttl ? Date.now() + msg.ttl * 1000 : undefined;
32
- this.store.set(msg.key, msg.value, expiresAt, msg.tags);
33
- // Broadcast to ALL workers (including sender) for consistency
34
- for (const [, state] of allWorkers) {
35
- if (state.worker.isConnected()) {
36
- state.worker.send({
37
- __orkify: true,
38
- type: 'cache:set',
39
- key: msg.key,
40
- value: msg.value,
41
- expiresAt,
42
- tags: msg.tags,
43
- });
44
- }
45
- }
46
- break;
47
- }
48
- case 'cache:delete': {
49
- this.store.delete(msg.key);
50
- for (const [, state] of allWorkers) {
51
- if (state.worker.isConnected()) {
52
- state.worker.send({
53
- __orkify: true,
54
- type: 'cache:delete',
55
- key: msg.key,
56
- });
57
- }
58
- }
59
- break;
60
- }
61
- case 'cache:clear': {
62
- this.store.clear();
63
- for (const [, state] of allWorkers) {
64
- if (state.worker.isConnected()) {
65
- state.worker.send({ __orkify: true, type: 'cache:clear' });
66
- }
67
- }
68
- break;
69
- }
70
- case 'cache:invalidate-tag': {
71
- this.store.invalidateTag(msg.tag);
72
- const tagTimestamp = this.store.getTagExpiration([msg.tag]);
73
- for (const [, state] of allWorkers) {
74
- if (state.worker.isConnected()) {
75
- state.worker.send({
76
- __orkify: true,
77
- type: 'cache:invalidate-tag',
78
- tag: msg.tag,
79
- tagTimestamp,
80
- });
81
- }
82
- }
83
- break;
84
- }
85
- case 'cache:update-tag-timestamp': {
86
- this.store.applyTagTimestamp(msg.tag, msg.tagTimestamp);
87
- for (const [, state] of allWorkers) {
88
- if (state.worker.isConnected()) {
89
- state.worker.send({
90
- __orkify: true,
91
- type: 'cache:update-tag-timestamp',
92
- tag: msg.tag,
93
- tagTimestamp: msg.tagTimestamp,
94
- });
95
- }
96
- }
97
- break;
98
- }
99
- case 'cache:configure': {
100
- this.applyConfig(msg.config);
101
- break;
102
- }
103
- }
104
- }
105
-
106
- /**
107
- * Upgrade from CacheStore to CacheFileStore when a worker reports fileBacked config.
108
- * Migrates existing in-memory entries and loads the disk index from any previous session.
109
- */
110
- private applyConfig(config: CacheConfig): void {
111
- if (!config.fileBacked || this.fileBacked) return;
112
-
113
- const snapshot = this.store.serialize();
114
- const newStore = new CacheFileStore(this.processName, config);
115
- this.store.destroy();
116
- this.store = newStore;
117
-
118
- // Migrate existing in-memory entries (from CachePersistence restore or earlier sets)
119
- for (const [key, entry] of snapshot.entries) {
120
- this.store.set(key, entry.value, entry.expiresAt, entry.tags);
121
- }
122
- for (const [tag, ts] of snapshot.tagTimestamps) {
123
- this.store.applyTagTimestamp(tag, ts);
124
- }
125
- this.fileBacked = true;
126
-
127
- // Load disk index from previous file-backed sessions (async, entries promoted lazily)
128
- void (this.store as CacheFileStore).loadIndex();
129
- }
130
-
131
- sendSnapshot(worker: Worker): void {
132
- const snapshot = this.store.serialize();
133
- if (snapshot.entries.length === 0 && snapshot.tagTimestamps.length === 0) return;
134
- worker.send({
135
- __orkify: true,
136
- type: 'cache:snapshot',
137
- entries: snapshot.entries,
138
- tagTimestamps: snapshot.tagTimestamps,
139
- });
140
- }
141
-
142
- async persist(): Promise<void> {
143
- if (this.fileBacked) {
144
- await (this.store as CacheFileStore).flush();
145
- // Clear CachePersistence so stale snapshot data isn't loaded on next restart
146
- await this.persistence.clear();
147
- } else {
148
- const snapshot = this.store.serialize();
149
- await this.persistence.save(snapshot);
150
- }
151
- }
152
-
153
- async restore(): Promise<void> {
154
- if (this.fileBacked) {
155
- await (this.store as CacheFileStore).loadIndex();
156
- } else {
157
- const snapshot = await this.persistence.load();
158
- if (snapshot.entries.length > 0 || snapshot.tagTimestamps.length > 0) {
159
- this.store.applySnapshot(snapshot);
160
- }
161
- }
162
- }
163
-
164
- destroy(): void {
165
- this.store.destroy();
166
- }
167
-
168
- async shutdown(): Promise<void> {
169
- await this.persist();
170
- this.store.destroy();
171
- }
172
- }
@@ -1,308 +0,0 @@
1
- import type {
2
- CacheConfig,
3
- CacheEntry,
4
- CacheSnapshot,
5
- CacheStats,
6
- EvictReason,
7
- ICacheStore,
8
- SerializedCacheEntry,
9
- } from './types.js';
10
- import {
11
- CACHE_CLEANUP_INTERVAL,
12
- CACHE_DEFAULT_MAX_ENTRIES,
13
- CACHE_DEFAULT_MAX_MEMORY_SIZE,
14
- } from './constants.js';
15
- import { serialize, serializedByteLength } from './serialize.js';
16
-
17
- export type OnEvictCallback = (key: string, entry: CacheEntry, reason: EvictReason) => void;
18
-
19
- export class CacheStore implements ICacheStore {
20
- private entries = new Map<string, CacheEntry>();
21
- private hits = 0;
22
- private maxEntries: number;
23
- private maxMemorySize: number;
24
- private misses = 0;
25
- private onEvict: OnEvictCallback | undefined;
26
- private sweepTimer: ReturnType<typeof setInterval> | undefined;
27
- private tagIndex = new Map<string, Set<string>>(); // tag → keys
28
- private tagTimestamps = new Map<string, number>(); // tag → epoch ms of last invalidation
29
- totalBytes = 0;
30
-
31
- constructor(config?: CacheConfig, onEvict?: OnEvictCallback) {
32
- this.maxEntries = config?.maxEntries ?? CACHE_DEFAULT_MAX_ENTRIES;
33
- this.maxMemorySize = config?.maxMemorySize ?? CACHE_DEFAULT_MAX_MEMORY_SIZE;
34
- this.onEvict = onEvict;
35
-
36
- this.sweepTimer = setInterval(() => this.sweep(), CACHE_CLEANUP_INTERVAL);
37
- this.sweepTimer.unref();
38
- }
39
-
40
- get<T>(key: string): T | undefined {
41
- const entry = this.entries.get(key);
42
- if (!entry) {
43
- this.misses++;
44
- return undefined;
45
- }
46
- if (entry.expiresAt !== undefined && entry.expiresAt < Date.now()) {
47
- this.removeEntry(key, entry);
48
- this.misses++;
49
- return undefined;
50
- }
51
- entry.lastAccessedAt = Date.now();
52
- this.hits++;
53
- return entry.value as T;
54
- }
55
-
56
- set(
57
- key: string,
58
- value: unknown,
59
- expiresAt?: number,
60
- tags?: string[],
61
- precomputedByteSize?: number
62
- ): void {
63
- const byteSize = precomputedByteSize ?? serializedByteLength(serialize(value));
64
- const existing = this.entries.get(key);
65
- if (existing) {
66
- // Remove old tag associations and byte count before overwriting
67
- this.removeFromTagIndex(key, existing.tags);
68
- this.totalBytes -= existing.byteSize;
69
- }
70
-
71
- // Evict until we're under both limits (entry count for new keys, byte limit always)
72
- while (this.overLimit(existing ? 0 : 1, byteSize)) {
73
- if (!this.evictLru()) break; // nothing left to evict
74
- }
75
-
76
- this.entries.set(key, {
77
- byteSize,
78
- value,
79
- expiresAt,
80
- lastAccessedAt: Date.now(),
81
- tags,
82
- });
83
- this.totalBytes += byteSize;
84
- if (tags) {
85
- this.addToTagIndex(key, tags);
86
- }
87
- }
88
-
89
- delete(key: string): boolean {
90
- const entry = this.entries.get(key);
91
- if (!entry) return false;
92
- this.removeFromTagIndex(key, entry.tags);
93
- this.totalBytes -= entry.byteSize;
94
- this.entries.delete(key);
95
- return true;
96
- }
97
-
98
- clear(): void {
99
- this.entries.clear();
100
- this.tagIndex.clear();
101
- this.tagTimestamps.clear();
102
- this.totalBytes = 0;
103
- }
104
-
105
- has(key: string): boolean {
106
- const entry = this.entries.get(key);
107
- if (!entry) return false;
108
- if (entry.expiresAt !== undefined && entry.expiresAt < Date.now()) {
109
- this.removeEntry(key, entry);
110
- return false;
111
- }
112
- return true;
113
- }
114
-
115
- stats(): CacheStats {
116
- const total = this.hits + this.misses;
117
- return {
118
- size: this.entries.size,
119
- hits: this.hits,
120
- misses: this.misses,
121
- hitRate: total === 0 ? 0 : this.hits / total,
122
- totalBytes: this.totalBytes,
123
- };
124
- }
125
-
126
- /** Invalidate all entries with the given tag. Returns deleted keys. */
127
- invalidateTag(tag: string): string[] {
128
- this.tagTimestamps.set(tag, Date.now());
129
-
130
- const keys = this.tagIndex.get(tag);
131
- if (!keys || keys.size === 0) return [];
132
-
133
- const deleted: string[] = [];
134
- for (const key of keys) {
135
- const entry = this.entries.get(key);
136
- if (entry) {
137
- // Remove this key from all its other tag sets
138
- this.removeFromTagIndex(key, entry.tags, tag);
139
- this.totalBytes -= entry.byteSize;
140
- this.entries.delete(key);
141
- deleted.push(key);
142
- }
143
- }
144
- // Remove the tag itself from the index
145
- this.tagIndex.delete(tag);
146
- return deleted;
147
- }
148
-
149
- /** Returns the most recent invalidation timestamp across the given tags (0 if none). */
150
- getTagExpiration(tags: string[]): number {
151
- let max = 0;
152
- for (const tag of tags) {
153
- const ts = this.tagTimestamps.get(tag);
154
- if (ts !== undefined && ts > max) {
155
- max = ts;
156
- }
157
- }
158
- return max;
159
- }
160
-
161
- /** Set a tag invalidation timestamp without deleting entries (for IPC replay). */
162
- applyTagTimestamp(tag: string, timestamp: number): void {
163
- this.tagTimestamps.set(tag, timestamp);
164
- }
165
-
166
- async getAsync<T>(key: string): Promise<T | undefined> {
167
- return this.get<T>(key);
168
- }
169
-
170
- /** Apply a set from IPC — no broadcast triggered */
171
- applySet(key: string, value: unknown, expiresAt?: number, tags?: string[]): void {
172
- this.set(key, value, expiresAt, tags);
173
- }
174
-
175
- /** Apply a delete from IPC — no broadcast triggered */
176
- applyDelete(key: string): void {
177
- this.delete(key);
178
- }
179
-
180
- /** Apply a full snapshot from primary — replaces all entries and tag timestamps */
181
- applySnapshot(snapshot: CacheSnapshot): void {
182
- this.entries.clear();
183
- this.tagIndex.clear();
184
- this.tagTimestamps.clear();
185
- this.totalBytes = 0;
186
- const now = Date.now();
187
- for (const [key, serialized] of snapshot.entries) {
188
- // Skip expired entries
189
- if (serialized.expiresAt !== undefined && serialized.expiresAt < now) continue;
190
- const tags = serialized.tags;
191
- const byteSize = serializedByteLength(serialize(serialized.value));
192
- this.entries.set(key, {
193
- byteSize,
194
- value: serialized.value,
195
- expiresAt: serialized.expiresAt,
196
- lastAccessedAt: now,
197
- tags,
198
- });
199
- this.totalBytes += byteSize;
200
- if (tags) {
201
- this.addToTagIndex(key, tags);
202
- }
203
- }
204
- for (const [tag, ts] of snapshot.tagTimestamps) {
205
- this.tagTimestamps.set(tag, ts);
206
- }
207
- }
208
-
209
- /** Export for snapshots and persistence */
210
- serialize(): CacheSnapshot {
211
- const now = Date.now();
212
- const entries: Array<[string, SerializedCacheEntry]> = [];
213
- for (const [key, entry] of this.entries) {
214
- // Skip expired entries
215
- if (entry.expiresAt !== undefined && entry.expiresAt < now) continue;
216
- const serialized: SerializedCacheEntry = { value: entry.value, expiresAt: entry.expiresAt };
217
- if (entry.tags && entry.tags.length > 0) {
218
- serialized.tags = entry.tags;
219
- }
220
- entries.push([key, serialized]);
221
- }
222
- return { entries, tagTimestamps: [...this.tagTimestamps] };
223
- }
224
-
225
- destroy(): void {
226
- if (this.sweepTimer) {
227
- clearInterval(this.sweepTimer);
228
- this.sweepTimer = undefined;
229
- }
230
- this.entries.clear();
231
- this.tagIndex.clear();
232
- this.tagTimestamps.clear();
233
- this.totalBytes = 0;
234
- }
235
-
236
- /** Remove all expired entries */
237
- private sweep(): void {
238
- const now = Date.now();
239
- for (const [key, entry] of this.entries) {
240
- if (entry.expiresAt !== undefined && entry.expiresAt < now) {
241
- this.onEvict?.(key, entry, 'expired');
242
- this.removeEntry(key, entry);
243
- }
244
- }
245
- }
246
-
247
- /** Check if adding pendingEntries + pendingBytes would exceed limits */
248
- private overLimit(pendingEntries: number, pendingBytes: number): boolean {
249
- if (this.entries.size + pendingEntries > this.maxEntries) return true;
250
- if (this.totalBytes + pendingBytes > this.maxMemorySize) return true;
251
- return false;
252
- }
253
-
254
- /** Evict the entry with the oldest lastAccessedAt. Returns false if nothing to evict. */
255
- private evictLru(): boolean {
256
- let oldestKey: string | undefined;
257
- let oldestTime = Infinity;
258
- for (const [key, entry] of this.entries) {
259
- if (entry.lastAccessedAt < oldestTime) {
260
- oldestTime = entry.lastAccessedAt;
261
- oldestKey = key;
262
- }
263
- }
264
- if (oldestKey === undefined) return false;
265
- const entry = this.entries.get(oldestKey);
266
- if (entry) {
267
- this.onEvict?.(oldestKey, entry, 'lru');
268
- this.removeFromTagIndex(oldestKey, entry.tags);
269
- this.totalBytes -= entry.byteSize;
270
- }
271
- this.entries.delete(oldestKey);
272
- return true;
273
- }
274
-
275
- /** Remove a key from the entries map and clean up its tag index entries */
276
- private removeEntry(key: string, entry: CacheEntry): void {
277
- this.removeFromTagIndex(key, entry.tags);
278
- this.totalBytes -= entry.byteSize;
279
- this.entries.delete(key);
280
- }
281
-
282
- /** Add a key to the tag index for each of its tags */
283
- private addToTagIndex(key: string, tags: string[]): void {
284
- for (const tag of tags) {
285
- let keys = this.tagIndex.get(tag);
286
- if (!keys) {
287
- keys = new Set();
288
- this.tagIndex.set(tag, keys);
289
- }
290
- keys.add(key);
291
- }
292
- }
293
-
294
- /** Remove a key from the tag index. Optionally skip a specific tag (already being deleted). */
295
- private removeFromTagIndex(key: string, tags?: string[], skipTag?: string): void {
296
- if (!tags) return;
297
- for (const tag of tags) {
298
- if (tag === skipTag) continue;
299
- const keys = this.tagIndex.get(tag);
300
- if (keys) {
301
- keys.delete(key);
302
- if (keys.size === 0) {
303
- this.tagIndex.delete(tag);
304
- }
305
- }
306
- }
307
- }
308
- }
@@ -1,10 +0,0 @@
1
- import { homedir } from 'node:os';
2
- import { join } from 'node:path';
3
-
4
- const ORKIFY_HOME = join(homedir(), '.orkify');
5
-
6
- export const CACHE_DIR = join(ORKIFY_HOME, 'cache');
7
- export const CACHE_DEFAULT_MAX_ENTRIES = 10_000;
8
- export const CACHE_DEFAULT_MAX_MEMORY_SIZE = 64 * 1024 * 1024; // 64 MB
9
- export const CACHE_DEFAULT_MAX_VALUE_SIZE = 1024 * 1024; // 1 MB
10
- export const CACHE_CLEANUP_INTERVAL = 60_000; // 60s
@@ -1,100 +0,0 @@
1
- import type { CacheConfig } from './types.js';
2
- import { CacheClient } from './CacheClient.js';
3
-
4
- let instance: CacheClient | undefined;
5
- let pendingConfig: CacheConfig | undefined;
6
-
7
- // In cluster mode, IPC cache messages (including snapshots) can arrive before
8
- // this module loads. The metrics probe registers a global buffer on
9
- // `globalThis.__orkifyCacheBuffer` synchronously before any `await`, so those
10
- // early messages are captured even when this module loads late.
11
- //
12
- // Messages that arrive *after* this module loads but *before* the proxy is first
13
- // accessed are captured by the local early listener below. Both sources are
14
- // merged and drained when the CacheClient is created.
15
- const g = globalThis as Record<string, unknown>;
16
- const probeBuffer: unknown[] = Array.isArray(g.__orkifyCacheBuffer)
17
- ? (g.__orkifyCacheBuffer as unknown[])
18
- : [];
19
-
20
- const localBuffer: unknown[] = [];
21
- let earlyListener: ((msg: unknown) => void) | undefined;
22
-
23
- if (process.env.ORKIFY_CLUSTER_MODE === 'true' && typeof process.send === 'function') {
24
- earlyListener = (msg: unknown) => {
25
- const m = msg as { __orkify?: boolean; type?: string };
26
- if (m?.__orkify && m.type?.startsWith('cache:')) {
27
- localBuffer.push(msg);
28
- }
29
- };
30
- process.on('message', earlyListener);
31
- }
32
-
33
- function createInstance(): void {
34
- // Stop the local early listener — CacheClient registers its own
35
- if (earlyListener) {
36
- process.removeListener('message', earlyListener);
37
- earlyListener = undefined;
38
- }
39
-
40
- // Stop the probe's global buffer listener
41
- if (typeof g.__orkifyCacheBufferCleanup === 'function') {
42
- (g.__orkifyCacheBufferCleanup as () => void)();
43
- delete g.__orkifyCacheBufferCleanup;
44
- delete g.__orkifyCacheBuffer;
45
- }
46
-
47
- // Merge probe buffer + local buffer — probe messages arrived first
48
- const merged = [...probeBuffer, ...localBuffer];
49
- probeBuffer.length = 0;
50
- localBuffer.length = 0;
51
-
52
- // Default to fileBacked: true unless explicitly disabled
53
- const config: CacheConfig | undefined =
54
- pendingConfig?.fileBacked === false ? pendingConfig : { fileBacked: true, ...pendingConfig };
55
-
56
- instance = new CacheClient(config, merged);
57
- const client = instance;
58
- (g as Record<string, unknown>).__orkifyCacheStats = () => client.stats();
59
- }
60
-
61
- /**
62
- * @deprecated Use `cache.configure()` instead. Will be removed in a future version.
63
- */
64
- export function configure(config: CacheConfig): void {
65
- if (instance) {
66
- throw new Error('orkify/cache: configure() must be called before the first use of cache');
67
- }
68
- pendingConfig = config;
69
- }
70
-
71
- /**
72
- * Shared cache singleton. Reads are always synchronous local Map lookups.
73
- * In cluster mode, writes broadcast via IPC so all workers converge.
74
- * In standalone/fork mode, behaves as a local in-memory cache.
75
- *
76
- * Call `cache.configure()` before any other method to set options.
77
- */
78
- export const cache: CacheClient = new Proxy({} as CacheClient, {
79
- get(_target, prop, receiver) {
80
- // configure() must run before the instance is created
81
- if (prop === 'configure') {
82
- return (config: CacheConfig) => {
83
- if (instance) {
84
- throw new Error('orkify/cache: configure() must be called before the first use of cache');
85
- }
86
- pendingConfig = config;
87
- };
88
- }
89
-
90
- if (!instance) createInstance();
91
- const inst = instance as CacheClient;
92
- const value = Reflect.get(inst, prop, receiver);
93
- if (typeof value === 'function') {
94
- return value.bind(inst);
95
- }
96
- return value;
97
- },
98
- });
99
-
100
- export type { CacheConfig, CacheSetOptions, CacheStats } from './types.js';
@@ -1,49 +0,0 @@
1
- import v8 from 'node:v8';
2
-
3
- export type Encoding = 'json' | 'v8';
4
-
5
- export interface Serialized {
6
- data: string;
7
- encoding: Encoding;
8
- }
9
-
10
- function needsV8(value: unknown): boolean {
11
- if (
12
- value instanceof Map ||
13
- value instanceof Set ||
14
- value instanceof Date ||
15
- value instanceof RegExp ||
16
- value instanceof Error
17
- )
18
- return true;
19
- if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) return true;
20
- if (Array.isArray(value)) return value.some(needsV8);
21
- if (value !== null && typeof value === 'object') {
22
- return Object.values(value).some(needsV8);
23
- }
24
- return false;
25
- }
26
-
27
- export function serialize(value: unknown): Serialized {
28
- if (typeof value === 'function') {
29
- throw new Error('cache: functions are not serializable');
30
- }
31
- if (typeof value === 'symbol') {
32
- throw new Error('cache: symbols are not serializable');
33
- }
34
- if (needsV8(value)) {
35
- return { data: v8.serialize(value).toString('base64'), encoding: 'v8' };
36
- }
37
- return { data: JSON.stringify(value), encoding: 'json' };
38
- }
39
-
40
- export function deserialize({ data, encoding }: Serialized): unknown {
41
- if (encoding === 'v8') return v8.deserialize(Buffer.from(data, 'base64'));
42
- return JSON.parse(data);
43
- }
44
-
45
- export function serializedByteLength(s: Serialized): number {
46
- return s.encoding === 'v8'
47
- ? Math.ceil((s.data.length * 3) / 4) // base64 → raw bytes
48
- : Buffer.byteLength(s.data, 'utf-8');
49
- }