Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON Type Value #478

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@
"json-stable",
"json-text",
"json-type",
"json-type-value",
"reactive-rpc",
"util"
]
Expand Down
2 changes: 1 addition & 1 deletion src/json-type-cli/Cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import {formatError} from './util';
import {defineBuiltinRoutes} from './methods';
import {defaultParams} from './defaultParams';
import type {CliCodecs} from './CliCodecs';
import type {Value} from '../reactive-rpc/common/messages/Value';
import type {TypeBuilder} from '../json-type/type/TypeBuilder';
import type {WriteStream, ReadStream} from 'tty';
import type {CliCodec, CliContext, CliParam, CliParamInstance} from './types';
import type {Value} from '../json-type-value';

export interface CliOptions<Router extends TypeRouter<any>> {
codecs: CliCodecs;
Expand Down
2 changes: 1 addition & 1 deletion src/json-type-cli/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Value} from '../reactive-rpc/common/messages/Value';
import {Value} from '../json-type-value';
import {RpcError} from '../reactive-rpc/common/rpc/caller';

export const formatError = (err: unknown): unknown => {
Expand Down
10 changes: 10 additions & 0 deletions src/json-type-value/AnyValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {t, AnyType} from '../json-type';
import {Value} from './Value';

const anyType = t.any;

export class AnyValue extends Value<AnyType> {
constructor(public data: unknown) {
super(anyType, data);
}
}
94 changes: 94 additions & 0 deletions src/json-type-value/ObjectValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {Value} from './Value';
import {toText} from '../json-type/typescript/toText';
import type {ResolveType} from '../json-type';
import type * as classes from '../json-type/type';
import type * as ts from '../json-type/typescript/types';

type UnObjectType<T> = T extends classes.ObjectType<infer U> ? U : never;
type UnObjectFieldTypeVal<T> = T extends classes.ObjectFieldType<any, infer U> ? U : never;

// export type MergeObjectsTypes<A, B> =
// A extends classes.ObjectType<infer A2>
// ? B extends classes.ObjectType<infer B2>
// ? classes.ObjectType<[...A2, ...B2]> :
// never :
// never;

// export type MergeObjectValues<A, B> =
// A extends ObjectValue<infer A2>
// ? B extends ObjectValue<infer B2>
// ? ObjectValue<MergeObjectsTypes<A2, B2>> :
// never :
// never;

export class ObjectValue<T extends classes.ObjectType<any>> extends Value<T> {
public field<F extends classes.ObjectFieldType<any, any>>(field: F, data: ResolveType<UnObjectFieldTypeVal<F>>): ObjectValue<classes.ObjectType<[...UnObjectType<T>, F]>> {
const extendedData = {...this.data, [field.key]: data};
const type = this.type;
const system = type.system;
if (!system) throw new Error('NO_SYSTEM');
const extendedType = system.t.Object(...type.fields, field);
return new ObjectValue(extendedType, extendedData) as any;
}

public prop<K extends string, V extends classes.Type>(key: K, type: V, data: ResolveType<V>) {
const system = type.system;
if (!system) throw new Error('NO_SYSTEM');
return this.field(system.t.prop(key, type), data);
}

public merge<O extends ObjectValue<any>>(obj: O): ObjectValue<classes.ObjectType<[...UnObjectType<T>, ...UnObjectType<O['type']>]>> {
const extendedData = {...this.data, ...obj.data};
const type = this.type;
const system = type.system;
if (!system) throw new Error('NO_SYSTEM');
const extendedType = system.t.Object(...type.fields, ...obj.type.fields);
return new ObjectValue(extendedType, extendedData) as any;
}

public toTypeScriptAst(): ts.TsTypeLiteral {
const node: ts.TsTypeLiteral = {
node: 'TypeLiteral',
members: [],
};
const data = this.data as Record<string, classes.Type>;
for (const [name, type] of Object.entries(data)) {
const schema = type.getSchema();
const property: ts.TsPropertySignature = {
node: 'PropertySignature',
name,
type: type.toTypeScriptAst(),
};
if (schema.title) property.comment = schema.title;
node.members.push(property);
}
return node;
}

public toTypeScriptModuleAst(): ts.TsModuleDeclaration {
const node: ts.TsModuleDeclaration = {
node: 'ModuleDeclaration',
name: 'Router',
export: true,
statements: [
{
node: 'TypeAliasDeclaration',
name: 'Routes',
type: this.toTypeScriptAst(),
export: true,
},
],
};
const system = this.type.system;
if (!system) throw new Error('system is undefined');
for (const alias of system.aliases.values()) node.statements.push({...alias.toTypeScriptAst(), export: true});
return node;
}

/**
* @todo This could go into {@link ObjectType}.
*/
public toTypeScript(): string {
return toText(this.toTypeScriptModuleAst());
}
}
12 changes: 12 additions & 0 deletions src/json-type-value/Value.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type {JsonValueCodec} from '../json-pack/codecs/types';
import type {ResolveType, Type} from '../json-type';

export class Value<T extends Type = Type> {
constructor(public type: T, public data: ResolveType<T>) {}

public encode(codec: JsonValueCodec): void {
const data = this.data;
if (data === undefined) return;
this.type.encoder(codec.format)(this.data, codec.encoder);
}
}
4 changes: 4 additions & 0 deletions src/json-type-value/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './Value';
export * from './AnyValue';
export * from './ObjectValue';
export * from './util';
14 changes: 14 additions & 0 deletions src/json-type-value/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {Value} from './Value';
import {AnyValue} from './AnyValue';
import {ObjectValue} from './ObjectValue';
import * as classes from '../json-type/type';

export const makeValue: {
(type: undefined | classes.Type, data: unknown): AnyValue;
<T extends classes.ObjectType>(type: T, data: unknown): ObjectValue<T>;
<T extends classes.Type>(type: T, data: unknown): Value<T>;
} = (type: any, data: any): any => {
if (!type) return new AnyValue(data);
if (type instanceof classes.ObjectType) return new ObjectValue(type as classes.ObjectType, <any>data);
return new Value(type as classes.Type, <any>data);
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Codegen, CodegenStepExecJs} from '../../../util/codegen';
import {WriteBlobStep} from '../WriteBlobStep';
import {concat} from '../../../util/buffers/concat';
import {Value} from '../../../reactive-rpc/common/messages/Value';
import {Value} from '../../../json-type-value';
import type {TypeSystem} from '../../system';
import type {Type} from '../../type';
import type {CompiledBinaryEncoder} from '../types';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Codegen, CodegenStepExecJs} from '../../../util/codegen';
import {maxEncodingCapacity} from '../../../json-size';
import {Value} from '../../../reactive-rpc/common/messages/Value';
import {Value} from '../../../json-type-value';
import type {TypeSystem} from '../../system';
import type {Type} from '../../type';

