From 6d7fa292bdf86975727859123333dd825f465ee8 Mon Sep 17 00:00:00 2001 From: VictorS67 <185000048@qq.com> Date: Wed, 7 Feb 2024 13:20:17 -0500 Subject: [PATCH 1/3] add getAttributes for callables with metadata --- packages/core/src/load/serializable.ts | 5 +- packages/core/src/record/callable.ts | 140 +++++++++++++++++++++++++ packages/core/src/utils/copy.ts | 3 + 3 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 packages/core/src/utils/copy.ts diff --git a/packages/core/src/load/serializable.ts b/packages/core/src/load/serializable.ts index 2cbd5e03..27ba9571 100644 --- a/packages/core/src/load/serializable.ts +++ b/packages/core/src/load/serializable.ts @@ -1,5 +1,6 @@ import { v4 as uuidv4 } from 'uuid'; +import { shallowCopy } from '../utils/copy.js'; import { IdProvider } from '../utils/nanoid.js'; // import { nanoid } from 'nanoid'; import { @@ -81,9 +82,7 @@ export type Serialized = | SerializedNotImplemented | SerializedRecord; -function shallowCopy(obj: T): T { - return Array.isArray(obj) ? ([...obj] as T) : ({ ...obj } as T); -} + export function safeAssign(target: T, source: T): T { for (const key in source) { diff --git a/packages/core/src/record/callable.ts b/packages/core/src/record/callable.ts index 708d88f5..9324b695 100644 --- a/packages/core/src/record/callable.ts +++ b/packages/core/src/record/callable.ts @@ -14,6 +14,7 @@ import { SerializedInputRecord, } from '../load/serializable.js'; import { AsyncCallError, AsyncCaller } from '../utils/asyncCaller.js'; +import { shallowCopy } from '../utils/copy.js'; import { ReadableStreamAsyncIterable } from '../utils/stream.js'; import { convertCallableLikeToCallable, isValidLambdaFunc } from './utils.js'; @@ -107,6 +108,10 @@ export type CallableBatchOptions = { returnExceptions?: boolean; }; +export type SerializedCallableFields = { + [key: string]: Callable | Record | Array; +}; + /** * Abstract class representing a callable. A callable is an object that can be * "called" or invoked with an input to produce an output, potentially asynchronously. @@ -388,6 +393,69 @@ export abstract class Callable< }); } + protected _getCallableMetadata(): { + type: string; + callables?: SerializedCallableFields; + } { + return { + type: 'Callable', + }; + } + + protected _removeCallable( + root: SerializedFields, + callablesMap: SerializedCallableFields + ): SerializedFields { + const result: SerializedFields = shallowCopy(root); + + for (const [path, callable] of Object.entries(callablesMap)) { + const [last, ...partsReverse] = path.split('.').reverse(); + + let current: SerializedFields = result; + for (const key of partsReverse.reverse()) { + if (current[key] === undefined) { + break; + } + + current[key] = shallowCopy(current[key] as SerializedFields); + current = current[key] as SerializedFields; + } + + if (current[last] !== undefined) { + if (path.split('.').length > 1) { + delete result[path.split('.')[0]]; + } else { + delete current[last]; + } + } + } + + return result; + } + + getAttributes(): { + aliases: SerializedKeyAlias; + secrets: SecretFields; + kwargs: SerializedFields; + metadata: { type: string; callables?: SerializedCallableFields }; + } { + const { aliases, secrets, kwargs } = super.getAttributes(); + + const metadata = this._getCallableMetadata(); + + const filteredKwargs = + metadata.callables && Object.keys(metadata.callables).length + ? this._removeCallable(kwargs, metadata.callables) + : kwargs; + + return { + aliases, + secrets, + kwargs: filteredKwargs, + metadata, + }; + } + async toRecord( outputs: CallOutput, parent?: RecordId | undefined @@ -607,6 +675,18 @@ export class CallableBind< ); } + protected _getCallableMetadata(): { + type: string; + callables?: SerializedCallableFields; + } { + return { + type: 'CallableBind', + callables: { + bound: this.bound, + }, + }; + } + async toInputRecord( aliases: SerializedKeyAlias, secrets: SecretFields, @@ -820,6 +900,15 @@ export class CallableLambda extends Callable< ): Promise { return this._callWithConfig(this._invoke, input, options); } + + protected _getCallableMetadata(): { + type: string; + callables?: SerializedCallableFields; + } { + return { + type: 'CallableLambda', + }; + } } /** @@ -897,6 +986,18 @@ export class CallableMap extends Callable< return output; } + + protected _getCallableMetadata(): { + type: string; + callables?: SerializedCallableFields; + } { + return { + type: 'CallableMap', + callables: { + steps: this.steps, + }, + }; + } } /** @@ -975,6 +1076,18 @@ export class CallableEach< return this.bound.batch(inputs, options); } + protected _getCallableMetadata(): { + type: string; + callables?: SerializedCallableFields; + } { + return { + type: 'CallableEach', + callables: { + bound: this.bound, + }, + }; + } + async toInputRecord( aliases: SerializedKeyAlias, secrets: SecretFields, @@ -1195,6 +1308,19 @@ export class CallableWithFallbacks extends Callable< throw firstError; } + protected _getCallableMetadata(): { + type: string; + callables?: SerializedCallableFields; + } { + return { + type: 'CallableWithFallbacks', + callables: { + callable: this.callable, + fallbacks: this.fallbacks, + }, + }; + } + async toInputRecord( aliases: SerializedKeyAlias, secrets: SecretFields, @@ -1485,4 +1611,18 @@ export class CallableSequence< last: convertCallableLikeToCallable(callableLike), }); } + + protected _getCallableMetadata(): { + type: string; + callables?: SerializedCallableFields; + } { + return { + type: 'CallableSequence', + callables: { + first: this.first, + middle: this.middle, + last: this.last + }, + }; + } } diff --git a/packages/core/src/utils/copy.ts b/packages/core/src/utils/copy.ts new file mode 100644 index 00000000..3f63a94c --- /dev/null +++ b/packages/core/src/utils/copy.ts @@ -0,0 +1,3 @@ +export function shallowCopy(obj: T): T { + return Array.isArray(obj) ? ([...obj] as T) : ({ ...obj } as T); +} \ No newline at end of file From 1f74ecaa7422eb50362eac0e47f9019d21badd23 Mon Sep 17 00:00:00 2001 From: VictorS67 <185000048@qq.com> Date: Wed, 7 Feb 2024 13:20:39 -0500 Subject: [PATCH 2/3] add test cases for all serializable callables --- .../tests/serializable.callable.test.ts | 804 ++++++++++++++++++ 1 file changed, 804 insertions(+) create mode 100644 packages/core/src/record/tests/serializable.callable.test.ts diff --git a/packages/core/src/record/tests/serializable.callable.test.ts b/packages/core/src/record/tests/serializable.callable.test.ts new file mode 100644 index 00000000..f8d129dd --- /dev/null +++ b/packages/core/src/record/tests/serializable.callable.test.ts @@ -0,0 +1,804 @@ +import { beforeAll, describe, expect, test } from '@jest/globals'; +import { OptionalImportMap, SecretMap } from '../../load/importType.js'; +import { load } from '../../load/index.js'; +import { SecretFields, SerializedFields } from '../../load/keymap.js'; +import { + Callable, + CallableBind, + CallableConfig, + CallableEach, + CallableLambda, + CallableMap, + CallableSequence, + CallableWithFallbacks, +} from '../callable.js'; + +describe('information stored in serializable', () => { + interface SimpleCallableParams { + attr1: number; + attr2: string; + secret1: string | undefined; + } + + type SimpleCallableInput = string | number; + + interface SimpleCallableOutput { + output1: string; + output2: boolean; + } + + interface SimpleCallableOptions extends CallableConfig { + option1: string; + } + + class SimpleCallable< + CallOptions extends SimpleCallableOptions = SimpleCallableOptions, + > + extends Callable + implements SimpleCallableParams + { + _isSerializable = true; + + _namespace: string[] = ['custom', 'tests']; + + static _name(): string { + return 'SimpleCallable'; + } + + get _attributes(): SerializedFields { + return { + attr2: '2', + }; + } + + get _secrets(): SecretFields { + return { + secret1: 'TEST_SECRET_1', + }; + } + + attr1: number; + + attr2: string; + + secret1: string | undefined; + + constructor(fields: Partial) { + super(fields); + + this.attr1 = fields.attr1 ?? 1; + this.attr2 = fields.attr2 ?? (this._attributes.attr2 as string); + this.secret1 = fields.secret1; + } + + async invoke( + input: string | number, + options?: Partial | undefined + ): Promise { + return { + output1: 'output1', + output2: true, + }; + } + } + + test('callable', async () => { + const simpleCallable = new SimpleCallable({ + attr1: 1, + secret1: 'this is a secret', + }); + + expect(simpleCallable.getAttributes()).toStrictEqual({ + aliases: {}, + secrets: { + secret1: 'TEST_SECRET_1', + }, + kwargs: { + attr1: 1, + attr2: '2', + }, + metadata: { + type: 'Callable', + }, + }); + + const str: string = JSON.stringify(simpleCallable, null, 2); + + expect(str).toBe( + JSON.stringify( + { + _grp: 1, + _type: 'constructor', + _id: ['custom', 'tests', 'SimpleCallable'], + _kwargs: { + attr1: 1, + secret1: { + _grp: 1, + _type: 'secret', + _id: ['TEST_SECRET_1'], + }, + attr2: '2', + }, + }, + null, + 2 + ) + ); + + const anotherCallable: SimpleCallable = await load( + str, + { TEST_SECRET_1: 'other secret 1' } as SecretMap, + { 'custom/tests': { SimpleCallable } } as OptionalImportMap + ); + + expect(anotherCallable).toBeInstanceOf(SimpleCallable); + expect(JSON.stringify(anotherCallable, null, 2)).toBe(str); + + expect(anotherCallable.getAttributes()).toStrictEqual({ + aliases: {}, + secrets: { + secret1: 'TEST_SECRET_1', + }, + kwargs: { + attr1: 1, + attr2: '2', + }, + metadata: { + type: 'Callable', + }, + }); + }); + + test('callableBind', async () => { + const simpleCallable = new SimpleCallable({ + attr1: 1, + secret1: 'this is a secret', + }); + + const simpleCallableBind = simpleCallable.bind({ + option1: 'new option 1', + }); + + expect(simpleCallableBind.getAttributes()).toStrictEqual({ + aliases: {}, + secrets: {}, + kwargs: { + kwargs: { + option1: 'new option 1', + }, + config: {}, + }, + metadata: { + type: 'CallableBind', + callables: { + bound: simpleCallable, + }, + }, + }); + + const str: string = JSON.stringify(simpleCallableBind, null, 2); + + expect(str).toBe( + JSON.stringify( + { + _grp: 1, + _type: 'constructor', + _id: ['record', 'callable', 'CallableBind'], + _kwargs: { + bound: { + _grp: 1, + _type: 'constructor', + _id: ['custom', 'tests', 'SimpleCallable'], + _kwargs: { + attr1: 1, + secret1: { + _grp: 1, + _type: 'secret', + _id: ['TEST_SECRET_1'], + }, + attr2: '2', + }, + }, + kwargs: { + option1: 'new option 1', + }, + config: {}, + }, + }, + null, + 2 + ) + ); + + const anotherCallableBind = await load< + CallableBind< + SimpleCallableInput, + SimpleCallableOutput, + SimpleCallableOptions + > + >( + str, + { TEST_SECRET_1: 'other secret 1' } as SecretMap, + { + 'custom/tests': { SimpleCallable }, + 'record/callable': { CallableBind }, + } as OptionalImportMap + ); + + expect(anotherCallableBind).toBeInstanceOf(CallableBind); + expect(JSON.stringify(anotherCallableBind, null, 2)).toBe(str); + + expect( + anotherCallableBind.getAttributes().metadata.callables?.bound + ).toBeInstanceOf(SimpleCallable); + + const anotherSimpleCallable = anotherCallableBind.getAttributes().metadata + .callables?.bound as SimpleCallable; + + expect(anotherSimpleCallable.attr1).toBe(1); + expect(anotherSimpleCallable.attr2).toBe('2'); + expect(anotherSimpleCallable.secret1).toBe('other secret 1'); + }); + + test('callableLambda', async () => { + const lambda = async ( + input: SimpleCallableInput, + options?: Partial | undefined + ): Promise => { + return { + output1: 'output1', + output2: true, + }; + }; + + const simpleCallableLambda = CallableLambda.from(lambda); + + expect(simpleCallableLambda.getAttributes()).toStrictEqual({ + aliases: {}, + secrets: {}, + kwargs: { + func: lambda.toString(), + }, + metadata: { + type: 'CallableLambda', + }, + }); + + const str: string = JSON.stringify(simpleCallableLambda, null, 2); + + expect(str).toBe( + JSON.stringify( + { + _grp: 1, + _type: 'constructor', + _id: ['record', 'callable', 'CallableLambda'], + _kwargs: { + func: lambda.toString(), + }, + }, + null, + 2 + ) + ); + + const anotherCallableLambda = await load< + CallableLambda + >( + str, + {} as SecretMap, + { + 'record/callable': { CallableLambda }, + } as OptionalImportMap + ); + + expect(anotherCallableLambda).toBeInstanceOf(CallableLambda); + expect(JSON.stringify(anotherCallableLambda, null, 2)).toBe(str); + }); + + test('callableMap', async () => { + const simpleCallable1 = new SimpleCallable({ + attr1: 1, + secret1: 'this is a secret 1', + }); + + const simpleCallable2 = new SimpleCallable({ + attr1: 2, + secret1: 'this is a secret 2', + }); + + const simpleCallable3 = new SimpleCallable({ + attr1: 3, + secret1: 'this is a secret 3', + }); + + const simpleCallableMap = CallableMap.from({ + first: simpleCallable1, + second: simpleCallable2, + third: simpleCallable3, + }); + + expect(simpleCallableMap.getAttributes()).toStrictEqual({ + aliases: {}, + secrets: {}, + kwargs: {}, + metadata: { + type: 'CallableMap', + callables: { + steps: { + first: simpleCallable1, + second: simpleCallable2, + third: simpleCallable3, + }, + }, + }, + }); + + const str: string = JSON.stringify(simpleCallableMap, null, 2); + + expect(str).toBe( + JSON.stringify( + { + _grp: 1, + _type: 'constructor', + _id: ['record', 'callable', 'CallableMap'], + _kwargs: { + steps: { + first: { + _grp: 1, + _type: 'constructor', + _id: ['custom', 'tests', 'SimpleCallable'], + _kwargs: { + attr1: 1, + secret1: { + _grp: 1, + _type: 'secret', + _id: ['TEST_SECRET_1'], + }, + attr2: '2', + }, + }, + second: { + _grp: 1, + _type: 'constructor', + _id: ['custom', 'tests', 'SimpleCallable'], + _kwargs: { + attr1: 2, + secret1: { + _grp: 1, + _type: 'secret', + _id: ['TEST_SECRET_1'], + }, + attr2: '2', + }, + }, + third: { + _grp: 1, + _type: 'constructor', + _id: ['custom', 'tests', 'SimpleCallable'], + _kwargs: { + attr1: 3, + secret1: { + _grp: 1, + _type: 'secret', + _id: ['TEST_SECRET_1'], + }, + attr2: '2', + }, + }, + }, + }, + }, + null, + 2 + ) + ); + + const anotherCallableMap = await load>( + str, + { TEST_SECRET_1: 'other secret 1' } as SecretMap, + { + 'custom/tests': { SimpleCallable }, + 'record/callable': { CallableMap }, + } as OptionalImportMap + ); + + expect(anotherCallableMap).toBeInstanceOf(CallableMap); + expect(JSON.stringify(anotherCallableMap, null, 2)).toBe(str); + + expect( + typeof anotherCallableMap.getAttributes().metadata.callables?.steps === + 'object' && + !Array.isArray( + anotherCallableMap.getAttributes().metadata.callables?.steps + ) + ).toBeTruthy(); + + expect( + anotherCallableMap.getAttributes().metadata.callables?.steps['first'] + ).toBeInstanceOf(SimpleCallable); + expect( + anotherCallableMap.getAttributes().metadata.callables?.steps['second'] + ).toBeInstanceOf(SimpleCallable); + expect( + anotherCallableMap.getAttributes().metadata.callables?.steps['third'] + ).toBeInstanceOf(SimpleCallable); + + const firstSimpleCallable = anotherCallableMap.getAttributes().metadata + .callables?.steps['first'] as SimpleCallable; + + expect(firstSimpleCallable.attr1).toBe(1); + expect(firstSimpleCallable.attr2).toBe('2'); + expect(firstSimpleCallable.secret1).toBe('other secret 1'); + + const secondSimpleCallable = anotherCallableMap.getAttributes().metadata + .callables?.steps['second'] as SimpleCallable; + + expect(secondSimpleCallable.attr1).toBe(2); + expect(secondSimpleCallable.attr2).toBe('2'); + expect(secondSimpleCallable.secret1).toBe('other secret 1'); + + const thirdSimpleCallable = anotherCallableMap.getAttributes().metadata + .callables?.steps['third'] as SimpleCallable; + + expect(thirdSimpleCallable.attr1).toBe(3); + expect(thirdSimpleCallable.attr2).toBe('2'); + expect(thirdSimpleCallable.secret1).toBe('other secret 1'); + }); + + test('callableEach', async () => { + const simpleCallable = new SimpleCallable({ + attr1: 1, + secret1: 'this is a secret', + }); + + const simpleCallableEach = simpleCallable.map(); + + expect(simpleCallableEach.getAttributes()).toStrictEqual({ + aliases: {}, + secrets: {}, + kwargs: {}, + metadata: { + type: 'CallableEach', + callables: { + bound: simpleCallable, + }, + }, + }); + + const str: string = JSON.stringify(simpleCallableEach, null, 2); + + expect(str).toBe( + JSON.stringify( + { + _grp: 1, + _type: 'constructor', + _id: ['record', 'callable', 'CallableEach'], + _kwargs: { + bound: { + _grp: 1, + _type: 'constructor', + _id: ['custom', 'tests', 'SimpleCallable'], + _kwargs: { + attr1: 1, + secret1: { + _grp: 1, + _type: 'secret', + _id: ['TEST_SECRET_1'], + }, + attr2: '2', + }, + }, + }, + }, + null, + 2 + ) + ); + + const anotherCallableEach = await load< + CallableEach< + SimpleCallableInput, + SimpleCallableOutput, + SimpleCallableOptions + > + >( + str, + { TEST_SECRET_1: 'other secret 1' } as SecretMap, + { + 'custom/tests': { SimpleCallable }, + 'record/callable': { CallableEach }, + } as OptionalImportMap + ); + + expect(anotherCallableEach).toBeInstanceOf(CallableEach); + expect(JSON.stringify(anotherCallableEach, null, 2)).toBe(str); + + expect( + anotherCallableEach.getAttributes().metadata.callables?.bound + ).toBeInstanceOf(SimpleCallable); + + const anotherSimpleCallable = anotherCallableEach.getAttributes().metadata + .callables?.bound as SimpleCallable; + + expect(anotherSimpleCallable.attr1).toBe(1); + expect(anotherSimpleCallable.attr2).toBe('2'); + expect(anotherSimpleCallable.secret1).toBe('other secret 1'); + }); + + test('CallableWithFallbacks', async () => { + const simpleCallable1 = new SimpleCallable({ + attr1: 1, + secret1: 'this is a secret 1', + }); + + const fallbackCallable2 = new SimpleCallable({ + attr1: 2, + secret1: 'this is a secret 2', + }); + + const fallbackCallable3 = new SimpleCallable({ + attr1: 3, + secret1: 'this is a secret 3', + }); + + const simpleCallableWithFallbacks = simpleCallable1.withFallbacks({ + fallbacks: [fallbackCallable2, fallbackCallable3], + }); + + expect(simpleCallableWithFallbacks.getAttributes()).toStrictEqual({ + aliases: {}, + secrets: {}, + kwargs: {}, + metadata: { + type: 'CallableWithFallbacks', + callables: { + callable: simpleCallable1, + fallbacks: [fallbackCallable2, fallbackCallable3], + }, + }, + }); + + const str: string = JSON.stringify(simpleCallableWithFallbacks, null, 2); + + expect(str).toBe( + JSON.stringify( + { + _grp: 1, + _type: 'constructor', + _id: ['record', 'callable', 'CallableWithFallbacks'], + _kwargs: { + callable: { + _grp: 1, + _type: 'constructor', + _id: ['custom', 'tests', 'SimpleCallable'], + _kwargs: { + attr1: 1, + secret1: { + _grp: 1, + _type: 'secret', + _id: ['TEST_SECRET_1'], + }, + attr2: '2', + }, + }, + fallbacks: [ + { + _grp: 1, + _type: 'constructor', + _id: ['custom', 'tests', 'SimpleCallable'], + _kwargs: { + attr1: 2, + secret1: { + _grp: 1, + _type: 'secret', + _id: ['TEST_SECRET_1'], + }, + attr2: '2', + }, + }, + { + _grp: 1, + _type: 'constructor', + _id: ['custom', 'tests', 'SimpleCallable'], + _kwargs: { + attr1: 3, + secret1: { + _grp: 1, + _type: 'secret', + _id: ['TEST_SECRET_1'], + }, + attr2: '2', + }, + }, + ], + }, + }, + null, + 2 + ) + ); + + const anotherCallableWithFallbacks = await load< + CallableWithFallbacks + >( + str, + { TEST_SECRET_1: 'other secret 1' } as SecretMap, + { + 'custom/tests': { SimpleCallable }, + 'record/callable': { CallableWithFallbacks }, + } as OptionalImportMap + ); + + expect(anotherCallableWithFallbacks).toBeInstanceOf(CallableWithFallbacks); + expect(JSON.stringify(anotherCallableWithFallbacks, null, 2)).toBe(str); + + expect( + anotherCallableWithFallbacks.getAttributes().metadata.callables?.callable + ).toBeInstanceOf(SimpleCallable); + + expect( + Array.isArray( + anotherCallableWithFallbacks.getAttributes().metadata.callables + ?.fallbacks + ) + ).toBeTruthy(); + + expect( + anotherCallableWithFallbacks.getAttributes().metadata.callables + ?.fallbacks[0] + ).toBeInstanceOf(SimpleCallable); + expect( + anotherCallableWithFallbacks.getAttributes().metadata.callables + ?.fallbacks[1] + ).toBeInstanceOf(SimpleCallable); + + const firstSimpleCallable = anotherCallableWithFallbacks.getAttributes() + .metadata.callables?.callable as SimpleCallable; + + expect(firstSimpleCallable.attr1).toBe(1); + expect(firstSimpleCallable.attr2).toBe('2'); + expect(firstSimpleCallable.secret1).toBe('other secret 1'); + + const secondSimpleCallable = anotherCallableWithFallbacks.getAttributes() + .metadata.callables?.fallbacks[0] as SimpleCallable; + + expect(secondSimpleCallable.attr1).toBe(2); + expect(secondSimpleCallable.attr2).toBe('2'); + expect(secondSimpleCallable.secret1).toBe('other secret 1'); + + const thirdSimpleCallable = anotherCallableWithFallbacks.getAttributes() + .metadata.callables?.fallbacks[1] as SimpleCallable; + + expect(thirdSimpleCallable.attr1).toBe(3); + expect(thirdSimpleCallable.attr2).toBe('2'); + expect(thirdSimpleCallable.secret1).toBe('other secret 1'); + }); + + test('CallableSequence', async () => { + const simpleCallable1 = new SimpleCallable({ + attr1: 1, + secret1: 'this is a secret 1', + }); + + const lambda = async ( + input: SimpleCallableOutput, + options?: Partial | undefined + ): Promise => { + return { + output1: 'output1', + output2: true, + }; + }; + + const simpleCallable2 = CallableLambda.from(lambda); + + const simpleCallable3 = CallableLambda.from(lambda); + + const simpleCallableSequence = CallableSequence.from([ + simpleCallable1, + simpleCallable2, + simpleCallable3, + ]); + + expect(simpleCallableSequence.getAttributes()).toStrictEqual({ + aliases: {}, + secrets: {}, + kwargs: {}, + metadata: { + type: 'CallableSequence', + callables: { + first: simpleCallable1, + middle: [simpleCallable2], + last: simpleCallable3, + }, + }, + }); + + const str: string = JSON.stringify(simpleCallableSequence, null, 2); + + expect(str).toBe( + JSON.stringify( + { + _grp: 1, + _type: 'constructor', + _id: ['record', 'callable', 'CallableSequence'], + _kwargs: { + first: { + _grp: 1, + _type: 'constructor', + _id: ['custom', 'tests', 'SimpleCallable'], + _kwargs: { + attr1: 1, + secret1: { + _grp: 1, + _type: 'secret', + _id: ['TEST_SECRET_1'], + }, + attr2: '2', + }, + }, + middle: [ + { + _grp: 1, + _type: 'constructor', + _id: ['record', 'callable', 'CallableLambda'], + _kwargs: { + func: lambda.toString(), + }, + }, + ], + last: { + _grp: 1, + _type: 'constructor', + _id: ['record', 'callable', 'CallableLambda'], + _kwargs: { + func: lambda.toString(), + }, + }, + }, + }, + null, + 2 + ) + ); + + const anotherCallableSequence = await load< + CallableSequence + >( + str, + { TEST_SECRET_1: 'other secret 1' } as SecretMap, + { + 'custom/tests': { SimpleCallable }, + 'record/callable': { CallableLambda, CallableSequence }, + } as OptionalImportMap + ); + + expect(anotherCallableSequence).toBeInstanceOf(CallableSequence); + expect(JSON.stringify(anotherCallableSequence, null, 2)).toBe(str); + + expect( + anotherCallableSequence.getAttributes().metadata.callables?.first + ).toBeInstanceOf(SimpleCallable); + + expect( + Array.isArray( + anotherCallableSequence.getAttributes().metadata.callables?.middle + ) + ).toBeTruthy(); + + expect( + anotherCallableSequence.getAttributes().metadata.callables?.middle[0] + ).toBeInstanceOf(CallableLambda); + + expect( + anotherCallableSequence.getAttributes().metadata.callables?.last + ).toBeInstanceOf(CallableLambda); + + const firstSimpleCallable = anotherCallableSequence.getAttributes().metadata + .callables?.first as SimpleCallable; + + expect(firstSimpleCallable.attr1).toBe(1); + expect(firstSimpleCallable.attr2).toBe('2'); + expect(firstSimpleCallable.secret1).toBe('other secret 1'); + }); +}); From 421284adb59229e99edf935a1c7d64957bcf3c2b Mon Sep 17 00:00:00 2001 From: VictorS67 <185000048@qq.com> Date: Wed, 7 Feb 2024 13:29:41 -0500 Subject: [PATCH 3/3] fix test case description typo --- packages/core/src/record/tests/serializable.callable.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/record/tests/serializable.callable.test.ts b/packages/core/src/record/tests/serializable.callable.test.ts index f8d129dd..d3b85499 100644 --- a/packages/core/src/record/tests/serializable.callable.test.ts +++ b/packages/core/src/record/tests/serializable.callable.test.ts @@ -525,7 +525,7 @@ describe('information stored in serializable', () => { expect(anotherSimpleCallable.secret1).toBe('other secret 1'); }); - test('CallableWithFallbacks', async () => { + test('callableWithFallbacks', async () => { const simpleCallable1 = new SimpleCallable({ attr1: 1, secret1: 'this is a secret 1', @@ -674,7 +674,7 @@ describe('information stored in serializable', () => { expect(thirdSimpleCallable.secret1).toBe('other secret 1'); }); - test('CallableSequence', async () => { + test('callableSequence', async () => { const simpleCallable1 = new SimpleCallable({ attr1: 1, secret1: 'this is a secret 1',