Skip to content
20 changes: 20 additions & 0 deletions src/codecs/array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { ArgCodec } from '../types.js';

export const arrayCodec: ArgCodec<unknown[]> = {
tag: Symbol.for('sr:a'),
is: (v): v is unknown[] => Array.isArray(v),
encode: (v, recurse) => v.map(recurse),
decode: (data, recurse) => (data as unknown[]).map(recurse),
writeBack: (original, mutated, reconcile) => {
const minLen = Math.min(original.length, mutated.length);

for (let i = 0; i < minLen; i++) {
if (!reconcile(original[i], mutated[i])) original[i] = mutated[i];
}

if (original.length > mutated.length) original.splice(mutated.length);

for (let i = original.length; i < mutated.length; i++)
original.push(mutated[i]);
},
};
8 changes: 8 additions & 0 deletions src/codecs/bigint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ArgCodec } from '../types.js';

export const bigIntCodec: ArgCodec<bigint> = {
tag: Symbol.for('sr:bi'),
is: (v): v is bigint => typeof v === 'bigint',
encode: (v) => v.toString(),
decode: (data) => BigInt(data as string),
};
11 changes: 11 additions & 0 deletions src/codecs/date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { ArgCodec } from '../types.js';

export const dateCodec: ArgCodec<Date> = {
tag: Symbol.for('sr:d'),
is: (v): v is Date => v instanceof Date,
encode: (v) => v.toISOString(),
decode: (data) => new Date(data as string),
writeBack: (original, mutated) => {
original.setTime(mutated.getTime());
},
};
18 changes: 18 additions & 0 deletions src/codecs/map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { ArgCodec } from '../types.js';

export const mapCodec: ArgCodec<Map<unknown, unknown>> = {
tag: Symbol.for('sr:m'),
is: (v): v is Map<unknown, unknown> => v instanceof Map,
encode: (v, recurse) =>
Array.from(v.entries(), (e) => [recurse(e[0]), recurse(e[1])]),
decode: (data, recurse) => {
const entries = data as [unknown, unknown][];
return new Map(
entries.map((e) => [recurse(e[0]), recurse(e[1])] as [unknown, unknown])
);
},
writeBack: (original, mutated) => {
original.clear();
for (const [k, v] of mutated) original.set(k, v);
},
};
37 changes: 37 additions & 0 deletions src/codecs/object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { ArgCodec } from '../types.js';

export const isPlainObject = (v: unknown): v is Record<string, unknown> => {
if (v === null || typeof v !== 'object' || Array.isArray(v)) return false;
const proto = Object.getPrototypeOf(v) as unknown;
return proto === Object.prototype || proto === null;
};

export const objectCodec: ArgCodec<Record<string, unknown>> = {
tag: Symbol.for('sr:obj'),
is: isPlainObject,
// No collision-escape needed: the outer { __sr_enc: 'c', t: 'obj', v: ... }
// sentinel wraps the encoded values, so inner keys are iterated individually
// by decode — any __sr_enc key in the original object is just a normal value.
encode: (v, recurse) => {
const result: Record<string, unknown> = {};
for (const key of Object.keys(v)) {
if (typeof v[key] !== 'function') result[key] = recurse(v[key]);
}
return result;
},
decode: (data, recurse) => {
const obj = data as Record<string, unknown>;
const result: Record<string, unknown> = {};
for (const key of Object.keys(obj)) result[key] = recurse(obj[key]);
return result;
},
writeBack: (original, mutated, reconcile) => {
for (const key of Object.keys(original)) {
if (!(key in mutated) && typeof original[key] !== 'function')
delete original[key];
}
for (const key of Object.keys(mutated)) {
if (!reconcile(original[key], mutated[key])) original[key] = mutated[key];
}
},
};
12 changes: 12 additions & 0 deletions src/codecs/set.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { ArgCodec } from '../types.js';

export const setCodec: ArgCodec<Set<unknown>> = {
tag: Symbol.for('sr:s'),
is: (v): v is Set<unknown> => v instanceof Set,
encode: (v, recurse) => Array.from(v, recurse),
decode: (data, recurse) => new Set((data as unknown[]).map(recurse)),
writeBack: (original, mutated) => {
original.clear();
for (const v of mutated) original.add(v);
},
};
8 changes: 8 additions & 0 deletions src/codecs/undefined.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ArgCodec } from '../types.js';

export const undefinedCodec: ArgCodec<undefined> = {
tag: Symbol.for('sr:u'),
is: (v): v is undefined => v === undefined,
encode: () => null,
decode: () => undefined,
};
36 changes: 23 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import type { PokuPlugin } from 'poku/plugins';
import { globalRegistry, setupSharedResourceIPC } from './shared-resources.js';
import type { SharedResourcesConfig } from './types.js';
import {
configureCodecs,
globalRegistry,
setupSharedResourceIPC,
} from './shared-resources.js';

export const sharedResources = (): PokuPlugin => ({
name: 'shared-resources',
ipc: true,
onTestProcess(child) {
setupSharedResourceIPC(child);
},
async teardown() {
const entries = Object.values(globalRegistry);
export const sharedResources = (config?: SharedResourcesConfig): PokuPlugin => {
if (config?.codecs && config.codecs.length > 0)
configureCodecs(config.codecs);
return {
name: 'shared-resources',
ipc: true,
onTestProcess(child) {
setupSharedResourceIPC(child);
},
async teardown() {
const entries = Object.values(globalRegistry);

for (const entry of entries)
if (entry.onDestroy) await entry.onDestroy(entry.state);
},
});
for (const entry of entries)
if (entry.onDestroy) await entry.onDestroy(entry.state);
},
};
};

export { resource } from './shared-resources.js';
export type { ArgCodec, SharedResourcesConfig } from './types.js';
Loading
Loading