Expand Down
1 change: 0 additions & 1 deletion src/json-type/system/TypeRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ export class TypeRouter<Routes extends RoutesBase> {
}

public toTypeScript(): string {
this.system.exportTypes;
return toText(this.toTypeScriptModuleAst());
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/json-type/type/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type * as schema from '../schema';
import type * as classes from './classes';

export type * from './classes';

export interface BaseType<S extends schema.TType> {
getSchema(): S;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import {
ResponseErrorMessage,
ResponseUnsubscribeMessage,
} from '../../../messages';
import {Value} from '../../../messages/Value';
import {messages} from '../../../messages/__tests__/fixtures';
import {AnyValue} from '../../../../../json-type-value/AnyValue';

const writer = new Writer(8 * Math.round(Math.random() * 100));
const codecs = new Codecs(writer);
Expand All @@ -31,25 +31,25 @@ for (const jsonCodec of codecList) {

describe(jsonCodec.id, () => {
test('Notification message', () => {
const value = new Value({foo: 'bar'}, undefined);
const value = new AnyValue({foo: 'bar'});
const message = new NotificationMessage('abc', value);
assertMessage(message);
});

test('Request Data message', () => {
const value = new Value([1, 2, 3], undefined);
const value = new AnyValue([1, 2, 3]);
const message = new RequestDataMessage(9999, 'a', value);
assertMessage(message);
});

test('Request Complete message', () => {
const value = new Value(true, undefined);
const value = new AnyValue(true);
const message = new RequestCompleteMessage(3, 'abc', value);
assertMessage(message);
});

test('Request Error message', () => {
const value = new Value({message: 'Error!', errno: 123, code: 'ERROR'}, undefined);
const value = new AnyValue({message: 'Error!', errno: 123, code: 'ERROR'});
const message = new RequestErrorMessage(0, 'wtf', value);
assertMessage(message);
});
Expand All @@ -60,19 +60,19 @@ for (const jsonCodec of codecList) {
});

test('Response Data message', () => {
const value = new Value([1, 2, 3], undefined);
const value = new AnyValue([1, 2, 3]);
const message = new ResponseDataMessage(30000, value);
assertMessage(message);
});

test('Response Complete message', () => {
const value = new Value(true, undefined);
const value = new AnyValue(true);
const message = new ResponseCompleteMessage(3, value);
assertMessage(message);
});

test('Response Error message', () => {
const value = new Value({message: 'Error!', errno: 123, code: 'ERROR'}, undefined);
const value = new AnyValue({message: 'Error!', errno: 123, code: 'ERROR'});
const message = new ResponseErrorMessage(0, value);
assertMessage(message);
});
Expand All @@ -85,7 +85,7 @@ for (const jsonCodec of codecList) {
}

describe('batch of messages', () => {
const value = new Value({foo: 'bar'}, undefined);
const value = new AnyValue({foo: 'bar'});
const message1 = new NotificationMessage('abc', value);
const message2 = new RequestDataMessage(888, 'a', value);
const message3 = new ResponseCompleteMessage(3, value);
Expand Down
8 changes: 4 additions & 4 deletions src/reactive-rpc/common/codec/binary/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
ResponseErrorMessage,
ResponseUnsubscribeMessage,
} from '../../messages';
import {Value} from '../../messages/Value';
import {BinaryMessageType} from './constants';
import {AnyValue} from '../../../../json-type-value/AnyValue';
import type {Reader} from '../../../../util/buffers/Reader';

export const decode = (reader: Reader): ReactiveRpcMessage => {
Expand All @@ -24,7 +24,7 @@ export const decode = (reader: Reader): ReactiveRpcMessage => {
const x = word >>> 8;
const name = reader.ascii(z);
const cut = new Uint8ArrayCut(reader.uint8, reader.x, x);
const value = new Value(cut, undefined);
const value = new AnyValue(cut);
reader.skip(x);
return new NotificationMessage(name, value);
}
Expand Down Expand Up @@ -52,7 +52,7 @@ export const decode = (reader: Reader): ReactiveRpcMessage => {
reader.skip(x);
}
const cut = new Uint8ArrayCut(reader.uint8, cutStart, x);
const value = new Value(cut, undefined);
const value = new AnyValue(cut);
switch (type) {
case BinaryMessageType.RequestData:
return new RequestDataMessage(y, name, value);
Expand Down Expand Up @@ -85,7 +85,7 @@ export const decode = (reader: Reader): ReactiveRpcMessage => {
reader.skip(x);
}
const cut = new Uint8ArrayCut(reader.uint8, cutStart, x);
const value = new Value(cut, undefined);
const value = new AnyValue(cut);
switch (type) {
case BinaryMessageType.ResponseData:
return new ResponseDataMessage(y, value);
Expand Down
16 changes: 8 additions & 8 deletions src/reactive-rpc/common/codec/compact/CompactRpcMessageCodec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import {RpcMessageFormat} from '../constants';
import {RpcError, RpcErrorCodes} from '../../rpc/caller/error';
import * as msg from '../../messages';
import {CompactMessageType} from './constants';
import {Value} from '../../messages/Value';
import {CborEncoder} from '../../../../json-pack/cbor/CborEncoder';
import {MsgPackEncoder} from '../../../../json-pack/msgpack';
import {JsonEncoder} from '../../../../json-pack/json/JsonEncoder';
import {AnyValue} from '../../../../json-type-value/AnyValue';
import type {RpcMessageCodec} from '../types';
import type {JsonValueCodec} from '../../../../json-pack/codecs/types';
import type * as types from './types';
Expand All @@ -16,36 +16,36 @@ const fromJson = (arr: unknown | unknown[] | types.CompactMessage): msg.Reactive
switch (type) {
case CompactMessageType.RequestComplete: {
const data = arr[3];
const value = data === undefined ? data : new Value(data, undefined);
const value = data === undefined ? data : new AnyValue(data);
return new msg.RequestCompleteMessage(arr[1], arr[2], value);
}
case CompactMessageType.RequestData: {
const data = arr[3];
const value = data === undefined ? data : new Value(data, undefined);
const value = data === undefined ? data : new AnyValue(data);
return new msg.RequestDataMessage(arr[1], arr[2], value);
}
case CompactMessageType.RequestError: {
return new msg.RequestErrorMessage(arr[1], arr[2], new Value(arr[3], undefined));
return new msg.RequestErrorMessage(arr[1], arr[2], new AnyValue(arr[3]));
}
case CompactMessageType.RequestUnsubscribe: {
return new msg.RequestUnsubscribeMessage(arr[1]);
}
case CompactMessageType.ResponseComplete: {
const data = arr[2];
const value = data === undefined ? data : new Value(data, undefined);
const value = data === undefined ? data : new AnyValue(data);
return new msg.ResponseCompleteMessage(arr[1], value);
}
case CompactMessageType.ResponseData: {
return new msg.ResponseDataMessage(arr[1], new Value(arr[2], undefined));
return new msg.ResponseDataMessage(arr[1], new AnyValue(arr[2]));
}
case CompactMessageType.ResponseError: {
return new msg.ResponseErrorMessage(arr[1], new Value(arr[2], undefined));
return new msg.ResponseErrorMessage(arr[1], new AnyValue(arr[2]));
}
case CompactMessageType.ResponseUnsubscribe: {
return new msg.ResponseUnsubscribeMessage(arr[1]);
}
case CompactMessageType.Notification: {
return new msg.NotificationMessage(arr[1], new Value(arr[2], undefined));
return new msg.NotificationMessage(arr[1], new AnyValue(arr[2]));
}
}
throw RpcError.value(RpcError.validation('Unknown message type'));
Expand Down
Loading
Loading