Skip to content

Commit

Permalink
[pkg/inline-mod]: Implements new lazyValue utility
Browse files Browse the repository at this point in the history
  • Loading branch information
Fryuni committed Apr 13, 2024
1 parent c55433b commit 721ab06
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 88 deletions.
6 changes: 6 additions & 0 deletions .changeset/wicked-fireants-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@inox-tools/inline-mod": minor
"@inox-tools/aik-mod": patch
---

Implements new `lazyValue` utility
8 changes: 7 additions & 1 deletion packages/aik-mod/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import { definePlugin } from 'astro-integration-kit';
import { AstroError } from 'astro/errors';
import type { PluginOption } from 'vite';

export { asyncFactory, factory } from '@inox-tools/inline-mod';
export {
asyncFactory,
factory,
lazyValue,
type LazyValue,
type ResolvedLazyValue,
} from '@inox-tools/inline-mod';

type HookParams = HookParameters<'astro:config:setup'>;

Expand Down
56 changes: 56 additions & 0 deletions packages/inline-mod/src/closure/inspectCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,14 @@ class Inspector {
};
}

const lazyValue = tryExtractLazyValue(value);
if (lazyValue) {
console.log('Detected lazy value');
const resolvedValue = await lazyValue;
console.log('Resolved lazy value', resolvedValue);
return this.inspect(resolvedValue);
}

if (this.doNotCapture(value)) {
log('Value should skip capture');
return Entry.json();
Expand Down Expand Up @@ -1151,6 +1159,54 @@ function tryExtractMagicFactory(value: any): MagicPlaceholder[typeof factorySymb
}
}

const lazyValueSymbol = Symbol('inox-tool/lazy-value');

export interface LazyValue<T> {
[lazyValueSymbol]: Promise<T>;
resolved: boolean;
resolve(this: LazyValue<T>, value: T): asserts this is ResolvedLazyValue<T>;
}

export type ResolvedLazyValue<T> = {
[lazyValueSymbol]: Promise<T>;
resolved: true;
resolve: never;
};

const lazyProxyHandler: ProxyHandler<LazyValue<any>> = {
set: () => {
throw new Error('Cannot set properties on a lazy value');
},
};

export function makeLazyValue<T>(): LazyValue<T> {
let resolve: (value: T) => void;
const promise = new Promise<T>((_resolve) => {
resolve = _resolve;
});

const target: LazyValue<T> = {
[lazyValueSymbol]: promise,
resolved: false,
resolve(value) {
if (target.resolved) {
throw new Error('A lazy value can only be resolved once.');
}

resolve(value);
target.resolved = true;
},
};

return new Proxy<LazyValue<T>>(target, lazyProxyHandler);
}

function tryExtractLazyValue(value: any): Promise<unknown> | undefined {
if (lazyValueSymbol in value) {
return value[lazyValueSymbol];
}
}

/**
* Cache of global entries
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/inline-mod/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { magicFactory } from './closure/inspectCode.js';
export {
type LazyValue,
type ResolvedLazyValue,
makeLazyValue as lazyValue,
} from './closure/inspectCode.js';

export function factory<T>(factoryFn: () => T): T {
return magicFactory({
Expand Down
86 changes: 0 additions & 86 deletions packages/inline-mod/tests/functions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,89 +82,3 @@ test('recurring function', async () => {
export default __f;
`);
});

test('factory values', async () => {
let callCount = 0;

const factoryValue = magicFactory({
isAsync: false,
fn: () => {
callCount++;

return {
value: 'foo',
};
},
});

const modInfo = await inspectInlineMod({
defaultExport: factoryValue,
});

expect(modInfo.text).toEqualIgnoringWhitespace(`
function __f0() {
return (function() {
const callCount = 0;
return () => {
callCount++;
return {
value: "foo"
};
};
}).apply(undefined, undefined).apply(this, arguments);
}
const __defaultExport = __f0();
export default __defaultExport;
`);

expect(callCount).toBe(0);

expect(factoryValue.value).toEqual('foo');

expect(callCount).toBe(1);

factoryValue.value = 'bar';
expect(factoryValue.value).toEqual('bar');

expect(callCount).toBe(1);
});

test('async factory values', async () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Used to test serialization
let callCount = 0;

const factoryValue = magicFactory({
isAsync: true,
fn: () => {
callCount++;

return Promise.resolve({
value: 'foo',
});
},
});

const modInfo = await inspectInlineMod({
defaultExport: factoryValue,
});

expect(modInfo.text).toEqualIgnoringWhitespace(`
function __f0() {
return (function() {
const callCount = 0;
return () => {
callCount++;
return Promise.resolve({
value: "foo"
});
};
}).apply(undefined, undefined).apply(this, arguments);
}
const __defaultExport = await __f0();
export default __defaultExport;
`);
});
99 changes: 99 additions & 0 deletions packages/inline-mod/tests/specialValues.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { expect, test } from 'vitest';
import { inspectInlineMod } from '../src/inlining.js';
import { asyncFactory, factory, lazyValue } from '../src/index.js';

test('factory values', async () => {
let callCount = 0;

const factoryValue = factory(() => {
callCount++;

return {
value: 'foo',
};
});

const modInfo = await inspectInlineMod({
defaultExport: factoryValue,
});

expect(modInfo.text).toEqualIgnoringWhitespace(`
function __f0() {
return (function() {
const callCount = 0;
return () => {
callCount++;
return {
value: "foo"
};
};
}).apply(undefined, undefined).apply(this, arguments);
}
const __defaultExport = __f0();
export default __defaultExport;
`);

expect(callCount).toBe(0);

expect(factoryValue.value).toEqual('foo');

expect(callCount).toBe(1);

factoryValue.value = 'bar';
expect(factoryValue.value).toEqual('bar');

expect(callCount).toBe(1);
});

test('async factory values', async () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Used to test serialization
let callCount = 0;

const factoryValue = asyncFactory(() => {
callCount++;

return Promise.resolve({
value: 'foo',
});
});

const modInfo = await inspectInlineMod({
defaultExport: factoryValue,
});

expect(modInfo.text).toEqualIgnoringWhitespace(`
function __f0() {
return (function() {
const callCount = 0;
return () => {
callCount++;
return Promise.resolve({
value: "foo"
});
};
}).apply(undefined, undefined).apply(this, arguments);
}
const __defaultExport = await __f0();
export default __defaultExport;
`);
});

test('lazy values', async () => {
const value = lazyValue<string>();

setTimeout(() => {
value.resolve('foo');
}, 500);

const modInfo = await inspectInlineMod({
defaultExport: value,
});

expect(modInfo.text).toEqualIgnoringWhitespace(`
export default "foo";
`);
});
5 changes: 4 additions & 1 deletion packages/inline-mod/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../tsconfig.base.json"
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"lib": ["ESNext"]
}
}

0 comments on commit 721ab06

Please sign in to comment.